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