/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=2 sw=2 et 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/. */ #include "ChildIterator.h" #include "nsContentUtils.h" #include "mozilla/dom/XBLChildrenElement.h" #include "mozilla/dom/HTMLContentElement.h" #include "mozilla/dom/HTMLShadowElement.h" #include "mozilla/dom/ShadowRoot.h" #include "nsIAnonymousContentCreator.h" #include "nsIFrame.h" namespace mozilla { namespace dom { class MatchedNodes { public: explicit MatchedNodes(HTMLContentElement* aInsertionPoint) : mIsContentElement(true), mContentElement(aInsertionPoint) {} explicit MatchedNodes(XBLChildrenElement* aInsertionPoint) : mIsContentElement(false), mChildrenElement(aInsertionPoint) {} uint32_t Length() const { return mIsContentElement ? mContentElement->MatchedNodes().Length() : mChildrenElement->InsertedChildrenLength(); } nsIContent* operator[](int32_t aIndex) const { return mIsContentElement ? mContentElement->MatchedNodes()[aIndex] : mChildrenElement->InsertedChild(aIndex); } bool IsEmpty() const { return mIsContentElement ? mContentElement->MatchedNodes().IsEmpty() : !mChildrenElement->HasInsertedChildren(); } protected: bool mIsContentElement; union { HTMLContentElement* mContentElement; XBLChildrenElement* mChildrenElement; }; }; static inline MatchedNodes GetMatchedNodesForPoint(nsIContent* aContent) { if (aContent->NodeInfo()->Equals(nsGkAtoms::children, kNameSpaceID_XBL)) { // XBL case return MatchedNodes(static_cast(aContent)); } // Web components case MOZ_ASSERT(aContent->IsHTMLElement(nsGkAtoms::content)); return MatchedNodes(static_cast(aContent)); } nsIContent* ExplicitChildIterator::GetNextChild() { // If we're already in the inserted-children array, look there first if (mIndexInInserted) { MOZ_ASSERT(mChild); MOZ_ASSERT(nsContentUtils::IsContentInsertionPoint(mChild)); MOZ_ASSERT(!mDefaultChild); MatchedNodes assignedChildren = GetMatchedNodesForPoint(mChild); if (mIndexInInserted < assignedChildren.Length()) { return assignedChildren[mIndexInInserted++]; } mIndexInInserted = 0; mChild = mChild->GetNextSibling(); } else if (mShadowIterator) { // If we're inside of a element, look through the // explicit children of the projected ShadowRoot via // the mShadowIterator. nsIContent* nextChild = mShadowIterator->GetNextChild(); if (nextChild) { return nextChild; } mShadowIterator = nullptr; mChild = mChild->GetNextSibling(); } else if (mDefaultChild) { // If we're already in default content, check if there are more nodes there MOZ_ASSERT(mChild); MOZ_ASSERT(nsContentUtils::IsContentInsertionPoint(mChild)); mDefaultChild = mDefaultChild->GetNextSibling(); if (mDefaultChild) { return mDefaultChild; } mChild = mChild->GetNextSibling(); } else if (mIsFirst) { // at the beginning of the child list mChild = mParent->GetFirstChild(); mIsFirst = false; } else if (mChild) { // in the middle of the child list mChild = mChild->GetNextSibling(); } // Iterate until we find a non-insertion point, or an insertion point with // content. while (mChild) { // If the current child being iterated is a shadow insertion point then // the iterator needs to go into the projected ShadowRoot. if (ShadowRoot::IsShadowInsertionPoint(mChild)) { // Look for the next child in the projected ShadowRoot for the // element. HTMLShadowElement* shadowElem = static_cast(mChild); ShadowRoot* projectedShadow = shadowElem->GetOlderShadowRoot(); if (projectedShadow) { mShadowIterator = new ExplicitChildIterator(projectedShadow); nsIContent* nextChild = mShadowIterator->GetNextChild(); if (nextChild) { return nextChild; } mShadowIterator = nullptr; } mChild = mChild->GetNextSibling(); } else if (nsContentUtils::IsContentInsertionPoint(mChild)) { // If the current child being iterated is a content insertion point // then the iterator needs to return the nodes distributed into // the content insertion point. MatchedNodes assignedChildren = GetMatchedNodesForPoint(mChild); if (!assignedChildren.IsEmpty()) { // Iterate through elements projected on insertion point. mIndexInInserted = 1; return assignedChildren[0]; } // Insertion points inside fallback/default content // are considered inactive and do not get assigned nodes. mDefaultChild = mChild->GetFirstChild(); if (mDefaultChild) { return mDefaultChild; } // If we have an insertion point with no assigned nodes and // no default content, move on to the next node. mChild = mChild->GetNextSibling(); } else { // mChild is not an insertion point, thus it is the next node to // return from this iterator. break; } } return mChild; } void FlattenedChildIterator::Init(bool aIgnoreXBL) { if (aIgnoreXBL) { return; } nsXBLBinding* binding = mParent->OwnerDoc()->BindingManager()->GetBindingWithContent(mParent); if (binding) { nsIContent* anon = binding->GetAnonymousContent(); if (anon) { mParent = anon; mXBLInvolved = true; } } // We set mXBLInvolved to true if either: // - The node we're iterating has a binding with content attached to it. // - The node is generated XBL content and has an child. if (!mXBLInvolved && mParent->GetBindingParent()) { for (nsIContent* child = mParent->GetFirstChild(); child; child = child->GetNextSibling()) { if (child->NodeInfo()->Equals(nsGkAtoms::children, kNameSpaceID_XBL)) { MOZ_ASSERT(child->GetBindingParent()); mXBLInvolved = true; break; } } } } nsIContent* ExplicitChildIterator::Get() { MOZ_ASSERT(!mIsFirst); if (mIndexInInserted) { MatchedNodes assignedChildren = GetMatchedNodesForPoint(mChild); return assignedChildren[mIndexInInserted - 1]; } else if (mShadowIterator) { return mShadowIterator->Get(); } return mDefaultChild ? mDefaultChild : mChild; } nsIContent* ExplicitChildIterator::GetPreviousChild() { // If we're already in the inserted-children array, look there first if (mIndexInInserted) { // NB: mIndexInInserted points one past the last returned child so we need // to look *two* indices back in order to return the previous child. MatchedNodes assignedChildren = GetMatchedNodesForPoint(mChild); if (--mIndexInInserted) { return assignedChildren[mIndexInInserted - 1]; } mChild = mChild->GetPreviousSibling(); } else if (mShadowIterator) { nsIContent* previousChild = mShadowIterator->GetPreviousChild(); if (previousChild) { return previousChild; } mShadowIterator = nullptr; mChild = mChild->GetPreviousSibling(); } else if (mDefaultChild) { // If we're already in default content, check if there are more nodes there mDefaultChild = mDefaultChild->GetPreviousSibling(); if (mDefaultChild) { return mDefaultChild; } mChild = mChild->GetPreviousSibling(); } else if (mIsFirst) { // at the beginning of the child list return nullptr; } else if (mChild) { // in the middle of the child list mChild = mChild->GetPreviousSibling(); } else { // at the end of the child list mChild = mParent->GetLastChild(); } // Iterate until we find a non-insertion point, or an insertion point with // content. while (mChild) { if (ShadowRoot::IsShadowInsertionPoint(mChild)) { // If the current child being iterated is a shadow insertion point then // the iterator needs to go into the projected ShadowRoot. HTMLShadowElement* shadowElem = static_cast(mChild); ShadowRoot* projectedShadow = shadowElem->GetOlderShadowRoot(); if (projectedShadow) { // Create a ExplicitChildIterator that begins iterating from the end. mShadowIterator = new ExplicitChildIterator(projectedShadow, false); nsIContent* previousChild = mShadowIterator->GetPreviousChild(); if (previousChild) { return previousChild; } mShadowIterator = nullptr; } mChild = mChild->GetPreviousSibling(); } else if (nsContentUtils::IsContentInsertionPoint(mChild)) { // If the current child being iterated is a content insertion point // then the iterator needs to return the nodes distributed into // the content insertion point. MatchedNodes assignedChildren = GetMatchedNodesForPoint(mChild); if (!assignedChildren.IsEmpty()) { mIndexInInserted = assignedChildren.Length(); return assignedChildren[mIndexInInserted - 1]; } mDefaultChild = mChild->GetLastChild(); if (mDefaultChild) { return mDefaultChild; } mChild = mChild->GetPreviousSibling(); } else { // mChild is not an insertion point, thus it is the next node to // return from this iterator. break; } } if (!mChild) { mIsFirst = true; } return mChild; } nsIContent* AllChildrenIterator::GetNextChild() { if (mPhase == eNeedBeforeKid) { mPhase = eNeedExplicitKids; nsIFrame* frame = mOriginalContent->GetPrimaryFrame(); if (frame) { nsIFrame* beforeFrame = nsLayoutUtils::GetBeforeFrame(frame); if (beforeFrame) { return beforeFrame->GetContent(); } } } if (mPhase == eNeedExplicitKids) { nsIContent* kid = ExplicitChildIterator::GetNextChild(); if (kid) { return kid; } mPhase = eNeedAnonKids; } if (mPhase == eNeedAnonKids) { if (mAnonKids.IsEmpty()) { nsIAnonymousContentCreator* ac = do_QueryFrame(mOriginalContent->GetPrimaryFrame()); if (ac) { ac->AppendAnonymousContentTo(mAnonKids, mFlags); } } if (!mAnonKids.IsEmpty()) { nsIContent* nextKid = mAnonKids[0]; mAnonKids.RemoveElementAt(0); if (mAnonKids.IsEmpty()) { mPhase = eNeedAfterKid; } return nextKid; } mPhase = eNeedAfterKid; } if (mPhase == eNeedAfterKid) { mPhase = eDone; nsIFrame* frame = mOriginalContent->GetPrimaryFrame(); if (frame) { nsIFrame* afterFrame = nsLayoutUtils::GetAfterFrame(frame); if (afterFrame) { return afterFrame->GetContent(); } } } return nullptr; } } // namespace dom } // namespace mozilla