1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7 #include "mozilla/Preferences.h"
8 #include "mozilla/dom/BindContext.h"
9 #include "mozilla/dom/ShadowRoot.h"
10 #include "mozilla/dom/DocumentFragment.h"
11 #include "ChildIterator.h"
12 #include "nsContentUtils.h"
13 #include "nsWindowSizes.h"
14 #include "mozilla/dom/DirectionalityUtils.h"
15 #include "mozilla/dom/Element.h"
16 #include "mozilla/dom/HTMLSlotElement.h"
17 #include "mozilla/dom/Text.h"
18 #include "mozilla/dom/TreeOrderedArrayInlines.h"
19 #include "mozilla/EventDispatcher.h"
20 #include "mozilla/IdentifierMapEntry.h"
21 #include "mozilla/PresShell.h"
22 #include "mozilla/PresShellInlines.h"
23 #include "mozilla/ScopeExit.h"
24 #include "mozilla/ServoStyleRuleMap.h"
25 #include "mozilla/StyleSheet.h"
26 #include "mozilla/StyleSheetInlines.h"
27 #include "mozilla/dom/StyleSheetList.h"
28
29 using namespace mozilla;
30 using namespace mozilla::dom;
31
32 NS_IMPL_CYCLE_COLLECTION_CLASS(ShadowRoot)
33
34 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(ShadowRoot, DocumentFragment)
35 DocumentOrShadowRoot::Traverse(tmp, cb);
36 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
37
38 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ShadowRoot)
39 DocumentOrShadowRoot::Unlink(tmp);
40 NS_IMPL_CYCLE_COLLECTION_UNLINK_END_INHERITED(DocumentFragment)
41
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ShadowRoot)42 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ShadowRoot)
43 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIContent)
44 NS_INTERFACE_MAP_ENTRY(nsIRadioGroupContainer)
45 NS_INTERFACE_MAP_END_INHERITING(DocumentFragment)
46
47 NS_IMPL_ADDREF_INHERITED(ShadowRoot, DocumentFragment)
48 NS_IMPL_RELEASE_INHERITED(ShadowRoot, DocumentFragment)
49
50 ShadowRoot::ShadowRoot(Element* aElement, ShadowRootMode aMode,
51 Element::DelegatesFocus aDelegatesFocus,
52 SlotAssignmentMode aSlotAssignment,
53 already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)
54 : DocumentFragment(std::move(aNodeInfo)),
55 DocumentOrShadowRoot(this),
56 mMode(aMode),
57 mDelegatesFocus(aDelegatesFocus),
58 mSlotAssignment(aSlotAssignment),
59 mIsUAWidget(false),
60 mIsAvailableToElementInternals(false) {
61 SetHost(aElement);
62
63 // Nodes in a shadow tree should never store a value
64 // in the subtree root pointer, nodes in the shadow tree
65 // track the subtree root using GetContainingShadow().
66 ClearSubtreeRootPointer();
67
68 SetFlags(NODE_IS_IN_SHADOW_TREE);
69 Bind();
70
71 ExtendedDOMSlots()->mContainingShadow = this;
72 }
73
~ShadowRoot()74 ShadowRoot::~ShadowRoot() {
75 if (IsInComposedDoc()) {
76 OwnerDoc()->RemoveComposedDocShadowRoot(*this);
77 }
78
79 MOZ_DIAGNOSTIC_ASSERT(!OwnerDoc()->IsComposedDocShadowRoot(*this));
80
81 UnsetFlags(NODE_IS_IN_SHADOW_TREE);
82
83 // nsINode destructor expects mSubtreeRoot == this.
84 SetSubtreeRootPointer(this);
85 }
86
87 MOZ_DEFINE_MALLOC_SIZE_OF(ShadowRootAuthorStylesMallocSizeOf)
MOZ_DEFINE_MALLOC_ENCLOSING_SIZE_OF(ShadowRootAuthorStylesMallocEnclosingSizeOf)88 MOZ_DEFINE_MALLOC_ENCLOSING_SIZE_OF(ShadowRootAuthorStylesMallocEnclosingSizeOf)
89
90 void ShadowRoot::AddSizeOfExcludingThis(nsWindowSizes& aSizes,
91 size_t* aNodeSize) const {
92 DocumentFragment::AddSizeOfExcludingThis(aSizes, aNodeSize);
93 DocumentOrShadowRoot::AddSizeOfExcludingThis(aSizes);
94 aSizes.mLayoutShadowDomAuthorStyles += Servo_AuthorStyles_SizeOfIncludingThis(
95 ShadowRootAuthorStylesMallocSizeOf,
96 ShadowRootAuthorStylesMallocEnclosingSizeOf, mServoStyles.get());
97 }
98
WrapNode(JSContext * aCx,JS::Handle<JSObject * > aGivenProto)99 JSObject* ShadowRoot::WrapNode(JSContext* aCx,
100 JS::Handle<JSObject*> aGivenProto) {
101 return mozilla::dom::ShadowRoot_Binding::Wrap(aCx, this, aGivenProto);
102 }
103
CloneInternalDataFrom(ShadowRoot * aOther)104 void ShadowRoot::CloneInternalDataFrom(ShadowRoot* aOther) {
105 if (aOther->IsUAWidget()) {
106 SetIsUAWidget();
107 }
108
109 size_t sheetCount = aOther->SheetCount();
110 for (size_t i = 0; i < sheetCount; ++i) {
111 StyleSheet* sheet = aOther->SheetAt(i);
112 if (sheet->IsApplicable()) {
113 RefPtr<StyleSheet> clonedSheet = sheet->Clone(nullptr, this);
114 if (clonedSheet) {
115 AppendStyleSheet(*clonedSheet.get());
116 }
117 }
118 }
119 CloneAdoptedSheetsFrom(*aOther);
120 }
121
Bind()122 nsresult ShadowRoot::Bind() {
123 MOZ_ASSERT(!IsInComposedDoc(), "Forgot to unbind?");
124 if (Host()->IsInComposedDoc()) {
125 SetIsConnected(true);
126 Document* doc = OwnerDoc();
127 doc->AddComposedDocShadowRoot(*this);
128 // If our stylesheets somehow mutated when we were disconnected, we need to
129 // ensure that our style data gets flushed as appropriate.
130 if (mServoStyles && Servo_AuthorStyles_IsDirty(mServoStyles.get())) {
131 doc->RecordShadowStyleChange(*this);
132 }
133 }
134
135 BindContext context(*this);
136 for (nsIContent* child = GetFirstChild(); child;
137 child = child->GetNextSibling()) {
138 nsresult rv = child->BindToTree(context, *this);
139 NS_ENSURE_SUCCESS(rv, rv);
140 }
141
142 return NS_OK;
143 }
144
Unbind()145 void ShadowRoot::Unbind() {
146 if (IsInComposedDoc()) {
147 SetIsConnected(false);
148 OwnerDoc()->RemoveComposedDocShadowRoot(*this);
149 }
150
151 for (nsIContent* child = GetFirstChild(); child;
152 child = child->GetNextSibling()) {
153 child->UnbindFromTree(false);
154 }
155 }
156
Unattach()157 void ShadowRoot::Unattach() {
158 MOZ_ASSERT(!HasSlots(), "Won't work!");
159 if (!GetHost()) {
160 // It is possible that we've been unlinked already. In such case host
161 // should have called Unbind and ShadowRoot's own unlink.
162 return;
163 }
164
165 Unbind();
166 SetHost(nullptr);
167 }
168
InvalidateStyleAndLayoutOnSubtree(Element * aElement)169 void ShadowRoot::InvalidateStyleAndLayoutOnSubtree(Element* aElement) {
170 MOZ_ASSERT(aElement);
171 Document* doc = GetComposedDoc();
172 if (!doc) {
173 return;
174 }
175
176 PresShell* presShell = doc->GetPresShell();
177 if (!presShell) {
178 return;
179 }
180
181 presShell->DestroyFramesForAndRestyle(aElement);
182 }
183
PartAdded(const Element & aPart)184 void ShadowRoot::PartAdded(const Element& aPart) {
185 MOZ_ASSERT(aPart.HasPartAttribute());
186 MOZ_ASSERT(!mParts.Contains(&aPart));
187 mParts.AppendElement(&aPart);
188 }
189
PartRemoved(const Element & aPart)190 void ShadowRoot::PartRemoved(const Element& aPart) {
191 MOZ_ASSERT(mParts.Contains(&aPart));
192 mParts.RemoveElement(&aPart);
193 MOZ_ASSERT(!mParts.Contains(&aPart));
194 }
195
AddSlot(HTMLSlotElement * aSlot)196 void ShadowRoot::AddSlot(HTMLSlotElement* aSlot) {
197 MOZ_ASSERT(aSlot);
198
199 // Note that if name attribute missing, the slot is a default slot.
200 nsAutoString name;
201 aSlot->GetName(name);
202
203 SlotArray& currentSlots = *mSlotMap.GetOrInsertNew(name);
204
205 size_t index = currentSlots.Insert(*aSlot);
206
207 // For Named slots, slottables are inserted into the other slot
208 // which has the same name already, however it's not the case
209 // for manual slots
210 if (index != 0 && SlotAssignment() == SlotAssignmentMode::Named) {
211 return;
212 }
213
214 HTMLSlotElement* oldSlot = currentSlots->SafeElementAt(1);
215 if (SlotAssignment() == SlotAssignmentMode::Named) {
216 if (oldSlot) {
217 MOZ_DIAGNOSTIC_ASSERT(oldSlot != aSlot);
218
219 // Move assigned nodes from old slot to new slot.
220 InvalidateStyleAndLayoutOnSubtree(oldSlot);
221 const nsTArray<RefPtr<nsINode>>& assignedNodes = oldSlot->AssignedNodes();
222 bool doEnqueueSlotChange = false;
223 while (assignedNodes.Length() > 0) {
224 nsINode* assignedNode = assignedNodes[0];
225
226 oldSlot->RemoveAssignedNode(*assignedNode->AsContent());
227 aSlot->AppendAssignedNode(*assignedNode->AsContent());
228 doEnqueueSlotChange = true;
229 }
230
231 if (doEnqueueSlotChange) {
232 oldSlot->EnqueueSlotChangeEvent();
233 aSlot->EnqueueSlotChangeEvent();
234 SlotStateChanged(oldSlot);
235 SlotStateChanged(aSlot);
236 }
237 } else {
238 bool doEnqueueSlotChange = false;
239 // Otherwise add appropriate nodes to this slot from the host.
240 for (nsIContent* child = GetHost()->GetFirstChild(); child;
241 child = child->GetNextSibling()) {
242 nsAutoString slotName;
243 if (auto* element = Element::FromNode(*child)) {
244 element->GetAttr(nsGkAtoms::slot, slotName);
245 }
246 if (!child->IsSlotable() || !slotName.Equals(name)) {
247 continue;
248 }
249 doEnqueueSlotChange = true;
250 aSlot->AppendAssignedNode(*child);
251 }
252
253 if (doEnqueueSlotChange) {
254 aSlot->EnqueueSlotChangeEvent();
255 SlotStateChanged(aSlot);
256 }
257 }
258 } else {
259 bool doEnqueueSlotChange = false;
260 for (const auto& node : aSlot->ManuallyAssignedNodes()) {
261 if (GetHost() != node->GetParent()) {
262 continue;
263 }
264
265 MOZ_ASSERT(node->IsContent(),
266 "Manually assigned nodes should be an element or a text");
267 nsIContent* content = node->AsContent();
268
269 aSlot->AppendAssignedNode(*content);
270 doEnqueueSlotChange = true;
271 }
272 if (doEnqueueSlotChange) {
273 aSlot->EnqueueSlotChangeEvent();
274 SlotStateChanged(aSlot);
275 }
276 }
277 }
278
RemoveSlot(HTMLSlotElement * aSlot)279 void ShadowRoot::RemoveSlot(HTMLSlotElement* aSlot) {
280 MOZ_ASSERT(aSlot);
281
282 nsAutoString name;
283 aSlot->GetName(name);
284
285 MOZ_ASSERT(mSlotMap.Get(name));
286
287 SlotArray& currentSlots = *mSlotMap.Get(name);
288 MOZ_DIAGNOSTIC_ASSERT(currentSlots->Contains(aSlot),
289 "Slot to de-register wasn't found?");
290 if (currentSlots->Length() == 1) {
291 MOZ_ASSERT_IF(SlotAssignment() == SlotAssignmentMode::Named,
292 currentSlots->ElementAt(0) == aSlot);
293
294 InvalidateStyleAndLayoutOnSubtree(aSlot);
295
296 mSlotMap.Remove(name);
297 if (!aSlot->AssignedNodes().IsEmpty()) {
298 aSlot->ClearAssignedNodes();
299 aSlot->EnqueueSlotChangeEvent();
300 }
301
302 return;
303 }
304 if (SlotAssignment() == SlotAssignmentMode::Manual) {
305 InvalidateStyleAndLayoutOnSubtree(aSlot);
306 if (!aSlot->AssignedNodes().IsEmpty()) {
307 aSlot->ClearAssignedNodes();
308 aSlot->EnqueueSlotChangeEvent();
309 }
310 }
311
312 const bool wasFirstSlot = currentSlots->ElementAt(0) == aSlot;
313 currentSlots.RemoveElement(*aSlot);
314 if (!wasFirstSlot || SlotAssignment() == SlotAssignmentMode::Manual) {
315 return;
316 }
317
318 // Move assigned nodes from removed slot to the next slot in
319 // tree order with the same name.
320 InvalidateStyleAndLayoutOnSubtree(aSlot);
321 HTMLSlotElement* replacementSlot = currentSlots->ElementAt(0);
322 const nsTArray<RefPtr<nsINode>>& assignedNodes = aSlot->AssignedNodes();
323 if (assignedNodes.IsEmpty()) {
324 return;
325 }
326
327 InvalidateStyleAndLayoutOnSubtree(replacementSlot);
328 while (!assignedNodes.IsEmpty()) {
329 nsINode* assignedNode = assignedNodes[0];
330
331 aSlot->RemoveAssignedNode(*assignedNode->AsContent());
332 replacementSlot->AppendAssignedNode(*assignedNode->AsContent());
333 }
334
335 aSlot->EnqueueSlotChangeEvent();
336 replacementSlot->EnqueueSlotChangeEvent();
337 }
338
339 // FIXME(emilio): There's a bit of code duplication between this and the
340 // equivalent ServoStyleSet methods, it'd be nice to not duplicate it...
RuleAdded(StyleSheet & aSheet,css::Rule & aRule)341 void ShadowRoot::RuleAdded(StyleSheet& aSheet, css::Rule& aRule) {
342 if (!aSheet.IsApplicable()) {
343 return;
344 }
345
346 MOZ_ASSERT(mServoStyles);
347 if (mStyleRuleMap) {
348 mStyleRuleMap->RuleAdded(aSheet, aRule);
349 }
350
351 if (aRule.IsIncompleteImportRule()) {
352 return;
353 }
354
355 Servo_AuthorStyles_ForceDirty(mServoStyles.get());
356 ApplicableRulesChanged();
357 }
358
RuleRemoved(StyleSheet & aSheet,css::Rule & aRule)359 void ShadowRoot::RuleRemoved(StyleSheet& aSheet, css::Rule& aRule) {
360 if (!aSheet.IsApplicable()) {
361 return;
362 }
363
364 MOZ_ASSERT(mServoStyles);
365 if (mStyleRuleMap) {
366 mStyleRuleMap->RuleRemoved(aSheet, aRule);
367 }
368 Servo_AuthorStyles_ForceDirty(mServoStyles.get());
369 ApplicableRulesChanged();
370 }
371
RuleChanged(StyleSheet & aSheet,css::Rule *,StyleRuleChangeKind)372 void ShadowRoot::RuleChanged(StyleSheet& aSheet, css::Rule*,
373 StyleRuleChangeKind) {
374 if (!aSheet.IsApplicable()) {
375 return;
376 }
377
378 MOZ_ASSERT(mServoStyles);
379 Servo_AuthorStyles_ForceDirty(mServoStyles.get());
380 ApplicableRulesChanged();
381 }
382
ImportRuleLoaded(CSSImportRule &,StyleSheet & aSheet)383 void ShadowRoot::ImportRuleLoaded(CSSImportRule&, StyleSheet& aSheet) {
384 if (mStyleRuleMap) {
385 mStyleRuleMap->SheetAdded(aSheet);
386 }
387
388 if (!aSheet.IsApplicable()) {
389 return;
390 }
391
392 // TODO(emilio): Could handle it like a regular sheet insertion, I guess, to
393 // avoid throwing away the whole style data.
394 Servo_AuthorStyles_ForceDirty(mServoStyles.get());
395 ApplicableRulesChanged();
396 }
397
398 // We don't need to do anything else than forwarding to the document if
399 // necessary.
SheetCloned(StyleSheet & aSheet)400 void ShadowRoot::SheetCloned(StyleSheet& aSheet) {
401 if (Document* doc = GetComposedDoc()) {
402 if (PresShell* shell = doc->GetPresShell()) {
403 shell->StyleSet()->SheetCloned(aSheet);
404 }
405 }
406 }
407
ApplicableRulesChanged()408 void ShadowRoot::ApplicableRulesChanged() {
409 if (Document* doc = GetComposedDoc()) {
410 doc->RecordShadowStyleChange(*this);
411 }
412 }
413
InsertSheetAt(size_t aIndex,StyleSheet & aSheet)414 void ShadowRoot::InsertSheetAt(size_t aIndex, StyleSheet& aSheet) {
415 DocumentOrShadowRoot::InsertSheetAt(aIndex, aSheet);
416 if (aSheet.IsApplicable()) {
417 InsertSheetIntoAuthorData(aIndex, aSheet, mStyleSheets);
418 }
419 }
420
FirstApplicableAdoptedStyleSheet(const nsTArray<RefPtr<StyleSheet>> & aList)421 StyleSheet* FirstApplicableAdoptedStyleSheet(
422 const nsTArray<RefPtr<StyleSheet>>& aList) {
423 size_t i = 0;
424 for (StyleSheet* sheet : aList) {
425 // Deal with duplicate sheets by only considering the last one.
426 if (sheet->IsApplicable() && MOZ_LIKELY(aList.LastIndexOf(sheet) == i)) {
427 return sheet;
428 }
429 i++;
430 }
431 return nullptr;
432 }
433
InsertSheetIntoAuthorData(size_t aIndex,StyleSheet & aSheet,const nsTArray<RefPtr<StyleSheet>> & aList)434 void ShadowRoot::InsertSheetIntoAuthorData(
435 size_t aIndex, StyleSheet& aSheet,
436 const nsTArray<RefPtr<StyleSheet>>& aList) {
437 MOZ_ASSERT(aSheet.IsApplicable());
438 MOZ_ASSERT(aList[aIndex] == &aSheet);
439 MOZ_ASSERT(aList.LastIndexOf(&aSheet) == aIndex);
440 MOZ_ASSERT(&aList == &mAdoptedStyleSheets || &aList == &mStyleSheets);
441
442 if (!mServoStyles) {
443 mServoStyles = Servo_AuthorStyles_Create().Consume();
444 }
445
446 if (mStyleRuleMap) {
447 mStyleRuleMap->SheetAdded(aSheet);
448 }
449
450 auto changedOnExit =
451 mozilla::MakeScopeExit([&] { ApplicableRulesChanged(); });
452
453 for (size_t i = aIndex + 1; i < aList.Length(); ++i) {
454 StyleSheet* beforeSheet = aList.ElementAt(i);
455 if (!beforeSheet->IsApplicable()) {
456 continue;
457 }
458
459 // If this is a duplicate adopted stylesheet that is not in the right
460 // position (the last one) then we skip over it. Otherwise we're done.
461 if (&aList == &mAdoptedStyleSheets &&
462 MOZ_UNLIKELY(aList.LastIndexOf(beforeSheet) != i)) {
463 continue;
464 }
465
466 Servo_AuthorStyles_InsertStyleSheetBefore(mServoStyles.get(), &aSheet,
467 beforeSheet);
468 return;
469 }
470
471 if (mAdoptedStyleSheets.IsEmpty() || &aList == &mAdoptedStyleSheets) {
472 Servo_AuthorStyles_AppendStyleSheet(mServoStyles.get(), &aSheet);
473 return;
474 }
475
476 if (auto* before = FirstApplicableAdoptedStyleSheet(mAdoptedStyleSheets)) {
477 Servo_AuthorStyles_InsertStyleSheetBefore(mServoStyles.get(), &aSheet,
478 before);
479 } else {
480 Servo_AuthorStyles_AppendStyleSheet(mServoStyles.get(), &aSheet);
481 }
482 }
483
484 // FIXME(emilio): This needs to notify document observers and such,
485 // presumably.
StyleSheetApplicableStateChanged(StyleSheet & aSheet)486 void ShadowRoot::StyleSheetApplicableStateChanged(StyleSheet& aSheet) {
487 auto& sheetList = aSheet.IsConstructed() ? mAdoptedStyleSheets : mStyleSheets;
488 int32_t index = sheetList.LastIndexOf(&aSheet);
489 if (index < 0) {
490 // NOTE(emilio): @import sheets are handled in the relevant RuleAdded
491 // notification, which only notifies after the sheet is loaded.
492 //
493 // This setup causes weirdness in other places, we may want to fix this in
494 // bug 1465031.
495 MOZ_DIAGNOSTIC_ASSERT(aSheet.GetParentSheet(),
496 "It'd better be an @import sheet");
497 return;
498 }
499 if (aSheet.IsApplicable()) {
500 InsertSheetIntoAuthorData(size_t(index), aSheet, sheetList);
501 } else {
502 MOZ_ASSERT(mServoStyles);
503 if (mStyleRuleMap) {
504 mStyleRuleMap->SheetRemoved(aSheet);
505 }
506 Servo_AuthorStyles_RemoveStyleSheet(mServoStyles.get(), &aSheet);
507 ApplicableRulesChanged();
508 }
509 }
510
RemoveSheetFromStyles(StyleSheet & aSheet)511 void ShadowRoot::RemoveSheetFromStyles(StyleSheet& aSheet) {
512 MOZ_ASSERT(aSheet.IsApplicable());
513 MOZ_ASSERT(mServoStyles);
514 if (mStyleRuleMap) {
515 mStyleRuleMap->SheetRemoved(aSheet);
516 }
517 Servo_AuthorStyles_RemoveStyleSheet(mServoStyles.get(), &aSheet);
518 ApplicableRulesChanged();
519 }
520
AddToIdTable(Element * aElement,nsAtom * aId)521 void ShadowRoot::AddToIdTable(Element* aElement, nsAtom* aId) {
522 IdentifierMapEntry* entry = mIdentifierMap.PutEntry(aId);
523 if (entry) {
524 entry->AddIdElement(aElement);
525 }
526 }
527
RemoveFromIdTable(Element * aElement,nsAtom * aId)528 void ShadowRoot::RemoveFromIdTable(Element* aElement, nsAtom* aId) {
529 IdentifierMapEntry* entry = mIdentifierMap.GetEntry(aId);
530 if (entry) {
531 entry->RemoveIdElement(aElement);
532 if (entry->IsEmpty()) {
533 mIdentifierMap.RemoveEntry(entry);
534 }
535 }
536 }
537
GetEventTargetParent(EventChainPreVisitor & aVisitor)538 void ShadowRoot::GetEventTargetParent(EventChainPreVisitor& aVisitor) {
539 aVisitor.mCanHandle = true;
540 aVisitor.mRootOfClosedTree = IsClosed();
541 // Inform that we're about to exit the current scope.
542 aVisitor.mRelatedTargetRetargetedInCurrentScope = false;
543
544 // https://dom.spec.whatwg.org/#ref-for-get-the-parent%E2%91%A6
545 if (!aVisitor.mEvent->mFlags.mComposed) {
546 nsCOMPtr<nsIContent> originalTarget =
547 nsIContent::FromEventTargetOrNull(aVisitor.mEvent->mOriginalTarget);
548 if (originalTarget && originalTarget->GetContainingShadow() == this) {
549 // If we do stop propagation, we still want to propagate
550 // the event to chrome (nsPIDOMWindow::GetParentTarget()).
551 // The load event is special in that we don't ever propagate it
552 // to chrome.
553 nsCOMPtr<nsPIDOMWindowOuter> win = OwnerDoc()->GetWindow();
554 EventTarget* parentTarget = win && aVisitor.mEvent->mMessage != eLoad
555 ? win->GetParentTarget()
556 : nullptr;
557
558 aVisitor.SetParentTarget(parentTarget, true);
559 return;
560 }
561 }
562
563 nsIContent* shadowHost = GetHost();
564 aVisitor.SetParentTarget(shadowHost, false);
565
566 nsCOMPtr<nsIContent> content(
567 nsIContent::FromEventTargetOrNull(aVisitor.mEvent->mTarget));
568 if (content && content->GetContainingShadow() == this) {
569 aVisitor.mEventTargetAtParent = shadowHost;
570 }
571 }
572
SlotInsertionPointFor(nsIContent & aContent)573 ShadowRoot::SlotInsertionPoint ShadowRoot::SlotInsertionPointFor(
574 nsIContent& aContent) {
575 HTMLSlotElement* slot = nullptr;
576
577 if (SlotAssignment() == SlotAssignmentMode::Manual) {
578 slot = aContent.GetManualSlotAssignment();
579 if (!slot || slot->GetContainingShadow() != this) {
580 return {};
581 }
582 } else {
583 nsAutoString slotName;
584 // Note that if slot attribute is missing, assign it to the first default
585 // slot, if exists.
586 if (Element* element = Element::FromNode(aContent)) {
587 element->GetAttr(nsGkAtoms::slot, slotName);
588 }
589
590 SlotArray* slots = mSlotMap.Get(slotName);
591 if (!slots) {
592 return {};
593 }
594 slot = (*slots)->ElementAt(0);
595 }
596
597 MOZ_ASSERT(slot);
598
599 if (SlotAssignment() == SlotAssignmentMode::Named) {
600 if (!aContent.GetNextSibling()) {
601 // aContent is the last child, no need to loop through the assigned nodes,
602 // we're necessarily the last one.
603 //
604 // This prevents multiple appends into the host from getting quadratic.
605 return {slot, Nothing()};
606 }
607 } else {
608 // For manual slots, if aContent is the last element, we return Nothing
609 // because we just need to append the element to the assigned nodes. No need
610 // to return an index.
611 if (slot->ManuallyAssignedNodes().SafeLastElement(nullptr) == &aContent) {
612 return {slot, Nothing()};
613 }
614 }
615
616 // Find the appropriate position in the assigned node list for the newly
617 // assigned content.
618 if (SlotAssignment() == SlotAssignmentMode::Manual) {
619 const nsTArray<nsINode*>& manuallyAssignedNodes =
620 slot->ManuallyAssignedNodes();
621 auto index = manuallyAssignedNodes.IndexOf(&aContent);
622 if (index != manuallyAssignedNodes.NoIndex) {
623 return {slot, Some(index)};
624 }
625 } else {
626 const nsTArray<RefPtr<nsINode>>& assignedNodes = slot->AssignedNodes();
627 nsIContent* currentContent = GetHost()->GetFirstChild();
628 for (uint32_t i = 0; i < assignedNodes.Length(); i++) {
629 // Seek through the host's explicit children until the
630 // assigned content is found.
631 while (currentContent && currentContent != assignedNodes[i]) {
632 if (currentContent == &aContent) {
633 return {slot, Some(i)};
634 }
635 currentContent = currentContent->GetNextSibling();
636 }
637 }
638 }
639
640 return {slot, Nothing()};
641 }
642
MaybeReassignContent(nsIContent & aElementOrText)643 void ShadowRoot::MaybeReassignContent(nsIContent& aElementOrText) {
644 MOZ_ASSERT(aElementOrText.GetParent() == GetHost());
645 MOZ_ASSERT(aElementOrText.IsElement() || aElementOrText.IsText());
646 HTMLSlotElement* oldSlot = aElementOrText.GetAssignedSlot();
647
648 SlotInsertionPoint assignment = SlotInsertionPointFor(aElementOrText);
649
650 if (assignment.mSlot == oldSlot) {
651 // Nothing to do here.
652 return;
653 }
654
655 // The layout invalidation piece for Manual slots is handled in
656 // HTMLSlotElement::Assign
657 if (aElementOrText.IsElement() &&
658 SlotAssignment() == SlotAssignmentMode::Named) {
659 if (Document* doc = GetComposedDoc()) {
660 if (RefPtr<PresShell> presShell = doc->GetPresShell()) {
661 presShell->SlotAssignmentWillChange(*aElementOrText.AsElement(),
662 oldSlot, assignment.mSlot);
663 }
664 }
665 }
666
667 if (oldSlot) {
668 if (SlotAssignment() == SlotAssignmentMode::Named) {
669 oldSlot->RemoveAssignedNode(aElementOrText);
670 // Don't need to EnqueueSlotChangeEvent for Manual slots because it
671 // needs to be done in tree order, so
672 // HTMLSlotElement::Assign will handle it explicitly.
673 oldSlot->EnqueueSlotChangeEvent();
674 } else {
675 oldSlot->RemoveManuallyAssignedNode(aElementOrText);
676 }
677 }
678
679 if (assignment.mSlot) {
680 if (assignment.mIndex) {
681 assignment.mSlot->InsertAssignedNode(*assignment.mIndex, aElementOrText);
682 } else {
683 assignment.mSlot->AppendAssignedNode(aElementOrText);
684 }
685 // Similar as above, HTMLSlotElement::Assign handles enqueuing
686 // slotchange event.
687 if (SlotAssignment() == SlotAssignmentMode::Named) {
688 assignment.mSlot->EnqueueSlotChangeEvent();
689 }
690 }
691 }
692
GetActiveElement()693 Element* ShadowRoot::GetActiveElement() {
694 return GetRetargetedFocusedElement();
695 }
696
ImportNodeAndAppendChildAt(nsINode & aParentNode,nsINode & aNode,bool aDeep,mozilla::ErrorResult & rv)697 nsINode* ShadowRoot::ImportNodeAndAppendChildAt(nsINode& aParentNode,
698 nsINode& aNode, bool aDeep,
699 mozilla::ErrorResult& rv) {
700 MOZ_ASSERT(mIsUAWidget);
701
702 if (!aParentNode.IsInUAWidget()) {
703 rv.Throw(NS_ERROR_INVALID_ARG);
704 return nullptr;
705 }
706
707 RefPtr<nsINode> node = OwnerDoc()->ImportNode(aNode, aDeep, rv);
708 if (rv.Failed()) {
709 return nullptr;
710 }
711
712 return aParentNode.AppendChild(*node, rv);
713 }
714
CreateElementAndAppendChildAt(nsINode & aParentNode,const nsAString & aTagName,mozilla::ErrorResult & rv)715 nsINode* ShadowRoot::CreateElementAndAppendChildAt(nsINode& aParentNode,
716 const nsAString& aTagName,
717 mozilla::ErrorResult& rv) {
718 MOZ_ASSERT(mIsUAWidget);
719
720 if (!aParentNode.IsInUAWidget()) {
721 rv.Throw(NS_ERROR_INVALID_ARG);
722 return nullptr;
723 }
724
725 // This option is not exposed to UA Widgets
726 ElementCreationOptionsOrString options;
727
728 RefPtr<nsINode> node = OwnerDoc()->CreateElement(aTagName, options, rv);
729 if (rv.Failed()) {
730 return nullptr;
731 }
732
733 return aParentNode.AppendChild(*node, rv);
734 }
735
MaybeUnslotHostChild(nsIContent & aChild)736 void ShadowRoot::MaybeUnslotHostChild(nsIContent& aChild) {
737 // Need to null-check the host because we may be unlinked already.
738 MOZ_ASSERT(!GetHost() || aChild.GetParent() == GetHost());
739
740 HTMLSlotElement* slot = aChild.GetAssignedSlot();
741 if (!slot) {
742 return;
743 }
744
745 MOZ_DIAGNOSTIC_ASSERT(!aChild.IsRootOfNativeAnonymousSubtree(),
746 "How did aChild end up assigned to a slot?");
747 // If the slot is going to start showing fallback content, we need to tell
748 // layout about it.
749 if (slot->AssignedNodes().Length() == 1 && slot->HasChildren()) {
750 InvalidateStyleAndLayoutOnSubtree(slot);
751 }
752
753 slot->RemoveAssignedNode(aChild);
754 slot->EnqueueSlotChangeEvent();
755 }
756
GetFirstFocusable(bool aWithMouse) const757 Element* ShadowRoot::GetFirstFocusable(bool aWithMouse) const {
758 MOZ_ASSERT(DelegatesFocus(), "Why are we here?");
759
760 Element* potentialFocus = nullptr;
761
762 for (nsINode* node = GetFirstChild(); node; node = node->GetNextNode(this)) {
763 auto* el = Element::FromNode(*node);
764 if (!el) {
765 continue;
766 }
767 nsIFrame* frame = el->GetPrimaryFrame();
768 if (frame && frame->IsFocusable(aWithMouse)) {
769 if (el->GetBoolAttr(nsGkAtoms::autofocus)) {
770 return el;
771 }
772 if (!potentialFocus) {
773 potentialFocus = el;
774 }
775 }
776 if (!potentialFocus) {
777 ShadowRoot* shadow = el->GetShadowRoot();
778 if (shadow && shadow->DelegatesFocus()) {
779 if (Element* nested = shadow->GetFirstFocusable(aWithMouse)) {
780 potentialFocus = nested;
781 }
782 }
783 }
784 }
785 return potentialFocus;
786 }
787
MaybeSlotHostChild(nsIContent & aChild)788 void ShadowRoot::MaybeSlotHostChild(nsIContent& aChild) {
789 MOZ_ASSERT(aChild.GetParent() == GetHost());
790 // Check to ensure that the child not an anonymous subtree root because even
791 // though its parent could be the host it may not be in the host's child
792 // list.
793 if (aChild.IsRootOfNativeAnonymousSubtree()) {
794 return;
795 }
796
797 if (!aChild.IsSlotable()) {
798 return;
799 }
800
801 SlotInsertionPoint assignment = SlotInsertionPointFor(aChild);
802 if (!assignment.mSlot) {
803 return;
804 }
805
806 // Fallback content will go away, let layout know.
807 if (assignment.mSlot->AssignedNodes().IsEmpty() &&
808 assignment.mSlot->HasChildren()) {
809 InvalidateStyleAndLayoutOnSubtree(assignment.mSlot);
810 }
811
812 if (assignment.mIndex) {
813 assignment.mSlot->InsertAssignedNode(*assignment.mIndex, aChild);
814 } else {
815 assignment.mSlot->AppendAssignedNode(aChild);
816 }
817 assignment.mSlot->EnqueueSlotChangeEvent();
818 }
819
ServoStyleRuleMap()820 ServoStyleRuleMap& ShadowRoot::ServoStyleRuleMap() {
821 if (!mStyleRuleMap) {
822 mStyleRuleMap = MakeUnique<mozilla::ServoStyleRuleMap>();
823 }
824 mStyleRuleMap->EnsureTable(*this);
825 return *mStyleRuleMap;
826 }
827
Clone(dom::NodeInfo * aNodeInfo,nsINode ** aResult) const828 nsresult ShadowRoot::Clone(dom::NodeInfo* aNodeInfo, nsINode** aResult) const {
829 *aResult = nullptr;
830 return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
831 }
832