diff options
Diffstat (limited to 'dom/base/ShadowRoot.cpp')
-rw-r--r-- | dom/base/ShadowRoot.cpp | 877 |
1 files changed, 877 insertions, 0 deletions
diff --git a/dom/base/ShadowRoot.cpp b/dom/base/ShadowRoot.cpp new file mode 100644 index 0000000000..30c12a14b3 --- /dev/null +++ b/dom/base/ShadowRoot.cpp @@ -0,0 +1,877 @@ +/* -*- 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<mozilla::dom::NodeInfo>&& 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<nsINode*>(this) == reinterpret_cast<nsINode*>(this)); + MOZ_ASSERT(static_cast<nsIContent*>(this) == + reinterpret_cast<nsIContent*>(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<JSObject*> 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<StyleSheet> 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<RefPtr<nsINode>>& 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<RefPtr<nsINode>>& 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<RefPtr<StyleSheet>>& 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<RefPtr<StyleSheet>>& 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<nsIContent> 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<nsPIDOMWindowOuter> 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<nsIContent> 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<nsINode*>& manuallyAssignedNodes = + slot->ManuallyAssignedNodes(); + auto index = manuallyAssignedNodes.IndexOf(&aContent); + if (index != manuallyAssignedNodes.NoIndex) { + return {slot, Some(index)}; + } + } else { + const nsTArray<RefPtr<nsINode>>& 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> 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<nsINode> 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<nsINode> 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<mozilla::ServoStyleRuleMap>(); + } + mStyleRuleMap->EnsureTable(*this); + return *mStyleRuleMap; +} + +nsresult ShadowRoot::Clone(dom::NodeInfo* aNodeInfo, nsINode** aResult) const { + *aResult = nullptr; + return NS_ERROR_DOM_NOT_SUPPORTED_ERR; +} |