summaryrefslogtreecommitdiffstats
path: root/dom
diff options
context:
space:
mode:
authorMatt A. Tobin <email@mattatobin.com>2020-04-17 07:10:54 -0400
committerMatt A. Tobin <email@mattatobin.com>2020-04-17 07:10:54 -0400
commite31ed5b07466d4a579fe4b025f97c971003fbc3f (patch)
treeabd621b87578770973591fd03608993334f6af64 /dom
parent8beb65dd501cbdcfd6a793027b5de2a1fdfc7149 (diff)
downloadUXP-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.cpp67
-rw-r--r--dom/base/ChildIterator.h21
-rw-r--r--dom/base/FragmentOrElement.cpp72
-rw-r--r--dom/base/ShadowRoot.cpp240
-rw-r--r--dom/base/ShadowRoot.h33
-rw-r--r--dom/base/nsContentUtils.cpp8
-rw-r--r--dom/base/nsIContent.h6
-rw-r--r--dom/base/nsINode.h6
-rw-r--r--dom/events/EventDispatcher.h7
-rw-r--r--dom/html/HTMLSlotElement.cpp151
-rw-r--r--dom/html/HTMLSlotElement.h23
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*