forked from mirrors/gecko-dev
Make all UA widgets also NAC. Keep the UA widget flag but break at anonymous subtree boundaries, so that only nodes inside the UA widget directly (and not NAC from those) get the flag. This is important because two callers depend on this difference: * The style system, since we still want to match content rules from stylesheets in the UA widget. We also match user rules, which is a bit sketchy, but that was the previous behavior, will file a follow-up for that. * The reflector code, since we want the scope for UA widgets to not include the NAC nodes inside that UA widget. nsINode::IsInUAWidget got it wrong. After this patch, ChromeOnlyAccess is equivalent to IsInNativeAnonymousSubtree, so we should probably unify the naming. That's left for a follow-up patch because I don't have a strong preference. Differential Revision: https://phabricator.services.mozilla.com/D174310
872 lines
27 KiB
C++
872 lines
27 KiB
C++
/* -*- 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) {
|
|
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 = Servo_AuthorStyles_Create().Consume();
|
|
}
|
|
|
|
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;
|
|
}
|