/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "mozilla/Preferences.h" #include "mozilla/dom/BindContext.h" #include "mozilla/dom/ShadowRoot.h" #include "mozilla/dom/DocumentFragment.h" #include "ChildIterator.h" #include "nsContentUtils.h" #include "nsINode.h" #include "nsWindowSizes.h" #include "mozilla/dom/DirectionalityUtils.h" #include "mozilla/dom/Element.h" #include "mozilla/dom/HTMLDetailsElement.h" #include "mozilla/dom/HTMLSlotElement.h" #include "mozilla/dom/HTMLSummaryElement.h" #include "mozilla/dom/Text.h" #include "mozilla/dom/TreeOrderedArrayInlines.h" #include "mozilla/EventDispatcher.h" #include "mozilla/IdentifierMapEntry.h" #include "mozilla/PresShell.h" #include "mozilla/PresShellInlines.h" #include "mozilla/ScopeExit.h" #include "mozilla/ServoStyleRuleMap.h" #include "mozilla/StyleSheet.h" #include "mozilla/StyleSheetInlines.h" #include "mozilla/dom/StyleSheetList.h" using namespace mozilla; using namespace mozilla::dom; NS_IMPL_CYCLE_COLLECTION_CLASS(ShadowRoot) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(ShadowRoot, DocumentFragment) DocumentOrShadowRoot::Traverse(tmp, cb); NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ShadowRoot) DocumentOrShadowRoot::Unlink(tmp); NS_IMPL_CYCLE_COLLECTION_UNLINK_END_INHERITED(DocumentFragment) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ShadowRoot) NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIContent) NS_INTERFACE_MAP_ENTRY(nsIRadioGroupContainer) NS_INTERFACE_MAP_END_INHERITING(DocumentFragment) NS_IMPL_ADDREF_INHERITED(ShadowRoot, DocumentFragment) NS_IMPL_RELEASE_INHERITED(ShadowRoot, DocumentFragment) ShadowRoot::ShadowRoot(Element* aElement, ShadowRootMode aMode, Element::DelegatesFocus aDelegatesFocus, SlotAssignmentMode aSlotAssignment, already_AddRefed&& aNodeInfo) : DocumentFragment(std::move(aNodeInfo)), DocumentOrShadowRoot(this), mMode(aMode), mDelegatesFocus(aDelegatesFocus), mSlotAssignment(aSlotAssignment), mIsDetailsShadowTree(aElement->IsHTMLElement(nsGkAtoms::details)), mIsAvailableToElementInternals(false) { // nsINode.h relies on this. MOZ_ASSERT(static_cast(this) == reinterpret_cast(this)); MOZ_ASSERT(static_cast(this) == reinterpret_cast(this)); SetHost(aElement); // Nodes in a shadow tree should never store a value // in the subtree root pointer, nodes in the shadow tree // track the subtree root using GetContainingShadow(). ClearSubtreeRootPointer(); SetFlags(NODE_IS_IN_SHADOW_TREE); if (Host()->IsInNativeAnonymousSubtree()) { // NOTE(emilio): We could consider just propagating the // IN_NATIVE_ANONYMOUS_SUBTREE flag (not making this an anonymous root), but // that breaks the invariant that if two nodes have the same // NativeAnonymousSubtreeRoot() they are in the same DOM tree, which we rely // on a couple places and would need extra fixes. // // We don't hit this case for now anyways, bug 1824886 would start hitting // it. SetIsNativeAnonymousRoot(); } Bind(); ExtendedDOMSlots()->mContainingShadow = this; } ShadowRoot::~ShadowRoot() { if (IsInComposedDoc()) { OwnerDoc()->RemoveComposedDocShadowRoot(*this); } MOZ_DIAGNOSTIC_ASSERT(!OwnerDoc()->IsComposedDocShadowRoot(*this)); UnsetFlags(NODE_IS_IN_SHADOW_TREE); // nsINode destructor expects mSubtreeRoot == this. SetSubtreeRootPointer(this); } MOZ_DEFINE_MALLOC_SIZE_OF(ShadowRootAuthorStylesMallocSizeOf) MOZ_DEFINE_MALLOC_ENCLOSING_SIZE_OF(ShadowRootAuthorStylesMallocEnclosingSizeOf) void ShadowRoot::AddSizeOfExcludingThis(nsWindowSizes& aSizes, size_t* aNodeSize) const { DocumentFragment::AddSizeOfExcludingThis(aSizes, aNodeSize); DocumentOrShadowRoot::AddSizeOfExcludingThis(aSizes); aSizes.mLayoutShadowDomAuthorStyles += Servo_AuthorStyles_SizeOfIncludingThis( ShadowRootAuthorStylesMallocSizeOf, ShadowRootAuthorStylesMallocEnclosingSizeOf, mServoStyles.get()); } JSObject* ShadowRoot::WrapNode(JSContext* aCx, JS::Handle aGivenProto) { return mozilla::dom::ShadowRoot_Binding::Wrap(aCx, this, aGivenProto); } void ShadowRoot::NodeInfoChanged(Document* aOldDoc) { DocumentFragment::NodeInfoChanged(aOldDoc); Document* newDoc = OwnerDoc(); const bool fromOrToTemplate = aOldDoc->GetTemplateContentsOwnerIfExists() == newDoc || newDoc->GetTemplateContentsOwnerIfExists() == aOldDoc; if (!fromOrToTemplate) { ClearAdoptedStyleSheets(); } } void ShadowRoot::CloneInternalDataFrom(ShadowRoot* aOther) { if (aOther->IsRootOfNativeAnonymousSubtree()) { SetIsNativeAnonymousRoot(); } if (aOther->IsUAWidget()) { SetIsUAWidget(); } size_t sheetCount = aOther->SheetCount(); for (size_t i = 0; i < sheetCount; ++i) { StyleSheet* sheet = aOther->SheetAt(i); if (sheet->IsApplicable()) { RefPtr clonedSheet = sheet->Clone(nullptr, this); if (clonedSheet) { AppendStyleSheet(*clonedSheet.get()); } } } CloneAdoptedSheetsFrom(*aOther); } nsresult ShadowRoot::Bind() { MOZ_ASSERT(!IsInComposedDoc(), "Forgot to unbind?"); if (Host()->IsInComposedDoc()) { SetIsConnected(true); Document* doc = OwnerDoc(); doc->AddComposedDocShadowRoot(*this); // If our stylesheets somehow mutated when we were disconnected, we need to // ensure that our style data gets flushed as appropriate. if (mServoStyles && Servo_AuthorStyles_IsDirty(mServoStyles.get())) { doc->RecordShadowStyleChange(*this); } } BindContext context(*this); for (nsIContent* child = GetFirstChild(); child; child = child->GetNextSibling()) { nsresult rv = child->BindToTree(context, *this); NS_ENSURE_SUCCESS(rv, rv); } return NS_OK; } void ShadowRoot::Unbind() { if (IsInComposedDoc()) { SetIsConnected(false); OwnerDoc()->RemoveComposedDocShadowRoot(*this); } for (nsIContent* child = GetFirstChild(); child; child = child->GetNextSibling()) { child->UnbindFromTree(false); } } void ShadowRoot::Unattach() { MOZ_ASSERT(!HasSlots(), "Won't work!"); if (!GetHost()) { // It is possible that we've been unlinked already. In such case host // should have called Unbind and ShadowRoot's own unlink. return; } Unbind(); SetHost(nullptr); } void ShadowRoot::InvalidateStyleAndLayoutOnSubtree(Element* aElement) { MOZ_ASSERT(aElement); Document* doc = GetComposedDoc(); if (!doc) { return; } PresShell* presShell = doc->GetPresShell(); if (!presShell) { return; } presShell->DestroyFramesForAndRestyle(aElement); } void ShadowRoot::PartAdded(const Element& aPart) { MOZ_ASSERT(aPart.HasPartAttribute()); MOZ_ASSERT(!mParts.Contains(&aPart)); mParts.AppendElement(&aPart); } void ShadowRoot::PartRemoved(const Element& aPart) { MOZ_ASSERT(mParts.Contains(&aPart)); mParts.RemoveElement(&aPart); MOZ_ASSERT(!mParts.Contains(&aPart)); } 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); SlotArray& currentSlots = *mSlotMap.GetOrInsertNew(name); size_t index = currentSlots.Insert(*aSlot); // For Named slots, slottables are inserted into the other slot // which has the same name already, however it's not the case // for manual slots if (index != 0 && SlotAssignment() == SlotAssignmentMode::Named) { return; } HTMLSlotElement* oldSlot = currentSlots->SafeElementAt(1); if (SlotAssignment() == SlotAssignmentMode::Named) { if (oldSlot) { MOZ_DIAGNOSTIC_ASSERT(oldSlot != aSlot); // Move assigned nodes from old slot to new slot. InvalidateStyleAndLayoutOnSubtree(oldSlot); const nsTArray>& assignedNodes = oldSlot->AssignedNodes(); bool doEnqueueSlotChange = false; while (assignedNodes.Length() > 0) { nsINode* assignedNode = assignedNodes[0]; oldSlot->RemoveAssignedNode(*assignedNode->AsContent()); aSlot->AppendAssignedNode(*assignedNode->AsContent()); doEnqueueSlotChange = true; } if (doEnqueueSlotChange) { oldSlot->EnqueueSlotChangeEvent(); aSlot->EnqueueSlotChangeEvent(); SlotStateChanged(oldSlot); SlotStateChanged(aSlot); } } else { bool doEnqueueSlotChange = false; // Otherwise add appropriate nodes to this slot from the host. for (nsIContent* child = GetHost()->GetFirstChild(); child; child = child->GetNextSibling()) { nsAutoString slotName; GetSlotNameFor(*child, slotName); if (!child->IsSlotable() || !slotName.Equals(name)) { continue; } doEnqueueSlotChange = true; aSlot->AppendAssignedNode(*child); } if (doEnqueueSlotChange) { aSlot->EnqueueSlotChangeEvent(); SlotStateChanged(aSlot); } } } else { bool doEnqueueSlotChange = false; for (const auto& node : aSlot->ManuallyAssignedNodes()) { if (GetHost() != node->GetParent()) { continue; } MOZ_ASSERT(node->IsContent(), "Manually assigned nodes should be an element or a text"); nsIContent* content = node->AsContent(); aSlot->AppendAssignedNode(*content); doEnqueueSlotChange = true; } if (doEnqueueSlotChange) { aSlot->EnqueueSlotChangeEvent(); SlotStateChanged(aSlot); } } } void ShadowRoot::RemoveSlot(HTMLSlotElement* aSlot) { MOZ_ASSERT(aSlot); nsAutoString name; aSlot->GetName(name); MOZ_ASSERT(mSlotMap.Get(name)); SlotArray& currentSlots = *mSlotMap.Get(name); MOZ_DIAGNOSTIC_ASSERT(currentSlots->Contains(aSlot), "Slot to de-register wasn't found?"); if (currentSlots->Length() == 1) { MOZ_ASSERT_IF(SlotAssignment() == SlotAssignmentMode::Named, currentSlots->ElementAt(0) == aSlot); InvalidateStyleAndLayoutOnSubtree(aSlot); mSlotMap.Remove(name); if (!aSlot->AssignedNodes().IsEmpty()) { aSlot->ClearAssignedNodes(); aSlot->EnqueueSlotChangeEvent(); } return; } if (SlotAssignment() == SlotAssignmentMode::Manual) { InvalidateStyleAndLayoutOnSubtree(aSlot); if (!aSlot->AssignedNodes().IsEmpty()) { aSlot->ClearAssignedNodes(); aSlot->EnqueueSlotChangeEvent(); } } const bool wasFirstSlot = currentSlots->ElementAt(0) == aSlot; currentSlots.RemoveElement(*aSlot); if (!wasFirstSlot || SlotAssignment() == SlotAssignmentMode::Manual) { return; } // Move assigned nodes from removed slot to the next slot in // tree order with the same name. InvalidateStyleAndLayoutOnSubtree(aSlot); HTMLSlotElement* replacementSlot = currentSlots->ElementAt(0); const nsTArray>& assignedNodes = aSlot->AssignedNodes(); if (assignedNodes.IsEmpty()) { return; } InvalidateStyleAndLayoutOnSubtree(replacementSlot); while (!assignedNodes.IsEmpty()) { nsINode* assignedNode = assignedNodes[0]; aSlot->RemoveAssignedNode(*assignedNode->AsContent()); replacementSlot->AppendAssignedNode(*assignedNode->AsContent()); } aSlot->EnqueueSlotChangeEvent(); replacementSlot->EnqueueSlotChangeEvent(); } // FIXME(emilio): There's a bit of code duplication between this and the // equivalent ServoStyleSet methods, it'd be nice to not duplicate it... void ShadowRoot::RuleAdded(StyleSheet& aSheet, css::Rule& aRule) { if (!aSheet.IsApplicable()) { return; } MOZ_ASSERT(mServoStyles); if (mStyleRuleMap) { mStyleRuleMap->RuleAdded(aSheet, aRule); } if (aRule.IsIncompleteImportRule()) { return; } Servo_AuthorStyles_ForceDirty(mServoStyles.get()); ApplicableRulesChanged(); } void ShadowRoot::RuleRemoved(StyleSheet& aSheet, css::Rule& aRule) { if (!aSheet.IsApplicable()) { return; } MOZ_ASSERT(mServoStyles); if (mStyleRuleMap) { mStyleRuleMap->RuleRemoved(aSheet, aRule); } Servo_AuthorStyles_ForceDirty(mServoStyles.get()); ApplicableRulesChanged(); } void ShadowRoot::RuleChanged(StyleSheet& aSheet, css::Rule*, StyleRuleChangeKind) { if (!aSheet.IsApplicable()) { return; } MOZ_ASSERT(mServoStyles); Servo_AuthorStyles_ForceDirty(mServoStyles.get()); ApplicableRulesChanged(); } void ShadowRoot::ImportRuleLoaded(CSSImportRule&, StyleSheet& aSheet) { if (mStyleRuleMap) { mStyleRuleMap->SheetAdded(aSheet); } if (!aSheet.IsApplicable()) { return; } // TODO(emilio): Could handle it like a regular sheet insertion, I guess, to // avoid throwing away the whole style data. Servo_AuthorStyles_ForceDirty(mServoStyles.get()); ApplicableRulesChanged(); } // We don't need to do anything else than forwarding to the document if // necessary. void ShadowRoot::SheetCloned(StyleSheet& aSheet) { if (Document* doc = GetComposedDoc()) { if (PresShell* shell = doc->GetPresShell()) { shell->StyleSet()->SheetCloned(aSheet); } } } void ShadowRoot::ApplicableRulesChanged() { if (Document* doc = GetComposedDoc()) { doc->RecordShadowStyleChange(*this); } } void ShadowRoot::InsertSheetAt(size_t aIndex, StyleSheet& aSheet) { DocumentOrShadowRoot::InsertSheetAt(aIndex, aSheet); if (aSheet.IsApplicable()) { InsertSheetIntoAuthorData(aIndex, aSheet, mStyleSheets); } } StyleSheet* FirstApplicableAdoptedStyleSheet( const nsTArray>& aList) { size_t i = 0; for (StyleSheet* sheet : aList) { // Deal with duplicate sheets by only considering the last one. if (sheet->IsApplicable() && MOZ_LIKELY(aList.LastIndexOf(sheet) == i)) { return sheet; } i++; } return nullptr; } void ShadowRoot::InsertSheetIntoAuthorData( size_t aIndex, StyleSheet& aSheet, const nsTArray>& aList) { MOZ_ASSERT(aSheet.IsApplicable()); MOZ_ASSERT(aList[aIndex] == &aSheet); MOZ_ASSERT(aList.LastIndexOf(&aSheet) == aIndex); MOZ_ASSERT(&aList == &mAdoptedStyleSheets || &aList == &mStyleSheets); if (!mServoStyles) { mServoStyles.reset(Servo_AuthorStyles_Create()); } if (mStyleRuleMap) { mStyleRuleMap->SheetAdded(aSheet); } auto changedOnExit = mozilla::MakeScopeExit([&] { ApplicableRulesChanged(); }); for (size_t i = aIndex + 1; i < aList.Length(); ++i) { StyleSheet* beforeSheet = aList.ElementAt(i); if (!beforeSheet->IsApplicable()) { continue; } // If this is a duplicate adopted stylesheet that is not in the right // position (the last one) then we skip over it. Otherwise we're done. if (&aList == &mAdoptedStyleSheets && MOZ_UNLIKELY(aList.LastIndexOf(beforeSheet) != i)) { continue; } Servo_AuthorStyles_InsertStyleSheetBefore(mServoStyles.get(), &aSheet, beforeSheet); return; } if (mAdoptedStyleSheets.IsEmpty() || &aList == &mAdoptedStyleSheets) { Servo_AuthorStyles_AppendStyleSheet(mServoStyles.get(), &aSheet); return; } if (auto* before = FirstApplicableAdoptedStyleSheet(mAdoptedStyleSheets)) { Servo_AuthorStyles_InsertStyleSheetBefore(mServoStyles.get(), &aSheet, before); } else { Servo_AuthorStyles_AppendStyleSheet(mServoStyles.get(), &aSheet); } } // FIXME(emilio): This needs to notify document observers and such, // presumably. void ShadowRoot::StyleSheetApplicableStateChanged(StyleSheet& aSheet) { auto& sheetList = aSheet.IsConstructed() ? mAdoptedStyleSheets : mStyleSheets; int32_t index = sheetList.LastIndexOf(&aSheet); if (index < 0) { // NOTE(emilio): @import sheets are handled in the relevant RuleAdded // notification, which only notifies after the sheet is loaded. // // This setup causes weirdness in other places, we may want to fix this in // bug 1465031. MOZ_DIAGNOSTIC_ASSERT(aSheet.GetParentSheet(), "It'd better be an @import sheet"); return; } if (aSheet.IsApplicable()) { InsertSheetIntoAuthorData(size_t(index), aSheet, sheetList); } else { MOZ_ASSERT(mServoStyles); if (mStyleRuleMap) { mStyleRuleMap->SheetRemoved(aSheet); } Servo_AuthorStyles_RemoveStyleSheet(mServoStyles.get(), &aSheet); ApplicableRulesChanged(); } } void ShadowRoot::RemoveSheetFromStyles(StyleSheet& aSheet) { MOZ_ASSERT(aSheet.IsApplicable()); MOZ_ASSERT(mServoStyles); if (mStyleRuleMap) { mStyleRuleMap->SheetRemoved(aSheet); } Servo_AuthorStyles_RemoveStyleSheet(mServoStyles.get(), &aSheet); ApplicableRulesChanged(); } void ShadowRoot::AddToIdTable(Element* aElement, nsAtom* aId) { IdentifierMapEntry* entry = mIdentifierMap.PutEntry(aId); if (entry) { entry->AddIdElement(aElement); } } void ShadowRoot::RemoveFromIdTable(Element* aElement, nsAtom* aId) { IdentifierMapEntry* entry = mIdentifierMap.GetEntry(aId); if (entry) { entry->RemoveIdElement(aElement); if (entry->IsEmpty()) { mIdentifierMap.RemoveEntry(entry); } } } void ShadowRoot::GetEventTargetParent(EventChainPreVisitor& aVisitor) { aVisitor.mCanHandle = true; aVisitor.mRootOfClosedTree = IsClosed(); // Inform that we're about to exit the current scope. aVisitor.mRelatedTargetRetargetedInCurrentScope = false; // https://dom.spec.whatwg.org/#ref-for-get-the-parent%E2%91%A6 if (!aVisitor.mEvent->mFlags.mComposed) { nsCOMPtr originalTarget = nsIContent::FromEventTargetOrNull(aVisitor.mEvent->mOriginalTarget); if (originalTarget && 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 win = OwnerDoc()->GetWindow(); EventTarget* parentTarget = win && aVisitor.mEvent->mMessage != eLoad ? win->GetParentTarget() : nullptr; aVisitor.SetParentTarget(parentTarget, true); return; } } nsIContent* shadowHost = GetHost(); aVisitor.SetParentTarget(shadowHost, false); nsCOMPtr content( nsIContent::FromEventTargetOrNull(aVisitor.mEvent->mTarget)); if (content && content->GetContainingShadow() == this) { aVisitor.mEventTargetAtParent = shadowHost; } } void ShadowRoot::GetSlotNameFor(const nsIContent& aContent, nsAString& aName) const { if (mIsDetailsShadowTree) { const auto* summary = HTMLSummaryElement::FromNode(aContent); if (summary && summary->IsMainSummary()) { aName.AssignLiteral("internal-main-summary"); } // Otherwise use the default slot. return; } // Note that if slot attribute is missing, assign it to the first default // slot, if exists. if (const Element* element = Element::FromNode(aContent)) { element->GetAttr(nsGkAtoms::slot, aName); } } ShadowRoot::SlotInsertionPoint ShadowRoot::SlotInsertionPointFor( nsIContent& aContent) { HTMLSlotElement* slot = nullptr; if (SlotAssignment() == SlotAssignmentMode::Manual) { slot = aContent.GetManualSlotAssignment(); if (!slot || slot->GetContainingShadow() != this) { return {}; } } else { nsAutoString slotName; GetSlotNameFor(aContent, slotName); SlotArray* slots = mSlotMap.Get(slotName); if (!slots) { return {}; } slot = (*slots)->ElementAt(0); } MOZ_ASSERT(slot); if (SlotAssignment() == SlotAssignmentMode::Named) { if (!aContent.GetNextSibling()) { // aContent is the last child, no need to loop through the assigned nodes, // we're necessarily the last one. // // This prevents multiple appends into the host from getting quadratic. return {slot, Nothing()}; } } else { // For manual slots, if aContent is the last element, we return Nothing // because we just need to append the element to the assigned nodes. No need // to return an index. if (slot->ManuallyAssignedNodes().SafeLastElement(nullptr) == &aContent) { return {slot, Nothing()}; } } // Find the appropriate position in the assigned node list for the newly // assigned content. if (SlotAssignment() == SlotAssignmentMode::Manual) { const nsTArray& manuallyAssignedNodes = slot->ManuallyAssignedNodes(); auto index = manuallyAssignedNodes.IndexOf(&aContent); if (index != manuallyAssignedNodes.NoIndex) { return {slot, Some(index)}; } } else { const nsTArray>& assignedNodes = slot->AssignedNodes(); nsIContent* currentContent = GetHost()->GetFirstChild(); 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) { return {slot, Some(i)}; } currentContent = currentContent->GetNextSibling(); } } } return {slot, Nothing()}; } void ShadowRoot::MaybeReassignContent(nsIContent& aElementOrText) { MOZ_ASSERT(aElementOrText.GetParent() == GetHost()); MOZ_ASSERT(aElementOrText.IsElement() || aElementOrText.IsText()); HTMLSlotElement* oldSlot = aElementOrText.GetAssignedSlot(); SlotInsertionPoint assignment = SlotInsertionPointFor(aElementOrText); if (assignment.mSlot == oldSlot) { // Nothing to do here. return; } // The layout invalidation piece for Manual slots is handled in // HTMLSlotElement::Assign if (aElementOrText.IsElement() && SlotAssignment() == SlotAssignmentMode::Named) { if (Document* doc = GetComposedDoc()) { if (RefPtr presShell = doc->GetPresShell()) { presShell->SlotAssignmentWillChange(*aElementOrText.AsElement(), oldSlot, assignment.mSlot); } } } if (oldSlot) { if (SlotAssignment() == SlotAssignmentMode::Named) { oldSlot->RemoveAssignedNode(aElementOrText); // Don't need to EnqueueSlotChangeEvent for Manual slots because it // needs to be done in tree order, so // HTMLSlotElement::Assign will handle it explicitly. oldSlot->EnqueueSlotChangeEvent(); } else { oldSlot->RemoveManuallyAssignedNode(aElementOrText); } } if (assignment.mSlot) { if (assignment.mIndex) { assignment.mSlot->InsertAssignedNode(*assignment.mIndex, aElementOrText); } else { assignment.mSlot->AppendAssignedNode(aElementOrText); } // Similar as above, HTMLSlotElement::Assign handles enqueuing // slotchange event. if (SlotAssignment() == SlotAssignmentMode::Named) { assignment.mSlot->EnqueueSlotChangeEvent(); } } } void ShadowRoot::MaybeReassignMainSummary(SummaryChangeReason aReason) { MOZ_ASSERT(mIsDetailsShadowTree); if (aReason == SummaryChangeReason::Insertion) { // We've inserted a summary element, may need to remove the existing one. SlotArray* array = mSlotMap.Get(u"internal-main-summary"_ns); MOZ_RELEASE_ASSERT(array && (*array)->Length() == 1); HTMLSlotElement* slot = (*array)->ElementAt(0); auto* summary = HTMLSummaryElement::FromNodeOrNull( slot->AssignedNodes().SafeElementAt(0)); if (summary) { MaybeReassignContent(*summary); } } else if (MOZ_LIKELY(GetHost())) { // We need to null-check GetHost() in case we're unlinking already. auto* details = HTMLDetailsElement::FromNode(Host()); MOZ_DIAGNOSTIC_ASSERT(details); // We've removed a summary element, we may need to assign the new one. if (HTMLSummaryElement* newMainSummary = details->GetFirstSummary()) { MaybeReassignContent(*newMainSummary); } } } Element* ShadowRoot::GetActiveElement() { return GetRetargetedFocusedElement(); } nsINode* ShadowRoot::ImportNodeAndAppendChildAt(nsINode& aParentNode, nsINode& aNode, bool aDeep, mozilla::ErrorResult& rv) { MOZ_ASSERT(IsUAWidget()); if (aParentNode.SubtreeRoot() != this) { rv.Throw(NS_ERROR_INVALID_ARG); return nullptr; } RefPtr node = OwnerDoc()->ImportNode(aNode, aDeep, rv); if (rv.Failed()) { return nullptr; } return aParentNode.AppendChild(*node, rv); } nsINode* ShadowRoot::CreateElementAndAppendChildAt(nsINode& aParentNode, const nsAString& aTagName, mozilla::ErrorResult& rv) { MOZ_ASSERT(IsUAWidget()); if (aParentNode.SubtreeRoot() != this) { rv.Throw(NS_ERROR_INVALID_ARG); return nullptr; } // This option is not exposed to UA Widgets ElementCreationOptionsOrString options; RefPtr node = OwnerDoc()->CreateElement(aTagName, options, rv); if (rv.Failed()) { return nullptr; } return aParentNode.AppendChild(*node, rv); } void ShadowRoot::MaybeUnslotHostChild(nsIContent& aChild) { // Need to null-check the host because we may be unlinked already. MOZ_ASSERT(!GetHost() || aChild.GetParent() == GetHost()); HTMLSlotElement* slot = aChild.GetAssignedSlot(); if (!slot) { return; } MOZ_DIAGNOSTIC_ASSERT(!aChild.IsRootOfNativeAnonymousSubtree(), "How did aChild end up assigned to a slot?"); // If the slot is going to start showing fallback content, we need to tell // layout about it. if (slot->AssignedNodes().Length() == 1 && slot->HasChildren()) { InvalidateStyleAndLayoutOnSubtree(slot); } slot->RemoveAssignedNode(aChild); slot->EnqueueSlotChangeEvent(); if (mIsDetailsShadowTree && aChild.IsHTMLElement(nsGkAtoms::summary)) { MaybeReassignMainSummary(SummaryChangeReason::Deletion); } } void ShadowRoot::MaybeSlotHostChild(nsIContent& aChild) { MOZ_ASSERT(aChild.GetParent() == GetHost()); // Check to ensure that the child not an anonymous subtree root because even // though its parent could be the host it may not be in the host's child // list. if (aChild.IsRootOfNativeAnonymousSubtree()) { return; } if (!aChild.IsSlotable()) { return; } if (mIsDetailsShadowTree && aChild.IsHTMLElement(nsGkAtoms::summary)) { MaybeReassignMainSummary(SummaryChangeReason::Insertion); } SlotInsertionPoint assignment = SlotInsertionPointFor(aChild); if (!assignment.mSlot) { return; } // Fallback content will go away, let layout know. if (assignment.mSlot->AssignedNodes().IsEmpty() && assignment.mSlot->HasChildren()) { InvalidateStyleAndLayoutOnSubtree(assignment.mSlot); } if (assignment.mIndex) { assignment.mSlot->InsertAssignedNode(*assignment.mIndex, aChild); } else { assignment.mSlot->AppendAssignedNode(aChild); } assignment.mSlot->EnqueueSlotChangeEvent(); } ServoStyleRuleMap& ShadowRoot::ServoStyleRuleMap() { if (!mStyleRuleMap) { mStyleRuleMap = MakeUnique(); } mStyleRuleMap->EnsureTable(*this); return *mStyleRuleMap; } nsresult ShadowRoot::Clone(dom::NodeInfo* aNodeInfo, nsINode** aResult) const { *aResult = nullptr; return NS_ERROR_DOM_NOT_SUPPORTED_ERR; }