/* -*- 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/ShadowRoot.h" #include "mozilla/dom/ShadowRootBinding.h" #include "mozilla/dom/DocumentFragment.h" #include "ChildIterator.h" #include "nsContentUtils.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" using namespace mozilla; using namespace mozilla::dom; NS_IMPL_CYCLE_COLLECTION_CLASS(ShadowRoot) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(ShadowRoot, DocumentFragment) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDOMStyleSheets) for (auto iter = tmp->mIdentifierMap.ConstIter(); !iter.Done(); iter.Next()) { iter.Get()->Traverse(&cb); } NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ShadowRoot) if (tmp->GetHost()) { tmp->GetHost()->RemoveMutationObserver(tmp); } NS_IMPL_CYCLE_COLLECTION_UNLINK(mDOMStyleSheets) tmp->mIdentifierMap.Clear(); 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(nsIMutationObserver) NS_INTERFACE_MAP_END_INHERITING(DocumentFragment) NS_IMPL_ADDREF_INHERITED(ShadowRoot, DocumentFragment) NS_IMPL_RELEASE_INHERITED(ShadowRoot, DocumentFragment) ShadowRoot::ShadowRoot(Element* aElement, ShadowRootMode aMode, already_AddRefed&& aNodeInfo) : DocumentFragment(aNodeInfo) , DocumentOrShadowRoot(*this) , mMode(aMode) , mServoStyles(Servo_AuthorStyles_Create()) , mIsComposedDocParticipant(false) { 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); ExtendedDOMSlots()->mBindingParent = aElement; ExtendedDOMSlots()->mContainingShadow = this; // Add the ShadowRoot as a mutation observer on the host to watch // for mutations because the insertion points in this ShadowRoot // may need to be updated when the host children are modified. GetHost()->AddMutationObserver(this); } ShadowRoot::~ShadowRoot() { if (auto* host = GetHost()) { // mHost may have been unlinked. host->RemoveMutationObserver(this); } if (IsComposedDocParticipant()) { OwnerDoc()->RemoveComposedDocShadowRoot(*this); } MOZ_DIAGNOSTIC_ASSERT(!OwnerDoc()->IsComposedDocShadowRoot(*this)); UnsetFlags(NODE_IS_IN_SHADOW_TREE); // nsINode destructor expects mSubtreeRoot == this. SetSubtreeRootPointer(this); } void ShadowRoot::SetIsComposedDocParticipant(bool aIsComposedDocParticipant) { bool changed = mIsComposedDocParticipant != aIsComposedDocParticipant; mIsComposedDocParticipant = aIsComposedDocParticipant; if (!changed) { return; } nsIDocument* doc = OwnerDoc(); if (IsComposedDocParticipant()) { doc->AddComposedDocShadowRoot(*this); } else { doc->RemoveComposedDocShadowRoot(*this); } } JSObject* ShadowRoot::WrapObject(JSContext* aCx, JS::Handle aGivenProto) { return mozilla::dom::ShadowRootBinding::Wrap(aCx, this, aGivenProto); } void ShadowRoot::CloneInternalDataFrom(ShadowRoot* aOther) { 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, nullptr, nullptr, nullptr); if (clonedSheet) { AppendStyleSheet(*clonedSheet.get()); } } } } void ShadowRoot::InvalidateStyleAndLayoutOnSubtree(Element* aElement) { MOZ_ASSERT(aElement); if (!IsComposedDocParticipant()) { return; } MOZ_ASSERT(GetComposedDoc() == OwnerDoc()); nsIPresShell* shell = OwnerDoc()->GetShell(); if (!shell) { return; } shell->DestroyFramesForAndRestyle(aElement); } 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* currentSlots = mSlotMap.LookupOrAdd(name); MOZ_ASSERT(currentSlots); HTMLSlotElement* oldSlot = currentSlots->SafeElementAt(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. InvalidateStyleAndLayoutOnSubtree(oldSlot); const nsTArray>& assignedNodes = oldSlot->AssignedNodes(); bool doEnqueueSlotChange = false; while (assignedNodes.Length() > 0) { nsINode* assignedNode = assignedNodes[0]; oldSlot->RemoveAssignedNode(assignedNode); currentSlot->AppendAssignedNode(assignedNode); doEnqueueSlotChange = true; } if (doEnqueueSlotChange) { oldSlot->EnqueueSlotChangeEvent(); currentSlot->EnqueueSlotChangeEvent(); } } 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; if (child->IsElement()) { child->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::slot, slotName); } if (!child->IsSlotable() || !slotName.Equals(name)) { continue; } doEnqueueSlotChange = true; currentSlot->AppendAssignedNode(child); } if (doEnqueueSlotChange) { currentSlot->EnqueueSlotChangeEvent(); } } } void ShadowRoot::RemoveSlot(HTMLSlotElement* aSlot) { MOZ_ASSERT(aSlot); nsAutoString name; aSlot->GetName(name); SlotArray* currentSlots = mSlotMap.Get(name); MOZ_DIAGNOSTIC_ASSERT(currentSlots && currentSlots->Contains(aSlot), "Slot to deregister wasn't found?"); if (currentSlots->Length() == 1) { MOZ_ASSERT(currentSlots->ElementAt(0) == aSlot); mSlotMap.Remove(name); if (!aSlot->AssignedNodes().IsEmpty()) { aSlot->ClearAssignedNodes(); aSlot->EnqueueSlotChangeEvent(); } return; } const bool wasFirstSlot = currentSlots->ElementAt(0) == aSlot; currentSlots->RemoveElement(aSlot); // Move assigned nodes from removed slot to the next slot in // tree order with the same name. if (!wasFirstSlot) { return; } HTMLSlotElement* replacementSlot = currentSlots->ElementAt(0); const nsTArray>& assignedNodes = aSlot->AssignedNodes(); bool slottedNodesChanged = !assignedNodes.IsEmpty(); while (!assignedNodes.IsEmpty()) { nsINode* assignedNode = assignedNodes[0]; aSlot->RemoveAssignedNode(assignedNode); replacementSlot->AppendAssignedNode(assignedNode); } if (slottedNodesChanged) { 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 (mStyleRuleMap) { mStyleRuleMap->RuleAdded(*aSheet.AsServo(), aRule); } Servo_AuthorStyles_ForceDirty(mServoStyles.get()); ApplicableRulesChanged(); } void ShadowRoot::RuleRemoved(StyleSheet& aSheet, css::Rule& aRule) { if (mStyleRuleMap) { mStyleRuleMap->RuleRemoved(*aSheet.AsServo(), aRule); } Servo_AuthorStyles_ForceDirty(mServoStyles.get()); ApplicableRulesChanged(); } void ShadowRoot::RuleChanged(StyleSheet&, css::Rule*) { Servo_AuthorStyles_ForceDirty(mServoStyles.get()); ApplicableRulesChanged(); } void ShadowRoot::ApplicableRulesChanged() { if (!IsComposedDocParticipant()) { return; } nsIDocument* doc = OwnerDoc(); if (nsIPresShell* shell = doc->GetShell()) { doc->BeginUpdate(UPDATE_STYLE); shell->RecordShadowStyleChange(*this); doc->EndUpdate(UPDATE_STYLE); } } void ShadowRoot::InsertSheetAt(size_t aIndex, StyleSheet& aSheet) { DocumentOrShadowRoot::InsertSheetAt(aIndex, aSheet); if (aSheet.IsApplicable()) { InsertSheetIntoAuthorData(aIndex, aSheet); } } void ShadowRoot::AppendStyleSheet(StyleSheet& aSheet) { DocumentOrShadowRoot::AppendStyleSheet(aSheet); if (aSheet.IsApplicable()) { Servo_AuthorStyles_AppendStyleSheet(mServoStyles.get(), aSheet.AsServo()); if (mStyleRuleMap) { mStyleRuleMap->SheetAdded(*aSheet.AsServo()); } ApplicableRulesChanged(); } } void ShadowRoot::InsertSheet(StyleSheet* aSheet, nsIContent* aLinkingContent) { nsCOMPtr linkingElement = do_QueryInterface(aLinkingContent); // FIXME(emilio, bug 1410578): should probably also be allowed here. MOZ_ASSERT(linkingElement, "The only styles in a ShadowRoot should come " "from