From ccc506dc385022c0b08c193571e803daa2f8583c Mon Sep 17 00:00:00 2001 From: William Chen Date: Wed, 21 May 2014 23:11:53 -0700 Subject: [PATCH] Bug 999271 - Implement web components getDestinationInsertionPoints() extension to Element interface. r=mrbkap --- content/base/public/Element.h | 29 ++++++- content/base/public/FragmentOrElement.h | 8 ++ content/base/public/nsIContent.h | 18 ++++- content/base/src/Element.cpp | 78 +++++++++++++++++++ content/base/src/FragmentOrElement.cpp | 17 ++++ content/base/src/ShadowRoot.cpp | 63 ++++++++++++--- content/base/src/ShadowRoot.h | 3 + content/base/src/nsGenericDOMDataNode.cpp | 17 ++++ content/base/src/nsGenericDOMDataNode.h | 7 ++ .../html/content/src/HTMLContentElement.cpp | 65 +++++++++++++++- content/html/content/src/HTMLContentElement.h | 12 +++ .../html/content/src/HTMLShadowElement.cpp | 47 ++++++++++- .../mochitest/webcomponents/mochitest.ini | 3 + .../test_dest_insertion_points.html | 73 +++++++++++++++++ .../test_dest_insertion_points_shadow.html | 68 ++++++++++++++++ .../test_fallback_dest_insertion_points.html | 71 +++++++++++++++++ dom/webidl/Element.webidl | 4 +- 17 files changed, 561 insertions(+), 22 deletions(-) create mode 100644 dom/tests/mochitest/webcomponents/test_dest_insertion_points.html create mode 100644 dom/tests/mochitest/webcomponents/test_dest_insertion_points_shadow.html create mode 100644 dom/tests/mochitest/webcomponents/test_fallback_dest_insertion_points.html diff --git a/content/base/public/Element.h b/content/base/public/Element.h index 941af34605f3..5eb59bde5d59 100644 --- a/content/base/public/Element.h +++ b/content/base/public/Element.h @@ -114,11 +114,12 @@ class Link; class UndoManager; class DOMRect; class DOMRectList; +class DestinationInsertionPointList; // IID for the dom::Element interface #define NS_ELEMENT_IID \ -{ 0xf7c18f0f, 0xa8fd, 0x4a95, \ - { 0x91, 0x72, 0xd3, 0xa7, 0x4a, 0xb8, 0xc4, 0xbe } } +{ 0xd123f791, 0x124a, 0x43f3, \ + { 0x84, 0xe3, 0x55, 0x81, 0x0b, 0x6c, 0xf3, 0x08 } } class Element : public FragmentOrElement { @@ -693,6 +694,7 @@ public: already_AddRefed GetBoundingClientRect(); already_AddRefed CreateShadowRoot(ErrorResult& aError); + already_AddRefed GetDestinationInsertionPoints(); void ScrollIntoView() { @@ -1185,6 +1187,29 @@ private: EventStates mState; }; +class DestinationInsertionPointList : public nsINodeList +{ +public: + DestinationInsertionPointList(Element* aElement); + virtual ~DestinationInsertionPointList(); + + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_CLASS(DestinationInsertionPointList) + + // nsIDOMNodeList + NS_DECL_NSIDOMNODELIST + + // nsINodeList + virtual nsIContent* Item(uint32_t aIndex); + virtual int32_t IndexOf(nsIContent* aContent); + virtual nsINode* GetParentObject() { return mParent; } + virtual uint32_t Length() const; + virtual JSObject* WrapObject(JSContext* aCx) MOZ_OVERRIDE; +protected: + nsRefPtr mParent; + nsCOMArray mDestinationPoints; +}; + NS_DEFINE_STATIC_IID_ACCESSOR(Element, NS_ELEMENT_IID) inline bool diff --git a/content/base/public/FragmentOrElement.h b/content/base/public/FragmentOrElement.h index 916ec1293bd7..e73f9b9342f3 100644 --- a/content/base/public/FragmentOrElement.h +++ b/content/base/public/FragmentOrElement.h @@ -209,6 +209,8 @@ public: nsBindingManager* aOldBindingManager = nullptr) MOZ_OVERRIDE; virtual ShadowRoot *GetShadowRoot() const MOZ_OVERRIDE; virtual ShadowRoot *GetContainingShadow() const MOZ_OVERRIDE; + virtual nsTArray &DestInsertionPoints() MOZ_OVERRIDE; + virtual nsTArray *GetExistingDestInsertionPoints() const MOZ_OVERRIDE; virtual void SetShadowRoot(ShadowRoot* aBinding) MOZ_OVERRIDE; virtual nsIContent *GetXBLInsertionParent() const MOZ_OVERRIDE; virtual void SetXBLInsertionParent(nsIContent* aContent) MOZ_OVERRIDE; @@ -375,6 +377,12 @@ public: */ nsRefPtr mContainingShadow; + /** + * An array of web component insertion points to which this element + * is distributed. + */ + nsTArray mDestInsertionPoints; + /** * XBL binding installed on the element. */ diff --git a/content/base/public/nsIContent.h b/content/base/public/nsIContent.h index 01385e4b3036..5cb959108d5e 100644 --- a/content/base/public/nsIContent.h +++ b/content/base/public/nsIContent.h @@ -39,8 +39,8 @@ enum nsLinkState { // IID for the nsIContent interface #define NS_ICONTENT_IID \ -{ 0x1329e5b7, 0x4bcd, 0x450c, \ - { 0xa2, 0x3a, 0x98, 0xc5, 0x85, 0xcd, 0x73, 0xf9 } } +{ 0xc534a378, 0x7b5f, 0x43a4, \ + { 0xaf, 0x65, 0x5f, 0xfe, 0xea, 0xd6, 0x00, 0xfb } } /** * A node of content in a document's content model. This interface @@ -668,6 +668,20 @@ public: */ virtual mozilla::dom::ShadowRoot *GetContainingShadow() const = 0; + /** + * Gets an array of destination insertion points where this content + * is distributed by web component distribution algorithms. + * The array is created if it does not already exist. + */ + virtual nsTArray &DestInsertionPoints() = 0; + + /** + * Same as DestInsertionPoints except that this method will return + * null if the array of destination insertion points does not already + * exist. + */ + virtual nsTArray *GetExistingDestInsertionPoints() const = 0; + /** * Gets the insertion parent element of the XBL binding. * The insertion parent is our one true parent in the transformed DOM. diff --git a/content/base/src/Element.cpp b/content/base/src/Element.cpp index e843c7f936cf..b980b4dab2d7 100644 --- a/content/base/src/Element.cpp +++ b/content/base/src/Element.cpp @@ -125,6 +125,7 @@ #include "mozilla/CORSMode.h" #include "mozilla/dom/ShadowRoot.h" +#include "mozilla/dom/NodeListBinding.h" #include "nsStyledElement.h" #include "nsXBLService.h" @@ -826,6 +827,83 @@ Element::CreateShadowRoot(ErrorResult& aError) return shadowRoot.forget(); } +NS_IMPL_CYCLE_COLLECTION(DestinationInsertionPointList, mParent, mDestinationPoints) + +NS_INTERFACE_TABLE_HEAD(DestinationInsertionPointList) + NS_INTERFACE_TABLE(DestinationInsertionPointList, nsINodeList) + NS_INTERFACE_TABLE_TO_MAP_SEGUE_CYCLE_COLLECTION(DestinationInsertionPointList) +NS_INTERFACE_MAP_END + +NS_IMPL_CYCLE_COLLECTING_ADDREF(DestinationInsertionPointList) +NS_IMPL_CYCLE_COLLECTING_RELEASE(DestinationInsertionPointList) + +DestinationInsertionPointList::DestinationInsertionPointList(Element* aElement) + : mParent(aElement) +{ + SetIsDOMBinding(); + + nsTArray* destPoints = aElement->GetExistingDestInsertionPoints(); + if (destPoints) { + for (uint32_t i = 0; i < destPoints->Length(); i++) { + mDestinationPoints.AppendElement(destPoints->ElementAt(i)); + } + } +} + +DestinationInsertionPointList::~DestinationInsertionPointList() +{ +} + +nsIContent* +DestinationInsertionPointList::Item(uint32_t aIndex) +{ + return mDestinationPoints.SafeElementAt(aIndex); +} + +NS_IMETHODIMP +DestinationInsertionPointList::Item(uint32_t aIndex, nsIDOMNode** aReturn) +{ + nsIContent* item = Item(aIndex); + if (!item) { + return NS_ERROR_FAILURE; + } + + return CallQueryInterface(item, aReturn); +} + +uint32_t +DestinationInsertionPointList::Length() const +{ + return mDestinationPoints.Length(); +} + +NS_IMETHODIMP +DestinationInsertionPointList::GetLength(uint32_t* aLength) +{ + *aLength = Length(); + return NS_OK; +} + +int32_t +DestinationInsertionPointList::IndexOf(nsIContent* aContent) +{ + return mDestinationPoints.IndexOf(aContent); +} + +JSObject* +DestinationInsertionPointList::WrapObject(JSContext* aCx) +{ + return NodeListBinding::Wrap(aCx, this); +} + +already_AddRefed +Element::GetDestinationInsertionPoints() +{ + nsRefPtr list = + new DestinationInsertionPointList(this); + return list.forget(); +} + void Element::GetAttribute(const nsAString& aName, DOMString& aReturn) { diff --git a/content/base/src/FragmentOrElement.cpp b/content/base/src/FragmentOrElement.cpp index 374e259738c7..0a15e4c3435d 100644 --- a/content/base/src/FragmentOrElement.cpp +++ b/content/base/src/FragmentOrElement.cpp @@ -1011,6 +1011,23 @@ FragmentOrElement::SetShadowRoot(ShadowRoot* aShadowRoot) slots->mShadowRoot = aShadowRoot; } +nsTArray& +FragmentOrElement::DestInsertionPoints() +{ + nsDOMSlots *slots = DOMSlots(); + return slots->mDestInsertionPoints; +} + +nsTArray* +FragmentOrElement::GetExistingDestInsertionPoints() const +{ + nsDOMSlots *slots = GetExistingDOMSlots(); + if (slots) { + return &slots->mDestInsertionPoints; + } + return nullptr; +} + void FragmentOrElement::SetXBLInsertionParent(nsIContent* aContent) { diff --git a/content/base/src/ShadowRoot.cpp b/content/base/src/ShadowRoot.cpp index c2c621cb9d6d..60ba744f2dcb 100644 --- a/content/base/src/ShadowRoot.cpp +++ b/content/base/src/ShadowRoot.cpp @@ -256,6 +256,22 @@ ShadowRoot::SetYoungerShadow(ShadowRoot* aYoungerShadow) ChangePoolHost(mYoungerShadow->GetShadowElement()); } +void +ShadowRoot::RemoveDestInsertionPoint(nsIContent* aInsertionPoint, + nsTArray& aDestInsertionPoints) +{ + // Remove the insertion point from the destination insertion points. + // Also remove all succeeding insertion points because it is no longer + // possible for the content to be distributed into deeper node trees. + int32_t index = aDestInsertionPoints.IndexOf(aInsertionPoint); + + // It's possible that we already removed the insertion point while processing + // other insertion point removals. + if (index >= 0) { + aDestInsertionPoints.SetLength(index); + } +} + void ShadowRoot::DistributeSingleNode(nsIContent* aContent) { @@ -295,7 +311,7 @@ ShadowRoot::DistributeSingleNode(nsIContent* aContent) // is found or when the current matched node is reached. if (childIterator.Seek(aContent, matchedNodes[i])) { // aContent was found before the current matched node. - matchedNodes.InsertElementAt(i, aContent); + insertionPoint->InsertMatchedNode(i, aContent); isIndexFound = true; break; } @@ -306,7 +322,7 @@ ShadowRoot::DistributeSingleNode(nsIContent* aContent) // thus it must be at the end. MOZ_ASSERT(childIterator.Seek(aContent), "Trying to match a node that is not a candidate to be matched"); - matchedNodes.AppendElement(aContent); + insertionPoint->AppendMatchedNode(aContent); } // Handle the case where the parent of the insertion point is a ShadowRoot @@ -354,7 +370,7 @@ ShadowRoot::RemoveDistributedNode(nsIContent* aContent) return; } - mInsertionPoints[i]->MatchedNodes().RemoveElement(aContent); + mInsertionPoints[i]->RemoveMatchedNode(aContent); // Handle the case where the parent of the insertion point is a ShadowRoot // that is projected into the younger ShadowRoot's shadow insertion point. @@ -412,8 +428,7 @@ ShadowRoot::DistributeAllNodes() // Assign matching nodes from node pool. for (uint32_t j = 0; j < nodePool.Length(); j++) { if (mInsertionPoints[i]->Match(nodePool[j])) { - mInsertionPoints[i]->MatchedNodes().AppendElement(nodePool[j]); - nodePool[j]->SetXBLInsertionParent(mInsertionPoints[i]); + mInsertionPoints[i]->AppendMatchedNode(nodePool[j]); nodePool.RemoveElementAt(j--); } } @@ -425,7 +440,7 @@ ShadowRoot::DistributeAllNodes() "mInsertionPoints array is to be a descendant of a" "ShadowRoot, in which case, it should have a parent"); - // If the parent of the insertion point has as ShadowRoot, the nodes distributed + // If the parent of the insertion point has a ShadowRoot, the nodes distributed // to the insertion point must be reprojected to the insertion points of the // parent's ShadowRoot. ShadowRoot* parentShadow = insertionParent->GetShadowRoot(); @@ -562,6 +577,11 @@ ShadowRoot::IsPooledNode(nsIContent* aContent, nsIContent* aContainer, return false; } + if (aContainer == aHost) { + // Any other child nodes of the host will end up in the pool. + return true; + } + if (aContainer->IsHTML(nsGkAtoms::content)) { // Fallback content will end up in pool if its parent is a child of the host. HTMLContentElement* content = static_cast(aContainer); @@ -569,11 +589,6 @@ ShadowRoot::IsPooledNode(nsIContent* aContent, nsIContent* aContainer, aContainer->GetParentNode() == aHost; } - if (aContainer == aHost) { - // Any other child nodes of the host will end up in the pool. - return true; - } - return false; } @@ -609,9 +624,18 @@ ShadowRoot::ContentAppended(nsIDocument* aDocument, // may need to be added to an insertion point. nsIContent* currentChild = aFirstNewContent; while (currentChild) { + // Add insertion point to destination insertion points of fallback content. + if (nsContentUtils::IsContentInsertionPoint(aContainer)) { + HTMLContentElement* content = static_cast(aContainer); + if (content->MatchedNodes().IsEmpty()) { + currentChild->DestInsertionPoints().AppendElement(aContainer); + } + } + if (IsPooledNode(currentChild, aContainer, mPoolHost)) { DistributeSingleNode(currentChild); } + currentChild = currentChild->GetNextSibling(); } } @@ -631,6 +655,14 @@ ShadowRoot::ContentInserted(nsIDocument* aDocument, // Watch for new nodes added to the pool because the node // may need to be added to an insertion point. if (IsPooledNode(aChild, aContainer, mPoolHost)) { + // Add insertion point to destination insertion points of fallback content. + if (nsContentUtils::IsContentInsertionPoint(aContainer)) { + HTMLContentElement* content = static_cast(aContainer); + if (content->MatchedNodes().IsEmpty()) { + aChild->DestInsertionPoints().AppendElement(aContainer); + } + } + DistributeSingleNode(aChild); } } @@ -648,6 +680,15 @@ ShadowRoot::ContentRemoved(nsIDocument* aDocument, return; } + // Clear destination insertion points for removed + // fallback content. + if (nsContentUtils::IsContentInsertionPoint(aContainer)) { + HTMLContentElement* content = static_cast(aContainer); + if (content->MatchedNodes().IsEmpty()) { + aChild->DestInsertionPoints().Clear(); + } + } + // Watch for node that is removed from the pool because // it may need to be removed from an insertion point. if (IsPooledNode(aChild, aContainer, mPoolHost)) { diff --git a/content/base/src/ShadowRoot.h b/content/base/src/ShadowRoot.h index 2505f2f6feb3..68e59ee3bffa 100644 --- a/content/base/src/ShadowRoot.h +++ b/content/base/src/ShadowRoot.h @@ -113,6 +113,9 @@ public: static ShadowRoot* FromNode(nsINode* aNode); static bool IsShadowInsertionPoint(nsIContent* aContent); + static void RemoveDestInsertionPoint(nsIContent* aInsertionPoint, + nsTArray& aDestInsertionPoints); + // WebIDL methods. Element* GetElementById(const nsAString& aElementId); already_AddRefed diff --git a/content/base/src/nsGenericDOMDataNode.cpp b/content/base/src/nsGenericDOMDataNode.cpp index 7809485e526e..2ebda28ece2f 100644 --- a/content/base/src/nsGenericDOMDataNode.cpp +++ b/content/base/src/nsGenericDOMDataNode.cpp @@ -685,6 +685,23 @@ nsGenericDOMDataNode::SetShadowRoot(ShadowRoot* aShadowRoot) { } +nsTArray& +nsGenericDOMDataNode::DestInsertionPoints() +{ + nsDataSlots *slots = DataSlots(); + return slots->mDestInsertionPoints; +} + +nsTArray* +nsGenericDOMDataNode::GetExistingDestInsertionPoints() const +{ + nsDataSlots *slots = GetExistingDataSlots(); + if (slots) { + return &slots->mDestInsertionPoints; + } + return nullptr; +} + nsXBLBinding * nsGenericDOMDataNode::GetXBLBinding() const { diff --git a/content/base/src/nsGenericDOMDataNode.h b/content/base/src/nsGenericDOMDataNode.h index 5f671aa40673..3df7e0eda656 100644 --- a/content/base/src/nsGenericDOMDataNode.h +++ b/content/base/src/nsGenericDOMDataNode.h @@ -159,6 +159,8 @@ public: nsBindingManager* aOldBindingManager = nullptr) MOZ_OVERRIDE; virtual mozilla::dom::ShadowRoot *GetContainingShadow() const MOZ_OVERRIDE; virtual mozilla::dom::ShadowRoot *GetShadowRoot() const MOZ_OVERRIDE; + virtual nsTArray &DestInsertionPoints() MOZ_OVERRIDE; + virtual nsTArray *GetExistingDestInsertionPoints() const MOZ_OVERRIDE; virtual void SetShadowRoot(mozilla::dom::ShadowRoot* aShadowRoot) MOZ_OVERRIDE; virtual nsIContent *GetXBLInsertionParent() const MOZ_OVERRIDE; virtual void SetXBLInsertionParent(nsIContent* aContent) MOZ_OVERRIDE; @@ -266,6 +268,11 @@ protected: * @see nsIContent::GetContainingShadow */ nsRefPtr mContainingShadow; + + /** + * @see nsIContent::GetDestInsertionPoints + */ + nsTArray mDestInsertionPoints; }; // Override from nsINode diff --git a/content/html/content/src/HTMLContentElement.cpp b/content/html/content/src/HTMLContentElement.cpp index 4d67b38c4f7e..29cb1fc9af87 100644 --- a/content/html/content/src/HTMLContentElement.cpp +++ b/content/html/content/src/HTMLContentElement.cpp @@ -92,9 +92,8 @@ HTMLContentElement::UnbindFromTree(bool aDeep, bool aNullParent) if (containingShadow) { containingShadow->RemoveInsertionPoint(this); - // Remove all the assigned nodes now that the - // insertion point now that the insertion point is - // no longer a descendant of a ShadowRoot. + // Remove all the matched nodes now that the + // insertion point is no longer an insertion point. ClearMatchedNodes(); containingShadow->SetInsertionPointChanged(); } @@ -105,13 +104,71 @@ HTMLContentElement::UnbindFromTree(bool aDeep, bool aNullParent) nsGenericHTMLElement::UnbindFromTree(aDeep, aNullParent); } +void +HTMLContentElement::AppendMatchedNode(nsIContent* aContent) +{ + mMatchedNodes.AppendElement(aContent); + nsTArray& destInsertionPoint = aContent->DestInsertionPoints(); + destInsertionPoint.AppendElement(this); + + if (mMatchedNodes.Length() == 1) { + // Fallback content gets dropped so we need to updated fallback + // content distribution. + UpdateFallbackDistribution(); + } +} + +void +HTMLContentElement::UpdateFallbackDistribution() +{ + for (nsIContent* child = nsINode::GetFirstChild(); + child; + child = child->GetNextSibling()) { + nsTArray& destInsertionPoint = child->DestInsertionPoints(); + destInsertionPoint.Clear(); + if (mMatchedNodes.IsEmpty()) { + destInsertionPoint.AppendElement(this); + } + } +} + +void +HTMLContentElement::RemoveMatchedNode(nsIContent* aContent) +{ + mMatchedNodes.RemoveElement(aContent); + ShadowRoot::RemoveDestInsertionPoint(this, aContent->DestInsertionPoints()); + + if (mMatchedNodes.IsEmpty()) { + // Fallback content is activated so we need to update fallback + // content distribution. + UpdateFallbackDistribution(); + } +} + +void +HTMLContentElement::InsertMatchedNode(uint32_t aIndex, nsIContent* aContent) +{ + mMatchedNodes.InsertElementAt(aIndex, aContent); + nsTArray& destInsertionPoint = aContent->DestInsertionPoints(); + destInsertionPoint.AppendElement(this); + + if (mMatchedNodes.Length() == 1) { + // Fallback content gets dropped so we need to updated fallback + // content distribution. + UpdateFallbackDistribution(); + } +} + void HTMLContentElement::ClearMatchedNodes() { for (uint32_t i = 0; i < mMatchedNodes.Length(); i++) { - mMatchedNodes[i]->SetXBLInsertionParent(nullptr); + ShadowRoot::RemoveDestInsertionPoint(this, mMatchedNodes[i]->DestInsertionPoints()); } + mMatchedNodes.Clear(); + + UpdateFallbackDistribution(); } static bool diff --git a/content/html/content/src/HTMLContentElement.h b/content/html/content/src/HTMLContentElement.h index eafbc3be121f..24828cfa9add 100644 --- a/content/html/content/src/HTMLContentElement.h +++ b/content/html/content/src/HTMLContentElement.h @@ -46,6 +46,9 @@ public: bool Match(nsIContent* aContent); bool IsInsertionPoint() const { return mIsInsertionPoint; } nsCOMArray& MatchedNodes() { return mMatchedNodes; } + void AppendMatchedNode(nsIContent* aContent); + void RemoveMatchedNode(nsIContent* aContent); + void InsertMatchedNode(uint32_t aIndex, nsIContent* aContent); void ClearMatchedNodes(); virtual nsresult SetAttr(int32_t aNameSpaceID, nsIAtom* aName, @@ -69,6 +72,15 @@ public: protected: virtual JSObject* WrapNode(JSContext *aCx) MOZ_OVERRIDE; + /** + * Updates the destination insertion points of the fallback + * content of this insertion point. If there are nodes matched + * to this insertion point, then destination insertion points + * of fallback are cleared, otherwise, this insertion point + * is a destination insertion point. + */ + void UpdateFallbackDistribution(); + /** * An array of nodes from the ShadowRoot host that match the * content insertion selector. diff --git a/content/html/content/src/HTMLShadowElement.cpp b/content/html/content/src/HTMLShadowElement.cpp index 0b39b5b03c50..8ab091967b89 100644 --- a/content/html/content/src/HTMLShadowElement.cpp +++ b/content/html/content/src/HTMLShadowElement.cpp @@ -5,6 +5,7 @@ #include "mozilla/dom/ShadowRoot.h" +#include "ChildIterator.h" #include "nsContentUtils.h" #include "mozilla/dom/HTMLShadowElement.h" #include "mozilla/dom/HTMLShadowElementBinding.h" @@ -60,10 +61,28 @@ HTMLShadowElement::SetProjectedShadow(ShadowRoot* aProjectedShadow) { if (mProjectedShadow) { mProjectedShadow->RemoveMutationObserver(this); + + // The currently projected ShadowRoot is going away, + // thus the destination insertion points need to be updated. + ExplicitChildIterator childIterator(mProjectedShadow); + for (nsIContent* content = childIterator.GetNextChild(); + content; + content = childIterator.GetNextChild()) { + ShadowRoot::RemoveDestInsertionPoint(this, content->DestInsertionPoints()); + } } mProjectedShadow = aProjectedShadow; if (mProjectedShadow) { + // A new ShadowRoot is being projected, thus its explcit + // children will be distributed to this shadow insertion point. + ExplicitChildIterator childIterator(mProjectedShadow); + for (nsIContent* content = childIterator.GetNextChild(); + content; + content = childIterator.GetNextChild()) { + content->DestInsertionPoints().AppendElement(this); + } + // Watch for mutations on the projected shadow because // it affects the nodes that are distributed to this shadow // insertion point. @@ -156,6 +175,14 @@ HTMLShadowElement::UnbindFromTree(bool aDeep, bool aNullParent) void HTMLShadowElement::DistributeSingleNode(nsIContent* aContent) { + if (aContent->DestInsertionPoints().Contains(this)) { + // Node has already been distrbuted this this node, + // we are done. + return; + } + + aContent->DestInsertionPoints().AppendElement(this); + // Handle the case where the shadow element is a child of // a node with a ShadowRoot. The nodes that have been distributed to // this shadow insertion point will need to be reprojected into the @@ -181,6 +208,8 @@ HTMLShadowElement::DistributeSingleNode(nsIContent* aContent) void HTMLShadowElement::RemoveDistributedNode(nsIContent* aContent) { + ShadowRoot::RemoveDestInsertionPoint(this, aContent->DestInsertionPoints()); + // Handle the case where the shadow element is a child of // a node with a ShadowRoot. The nodes that have been distributed to // this shadow insertion point will need to be removed from the @@ -206,6 +235,21 @@ HTMLShadowElement::RemoveDistributedNode(nsIContent* aContent) void HTMLShadowElement::DistributeAllNodes() { + // All the explicit children of the projected ShadowRoot are distributed + // into this shadow insertion point so update the destination insertion + // points. + ShadowRoot* containingShadow = GetContainingShadow(); + ShadowRoot* olderShadow = containingShadow->GetOlderShadow(); + if (olderShadow) { + ExplicitChildIterator childIterator(olderShadow); + for (nsIContent* content = childIterator.GetNextChild(); + content; + content = childIterator.GetNextChild()) { + ShadowRoot::RemoveDestInsertionPoint(this, content->DestInsertionPoints()); + content->DestInsertionPoints().AppendElement(this); + } + } + // Handle the case where the shadow element is a child of // a node with a ShadowRoot. The nodes that have been distributed to // this shadow insertion point will need to be reprojected into the @@ -218,7 +262,6 @@ HTMLShadowElement::DistributeAllNodes() // Handle the case where the parent of this shadow element is a ShadowRoot // that is projected into a shadow insertion point in the younger ShadowRoot. - ShadowRoot* containingShadow = GetContainingShadow(); ShadowRoot* youngerShadow = containingShadow->GetYoungerShadow(); if (youngerShadow && GetParent() == containingShadow) { HTMLShadowElement* youngerShadowElement = youngerShadow->GetShadowElement(); @@ -269,7 +312,7 @@ HTMLShadowElement::ContentRemoved(nsIDocument* aDocument, int32_t aIndexInContainer, nsIContent* aPreviousSibling) { - // Watch for content removed to the projected shadow (the ShadowRoot that + // Watch for content removed from the projected shadow (the ShadowRoot that // will be rendered in place of this shadow insertion point) because the // nodes may need to be removed from other insertion points. if (!ShadowRoot::IsPooledNode(aChild, aContainer, mProjectedShadow)) { diff --git a/dom/tests/mochitest/webcomponents/mochitest.ini b/dom/tests/mochitest/webcomponents/mochitest.ini index 930732095699..cb91c3aa6018 100644 --- a/dom/tests/mochitest/webcomponents/mochitest.ini +++ b/dom/tests/mochitest/webcomponents/mochitest.ini @@ -5,6 +5,9 @@ support-files = [test_bug900724.html] [test_content_element.html] [test_nested_content_element.html] +[test_dest_insertion_points.html] +[test_dest_insertion_points_shadow.html] +[test_fallback_dest_insertion_points.html] [test_dynamic_content_element_matching.html] [test_document_register.html] [test_document_register_base_queue.html] diff --git a/dom/tests/mochitest/webcomponents/test_dest_insertion_points.html b/dom/tests/mochitest/webcomponents/test_dest_insertion_points.html new file mode 100644 index 000000000000..2d4a92ed286b --- /dev/null +++ b/dom/tests/mochitest/webcomponents/test_dest_insertion_points.html @@ -0,0 +1,73 @@ + + + + + + Test for Bug 999999 + + + + +Mozilla Bug 999999 +

