Blob Blame History Raw
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef nsXBLChildrenElement_h___
#define nsXBLChildrenElement_h___

#include "nsIDOMElement.h"
#include "nsINodeList.h"
#include "nsBindingManager.h"
#include "mozilla/dom/nsXMLElement.h"

class nsAnonymousContentList;

namespace mozilla {
namespace dom {

class XBLChildrenElement : public nsXMLElement {
 public:
  explicit XBLChildrenElement(
      already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
      : nsXMLElement(aNodeInfo) {}
  explicit XBLChildrenElement(
      already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)
      : nsXMLElement(aNodeInfo) {}

  // nsISupports
  NS_DECL_ISUPPORTS_INHERITED

  // nsINode interface methods
  virtual nsresult Clone(mozilla::dom::NodeInfo* aNodeInfo, nsINode** aResult,
                         bool aPreallocateChildren) const override;

  virtual nsIDOMNode* AsDOMNode() override { return this; }

  void AppendInsertedChild(nsIContent* aChild) {
    mInsertedChildren.AppendElement(aChild);
    aChild->SetXBLInsertionPoint(this);

    // Appending an inserted child causes the inserted
    // children to be projected instead of default content.
    MaybeRemoveDefaultContent();
  }

  void InsertInsertedChildAt(nsIContent* aChild, uint32_t aIndex) {
    mInsertedChildren.InsertElementAt(aIndex, aChild);
    aChild->SetXBLInsertionPoint(this);

    // Inserting an inserted child causes the inserted
    // children to be projected instead of default content.
    MaybeRemoveDefaultContent();
  }

  void RemoveInsertedChild(nsIContent* aChild) {
    // Can't use this assertion as we cheat for dynamic insertions and
    // only insert in the innermost insertion point.
    // NS_ASSERTION(mInsertedChildren.Contains(aChild),
    //             "Removing child that's not there");
    mInsertedChildren.RemoveElement(aChild);

    // After removing the inserted child, default content
    // may be projected into this insertion point.
    MaybeSetupDefaultContent();
  }

  void ClearInsertedChildren() {
    for (auto* child : mInsertedChildren) {
      if (child->GetXBLInsertionPoint() == this) {
        child->SetXBLInsertionPoint(nullptr);
      }
    }
    mInsertedChildren.Clear();

    // After clearing inserted children, default content
    // will be projected into this insertion point.
    MaybeSetupDefaultContent();
  }

  void MaybeSetupDefaultContent() {
    if (!HasInsertedChildren()) {
      for (nsIContent* child = static_cast<nsINode*>(this)->GetFirstChild();
           child; child = child->GetNextSibling()) {
        child->SetXBLInsertionPoint(this);
      }
    }
  }

  void MaybeRemoveDefaultContent() {
    if (!HasInsertedChildren()) {
      for (nsIContent* child = static_cast<nsINode*>(this)->GetFirstChild();
           child; child = child->GetNextSibling()) {
        child->SetXBLInsertionPoint(nullptr);
      }
    }
  }

  uint32_t InsertedChildrenLength() { return mInsertedChildren.Length(); }

  bool HasInsertedChildren() { return !mInsertedChildren.IsEmpty(); }

  int32_t IndexOfInsertedChild(nsIContent* aChild) {
    return mInsertedChildren.IndexOf(aChild);
  }

  bool Includes(nsIContent* aChild) {
    NS_ASSERTION(!mIncludes.IsEmpty(),
                 "Shouldn't check for includes on default insertion point");
    return mIncludes.Contains(aChild->NodeInfo()->NameAtom());
  }

  bool IsDefaultInsertion() { return mIncludes.IsEmpty(); }

  nsIContent* InsertedChild(uint32_t aIndex) {
    return mInsertedChildren[aIndex];
  }

 protected:
  ~XBLChildrenElement();
  virtual nsresult BeforeSetAttr(int32_t aNamespaceID, nsAtom* aName,
                                 const nsAttrValueOrString* aValue,
                                 bool aNotify) override;

 private:
  nsTArray<nsIContent*> mInsertedChildren;  // WEAK
  nsTArray<RefPtr<nsAtom> > mIncludes;
};

}  // namespace dom
}  // namespace mozilla

class nsAnonymousContentList : public nsINodeList {
 public:
  explicit nsAnonymousContentList(nsIContent* aParent) : mParent(aParent) {}

  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(nsAnonymousContentList)
  // nsIDOMNodeList interface
  NS_DECL_NSIDOMNODELIST

  // nsINodeList interface
  virtual int32_t IndexOf(nsIContent* aContent) override;
  virtual nsINode* GetParentObject() override { return mParent; }
  virtual nsIContent* Item(uint32_t aIndex) override;

  virtual JSObject* WrapObject(JSContext* cx,
                               JS::Handle<JSObject*> aGivenProto) override;

  bool IsListFor(nsIContent* aContent) { return mParent == aContent; }

 private:
  virtual ~nsAnonymousContentList() {}

  nsCOMPtr<nsIContent> mParent;
};

#endif  // nsXBLChildrenElement_h___