diff options
49 files changed, 775 insertions, 429 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* diff --git a/layout/reftests/webcomponents/basic-insertion-point-1-ref.html b/layout/reftests/webcomponents/basic-insertion-point-1-ref.html deleted file mode 100644 index 16f6afb28..000000000 --- a/layout/reftests/webcomponents/basic-insertion-point-1-ref.html +++ /dev/null @@ -1,8 +0,0 @@ -<!DOCTYPE HTML> -<html> -<body> -<div> - <div style="border: 10px solid green">Hello</div> -</div> -</body> -</html> diff --git a/layout/reftests/webcomponents/basic-insertion-point-1.html b/layout/reftests/webcomponents/basic-insertion-point-1.html deleted file mode 100644 index 727175ef3..000000000 --- a/layout/reftests/webcomponents/basic-insertion-point-1.html +++ /dev/null @@ -1,21 +0,0 @@ -<!DOCTYPE HTML> -<html> -<head> - <script> - function tweak() { - // div with style "border: 10px solid green" - var shadowDiv = document.createElement("div"); - shadowDiv.style.border = "10px solid green"; - - var insertionPoint = document.createElement("content"); - shadowDiv.appendChild(insertionPoint); - - var shadowRoot = document.getElementById('outer').createShadowRoot(); - shadowRoot.appendChild(shadowDiv); - } - </script> -</head> -<body onload="tweak()"> -<div id="outer">Hello</div> -</body> -</html> diff --git a/layout/reftests/webcomponents/basic-insertion-point-2-ref.html b/layout/reftests/webcomponents/basic-insertion-point-2-ref.html deleted file mode 100644 index 5e9213775..000000000 --- a/layout/reftests/webcomponents/basic-insertion-point-2-ref.html +++ /dev/null @@ -1,11 +0,0 @@ -<!DOCTYPE HTML> -<html> -<body> -<div> - <div style="border: 10px solid green"> - <span style="background-color: purple">Hello</span> - <span style="background-color: orange">World</span> - </div> -</div> -</body> -</html> diff --git a/layout/reftests/webcomponents/basic-insertion-point-2.html b/layout/reftests/webcomponents/basic-insertion-point-2.html deleted file mode 100644 index 595edb471..000000000 --- a/layout/reftests/webcomponents/basic-insertion-point-2.html +++ /dev/null @@ -1,24 +0,0 @@ -<!DOCTYPE HTML> -<html> -<head> - <script> - function tweak() { - // div with style "border: 10px solid green" - var shadowDiv = document.createElement("div"); - shadowDiv.style.border = "10px solid green"; - - var insertionPoint = document.createElement("content"); - shadowDiv.appendChild(insertionPoint); - - var shadowRoot = document.getElementById('outer').createShadowRoot(); - shadowRoot.appendChild(shadowDiv); - } - </script> -</head> -<body onload="tweak()"> -<div id="outer"> - <span style="background-color: purple">Hello</span> - <span style="background-color: orange">World</span> -</div> -</body> -</html> diff --git a/layout/reftests/webcomponents/basic-shadow-1.html b/layout/reftests/webcomponents/basic-shadow-1.html index 944a594d2..8949dfc6d 100644 --- a/layout/reftests/webcomponents/basic-shadow-1.html +++ b/layout/reftests/webcomponents/basic-shadow-1.html @@ -9,7 +9,7 @@ shadowDiv.style.height = "100px"; shadowDiv.style.backgroundColor = "green"; - var shadowRoot = document.getElementById('outer').createShadowRoot(); + var shadowRoot = document.getElementById('outer').attachShadow({mode: 'open'}); shadowRoot.appendChild(shadowDiv); } </script> diff --git a/layout/reftests/webcomponents/basic-shadow-2.html b/layout/reftests/webcomponents/basic-shadow-2.html index 587c47221..8e066997d 100644 --- a/layout/reftests/webcomponents/basic-shadow-2.html +++ b/layout/reftests/webcomponents/basic-shadow-2.html @@ -6,7 +6,7 @@ var shadowDiv = document.createElement("div"); shadowDiv.style.border = "10px solid green"; - var shadowRoot = document.getElementById('outer').createShadowRoot(); + var shadowRoot = document.getElementById('outer').attachShadow({mode: 'open'}); shadowRoot.appendChild(shadowDiv); var orangeDiv = document.createElement("div"); diff --git a/layout/reftests/webcomponents/basic-shadow-3.html b/layout/reftests/webcomponents/basic-shadow-3.html index 2ae53b5a9..3226c4baa 100644 --- a/layout/reftests/webcomponents/basic-shadow-3.html +++ b/layout/reftests/webcomponents/basic-shadow-3.html @@ -6,7 +6,7 @@ var shadowDiv = document.createElement("div"); shadowDiv.style.border = "10px solid green"; - var shadowRoot = document.getElementById('outer').createShadowRoot(); + var shadowRoot = document.getElementById('outer').attachShadow({mode: 'open'}); shadowRoot.appendChild(shadowDiv); var orangeDiv = document.createElement("div"); diff --git a/layout/reftests/webcomponents/basic-shadow-4.html b/layout/reftests/webcomponents/basic-shadow-4.html index 70f91773e..39dc51a9e 100644 --- a/layout/reftests/webcomponents/basic-shadow-4.html +++ b/layout/reftests/webcomponents/basic-shadow-4.html @@ -6,7 +6,7 @@ var shadowDiv = document.createElement("div"); shadowDiv.style.border = "10px solid green"; - var shadowRoot = document.getElementById('outer').createShadowRoot(); + var shadowRoot = document.getElementById('outer').attachShadow({mode: 'open'}); shadowRoot.appendChild(shadowDiv); var orangeDiv = document.createElement("div"); diff --git a/layout/reftests/webcomponents/basic-slot-1-ref.html b/layout/reftests/webcomponents/basic-slot-1-ref.html new file mode 100644 index 000000000..4f7418498 --- /dev/null +++ b/layout/reftests/webcomponents/basic-slot-1-ref.html @@ -0,0 +1,11 @@ +<!DOCTYPE html> +<html> + <head> + <style> + body { color: green; } + </style> + </head> + <body> + This text should be green + </body> +</html> diff --git a/layout/reftests/webcomponents/basic-slot-1.html b/layout/reftests/webcomponents/basic-slot-1.html new file mode 100644 index 000000000..b31f4c1c3 --- /dev/null +++ b/layout/reftests/webcomponents/basic-slot-1.html @@ -0,0 +1,6 @@ +<!DOCTYPE HTML> +<html> + <body> + <slot style="color: green">This text should be green</slot> + </body> +</html> diff --git a/layout/reftests/webcomponents/basic-slot-2-ref.html b/layout/reftests/webcomponents/basic-slot-2-ref.html new file mode 100644 index 000000000..d4d1b8c06 --- /dev/null +++ b/layout/reftests/webcomponents/basic-slot-2-ref.html @@ -0,0 +1,16 @@ +<!DOCTYPE HTML> +<html> +<head> + <style> + div { + width: 100px; + height: 100px; + background: green; + } + </style> +</head> +<body> + <p>There should be a green box below.</p> + <div></div> +</body> +</html> diff --git a/layout/reftests/webcomponents/basic-slot-2.html b/layout/reftests/webcomponents/basic-slot-2.html new file mode 100644 index 000000000..3754ace20 --- /dev/null +++ b/layout/reftests/webcomponents/basic-slot-2.html @@ -0,0 +1,7 @@ +<!DOCTYPE HTML> +<html> + <body> + <p>There should be a green box below.</p> + <slot style="display: block; width: 100px; height: 100px; background: green;"></slot> + </body> +</html> diff --git a/layout/reftests/webcomponents/basic-slot-3-ref.html b/layout/reftests/webcomponents/basic-slot-3-ref.html new file mode 100644 index 000000000..54be54848 --- /dev/null +++ b/layout/reftests/webcomponents/basic-slot-3-ref.html @@ -0,0 +1,8 @@ +<!DOCTYPE HTML> +<html> +<body> +<div> + <div style="color: green">This text should be green</div> +</div> +</body> +</html> diff --git a/layout/reftests/webcomponents/basic-slot-3.html b/layout/reftests/webcomponents/basic-slot-3.html new file mode 100644 index 000000000..c00483fe2 --- /dev/null +++ b/layout/reftests/webcomponents/basic-slot-3.html @@ -0,0 +1,18 @@ +<!DOCTYPE HTML> +<html> +<head> + <script> + function tweak() { + var slot = document.createElement("slot"); + slot.style.color = "green"; + + var shadowRoot = + document.getElementById('outer').attachShadow({mode: 'open'}); + shadowRoot.appendChild(slot); + } + </script> +</head> +<body onload="tweak()"> +<div id="outer">This text should be green</div> +</body> +</html> diff --git a/layout/reftests/webcomponents/basic-slot-4.html b/layout/reftests/webcomponents/basic-slot-4.html new file mode 100644 index 000000000..496a92651 --- /dev/null +++ b/layout/reftests/webcomponents/basic-slot-4.html @@ -0,0 +1,20 @@ +<!DOCTYPE HTML> +<html> +<head> + <script> + function tweak() { + var slot = document.createElement("slot"); + // The border shouldn't be visible, due to display: contents. + slot.style.border = "1px solid red"; + slot.style.color = "green"; + + var shadowRoot = + document.getElementById('outer').attachShadow({mode: 'open'}); + shadowRoot.appendChild(slot); + } + </script> +</head> +<body onload="tweak()"> +<div id="outer">This text should be green</div> +</body> +</html> diff --git a/layout/reftests/webcomponents/dynamic-insertion-point-distribution-1.html b/layout/reftests/webcomponents/dynamic-insertion-point-distribution-1.html index c57f72b37..aefe84f25 100644 --- a/layout/reftests/webcomponents/dynamic-insertion-point-distribution-1.html +++ b/layout/reftests/webcomponents/dynamic-insertion-point-distribution-1.html @@ -5,9 +5,11 @@ <body> <div id="host"></div> <script> - var host = document.getElementById("host"); - var root = host.createShadowRoot(); - root.innerHTML = 'a <content></content> c'; + var host, root; + + host = document.getElementById("host"); + root = host.attachShadow({mode: 'open'}); + root.innerHTML = 'a <slot></slot> c'; function tweak() { var span = document.createElement("span"); @@ -19,7 +21,7 @@ document.documentElement.removeAttribute("class"); } - window.addEventListener("MozReftestInvalidate", tweak, false); + window.addEventListener("MozReftestInvalidate", tweak); </script> </body> </html> diff --git a/layout/reftests/webcomponents/dynamic-insertion-point-distribution-2.html b/layout/reftests/webcomponents/dynamic-insertion-point-distribution-2.html index 29a850e90..d753af09c 100644 --- a/layout/reftests/webcomponents/dynamic-insertion-point-distribution-2.html +++ b/layout/reftests/webcomponents/dynamic-insertion-point-distribution-2.html @@ -5,8 +5,10 @@ <body> <div id="host"></div> <script> - var host = document.getElementById("host"); - var root = host.createShadowRoot(); + var host, root; + + host = document.getElementById("host"); + root = host.attachShadow({mode: 'open'}); root.innerHTML = "<span>a</span>"; function tweak() { @@ -20,7 +22,7 @@ document.documentElement.removeAttribute("class"); } - window.addEventListener("MozReftestInvalidate", tweak, false); + window.addEventListener("MozReftestInvalidate", tweak); </script> </body> </html> diff --git a/layout/reftests/webcomponents/dynamic-shadow-element-1-ref.html b/layout/reftests/webcomponents/dynamic-shadow-element-1-ref.html deleted file mode 100644 index a3b5150f6..000000000 --- a/layout/reftests/webcomponents/dynamic-shadow-element-1-ref.html +++ /dev/null @@ -1,10 +0,0 @@ -<!DOCTYPE HTML> -<html> -<head> -</head> -<body> -<div> - <div style="background-color: green"><span>Hello</span><span> </span><span>World</span></div> -</div> -</body> -</html> diff --git a/layout/reftests/webcomponents/dynamic-shadow-element-1.html b/layout/reftests/webcomponents/dynamic-shadow-element-1.html deleted file mode 100644 index 0142164a1..000000000 --- a/layout/reftests/webcomponents/dynamic-shadow-element-1.html +++ /dev/null @@ -1,23 +0,0 @@ -<!DOCTYPE HTML> -<html> -<head> - <script> - function tweak() { - var oldestShadow = document.getElementById('outer').createShadowRoot(); - oldestShadow.innerHTML = 'Hello'; - - var olderShadow = document.getElementById('outer').createShadowRoot(); - - var youngerShadow = document.getElementById('outer').createShadowRoot(); - youngerShadow.innerHTML = '<div style="background-color:green"><shadow></shadow></div>'; - - olderShadow.innerHTML = '<shadow></shadow> World'; - } - </script> -</head> -<body onload="tweak()"> -<div id="outer"> - <div style="width:300px; height:100px; background-color:red;"></div> -</div> -</body> -</html> diff --git a/layout/reftests/webcomponents/fallback-content-1.html b/layout/reftests/webcomponents/fallback-content-1.html index ac3b5d02a..0bcd2abbd 100644 --- a/layout/reftests/webcomponents/fallback-content-1.html +++ b/layout/reftests/webcomponents/fallback-content-1.html @@ -8,13 +8,13 @@ shadowDiv.style.border = "10px solid green"; // Insertion point will match nothing and use fallback content. - var insertionPoint = document.createElement("content"); - shadowDiv.appendChild(insertionPoint); + var slot = document.createElement("slot"); + shadowDiv.appendChild(slot); // Append three nodes as children to use as fallback content. - insertionPoint.innerHTML = '<span style="background-color: orange">Hello</span> <span style="background-color: green">World</span>'; + slot.innerHTML = '<span style="background-color: orange">Hello</span> <span style="background-color: green">World</span>'; - var shadowRoot = document.getElementById('outer').createShadowRoot(); + var shadowRoot = document.getElementById('outer').attachShadow({mode: 'open'}); shadowRoot.appendChild(shadowDiv); } </script> diff --git a/layout/reftests/webcomponents/input-transition-1.html b/layout/reftests/webcomponents/input-transition-1.html index ede0fa40b..c11444d05 100644 --- a/layout/reftests/webcomponents/input-transition-1.html +++ b/layout/reftests/webcomponents/input-transition-1.html @@ -5,19 +5,21 @@ <body> <div id="host"></div> <script> - var host = document.getElementById("host"); - var root = host.createShadowRoot(); + var host, root; + + host = document.getElementById("host"); + root = host.attachShadow({mode: 'open'}); root.innerHTML = '<style>input ~ div { background: red; transition: background 100ms; } input:checked ~ div { background: green; }</style><input id="one" type="checkbox"><div style="height: 50px; width: 50px;"></div>'; function tweak() { - var el = root.getElementById("one"); - el.checked = true; - el.nextSibling.addEventListener("transitionend", function() { - document.documentElement.removeAttribute("class"); - }, false); + var el = root.getElementById("one"); + el.checked = true; + el.nextSibling.addEventListener("transitionend", function() { + setTimeout(()=>{document.documentElement.removeAttribute("class")}, 1000); // wait for the checkbox SVG image to load on Android + }); } - window.addEventListener("MozReftestInvalidate", tweak, false); + window.addEventListener("MozReftestInvalidate", tweak); </script> </body> </html> diff --git a/layout/reftests/webcomponents/nested-insertion-point-1.html b/layout/reftests/webcomponents/nested-insertion-point-1.html index 66892029a..3d0d92f0d 100644 --- a/layout/reftests/webcomponents/nested-insertion-point-1.html +++ b/layout/reftests/webcomponents/nested-insertion-point-1.html @@ -7,19 +7,20 @@ var outerShadow = document.createElement("div"); outerShadow.style.border = "10px solid green"; - var outerInsertionPoint = document.createElement("content"); + var outerInsertionPoint = document.createElement("slot"); outerShadow.appendChild(outerInsertionPoint); // div with style "border: 10px solid orange" var innerShadow = document.createElement("div"); innerShadow.style.border = "10px solid orange"; - var innerInsertionPoint = document.createElement("content"); - innerShadow.appendChild(innerInsertionPoint); + var slot = document.createElement("slot"); + innerShadow.appendChild(slot); - outerShadow.createShadowRoot().appendChild(innerShadow); + outerShadow.attachShadow({mode: 'open'}).appendChild(innerShadow); - var shadowRoot = document.getElementById('outer').createShadowRoot(); + var shadowRoot = + document.getElementById('outer').attachShadow({mode: 'open'}); shadowRoot.appendChild(outerShadow); } </script> diff --git a/layout/reftests/webcomponents/reframe-shadow-child-1.html b/layout/reftests/webcomponents/reframe-shadow-child-1.html new file mode 100644 index 000000000..d953beb6d --- /dev/null +++ b/layout/reftests/webcomponents/reframe-shadow-child-1.html @@ -0,0 +1,16 @@ +<!doctype html> +<template id="tmpl"> + <div style="display: table"> + Some text + <span style="display: table-cell">something</span> + More text + </div> +</template> +<div id="host"></div> +<script> + let shadowRoot = document.getElementById("host").attachShadow({mode: 'open'}); + let tmpl = document.getElementById("tmpl"); + shadowRoot.appendChild(document.importNode(tmpl.content, true)); + document.body.offsetTop; + shadowRoot.firstElementChild.querySelector("span").remove(); +</script> diff --git a/layout/reftests/webcomponents/reframe-shadow-child-2.html b/layout/reftests/webcomponents/reframe-shadow-child-2.html new file mode 100644 index 000000000..0ebbe7433 --- /dev/null +++ b/layout/reftests/webcomponents/reframe-shadow-child-2.html @@ -0,0 +1,15 @@ +<!doctype html> +<template id="tmpl"> + <div style="display: block"> + Some text + More text + </div> +</template> +<div id="host"></div> +<script> + let shadowRoot = document.getElementById("host").attachShadow({mode: 'open'}); + let tmpl = document.getElementById("tmpl"); + shadowRoot.appendChild(document.importNode(tmpl.content, true)); + document.body.offsetTop; + shadowRoot.firstElementChild.style.display = "table"; +</script> diff --git a/layout/reftests/webcomponents/reftest.list b/layout/reftests/webcomponents/reftest.list index 280cd204e..6afbc2720 100644 --- a/layout/reftests/webcomponents/reftest.list +++ b/layout/reftests/webcomponents/reftest.list @@ -3,8 +3,6 @@ pref(dom.webcomponents.enabled,true) == basic-shadow-1.html basic-shadow-1-ref.h pref(dom.webcomponents.enabled,true) == basic-shadow-2.html basic-shadow-2-ref.html pref(dom.webcomponents.enabled,true) == basic-shadow-3.html basic-shadow-3-ref.html pref(dom.webcomponents.enabled,true) == basic-shadow-4.html basic-shadow-4-ref.html -#bug 1421542 pref(dom.webcomponents.enabled,true) == basic-insertion-point-1.html basic-insertion-point-1-ref.html -#bug 1421542 pref(dom.webcomponents.enabled,true) == basic-insertion-point-2.html basic-insertion-point-2-ref.html pref(dom.webcomponents.enabled,true) == fallback-content-1.html fallback-content-1-ref.html pref(dom.webcomponents.enabled,true) == remove-insertion-point-1.html remove-insertion-point-1-ref.html #bug 1421542 pref(dom.webcomponents.enabled,true) == nested-insertion-point-1.html nested-insertion-point-1-ref.html @@ -13,3 +11,9 @@ pref(dom.webcomponents.enabled,true) == input-transition-1.html input-transition #bug 1421542 pref(dom.webcomponents.enabled,true) == dynamic-insertion-point-distribution-1.html dynamic-insertion-point-distribution-1-ref.html pref(dom.webcomponents.enabled,true) == dynamic-insertion-point-distribution-2.html dynamic-insertion-point-distribution-2-ref.html pref(dom.webcomponents.enabled,true) == remove-append-shadow-host-1.html remove-append-shadow-host-1-ref.html +pref(dom.webcomponents.enabled,true) == basic-slot-1.html basic-slot-1-ref.html +pref(dom.webcomponents.enabled,true) == basic-slot-2.html basic-slot-2-ref.html +pref(dom.webcomponents.enabled,true) == basic-slot-3.html basic-slot-3-ref.html +pref(dom.webcomponents.enabled,true) == basic-slot-4.html basic-slot-3-ref.html +pref(dom.webcomponents.enabled,true) == basic-slot-5.html basic-slot-5-ref.html +pref(dom.webcomponents.enabled,true) == basic-slot-6.html basic-slot-6-ref.html diff --git a/layout/reftests/webcomponents/remove-append-shadow-host-1.html b/layout/reftests/webcomponents/remove-append-shadow-host-1.html index 85161565e..1a752eba6 100644 --- a/layout/reftests/webcomponents/remove-append-shadow-host-1.html +++ b/layout/reftests/webcomponents/remove-append-shadow-host-1.html @@ -6,7 +6,7 @@ <div id="container"><div id="host"></div></div> <script> var host = document.getElementById("host"); - var root = host.createShadowRoot(); + var root = host.attachShadow({mode: 'open'}); root.innerHTML = 'inside shadow DOM'; var container = document.getElementById("container"); diff --git a/layout/reftests/webcomponents/remove-insertion-point-1.html b/layout/reftests/webcomponents/remove-insertion-point-1.html index 195673ca8..1b7588daf 100644 --- a/layout/reftests/webcomponents/remove-insertion-point-1.html +++ b/layout/reftests/webcomponents/remove-insertion-point-1.html @@ -8,15 +8,15 @@ shadowDiv.style.border = "10px solid green"; // Insertion point will match nothing and use fallback content. - var insertionPoint = document.createElement("content"); - shadowDiv.appendChild(insertionPoint); + var slot = document.createElement("slot"); + shadowDiv.appendChild(slot); - var shadowRoot = document.getElementById('outer').createShadowRoot(); + var shadowRoot = document.getElementById('outer').attachShadow({mode: 'open'}); shadowRoot.appendChild(shadowDiv); // Remove the insertion point from the ShadowRoot, "Hello" should no // longer be rendered. - shadowDiv.removeChild(insertionPoint); + shadowDiv.removeChild(slot); } </script> </head> diff --git a/layout/reftests/webcomponents/style-sharing-across-shadow.html b/layout/reftests/webcomponents/style-sharing-across-shadow.html new file mode 100644 index 000000000..b41cf7479 --- /dev/null +++ b/layout/reftests/webcomponents/style-sharing-across-shadow.html @@ -0,0 +1,22 @@ +<!doctype html> +<style> + div { display: contents } +</style> +<div></div> +<div></div> +<script> + let divs = document.querySelectorAll('div'); + divs[0].attachShadow({mode: 'open'}).innerHTML = ` + <style> + * { color: green; } + </style> + <span>Should be green</span> + `; + divs[1].attachShadow({mode: 'open'}).innerHTML = ` + <style> + * { color: initial; } + [foo] { } + </style> + <span>Should not be green</span> + `; +</script> diff --git a/layout/reftests/webcomponents/style-sharing.html b/layout/reftests/webcomponents/style-sharing.html new file mode 100644 index 000000000..0a1e3c95c --- /dev/null +++ b/layout/reftests/webcomponents/style-sharing.html @@ -0,0 +1,14 @@ +<!doctype html> +<div id="host"></div> +<script> + let root = host.attachShadow({mode: 'open'}); + root.innerHTML = ` + <style> + #test { + color: green; + } + </style> + <span id="test">Should be green</span> + <span id="test2">Should not be green</span> + `; +</script> diff --git a/layout/reftests/webcomponents/update-dist-node-descendants-1.html b/layout/reftests/webcomponents/update-dist-node-descendants-1.html index e2dd4ebb7..003c23394 100644 --- a/layout/reftests/webcomponents/update-dist-node-descendants-1.html +++ b/layout/reftests/webcomponents/update-dist-node-descendants-1.html @@ -5,17 +5,18 @@ <body> <div id="outer"><span id="distnode">text</span></div> <script> -var shadowRoot = document.getElementById('outer').createShadowRoot(); -shadowRoot.innerHTML = '<div><content></content></div>'; -function tweak() { - var distNode = document.getElementById("distnode"); - distNode.textContent = "Hello World"; + var shadowRoot = document.getElementById('outer').attachShadow({mode: 'open'}); + shadowRoot.innerHTML = '<div><slot></slot></div>'; - document.documentElement.removeAttribute("class"); -} + function tweak() { + var distNode = document.getElementById("distnode"); + distNode.textContent = "Hello World"; -window.addEventListener("MozReftestInvalidate", tweak); + document.documentElement.removeAttribute("class"); + } + + window.addEventListener("MozReftestInvalidate", tweak); </script> </body> </html> diff --git a/layout/style/res/ua.css b/layout/style/res/ua.css index 931b32eb8..504f5dc57 100644 --- a/layout/style/res/ua.css +++ b/layout/style/res/ua.css @@ -471,3 +471,9 @@ div:-moz-native-anonymous.moz-custom-content-container { width: 100%; height: 100%; } + +/* Shadow DOM v1 + * https://drafts.csswg.org/css-scoping/#slots-in-shadow-tree */ +slot { + display: contents; +}
\ No newline at end of file diff --git a/testing/web-platform/meta/html/dom/interfaces.html.ini b/testing/web-platform/meta/html/dom/interfaces.html.ini index db6a464d0..d0ebb0c7d 100644 --- a/testing/web-platform/meta/html/dom/interfaces.html.ini +++ b/testing/web-platform/meta/html/dom/interfaces.html.ini @@ -1,6 +1,6 @@ [interfaces.html] type: testharness - prefs: [dom.forms.inputmode:true, dom.dialog_element.enabled:true] + prefs: [dom.forms.inputmode:true, dom.dialog_element.enabled:true, dom.webcomponents.enabled:true] [Document interface: attribute domain] expected: FAIL @@ -2524,21 +2524,6 @@ [HTMLSlotElement interface: operation assignedNodes(AssignedNodesOptions)] expected: FAIL - [HTMLSlotElement must be primary interface of document.createElement("slot")] - expected: FAIL - - [Stringification of document.createElement("slot")] - expected: FAIL - - [HTMLSlotElement interface: document.createElement("slot") must inherit property "name" with the proper type (0)] - expected: FAIL - - [HTMLSlotElement interface: document.createElement("slot") must inherit property "assignedNodes" with the proper type (1)] - expected: FAIL - - [HTMLSlotElement interface: calling assignedNodes(AssignedNodesOptions) on document.createElement("slot") with too few arguments must throw TypeError] - expected: FAIL - [HTMLInputElement interface: createInput("text") must inherit property "dirName" with the proper type (6)] expected: FAIL diff --git a/testing/web-platform/meta/html/dom/reflection-misc.html.ini b/testing/web-platform/meta/html/dom/reflection-misc.html.ini index bc65d4191..05bb9c4cd 100644 --- a/testing/web-platform/meta/html/dom/reflection-misc.html.ini +++ b/testing/web-platform/meta/html/dom/reflection-misc.html.ini @@ -1,6 +1,6 @@ [reflection-misc.html] type: testharness - prefs: [dom.dialog_element.enabled: true] + prefs: [dom.dialog_element.enabled: true, dom.webcomponents.enabled:true] [html.tabIndex: setAttribute() to object "3" followed by getAttribute()] expected: FAIL diff --git a/testing/web-platform/meta/shadow-dom/HTMLSlotElement-interface.html.ini b/testing/web-platform/meta/shadow-dom/HTMLSlotElement-interface.html.ini deleted file mode 100644 index c045a3006..000000000 --- a/testing/web-platform/meta/shadow-dom/HTMLSlotElement-interface.html.ini +++ /dev/null @@ -1,44 +0,0 @@ -[HTMLSlotElement-interface.html] - type: testharness - [HTMLSlotElement must be defined on window] - expected: FAIL - - ["name" attribute on HTMLSlotElement must reflect "name" attribute] - expected: FAIL - - [assignedNodes() on a HTMLSlotElement must return an empty array when the slot element is not in a tree or in a document tree] - expected: FAIL - - [assignedNodes({"flattened":false}) on a HTMLSlotElement must return an empty array when the slot element is not in a tree or in a document tree] - expected: FAIL - - [assignedNodes({"flattened":true}) on a HTMLSlotElement must return an empty array when the slot element is not in a tree or in a document tree] - expected: FAIL - - [assignedNodes() must return the list of assigned nodes when none of the assigned nodes themselves are slots] - expected: FAIL - - [assignedNodes({"flattened":false}) must return the list of assigned nodes when none of the assigned nodes themselves are slots] - expected: FAIL - - [assignedNodes({"flattened":true}) must return the list of assigned nodes when none of the assigned nodes themselves are slots] - expected: FAIL - - [assignedNodes() must update when slot and name attributes are modified] - expected: FAIL - - [assignedNodes({"flattened":false}) must update when slot and name attributes are modified] - expected: FAIL - - [assignedNodes({"flattened":true}) must update when slot and name attributes are modified] - expected: FAIL - - [assignedNodes must update when a default slot is introduced dynamically by a slot rename] - expected: FAIL - - [assignedNodes must update when slot elements are inserted or removed] - expected: FAIL - - [assignedNodes({flatten: true}) must return the distributed nodes, and assignedNodes() and assignedNodes({flatten: false}) must returned the assigned nodes] - expected: FAIL - diff --git a/testing/web-platform/meta/shadow-dom/Slotable-interface.html.ini b/testing/web-platform/meta/shadow-dom/Slotable-interface.html.ini deleted file mode 100644 index 6ef193dee..000000000 --- a/testing/web-platform/meta/shadow-dom/Slotable-interface.html.ini +++ /dev/null @@ -1,12 +0,0 @@ -[Slotable-interface.html] - type: testharness - prefs: [dom.webcomponents.enabled:true] - [assignedSlot must return null when the node does not have an assigned node] - expected: FAIL - - [assignedSlot must return the assigned slot] - expected: FAIL - - [assignedSlot must return null when the assigned slot element is inside a closed shadow tree] - expected: FAIL - diff --git a/testing/web-platform/meta/shadow-dom/event-inside-slotted-node.html.ini b/testing/web-platform/meta/shadow-dom/event-inside-slotted-node.html.ini deleted file mode 100644 index 7e42670a3..000000000 --- a/testing/web-platform/meta/shadow-dom/event-inside-slotted-node.html.ini +++ /dev/null @@ -1,38 +0,0 @@ -[event-inside-slotted-node.html] - type: testharness - [Firing an event inside a grand child of a detached open mode shadow host] - expected: FAIL - - [Firing an event inside a grand child of a detached closed mode shadow host] - expected: FAIL - - [Firing an event inside a grand child of an in-document open mode shadow host] - expected: FAIL - - [Firing an event inside a grand child of an in-document closed mode shadow host] - expected: FAIL - - [Firing an event on a node with two ancestors with a detached open and open shadow trees with an inner open shadow tree] - expected: FAIL - - [Firing an event on a node with two ancestors with a detached open and open shadow trees with an inner closed shadow tree] - expected: FAIL - - [Firing an event on a node with two ancestors with a detached open and closed shadow trees with an inner open shadow tree] - expected: FAIL - - [Firing an event on a node with two ancestors with a detached open and closed shadow trees with an inner closed shadow tree] - expected: FAIL - - [Firing an event on a node with two ancestors with a detached closed and open shadow trees with an inner open shadow tree] - expected: FAIL - - [Firing an event on a node with two ancestors with a detached closed and open shadow trees with an inner closed shadow tree] - expected: FAIL - - [Firing an event on a node with two ancestors with a detached closed and closed shadow trees with an inner open shadow tree] - expected: FAIL - - [Firing an event on a node with two ancestors with a detached closed and closed shadow trees with an inner closed shadow tree] - expected: FAIL - diff --git a/testing/web-platform/meta/shadow-dom/slots-fallback.html.ini b/testing/web-platform/meta/shadow-dom/slots-fallback.html.ini deleted file mode 100644 index d32ae812b..000000000 --- a/testing/web-platform/meta/shadow-dom/slots-fallback.html.ini +++ /dev/null @@ -1,32 +0,0 @@ -[slots-fallback.html] - type: testharness - [Slots fallback: Basic.] - expected: FAIL - - [Slots fallback: Slots in Slots.] - expected: FAIL - - [Slots fallback: Fallback contents should not be used if a node is assigned.] - expected: FAIL - - [Slots fallback: Slots in Slots: Assinged nodes should be used as fallback contents of another slot] - expected: FAIL - - [Slots fallback: Complex case.] - expected: FAIL - - [Slots fallback: Mutation. Append fallback contents.] - expected: FAIL - - [Slots fallback: Mutation. Remove fallback contents.] - expected: FAIL - - [Slots fallback: Mutation. Assign a node to a slot so that fallback contens are no longer used.] - expected: FAIL - - [Slots fallback: Mutation. Remove an assigned node from a slot so that fallback contens will be used.] - expected: FAIL - - [Slots fallback: Mutation. Remove a slot which is a fallback content of another slot.] - expected: FAIL - diff --git a/testing/web-platform/meta/shadow-dom/slots.html.ini b/testing/web-platform/meta/shadow-dom/slots.html.ini deleted file mode 100644 index 3c047e482..000000000 --- a/testing/web-platform/meta/shadow-dom/slots.html.ini +++ /dev/null @@ -1,71 +0,0 @@ -[slots.html] - type: testharness - [Slots: Basic.] - expected: FAIL - - [Slots: Slots in closed.] - expected: FAIL - - [Slots: Slots not in a shadow tree.] - expected: FAIL - - [Slots: Distributed nooes for Slots not in a shadow tree.] - expected: FAIL - - [Slots: Name matching] - expected: FAIL - - [Slots: No direct host child.] - expected: FAIL - - [Slots: Default Slot.] - expected: FAIL - - [Slots: Slot in Slot does not matter in assignment.] - expected: FAIL - - [Slots: Slot is assigned to another slot] - expected: FAIL - - [Slots: Open > Closed.] - expected: FAIL - - [Slots: Closed > Closed.] - expected: FAIL - - [Slots: Closed > Open.] - expected: FAIL - - [Slots: Complex case: Basi line.] - expected: FAIL - - [Slots: Mutation: appendChild.] - expected: FAIL - - [Slots: Mutation: Change slot= attribute 1.] - expected: FAIL - - [Slots: Mutation: Change slot= attribute 2.] - expected: FAIL - - [Slots: Mutation: Change slot= attribute 3.] - expected: FAIL - - [Slots: Mutation: Remove a child.] - expected: FAIL - - [Slots: Mutation: Add a slot: after.] - expected: FAIL - - [Slots: Mutation: Add a slot: before.] - expected: FAIL - - [Slots: Mutation: Remove a slot.] - expected: FAIL - - [Slots: Mutation: Change slot name= attribute.] - expected: FAIL - - [Slots: Mutation: Change slot slot= attribute.] - expected: FAIL - |