+
+
+
+
+
+
+ + + diff --git a/dom/tests/mochitest/webcomponents/test_dest_insertion_points_shadow.html b/dom/tests/mochitest/webcomponents/test_dest_insertion_points_shadow.html new file mode 100644 index 000000000000..75286463e65d --- /dev/null +++ b/dom/tests/mochitest/webcomponents/test_dest_insertion_points_shadow.html @@ -0,0 +1,68 @@ + + + + + + Test for Bug 999999 + + + + +Mozilla Bug 999999 +

+
+
+
+
+
+ + + diff --git a/dom/tests/mochitest/webcomponents/test_fallback_dest_insertion_points.html b/dom/tests/mochitest/webcomponents/test_fallback_dest_insertion_points.html new file mode 100644 index 000000000000..4eefa165f2e2 --- /dev/null +++ b/dom/tests/mochitest/webcomponents/test_fallback_dest_insertion_points.html @@ -0,0 +1,71 @@ + + + + + + Test for Bug 999999 + + + + +Mozilla Bug 999999 +

+
+
+
+
+
+ + + diff --git a/dom/webidl/Element.webidl b/dom/webidl/Element.webidl index 60ece2d02168..005207aa3114 100644 --- a/dom/webidl/Element.webidl +++ b/dom/webidl/Element.webidl @@ -201,11 +201,13 @@ partial interface Element { NodeList querySelectorAll(DOMString selectors); }; -// https://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/shadow/index.html#shadow-root-object +// http://w3c.github.io/webcomponents/spec/shadow/#extensions-to-element-interface partial interface Element { [Throws,Pref="dom.webcomponents.enabled"] ShadowRoot createShadowRoot(); [Pref="dom.webcomponents.enabled"] + NodeList getDestinationInsertionPoints(); + [Pref="dom.webcomponents.enabled"] readonly attribute ShadowRoot? shadowRoot; };