diff options
author | Matt A. Tobin <email@mattatobin.com> | 2020-04-17 07:10:54 -0400 |
---|---|---|
committer | Matt A. Tobin <email@mattatobin.com> | 2020-04-17 07:10:54 -0400 |
commit | e31ed5b07466d4a579fe4b025f97c971003fbc3f (patch) | |
tree | abd621b87578770973591fd03608993334f6af64 /dom | |
parent | 8beb65dd501cbdcfd6a793027b5de2a1fdfc7149 (diff) | |
download | UXP-e31ed5b07466d4a579fe4b025f97c971003fbc3f.tar UXP-e31ed5b07466d4a579fe4b025f97c971003fbc3f.tar.gz UXP-e31ed5b07466d4a579fe4b025f97c971003fbc3f.tar.lz UXP-e31ed5b07466d4a579fe4b025f97c971003fbc3f.tar.xz UXP-e31ed5b07466d4a579fe4b025f97c971003fbc3f.zip |
Bug 1409975 - Implement node distribution for shadow tree slots
* Implementation for assignedNodes
* Include slots in the flat tree
* Fix event get-the-parent algorithm for a node
* Update and add reftests for Shadow DOM v1
* Update web platform tests expectations
Tag #1375
Diffstat (limited to 'dom')
-rw-r--r-- | dom/base/ChildIterator.cpp | 67 | ||||
-rw-r--r-- | dom/base/ChildIterator.h | 21 | ||||
-rw-r--r-- | dom/base/FragmentOrElement.cpp | 72 | ||||
-rw-r--r-- | dom/base/ShadowRoot.cpp | 240 | ||||
-rw-r--r-- | dom/base/ShadowRoot.h | 33 | ||||
-rw-r--r-- | dom/base/nsContentUtils.cpp | 8 | ||||
-rw-r--r-- | dom/base/nsIContent.h | 6 | ||||
-rw-r--r-- | dom/base/nsINode.h | 6 | ||||
-rw-r--r-- | dom/events/EventDispatcher.h | 7 | ||||
-rw-r--r-- | dom/html/HTMLSlotElement.cpp | 151 | ||||
-rw-r--r-- | dom/html/HTMLSlotElement.h | 23 |
11 files changed, 559 insertions, 75 deletions
diff --git a/dom/base/ChildIterator.cpp b/dom/base/ChildIterator.cpp index dc356fc01..ae5f4665d 100644 --- a/dom/base/ChildIterator.cpp +++ b/dom/base/ChildIterator.cpp @@ -6,6 +6,7 @@ #include "ChildIterator.h" #include "nsContentUtils.h" +#include "mozilla/dom/HTMLSlotElement.h" #include "mozilla/dom/XBLChildrenElement.h" #include "mozilla/dom/ShadowRoot.h" #include "nsIAnonymousContentCreator.h" @@ -57,15 +58,34 @@ GetMatchedNodesForPoint(nsIContent* aContent) // XXX handle <slot> element? } +ExplicitChildIterator::ExplicitChildIterator(const nsIContent* aParent, + bool aStartAtBeginning) + : mParent(aParent), + mChild(nullptr), + mDefaultChild(nullptr), + mIsFirst(aStartAtBeginning), + mIndexInInserted(0) +{ + mParentAsSlot = HTMLSlotElement::FromContent(mParent); +} + 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); + if (mParentAsSlot) { + const nsTArray<RefPtr<nsINode>>& assignedNodes = + mParentAsSlot->AssignedNodes(); + + mChild = (mIndexInInserted < assignedNodes.Length()) ? + assignedNodes[mIndexInInserted++]->AsContent() : nullptr; + return mChild; + } + MatchedNodes assignedChildren = GetMatchedNodesForPoint(mChild); if (mIndexInInserted < assignedChildren.Length()) { return assignedChildren[mIndexInInserted++]; @@ -84,6 +104,19 @@ ExplicitChildIterator::GetNextChild() mChild = mChild->GetNextSibling(); } else if (mIsFirst) { // at the beginning of the child list + // For slot parent, iterate over assigned nodes if not empty, otherwise + // fall through and iterate over direct children (fallback content). + if (mParentAsSlot) { + const nsTArray<RefPtr<nsINode>>& assignedNodes = + mParentAsSlot->AssignedNodes(); + if (!assignedNodes.IsEmpty()) { + mIndexInInserted = 1; + mChild = assignedNodes[0]->AsContent(); + mIsFirst = false; + return mChild; + } + } + mChild = mParent->GetFirstChild(); mIsFirst = false; } else if (mChild) { // in the middle of the child list @@ -185,6 +218,12 @@ ExplicitChildIterator::Get() const { MOZ_ASSERT(!mIsFirst); + // When mParentAsSlot is set, mChild is always set to the current child. It + // does not matter whether mChild is an assigned node or a fallback content. + if (mParentAsSlot) { + return mChild; + } + if (mIndexInInserted) { MatchedNodes assignedChildren = GetMatchedNodesForPoint(mChild); return assignedChildren[mIndexInInserted - 1]; @@ -198,6 +237,20 @@ ExplicitChildIterator::GetPreviousChild() { // If we're already in the inserted-children array, look there first if (mIndexInInserted) { + + if (mParentAsSlot) { + const nsTArray<RefPtr<nsINode>>& assignedNodes = + mParentAsSlot->AssignedNodes(); + + mChild = (--mIndexInInserted) ? + assignedNodes[mIndexInInserted - 1]->AsContent() : nullptr; + + if (!mChild) { + mIsFirst = true; + } + return mChild; + } + // 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); @@ -218,6 +271,18 @@ ExplicitChildIterator::GetPreviousChild() } else if (mChild) { // in the middle of the child list mChild = mChild->GetPreviousSibling(); } else { // at the end of the child list + // For slot parent, iterate over assigned nodes if not empty, otherwise + // fall through and iterate over direct children (fallback content). + if (mParentAsSlot) { + const nsTArray<RefPtr<nsINode>>& assignedNodes = + mParentAsSlot->AssignedNodes(); + if (!assignedNodes.IsEmpty()) { + mIndexInInserted = assignedNodes.Length(); + mChild = assignedNodes[mIndexInInserted - 1]->AsContent(); + return mChild; + } + } + mChild = mParent->GetLastChild(); } diff --git a/dom/base/ChildIterator.h b/dom/base/ChildIterator.h index f78ba7ca3..ec6dfa61d 100644 --- a/dom/base/ChildIterator.h +++ b/dom/base/ChildIterator.h @@ -36,22 +36,19 @@ class ExplicitChildIterator { public: explicit ExplicitChildIterator(const nsIContent* aParent, - bool aStartAtBeginning = true) - : mParent(aParent), - mChild(nullptr), - mDefaultChild(nullptr), - mIndexInInserted(0), - mIsFirst(aStartAtBeginning) - { - } + bool aStartAtBeginning = true); ExplicitChildIterator(const ExplicitChildIterator& aOther) - : mParent(aOther.mParent), mChild(aOther.mChild), + : mParent(aOther.mParent), + mParentAsSlot(aOther.mParentAsSlot), + mChild(aOther.mChild), mDefaultChild(aOther.mDefaultChild), mIndexInInserted(aOther.mIndexInInserted), mIsFirst(aOther.mIsFirst) {} ExplicitChildIterator(ExplicitChildIterator&& aOther) - : mParent(aOther.mParent), mChild(aOther.mChild), + : mParent(aOther.mParent), + mParentAsSlot(aOther.mParentAsSlot), + mChild(aOther.mChild), mDefaultChild(aOther.mDefaultChild), mIndexInInserted(aOther.mIndexInInserted), mIsFirst(aOther.mIsFirst) {} @@ -98,6 +95,10 @@ protected: // the <xbl:content> element for the binding. const nsIContent* mParent; + // If parent is a slot element, this points to the parent as HTMLSlotElement, + // otherwise, it's null. + const HTMLSlotElement* mParentAsSlot; + // The current child. When we encounter an insertion point, // mChild remains as the insertion point whose content we're iterating (and // our state is controled by mDefaultChild or mIndexInInserted depending on diff --git a/dom/base/FragmentOrElement.cpp b/dom/base/FragmentOrElement.cpp index 766e2b115..ecb18798f 100644 --- a/dom/base/FragmentOrElement.cpp +++ b/dom/base/FragmentOrElement.cpp @@ -183,6 +183,24 @@ nsIContent::GetAssignedSlotByMode() const } nsINode* +nsIContent::GetFlattenedTreeParentForMaybeAssignedNode() const +{ + if (HTMLSlotElement* assignedSlot = GetAssignedSlot()) { + return assignedSlot; + } + + HTMLSlotElement* parentSlot = HTMLSlotElement::FromContent(GetParent()); + if (!parentSlot) { + return nullptr; + } + + // If this is not an unassigned node, then it must be a fallback content. + MOZ_ASSERT(parentSlot->AssignedNodes().IsEmpty()); + + return parentSlot; +} + +nsINode* nsIContent::GetFlattenedTreeParentNodeInternal(FlattenedParentType aType) const { nsINode* parentNode = GetParentNode(); @@ -233,16 +251,10 @@ nsIContent::GetFlattenedTreeParentNodeInternal(FlattenedParentType aType) const if (parent && nsContentUtils::HasDistributedChildren(parent) && nsContentUtils::IsInSameAnonymousTree(parent, this)) { - // This node is distributed to insertion points, thus we - // need to consult the destination insertion points list to - // figure out where this node was inserted in the flattened tree. - // It may be the case that |parent| distributes its children - // but the child does not match any insertion points, thus - // the flattened tree parent is nullptr. - nsTArray<nsIContent*>* destInsertionPoints = GetExistingDestInsertionPoints(); - parent = destInsertionPoints && !destInsertionPoints->IsEmpty() ? - destInsertionPoints->LastElement()->GetParent() : nullptr; - } else if (HasFlag(NODE_MAY_BE_IN_BINDING_MNGR)) { + return GetFlattenedTreeParentForMaybeAssignedNode(); + } + + if (HasFlag(NODE_MAY_BE_IN_BINDING_MNGR)) { nsIContent* insertionParent = GetXBLInsertionParent(); if (insertionParent) { parent = insertionParent; @@ -890,42 +902,10 @@ nsIContent::GetEventTargetParent(EventChainPreVisitor& aVisitor) } } - nsIContent* parent = GetParent(); - - // Web components have a special event chain that need to account - // for destination insertion points where nodes have been distributed. - nsTArray<nsIContent*>* destPoints = GetExistingDestInsertionPoints(); - if (destPoints && !destPoints->IsEmpty()) { - // Push destination insertion points to aVisitor.mDestInsertionPoints. - for (uint32_t i = 0; i < destPoints->Length(); i++) { - nsIContent* point = destPoints->ElementAt(i); - aVisitor.mDestInsertionPoints.AppendElement(point); - } - } - - ShadowRoot* thisShadowRoot = ShadowRoot::FromNode(this); - if (thisShadowRoot) { - if (!aVisitor.mEvent->mFlags.mComposed) { - // If we do stop propagation, we still want to propagate - // the event to chrome (nsPIDOMWindow::GetParentTarget()). - // The load event is special in that we don't ever propagate it - // to chrome. - nsCOMPtr<nsPIDOMWindowOuter> win = OwnerDoc()->GetWindow(); - EventTarget* parentTarget = win && aVisitor.mEvent->mMessage != eLoad - ? win->GetParentTarget() : nullptr; - - aVisitor.mParentTarget = parentTarget; - return NS_OK; - } - - if (!aVisitor.mDestInsertionPoints.IsEmpty()) { - parent = aVisitor.mDestInsertionPoints.LastElement(); - aVisitor.mDestInsertionPoints.SetLength( - aVisitor.mDestInsertionPoints.Length() - 1); - } else { - parent = thisShadowRoot->GetHost(); - } - } + // Event parent is the assigned slot, if node is assigned, or node's parent + // otherwise. + HTMLSlotElement* slot = GetAssignedSlot(); + nsIContent* parent = slot ? slot : GetParent(); // Event may need to be retargeted if this is the root of a native // anonymous content subtree or event is dispatched somewhere inside XBL. diff --git a/dom/base/ShadowRoot.cpp b/dom/base/ShadowRoot.cpp index 97214e050..308f57cf7 100644 --- a/dom/base/ShadowRoot.cpp +++ b/dom/base/ShadowRoot.cpp @@ -14,7 +14,9 @@ #include "nsIDOMHTMLElement.h" #include "nsIStyleSheetLinkingElement.h" #include "mozilla/dom/Element.h" +#include "mozilla/dom/HTMLSlotElement.h" #include "nsXBLPrototypeBinding.h" +#include "mozilla/EventDispatcher.h" #include "mozilla/StyleSheet.h" #include "mozilla/StyleSheetInlines.h" @@ -110,6 +112,87 @@ ShadowRoot::FromNode(nsINode* aNode) } void +ShadowRoot::AddSlot(HTMLSlotElement* aSlot) +{ + MOZ_ASSERT(aSlot); + + // Note that if name attribute missing, the slot is a default slot. + nsAutoString name; + aSlot->GetName(name); + + nsTArray<HTMLSlotElement*>* currentSlots = mSlotMap.LookupOrAdd(name); + MOZ_ASSERT(currentSlots); + + HTMLSlotElement* oldSlot = currentSlots->IsEmpty() ? + nullptr : currentSlots->ElementAt(0); + + TreeOrderComparator comparator; + currentSlots->InsertElementSorted(aSlot, comparator); + + HTMLSlotElement* currentSlot = currentSlots->ElementAt(0); + if (currentSlot != aSlot) { + return; + } + + if (oldSlot && oldSlot != currentSlot) { + // Move assigned nodes from old slot to new slot. + const nsTArray<RefPtr<nsINode>>& assignedNodes = oldSlot->AssignedNodes(); + while (assignedNodes.Length() > 0) { + nsINode* assignedNode = assignedNodes[0]; + + oldSlot->RemoveAssignedNode(assignedNode); + currentSlot->AppendAssignedNode(assignedNode); + } + } else { + // Otherwise add appropriate nodes to this slot from the host. + for (nsIContent* child = GetHost()->GetFirstChild(); + child; + child = child->GetNextSibling()) { + nsAutoString slotName; + child->GetAttr(kNameSpaceID_None, nsGkAtoms::slot, slotName); + if (child->IsSlotable() && slotName.Equals(name)) { + currentSlot->AppendAssignedNode(child); + } + } + } +} + +void +ShadowRoot::RemoveSlot(HTMLSlotElement* aSlot) +{ + MOZ_ASSERT(aSlot); + + nsAutoString name; + aSlot->GetName(name); + + nsTArray<HTMLSlotElement*>* currentSlots = mSlotMap.Get(name); + + if (currentSlots) { + if (currentSlots->Length() == 1) { + MOZ_ASSERT(currentSlots->ElementAt(0) == aSlot); + mSlotMap.Remove(name); + aSlot->ClearAssignedNodes(); + } else { + bool doReplaceSlot = currentSlots->ElementAt(0) == aSlot; + currentSlots->RemoveElement(aSlot); + HTMLSlotElement* replacementSlot = currentSlots->ElementAt(0); + + // Move assigned nodes from removed slot to the next slot in + // tree order with the same name. + if (doReplaceSlot) { + const nsTArray<RefPtr<nsINode>>& assignedNodes = aSlot->AssignedNodes(); + while (assignedNodes.Length() > 0) { + nsINode* assignedNode = assignedNodes[0]; + + aSlot->RemoveAssignedNode(assignedNode); + replacementSlot->AppendAssignedNode(assignedNode); + } + } + } + } +} + +void ShadowRoot::StyleSheetChanged() { mProtoBinding->FlushSkinSheets(); @@ -222,6 +305,128 @@ ShadowRoot::GetElementsByClassName(const nsAString& aClasses) return nsContentUtils::GetElementsByClassName(this, aClasses); } +nsresult +ShadowRoot::GetEventTargetParent(EventChainPreVisitor& aVisitor) +{ + aVisitor.mCanHandle = true; + + // https://dom.spec.whatwg.org/#ref-for-get-the-parent%E2%91%A6 + if (!aVisitor.mEvent->mFlags.mComposed) { + nsCOMPtr<nsIContent> originalTarget = + do_QueryInterface(aVisitor.mEvent->mOriginalTarget); + if (originalTarget->GetContainingShadow() == this) { + // If we do stop propagation, we still want to propagate + // the event to chrome (nsPIDOMWindow::GetParentTarget()). + // The load event is special in that we don't ever propagate it + // to chrome. + nsCOMPtr<nsPIDOMWindowOuter> win = OwnerDoc()->GetWindow(); + EventTarget* parentTarget = win && aVisitor.mEvent->mMessage != eLoad + ? win->GetParentTarget() : nullptr; + + aVisitor.mParentTarget = parentTarget; + return NS_OK; + } + } + + nsIContent* shadowHost = GetHost(); + aVisitor.mParentTarget = shadowHost; + + if (aVisitor.mOriginalTargetIsInAnon) { + nsCOMPtr<nsIContent> content(do_QueryInterface(aVisitor.mEvent->mTarget)); + if (content && content->GetBindingParent() == shadowHost) { + aVisitor.mEventTargetAtParent = shadowHost; + } + } + + return NS_OK; +} + +const HTMLSlotElement* +ShadowRoot::AssignSlotFor(nsIContent* aContent) +{ + nsAutoString slotName; + // Note that if slot attribute is missing, assign it to the first default + // slot, if exists. + aContent->GetAttr(kNameSpaceID_None, nsGkAtoms::slot, slotName); + nsTArray<HTMLSlotElement*>* slots = mSlotMap.Get(slotName); + if (!slots) { + return nullptr; + } + + HTMLSlotElement* slot = slots->ElementAt(0); + MOZ_ASSERT(slot); + + // Find the appropriate position in the assigned node list for the + // newly assigned content. + const nsTArray<RefPtr<nsINode>>& assignedNodes = slot->AssignedNodes(); + nsIContent* currentContent = GetHost()->GetFirstChild(); + bool indexFound = false; + uint32_t insertionIndex; + for (uint32_t i = 0; i < assignedNodes.Length(); i++) { + // Seek through the host's explicit children until the + // assigned content is found. + while (currentContent && currentContent != assignedNodes[i]) { + if (currentContent == aContent) { + indexFound = true; + insertionIndex = i; + } + + currentContent = currentContent->GetNextSibling(); + } + + if (indexFound) { + break; + } + } + + if (indexFound) { + slot->InsertAssignedNode(insertionIndex, aContent); + } else { + slot->AppendAssignedNode(aContent); + } + + return slot; +} + +const HTMLSlotElement* +ShadowRoot::UnassignSlotFor(nsIContent* aNode, const nsAString& aSlotName) +{ + // Find the insertion point to which the content belongs. Note that if slot + // attribute is missing, unassign it from the first default slot, if exists. + nsTArray<HTMLSlotElement*>* slots = mSlotMap.Get(aSlotName); + if (!slots) { + return nullptr; + } + + HTMLSlotElement* slot = slots->ElementAt(0); + MOZ_ASSERT(slot); + + if (!slot->AssignedNodes().Contains(aNode)) { + return nullptr; + } + + slot->RemoveAssignedNode(aNode); + return slot; +} + +bool +ShadowRoot::MaybeReassignElement(Element* aElement, + const nsAttrValue* aOldValue) +{ + nsIContent* parent = aElement->GetParent(); + if (parent && parent == GetHost()) { + const HTMLSlotElement* oldSlot = UnassignSlotFor(aElement, + aOldValue ? aOldValue->GetStringValue() : EmptyString()); + const HTMLSlotElement* newSlot = AssignSlotFor(aElement); + + if (oldSlot != newSlot) { + return true; + } + } + + return false; +} + void ShadowRoot::DistributionChanged() { @@ -333,7 +538,12 @@ ShadowRoot::AttributeChanged(nsIDocument* aDocument, int32_t aModType, const nsAttrValue* aOldValue) { - if (!IsPooledNode(aElement)) { + if (aNameSpaceID != kNameSpaceID_None || aAttribute != nsGkAtoms::slot) { + return; + } + + // Attributes may change insertion point matching, find its new distribution. + if (!MaybeReassignElement(aElement, aOldValue)) { return; } @@ -369,11 +579,18 @@ ShadowRoot::ContentInserted(nsIDocument* aDocument, nsIContent* aChild, int32_t aIndexInContainer) { - if (mInsertionPointChanged) { - DistributeAllNodes(); - mInsertionPointChanged = false; + // Check to ensure that the content is in the same anonymous tree + // as the container because anonymous content may report its container + // as the host but it may not be in the host's child list. + if (!nsContentUtils::IsInSameAnonymousTree(aContainer, aChild)) { return; } + + if (!aChild->IsSlotable() || aContainer != GetHost()) { + return; + } + + AssignSlotFor(aChild); } void @@ -383,11 +600,20 @@ ShadowRoot::ContentRemoved(nsIDocument* aDocument, int32_t aIndexInContainer, nsIContent* aPreviousSibling) { - if (mInsertionPointChanged) { - DistributeAllNodes(); - mInsertionPointChanged = false; + // Check to ensure that the content is in the same anonymous tree + // as the container because anonymous content may report its container + // as the host but it may not be in the host's child list. + if (!nsContentUtils::IsInSameAnonymousTree(aContainer, aChild)) { return; } + + if (!aChild->IsSlotable() || aContainer != GetHost()) { + return; + } + + nsAutoString slotName; + aChild->GetAttr(kNameSpaceID_None, nsGkAtoms::slot, slotName); + UnassignSlotFor(aChild, slotName); } nsresult diff --git a/dom/base/ShadowRoot.h b/dom/base/ShadowRoot.h index 5efff5be7..c525ba8e8 100644 --- a/dom/base/ShadowRoot.h +++ b/dom/base/ShadowRoot.h @@ -21,6 +21,9 @@ class nsIContent; class nsXBLPrototypeBinding; namespace mozilla { + +class EventChainPreVisitor; + namespace dom { class Element; @@ -73,10 +76,26 @@ public: private: /** - * Redistributes a node of the pool, and returns whether the distribution + * Try to reassign an element to a slot and returns whether the assignment * changed. */ - bool RedistributeElement(Element*); + bool MaybeReassignElement(Element* aElement, const nsAttrValue* aOldValue); + + /** + * Try to assign aContent to a slot in the shadow tree, returns the assigned + * slot if found. + */ + const HTMLSlotElement* AssignSlotFor(nsIContent* aContent); + + /** + * Unassign aContent from the assigned slot in the shadow tree, returns the + * assigned slot if found. + * + * Note: slot attribute of aContent may have changed already, so pass slot + * name explicity here. + */ + const HTMLSlotElement* UnassignSlotFor(nsIContent* aContent, + const nsAString& aSlotName); /** * Called when we redistribute content after insertion points have changed. @@ -86,6 +105,9 @@ private: bool IsPooledNode(nsIContent* aChild) const; public: + void AddSlot(HTMLSlotElement* aSlot); + void RemoveSlot(HTMLSlotElement* aSlot); + void SetInsertionPointChanged() { mInsertionPointChanged = true; } void SetAssociatedBinding(nsXBLBinding* aBinding) { mAssociatedBinding = aBinding; } @@ -113,11 +135,18 @@ public: mIsComposedDocParticipant = aIsComposedDocParticipant; } + nsresult GetEventTargetParent(EventChainPreVisitor& aVisitor) override; + protected: virtual ~ShadowRoot(); ShadowRootMode mMode; + // Map from name of slot to an array of all slots in the shadow DOM with with + // the given name. The slots are stored as a weak pointer because the elements + // are in the shadow tree and should be kept alive by its parent. + nsClassHashtable<nsStringHashKey, nsTArray<mozilla::dom::HTMLSlotElement*>> mSlotMap; + nsTHashtable<nsIdentifierMapEntry> mIdentifierMap; nsXBLPrototypeBinding* mProtoBinding; diff --git a/dom/base/nsContentUtils.cpp b/dom/base/nsContentUtils.cpp index 038da24b4..62be71b4a 100644 --- a/dom/base/nsContentUtils.cpp +++ b/dom/base/nsContentUtils.cpp @@ -44,6 +44,7 @@ #include "mozilla/dom/Element.h" #include "mozilla/dom/FileSystemSecurity.h" #include "mozilla/dom/HTMLMediaElement.h" +#include "mozilla/dom/HTMLSlotElement.h" #include "mozilla/dom/HTMLTemplateElement.h" #include "mozilla/dom/ipc/BlobChild.h" #include "mozilla/dom/ipc/BlobParent.h" @@ -7053,6 +7054,13 @@ nsContentUtils::HasDistributedChildren(nsIContent* aContent) return true; } + HTMLSlotElement* slotEl = HTMLSlotElement::FromContent(aContent); + if (slotEl && slotEl->GetContainingShadow()) { + // Children of a slot are rendered if the slot does not have any assigned + // nodes (fallback content). + return slotEl->AssignedNodes().IsEmpty(); + } + return false; } diff --git a/dom/base/nsIContent.h b/dom/base/nsIContent.h index 8da0ba7f2..975173656 100644 --- a/dom/base/nsIContent.h +++ b/dom/base/nsIContent.h @@ -999,6 +999,12 @@ protected: */ nsIAtom* DoGetID() const; + /** + * Returns the assigned slot, if it exists, or the direct parent, if it's a + * fallback content of a slot. + */ + nsINode* GetFlattenedTreeParentForMaybeAssignedNode() const; + public: #ifdef DEBUG /** diff --git a/dom/base/nsINode.h b/dom/base/nsINode.h index 7c3eb9134..a07bcae51 100644 --- a/dom/base/nsINode.h +++ b/dom/base/nsINode.h @@ -408,6 +408,12 @@ public: */ virtual bool IsNodeOfType(uint32_t aFlags) const = 0; + bool + IsSlotable() const + { + return IsElement() || IsNodeOfType(eTEXT); + } + virtual JSObject* WrapObject(JSContext *aCx, JS::Handle<JSObject*> aGivenProto) override; /** diff --git a/dom/events/EventDispatcher.h b/dom/events/EventDispatcher.h index db7b47dbf..618c863d5 100644 --- a/dom/events/EventDispatcher.h +++ b/dom/events/EventDispatcher.h @@ -204,13 +204,6 @@ public: * which should be used when the event is handled at mParentTarget. */ dom::EventTarget* mEventTargetAtParent; - - /** - * An array of destination insertion points that need to be inserted - * into the event path of nodes that are distributed by the - * web components distribution algorithm. - */ - nsTArray<nsIContent*> mDestInsertionPoints; }; class EventChainPostVisitor : public mozilla::EventChainVisitor diff --git a/dom/html/HTMLSlotElement.cpp b/dom/html/HTMLSlotElement.cpp index 9f24f8ba3..1ffde7274 100644 --- a/dom/html/HTMLSlotElement.cpp +++ b/dom/html/HTMLSlotElement.cpp @@ -7,6 +7,7 @@ #include "mozilla/dom/HTMLSlotElement.h" #include "mozilla/dom/HTMLSlotElementBinding.h" #include "mozilla/dom/HTMLUnknownElement.h" +#include "mozilla/dom/ShadowRoot.h" #include "nsGkAtoms.h" #include "nsDocument.h" @@ -15,12 +16,10 @@ NS_NewHTMLSlotElement(already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo, mozilla::dom::FromParser aFromParser) { RefPtr<mozilla::dom::NodeInfo> nodeInfo(aNodeInfo); - /* Disabled for now if (nsDocument::IsWebComponentsEnabled(nodeInfo)) { already_AddRefed<mozilla::dom::NodeInfo> nodeInfoArg(nodeInfo.forget()); return new mozilla::dom::HTMLSlotElement(nodeInfoArg); } - */ already_AddRefed<mozilla::dom::NodeInfo> nodeInfoArg(nodeInfo.forget()); return new mozilla::dom::HTMLUnknownElement(nodeInfoArg); @@ -50,13 +49,161 @@ NS_INTERFACE_MAP_END_INHERITING(nsGenericHTMLElement) NS_IMPL_ELEMENT_CLONE(HTMLSlotElement) +nsresult +HTMLSlotElement::BindToTree(nsIDocument* aDocument, + nsIContent* aParent, + nsIContent* aBindingParent, + bool aCompileEventHandlers) +{ + RefPtr<ShadowRoot> oldContainingShadow = GetContainingShadow(); + + nsresult rv = nsGenericHTMLElement::BindToTree(aDocument, aParent, + aBindingParent, + aCompileEventHandlers); + NS_ENSURE_SUCCESS(rv, rv); + + ShadowRoot* containingShadow = GetContainingShadow(); + if (containingShadow && !oldContainingShadow) { + containingShadow->AddSlot(this); + } + + return NS_OK; +} + +void +HTMLSlotElement::UnbindFromTree(bool aDeep, bool aNullParent) +{ + RefPtr<ShadowRoot> oldContainingShadow = GetContainingShadow(); + + nsGenericHTMLElement::UnbindFromTree(aDeep, aNullParent); + + if (oldContainingShadow && !GetContainingShadow()) { + oldContainingShadow->RemoveSlot(this); + } +} + +nsresult +HTMLSlotElement::BeforeSetAttr(int32_t aNameSpaceID, nsIAtom* aName, + const nsAttrValueOrString* aValue, + bool aNotify) +{ + if (aNameSpaceID == kNameSpaceID_None && aName == nsGkAtoms::name) { + if (ShadowRoot* containingShadow = GetContainingShadow()) { + containingShadow->RemoveSlot(this); + } + } + + return nsGenericHTMLElement::BeforeSetAttr(aNameSpaceID, aName, aValue, + aNotify); +} + +nsresult +HTMLSlotElement::AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName, + const nsAttrValue* aValue, + const nsAttrValue* aOldValue, + bool aNotify) +{ + + if (aNameSpaceID == kNameSpaceID_None && aName == nsGkAtoms::name) { + if (ShadowRoot* containingShadow = GetContainingShadow()) { + containingShadow->AddSlot(this); + } + } + + return nsGenericHTMLElement::AfterSetAttr(aNameSpaceID, aName, aValue, + aOldValue, aNotify); +} + +/** + * Flatten assigned nodes given a slot, as in: + * https://dom.spec.whatwg.org/#find-flattened-slotables + */ +static void +FlattenAssignedNodes(HTMLSlotElement* aSlot, nsTArray<RefPtr<nsINode>>& aNodes) +{ + if (!aSlot->GetContainingShadow()) { + return; + } + + const nsTArray<RefPtr<nsINode>>& assignedNodes = aSlot->AssignedNodes(); + + // If assignedNodes is empty, use children of slot as fallback content. + if (assignedNodes.IsEmpty()) { + for (nsIContent* child = aSlot->AsContent()->GetFirstChild(); + child; + child = child->GetNextSibling()) { + if (!child->IsSlotable()) { + continue; + } + + if (child->IsHTMLElement(nsGkAtoms::slot)) { + FlattenAssignedNodes(HTMLSlotElement::FromContent(child), aNodes); + } else { + aNodes.AppendElement(child); + } + } + return; + } + + for (uint32_t i = 0; i < assignedNodes.Length(); i++) { + nsINode* assignedNode = assignedNodes[i]; + if (assignedNode->IsHTMLElement(nsGkAtoms::slot)) { + FlattenAssignedNodes( + HTMLSlotElement::FromContent(assignedNode->AsContent()), aNodes); + } else { + aNodes.AppendElement(assignedNode); + } + } +} + void HTMLSlotElement::AssignedNodes(const AssignedNodesOptions& aOptions, nsTArray<RefPtr<nsINode>>& aNodes) { + if (aOptions.mFlatten) { + return FlattenAssignedNodes(this, aNodes); + } + aNodes = mAssignedNodes; } +const nsTArray<RefPtr<nsINode>>& +HTMLSlotElement::AssignedNodes() const +{ + return mAssignedNodes; +} + +void +HTMLSlotElement::InsertAssignedNode(uint32_t aIndex, nsINode* aNode) +{ + mAssignedNodes.InsertElementAt(aIndex, aNode); + aNode->AsContent()->SetAssignedSlot(this); +} + +void +HTMLSlotElement::AppendAssignedNode(nsINode* aNode) +{ + mAssignedNodes.AppendElement(aNode); + aNode->AsContent()->SetAssignedSlot(this); +} + +void +HTMLSlotElement::RemoveAssignedNode(nsINode* aNode) +{ + mAssignedNodes.RemoveElement(aNode); + aNode->AsContent()->SetAssignedSlot(nullptr); +} + +void +HTMLSlotElement::ClearAssignedNodes() +{ + for (uint32_t i = 0; i < mAssignedNodes.Length(); i++) { + mAssignedNodes[i]->AsContent()->SetAssignedSlot(nullptr); + } + + mAssignedNodes.Clear(); +} + JSObject* HTMLSlotElement::WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) { diff --git a/dom/html/HTMLSlotElement.h b/dom/html/HTMLSlotElement.h index 187a295db..4f8546200 100644 --- a/dom/html/HTMLSlotElement.h +++ b/dom/html/HTMLSlotElement.h @@ -26,6 +26,22 @@ public: NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(HTMLSlotElement, nsGenericHTMLElement) virtual nsresult Clone(mozilla::dom::NodeInfo* aNodeInfo, nsINode** aResult) const override; + // nsIContent + virtual nsresult BindToTree(nsIDocument* aDocument, nsIContent* aParent, + nsIContent* aBindingParent, + bool aCompileEventHandlers) override; + virtual void UnbindFromTree(bool aDeep = true, + bool aNullParent = true) override; + + virtual nsresult BeforeSetAttr(int32_t aNameSpaceID, nsIAtom* aName, + const nsAttrValueOrString* aValue, + bool aNotify) override; + virtual nsresult AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName, + const nsAttrValue* aValue, + const nsAttrValue* aOldValue, + bool aNotify) override; + + // WebIDL void SetName(const nsAString& aName, ErrorResult& aRv) { SetHTMLAttr(nsGkAtoms::name, aName, aRv); @@ -39,6 +55,13 @@ public: void AssignedNodes(const AssignedNodesOptions& aOptions, nsTArray<RefPtr<nsINode>>& aNodes); + // Helper methods + const nsTArray<RefPtr<nsINode>>& AssignedNodes() const; + void InsertAssignedNode(uint32_t aIndex, nsINode* aNode); + void AppendAssignedNode(nsINode* aNode); + void RemoveAssignedNode(nsINode* aNode); + void ClearAssignedNodes(); + protected: virtual ~HTMLSlotElement(); virtual JSObject* |