michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set ts=2 sw=2 et tw=80: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "ChildIterator.h" michael@0: #include "nsContentUtils.h" michael@0: #include "mozilla/dom/XBLChildrenElement.h" michael@0: #include "mozilla/dom/HTMLContentElement.h" michael@0: #include "mozilla/dom/HTMLShadowElement.h" michael@0: #include "mozilla/dom/ShadowRoot.h" michael@0: michael@0: namespace mozilla { michael@0: namespace dom { michael@0: michael@0: class MatchedNodes { michael@0: public: michael@0: MatchedNodes(HTMLContentElement* aInsertionPoint) michael@0: : mIsContentElement(true), mContentElement(aInsertionPoint) {} michael@0: michael@0: MatchedNodes(XBLChildrenElement* aInsertionPoint) michael@0: : mIsContentElement(false), mChildrenElement(aInsertionPoint) {} michael@0: michael@0: uint32_t Length() const michael@0: { michael@0: return mIsContentElement ? mContentElement->MatchedNodes().Length() michael@0: : mChildrenElement->mInsertedChildren.Length(); michael@0: } michael@0: michael@0: nsIContent* operator[](int32_t aIndex) const michael@0: { michael@0: return mIsContentElement ? mContentElement->MatchedNodes()[aIndex] michael@0: : mChildrenElement->mInsertedChildren[aIndex]; michael@0: } michael@0: michael@0: bool IsEmpty() const michael@0: { michael@0: return mIsContentElement ? mContentElement->MatchedNodes().IsEmpty() michael@0: : mChildrenElement->mInsertedChildren.IsEmpty(); michael@0: } michael@0: protected: michael@0: bool mIsContentElement; michael@0: union { michael@0: HTMLContentElement* mContentElement; michael@0: XBLChildrenElement* mChildrenElement; michael@0: }; michael@0: }; michael@0: michael@0: static inline MatchedNodes michael@0: GetMatchedNodesForPoint(nsIContent* aContent) michael@0: { michael@0: if (aContent->NodeInfo()->Equals(nsGkAtoms::children, kNameSpaceID_XBL)) { michael@0: // XBL case michael@0: return MatchedNodes(static_cast(aContent)); michael@0: } michael@0: michael@0: // Web components case michael@0: MOZ_ASSERT(aContent->IsHTML(nsGkAtoms::content)); michael@0: return MatchedNodes(static_cast(aContent)); michael@0: } michael@0: michael@0: nsIContent* michael@0: ExplicitChildIterator::GetNextChild() michael@0: { michael@0: // If we're already in the inserted-children array, look there first michael@0: if (mIndexInInserted) { michael@0: MOZ_ASSERT(mChild); michael@0: MOZ_ASSERT(nsContentUtils::IsContentInsertionPoint(mChild)); michael@0: MOZ_ASSERT(!mDefaultChild); michael@0: michael@0: MatchedNodes assignedChildren = GetMatchedNodesForPoint(mChild); michael@0: if (mIndexInInserted < assignedChildren.Length()) { michael@0: return assignedChildren[mIndexInInserted++]; michael@0: } michael@0: mIndexInInserted = 0; michael@0: mChild = mChild->GetNextSibling(); michael@0: } else if (mShadowIterator) { michael@0: // If we're inside of a element, look through the michael@0: // explicit children of the projected ShadowRoot via michael@0: // the mShadowIterator. michael@0: nsIContent* nextChild = mShadowIterator->GetNextChild(); michael@0: if (nextChild) { michael@0: return nextChild; michael@0: } michael@0: michael@0: mShadowIterator = nullptr; michael@0: mChild = mChild->GetNextSibling(); michael@0: } else if (mDefaultChild) { michael@0: // If we're already in default content, check if there are more nodes there michael@0: MOZ_ASSERT(mChild); michael@0: MOZ_ASSERT(nsContentUtils::IsContentInsertionPoint(mChild)); michael@0: michael@0: mDefaultChild = mDefaultChild->GetNextSibling(); michael@0: if (mDefaultChild) { michael@0: return mDefaultChild; michael@0: } michael@0: michael@0: mChild = mChild->GetNextSibling(); michael@0: } else if (mIsFirst) { // at the beginning of the child list michael@0: mChild = mParent->GetFirstChild(); michael@0: mIsFirst = false; michael@0: } else if (mChild) { // in the middle of the child list michael@0: mChild = mChild->GetNextSibling(); michael@0: } michael@0: michael@0: // Iterate until we find a non-insertion point, or an insertion point with michael@0: // content. michael@0: while (mChild) { michael@0: // If the current child being iterated is a shadow insertion point then michael@0: // the iterator needs to go into the projected ShadowRoot. michael@0: if (ShadowRoot::IsShadowInsertionPoint(mChild)) { michael@0: // Look for the next child in the projected ShadowRoot for the michael@0: // element. michael@0: HTMLShadowElement* shadowElem = static_cast(mChild); michael@0: ShadowRoot* projectedShadow = shadowElem->GetOlderShadowRoot(); michael@0: if (projectedShadow) { michael@0: mShadowIterator = new ExplicitChildIterator(projectedShadow); michael@0: nsIContent* nextChild = mShadowIterator->GetNextChild(); michael@0: if (nextChild) { michael@0: return nextChild; michael@0: } michael@0: mShadowIterator = nullptr; michael@0: } michael@0: mChild = mChild->GetNextSibling(); michael@0: } else if (nsContentUtils::IsContentInsertionPoint(mChild)) { michael@0: // If the current child being iterated is a content insertion point michael@0: // then the iterator needs to return the nodes distributed into michael@0: // the content insertion point. michael@0: MatchedNodes assignedChildren = GetMatchedNodesForPoint(mChild); michael@0: if (!assignedChildren.IsEmpty()) { michael@0: // Iterate through elements projected on insertion point. michael@0: mIndexInInserted = 1; michael@0: return assignedChildren[0]; michael@0: } michael@0: michael@0: // Insertion points inside fallback/default content michael@0: // are considered inactive and do not get assigned nodes. michael@0: mDefaultChild = mChild->GetFirstChild(); michael@0: if (mDefaultChild) { michael@0: return mDefaultChild; michael@0: } michael@0: michael@0: // If we have an insertion point with no assigned nodes and michael@0: // no default content, move on to the next node. michael@0: mChild = mChild->GetNextSibling(); michael@0: } else { michael@0: // mChild is not an insertion point, thus it is the next node to michael@0: // return from this iterator. michael@0: break; michael@0: } michael@0: } michael@0: michael@0: return mChild; michael@0: } michael@0: michael@0: FlattenedChildIterator::FlattenedChildIterator(nsIContent* aParent) michael@0: : ExplicitChildIterator(aParent), mXBLInvolved(false) michael@0: { michael@0: nsXBLBinding* binding = michael@0: aParent->OwnerDoc()->BindingManager()->GetBindingWithContent(aParent); michael@0: michael@0: if (binding) { michael@0: nsIContent* anon = binding->GetAnonymousContent(); michael@0: if (anon) { michael@0: mParent = anon; michael@0: mXBLInvolved = true; michael@0: } michael@0: } michael@0: michael@0: // We set mXBLInvolved to true if either: michael@0: // - The node we're iterating has a binding with content attached to it. michael@0: // - The node is generated XBL content and has an child. michael@0: if (!mXBLInvolved && aParent->GetBindingParent()) { michael@0: for (nsIContent* child = aParent->GetFirstChild(); michael@0: child; michael@0: child = child->GetNextSibling()) { michael@0: if (child->NodeInfo()->Equals(nsGkAtoms::children, kNameSpaceID_XBL)) { michael@0: MOZ_ASSERT(child->GetBindingParent()); michael@0: mXBLInvolved = true; michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: nsIContent* michael@0: ExplicitChildIterator::Get() michael@0: { michael@0: MOZ_ASSERT(!mIsFirst); michael@0: michael@0: if (mIndexInInserted) { michael@0: XBLChildrenElement* point = static_cast(mChild); michael@0: return point->mInsertedChildren[mIndexInInserted - 1]; michael@0: } else if (mShadowIterator) { michael@0: return mShadowIterator->Get(); michael@0: } michael@0: return mDefaultChild ? mDefaultChild : mChild; michael@0: } michael@0: michael@0: nsIContent* michael@0: ExplicitChildIterator::GetPreviousChild() michael@0: { michael@0: // If we're already in the inserted-children array, look there first michael@0: if (mIndexInInserted) { michael@0: // NB: mIndexInInserted points one past the last returned child so we need michael@0: // to look *two* indices back in order to return the previous child. michael@0: MatchedNodes assignedChildren = GetMatchedNodesForPoint(mChild); michael@0: if (--mIndexInInserted) { michael@0: return assignedChildren[mIndexInInserted - 1]; michael@0: } michael@0: mChild = mChild->GetPreviousSibling(); michael@0: } else if (mShadowIterator) { michael@0: nsIContent* previousChild = mShadowIterator->GetPreviousChild(); michael@0: if (previousChild) { michael@0: return previousChild; michael@0: } michael@0: mShadowIterator = nullptr; michael@0: mChild = mChild->GetPreviousSibling(); michael@0: } else if (mDefaultChild) { michael@0: // If we're already in default content, check if there are more nodes there michael@0: mDefaultChild = mDefaultChild->GetPreviousSibling(); michael@0: if (mDefaultChild) { michael@0: return mDefaultChild; michael@0: } michael@0: michael@0: mChild = mChild->GetPreviousSibling(); michael@0: } else if (mIsFirst) { // at the beginning of the child list michael@0: return nullptr; michael@0: } else if (mChild) { // in the middle of the child list michael@0: mChild = mChild->GetPreviousSibling(); michael@0: } else { // at the end of the child list michael@0: mChild = mParent->GetLastChild(); michael@0: } michael@0: michael@0: // Iterate until we find a non-insertion point, or an insertion point with michael@0: // content. michael@0: while (mChild) { michael@0: if (ShadowRoot::IsShadowInsertionPoint(mChild)) { michael@0: // If the current child being iterated is a shadow insertion point then michael@0: // the iterator needs to go into the projected ShadowRoot. michael@0: HTMLShadowElement* shadowElem = static_cast(mChild); michael@0: ShadowRoot* projectedShadow = shadowElem->GetOlderShadowRoot(); michael@0: if (projectedShadow) { michael@0: // Create a ExplicitChildIterator that begins iterating from the end. michael@0: mShadowIterator = new ExplicitChildIterator(projectedShadow, false); michael@0: nsIContent* previousChild = mShadowIterator->GetPreviousChild(); michael@0: if (previousChild) { michael@0: return previousChild; michael@0: } michael@0: mShadowIterator = nullptr; michael@0: } michael@0: mChild = mChild->GetPreviousSibling(); michael@0: } else if (nsContentUtils::IsContentInsertionPoint(mChild)) { michael@0: // If the current child being iterated is a content insertion point michael@0: // then the iterator needs to return the nodes distributed into michael@0: // the content insertion point. michael@0: MatchedNodes assignedChildren = GetMatchedNodesForPoint(mChild); michael@0: if (!assignedChildren.IsEmpty()) { michael@0: mIndexInInserted = assignedChildren.Length(); michael@0: return assignedChildren[mIndexInInserted - 1]; michael@0: } michael@0: michael@0: mDefaultChild = mChild->GetLastChild(); michael@0: if (mDefaultChild) { michael@0: return mDefaultChild; michael@0: } michael@0: michael@0: mChild = mChild->GetPreviousSibling(); michael@0: } else { michael@0: // mChild is not an insertion point, thus it is the next node to michael@0: // return from this iterator. michael@0: break; michael@0: } michael@0: } michael@0: michael@0: if (!mChild) { michael@0: mIsFirst = true; michael@0: } michael@0: michael@0: return mChild; michael@0: } michael@0: michael@0: } // namespace dom michael@0: } // namespace mozilla