/src/mozilla-central/dom/base/ShadowRoot.cpp
Line | Count | Source (jump to first uncovered line) |
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/ShadowRoot.h" |
9 | | #include "mozilla/dom/DocumentFragment.h" |
10 | | #include "ChildIterator.h" |
11 | | #include "nsContentUtils.h" |
12 | | #include "nsIStyleSheetLinkingElement.h" |
13 | | #include "nsWindowSizes.h" |
14 | | #include "nsXBLPrototypeBinding.h" |
15 | | #include "mozilla/dom/DirectionalityUtils.h" |
16 | | #include "mozilla/dom/Element.h" |
17 | | #include "mozilla/dom/HTMLSlotElement.h" |
18 | | #include "mozilla/EventDispatcher.h" |
19 | | #include "mozilla/ServoStyleRuleMap.h" |
20 | | #include "mozilla/StyleSheet.h" |
21 | | #include "mozilla/StyleSheetInlines.h" |
22 | | #include "mozilla/dom/StyleSheetList.h" |
23 | | |
24 | | using namespace mozilla; |
25 | | using namespace mozilla::dom; |
26 | | |
27 | | NS_IMPL_CYCLE_COLLECTION_CLASS(ShadowRoot) |
28 | | |
29 | 0 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(ShadowRoot, DocumentFragment) |
30 | 0 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStyleSheets) |
31 | 0 | for (StyleSheet* sheet : tmp->mStyleSheets) { |
32 | 0 | // mServoStyles keeps another reference to it if applicable. |
33 | 0 | if (sheet->IsApplicable()) { |
34 | 0 | MOZ_ASSERT(tmp->mServoStyles); |
35 | 0 | NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mServoStyles->sheets[i]"); |
36 | 0 | cb.NoteXPCOMChild(sheet); |
37 | 0 | } |
38 | 0 | } |
39 | 0 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDOMStyleSheets) |
40 | 0 | for (auto iter = tmp->mIdentifierMap.ConstIter(); !iter.Done(); |
41 | 0 | iter.Next()) { |
42 | 0 | iter.Get()->Traverse(&cb); |
43 | 0 | } |
44 | 0 | DocumentOrShadowRoot::Traverse(tmp, cb); |
45 | 0 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END |
46 | | |
47 | 0 | NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ShadowRoot) |
48 | 0 | if (tmp->GetHost()) { |
49 | 0 | tmp->GetHost()->RemoveMutationObserver(tmp); |
50 | 0 | } |
51 | 0 | NS_IMPL_CYCLE_COLLECTION_UNLINK(mDOMStyleSheets) |
52 | 0 | tmp->mIdentifierMap.Clear(); |
53 | 0 | DocumentOrShadowRoot::Unlink(tmp); |
54 | 0 | NS_IMPL_CYCLE_COLLECTION_UNLINK_END_INHERITED(DocumentFragment) |
55 | | |
56 | 0 | NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ShadowRoot) |
57 | 0 | NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIContent) |
58 | 0 | NS_INTERFACE_MAP_ENTRY(nsIMutationObserver) |
59 | 0 | NS_INTERFACE_MAP_ENTRY(nsIRadioGroupContainer) |
60 | 0 | NS_INTERFACE_MAP_END_INHERITING(DocumentFragment) |
61 | | |
62 | | NS_IMPL_ADDREF_INHERITED(ShadowRoot, DocumentFragment) |
63 | | NS_IMPL_RELEASE_INHERITED(ShadowRoot, DocumentFragment) |
64 | | |
65 | | ShadowRoot::ShadowRoot(Element* aElement, ShadowRootMode aMode, |
66 | | already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) |
67 | | : DocumentFragment(std::move(aNodeInfo)) |
68 | | , DocumentOrShadowRoot(*this) |
69 | | , mMode(aMode) |
70 | | , mIsUAWidget(false) |
71 | 0 | { |
72 | 0 | SetHost(aElement); |
73 | 0 |
|
74 | 0 | // Nodes in a shadow tree should never store a value |
75 | 0 | // in the subtree root pointer, nodes in the shadow tree |
76 | 0 | // track the subtree root using GetContainingShadow(). |
77 | 0 | ClearSubtreeRootPointer(); |
78 | 0 |
|
79 | 0 | SetFlags(NODE_IS_IN_SHADOW_TREE); |
80 | 0 | Bind(); |
81 | 0 |
|
82 | 0 | ExtendedDOMSlots()->mBindingParent = aElement; |
83 | 0 | ExtendedDOMSlots()->mContainingShadow = this; |
84 | 0 |
|
85 | 0 | // Add the ShadowRoot as a mutation observer on the host to watch |
86 | 0 | // for mutations because the insertion points in this ShadowRoot |
87 | 0 | // may need to be updated when the host children are modified. |
88 | 0 | GetHost()->AddMutationObserver(this); |
89 | 0 | } |
90 | | |
91 | | ShadowRoot::~ShadowRoot() |
92 | 0 | { |
93 | 0 | if (auto* host = GetHost()) { |
94 | 0 | // mHost may have been unlinked. |
95 | 0 | host->RemoveMutationObserver(this); |
96 | 0 | } |
97 | 0 |
|
98 | 0 | if (IsInComposedDoc()) { |
99 | 0 | OwnerDoc()->RemoveComposedDocShadowRoot(*this); |
100 | 0 | } |
101 | 0 |
|
102 | 0 | MOZ_DIAGNOSTIC_ASSERT(!OwnerDoc()->IsComposedDocShadowRoot(*this)); |
103 | 0 |
|
104 | 0 | UnsetFlags(NODE_IS_IN_SHADOW_TREE); |
105 | 0 |
|
106 | 0 | // nsINode destructor expects mSubtreeRoot == this. |
107 | 0 | SetSubtreeRootPointer(this); |
108 | 0 | } |
109 | | |
110 | | MOZ_DEFINE_MALLOC_SIZE_OF(ShadowRootAuthorStylesMallocSizeOf) |
111 | | MOZ_DEFINE_MALLOC_ENCLOSING_SIZE_OF(ShadowRootAuthorStylesMallocEnclosingSizeOf) |
112 | | |
113 | | void |
114 | | ShadowRoot::AddSizeOfExcludingThis(nsWindowSizes& aSizes, size_t* aNodeSize) const |
115 | 0 | { |
116 | 0 | DocumentFragment::AddSizeOfExcludingThis(aSizes, aNodeSize); |
117 | 0 | DocumentOrShadowRoot::AddSizeOfExcludingThis(aSizes); |
118 | 0 | aSizes.mLayoutShadowDomAuthorStyles += |
119 | 0 | Servo_AuthorStyles_SizeOfIncludingThis( |
120 | 0 | ShadowRootAuthorStylesMallocSizeOf, |
121 | 0 | ShadowRootAuthorStylesMallocEnclosingSizeOf, |
122 | 0 | mServoStyles.get()); |
123 | 0 | } |
124 | | |
125 | | JSObject* |
126 | | ShadowRoot::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) |
127 | 0 | { |
128 | 0 | return mozilla::dom::ShadowRoot_Binding::Wrap(aCx, this, aGivenProto); |
129 | 0 | } |
130 | | |
131 | | void |
132 | | ShadowRoot::CloneInternalDataFrom(ShadowRoot* aOther) |
133 | 0 | { |
134 | 0 | size_t sheetCount = aOther->SheetCount(); |
135 | 0 | for (size_t i = 0; i < sheetCount; ++i) { |
136 | 0 | StyleSheet* sheet = aOther->SheetAt(i); |
137 | 0 | if (sheet->IsApplicable()) { |
138 | 0 | RefPtr<StyleSheet> clonedSheet = |
139 | 0 | sheet->Clone(nullptr, nullptr, this, nullptr); |
140 | 0 | if (clonedSheet) { |
141 | 0 | AppendStyleSheet(*clonedSheet.get()); |
142 | 0 | } |
143 | 0 | } |
144 | 0 | } |
145 | 0 | } |
146 | | |
147 | | nsresult |
148 | | ShadowRoot::Bind() |
149 | 0 | { |
150 | 0 | MOZ_ASSERT(!IsInComposedDoc(), "Forgot to unbind?"); |
151 | 0 | if (Host()->IsInComposedDoc()) { |
152 | 0 | SetIsConnected(true); |
153 | 0 | OwnerDoc()->AddComposedDocShadowRoot(*this); |
154 | 0 | } |
155 | 0 |
|
156 | 0 | for (nsIContent* child = GetFirstChild(); |
157 | 0 | child; |
158 | 0 | child = child->GetNextSibling()) { |
159 | 0 | nsresult rv = child->BindToTree(nullptr, this, Host()); |
160 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
161 | 0 | } |
162 | 0 |
|
163 | 0 | return NS_OK; |
164 | 0 | } |
165 | | |
166 | | void |
167 | | ShadowRoot::Unbind() |
168 | 0 | { |
169 | 0 | if (IsInComposedDoc()) { |
170 | 0 | SetIsConnected(false); |
171 | 0 | OwnerDoc()->RemoveComposedDocShadowRoot(*this); |
172 | 0 | } |
173 | 0 |
|
174 | 0 | for (nsIContent* child = GetFirstChild(); |
175 | 0 | child; |
176 | 0 | child = child->GetNextSibling()) { |
177 | 0 | child->UnbindFromTree(true, false); |
178 | 0 | } |
179 | 0 | } |
180 | | |
181 | | void |
182 | | ShadowRoot::InvalidateStyleAndLayoutOnSubtree(Element* aElement) |
183 | 0 | { |
184 | 0 | MOZ_ASSERT(aElement); |
185 | 0 | nsIDocument* doc = GetComposedDoc(); |
186 | 0 | if (!doc) { |
187 | 0 | return; |
188 | 0 | } |
189 | 0 | |
190 | 0 | nsIPresShell* shell = doc->GetShell(); |
191 | 0 | if (!shell) { |
192 | 0 | return; |
193 | 0 | } |
194 | 0 | |
195 | 0 | shell->DestroyFramesForAndRestyle(aElement); |
196 | 0 | } |
197 | | |
198 | | void |
199 | | ShadowRoot::AddSlot(HTMLSlotElement* aSlot) |
200 | 0 | { |
201 | 0 | MOZ_ASSERT(aSlot); |
202 | 0 |
|
203 | 0 | // Note that if name attribute missing, the slot is a default slot. |
204 | 0 | nsAutoString name; |
205 | 0 | aSlot->GetName(name); |
206 | 0 |
|
207 | 0 | nsTArray<HTMLSlotElement*>* currentSlots = mSlotMap.LookupOrAdd(name); |
208 | 0 | MOZ_ASSERT(currentSlots); |
209 | 0 |
|
210 | 0 | HTMLSlotElement* oldSlot = currentSlots->SafeElementAt(0); |
211 | 0 |
|
212 | 0 | TreeOrderComparator comparator; |
213 | 0 | currentSlots->InsertElementSorted(aSlot, comparator); |
214 | 0 |
|
215 | 0 | HTMLSlotElement* currentSlot = currentSlots->ElementAt(0); |
216 | 0 | if (currentSlot != aSlot) { |
217 | 0 | return; |
218 | 0 | } |
219 | 0 | |
220 | 0 | if (oldSlot && oldSlot != currentSlot) { |
221 | 0 | // Move assigned nodes from old slot to new slot. |
222 | 0 | InvalidateStyleAndLayoutOnSubtree(oldSlot); |
223 | 0 | const nsTArray<RefPtr<nsINode>>& assignedNodes = oldSlot->AssignedNodes(); |
224 | 0 | bool doEnqueueSlotChange = false; |
225 | 0 | while (assignedNodes.Length() > 0) { |
226 | 0 | nsINode* assignedNode = assignedNodes[0]; |
227 | 0 |
|
228 | 0 | oldSlot->RemoveAssignedNode(assignedNode); |
229 | 0 | currentSlot->AppendAssignedNode(assignedNode); |
230 | 0 | doEnqueueSlotChange = true; |
231 | 0 | } |
232 | 0 |
|
233 | 0 | if (doEnqueueSlotChange) { |
234 | 0 | oldSlot->EnqueueSlotChangeEvent(); |
235 | 0 | currentSlot->EnqueueSlotChangeEvent(); |
236 | 0 | SlotStateChanged(oldSlot); |
237 | 0 | SlotStateChanged(currentSlot); |
238 | 0 | } |
239 | 0 | } else { |
240 | 0 | bool doEnqueueSlotChange = false; |
241 | 0 | // Otherwise add appropriate nodes to this slot from the host. |
242 | 0 | for (nsIContent* child = GetHost()->GetFirstChild(); |
243 | 0 | child; |
244 | 0 | child = child->GetNextSibling()) { |
245 | 0 | nsAutoString slotName; |
246 | 0 | if (child->IsElement()) { |
247 | 0 | child->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::slot, slotName); |
248 | 0 | } |
249 | 0 | if (!child->IsSlotable() || !slotName.Equals(name)) { |
250 | 0 | continue; |
251 | 0 | } |
252 | 0 | doEnqueueSlotChange = true; |
253 | 0 | currentSlot->AppendAssignedNode(child); |
254 | 0 | } |
255 | 0 |
|
256 | 0 | if (doEnqueueSlotChange) { |
257 | 0 | currentSlot->EnqueueSlotChangeEvent(); |
258 | 0 | SlotStateChanged(currentSlot); |
259 | 0 | } |
260 | 0 | } |
261 | 0 | } |
262 | | |
263 | | void |
264 | | ShadowRoot::RemoveSlot(HTMLSlotElement* aSlot) |
265 | 0 | { |
266 | 0 | MOZ_ASSERT(aSlot); |
267 | 0 |
|
268 | 0 | nsAutoString name; |
269 | 0 | aSlot->GetName(name); |
270 | 0 |
|
271 | 0 | SlotArray* currentSlots = mSlotMap.Get(name); |
272 | 0 | MOZ_DIAGNOSTIC_ASSERT(currentSlots && currentSlots->Contains(aSlot), |
273 | 0 | "Slot to deregister wasn't found?"); |
274 | 0 | if (currentSlots->Length() == 1) { |
275 | 0 | MOZ_ASSERT(currentSlots->ElementAt(0) == aSlot); |
276 | 0 |
|
277 | 0 | InvalidateStyleAndLayoutOnSubtree(aSlot); |
278 | 0 |
|
279 | 0 | mSlotMap.Remove(name); |
280 | 0 | if (!aSlot->AssignedNodes().IsEmpty()) { |
281 | 0 | aSlot->ClearAssignedNodes(); |
282 | 0 | aSlot->EnqueueSlotChangeEvent(); |
283 | 0 | } |
284 | 0 |
|
285 | 0 | return; |
286 | 0 | } |
287 | 0 |
|
288 | 0 | const bool wasFirstSlot = currentSlots->ElementAt(0) == aSlot; |
289 | 0 | currentSlots->RemoveElement(aSlot); |
290 | 0 |
|
291 | 0 | // Move assigned nodes from removed slot to the next slot in |
292 | 0 | // tree order with the same name. |
293 | 0 | if (!wasFirstSlot) { |
294 | 0 | return; |
295 | 0 | } |
296 | 0 | |
297 | 0 | InvalidateStyleAndLayoutOnSubtree(aSlot); |
298 | 0 | HTMLSlotElement* replacementSlot = currentSlots->ElementAt(0); |
299 | 0 | const nsTArray<RefPtr<nsINode>>& assignedNodes = aSlot->AssignedNodes(); |
300 | 0 | bool slottedNodesChanged = !assignedNodes.IsEmpty(); |
301 | 0 | while (!assignedNodes.IsEmpty()) { |
302 | 0 | nsINode* assignedNode = assignedNodes[0]; |
303 | 0 |
|
304 | 0 | aSlot->RemoveAssignedNode(assignedNode); |
305 | 0 | replacementSlot->AppendAssignedNode(assignedNode); |
306 | 0 | } |
307 | 0 |
|
308 | 0 | if (slottedNodesChanged) { |
309 | 0 | aSlot->EnqueueSlotChangeEvent(); |
310 | 0 | replacementSlot->EnqueueSlotChangeEvent(); |
311 | 0 | } |
312 | 0 | } |
313 | | |
314 | | // FIXME(emilio): There's a bit of code duplication between this and the |
315 | | // equivalent ServoStyleSet methods, it'd be nice to not duplicate it... |
316 | | void |
317 | | ShadowRoot::RuleAdded(StyleSheet& aSheet, css::Rule& aRule) |
318 | 0 | { |
319 | 0 | if (!aSheet.IsApplicable()) { |
320 | 0 | return; |
321 | 0 | } |
322 | 0 | |
323 | 0 | MOZ_ASSERT(mServoStyles); |
324 | 0 | if (mStyleRuleMap) { |
325 | 0 | mStyleRuleMap->RuleAdded(aSheet, aRule); |
326 | 0 | } |
327 | 0 | Servo_AuthorStyles_ForceDirty(mServoStyles.get()); |
328 | 0 | ApplicableRulesChanged(); |
329 | 0 | } |
330 | | |
331 | | void |
332 | | ShadowRoot::RuleRemoved(StyleSheet& aSheet, css::Rule& aRule) |
333 | 0 | { |
334 | 0 | if (!aSheet.IsApplicable()) { |
335 | 0 | return; |
336 | 0 | } |
337 | 0 | |
338 | 0 | MOZ_ASSERT(mServoStyles); |
339 | 0 | if (mStyleRuleMap) { |
340 | 0 | mStyleRuleMap->RuleRemoved(aSheet, aRule); |
341 | 0 | } |
342 | 0 | Servo_AuthorStyles_ForceDirty(mServoStyles.get()); |
343 | 0 | ApplicableRulesChanged(); |
344 | 0 | } |
345 | | |
346 | | void |
347 | 0 | ShadowRoot::RuleChanged(StyleSheet& aSheet, css::Rule*) { |
348 | 0 | if (!aSheet.IsApplicable()) { |
349 | 0 | return; |
350 | 0 | } |
351 | 0 | |
352 | 0 | MOZ_ASSERT(mServoStyles); |
353 | 0 | Servo_AuthorStyles_ForceDirty(mServoStyles.get()); |
354 | 0 | ApplicableRulesChanged(); |
355 | 0 | } |
356 | | |
357 | | void |
358 | | ShadowRoot::ApplicableRulesChanged() |
359 | 0 | { |
360 | 0 | nsIDocument* doc = GetComposedDoc(); |
361 | 0 | if (!doc) { |
362 | 0 | return; |
363 | 0 | } |
364 | 0 | |
365 | 0 | if (nsIPresShell* shell = doc->GetShell()) { |
366 | 0 | shell->RecordShadowStyleChange(*this); |
367 | 0 | } |
368 | 0 | } |
369 | | |
370 | | void |
371 | | ShadowRoot::InsertSheetAt(size_t aIndex, StyleSheet& aSheet) |
372 | 0 | { |
373 | 0 | DocumentOrShadowRoot::InsertSheetAt(aIndex, aSheet); |
374 | 0 | if (aSheet.IsApplicable()) { |
375 | 0 | InsertSheetIntoAuthorData(aIndex, aSheet); |
376 | 0 | } |
377 | 0 | } |
378 | | |
379 | | void |
380 | | ShadowRoot::InsertSheetIntoAuthorData(size_t aIndex, StyleSheet& aSheet) |
381 | 0 | { |
382 | 0 | MOZ_ASSERT(SheetAt(aIndex) == &aSheet); |
383 | 0 | MOZ_ASSERT(aSheet.IsApplicable()); |
384 | 0 |
|
385 | 0 | if (!mServoStyles) { |
386 | 0 | mServoStyles.reset(Servo_AuthorStyles_Create()); |
387 | 0 | } |
388 | 0 |
|
389 | 0 | if (mStyleRuleMap) { |
390 | 0 | mStyleRuleMap->SheetAdded(aSheet); |
391 | 0 | } |
392 | 0 |
|
393 | 0 | for (size_t i = aIndex + 1; i < SheetCount(); ++i) { |
394 | 0 | StyleSheet* beforeSheet = SheetAt(i); |
395 | 0 | if (!beforeSheet->IsApplicable()) { |
396 | 0 | continue; |
397 | 0 | } |
398 | 0 | |
399 | 0 | Servo_AuthorStyles_InsertStyleSheetBefore( |
400 | 0 | mServoStyles.get(), &aSheet, beforeSheet); |
401 | 0 | ApplicableRulesChanged(); |
402 | 0 | return; |
403 | 0 | } |
404 | 0 |
|
405 | 0 | Servo_AuthorStyles_AppendStyleSheet(mServoStyles.get(), &aSheet); |
406 | 0 | ApplicableRulesChanged(); |
407 | 0 | } |
408 | | |
409 | | // FIXME(emilio): This needs to notify document observers and such, |
410 | | // presumably. |
411 | | void |
412 | | ShadowRoot::StyleSheetApplicableStateChanged(StyleSheet& aSheet, bool aApplicable) |
413 | 0 | { |
414 | 0 | int32_t index = IndexOfSheet(aSheet); |
415 | 0 | if (index < 0) { |
416 | 0 | // NOTE(emilio): @import sheets are handled in the relevant RuleAdded |
417 | 0 | // notification, which only notifies after the sheet is loaded. |
418 | 0 | // |
419 | 0 | // This setup causes weirdness in other places, we may want to fix this in |
420 | 0 | // bug 1465031. |
421 | 0 | MOZ_DIAGNOSTIC_ASSERT(aSheet.GetParentSheet(), |
422 | 0 | "It'd better be an @import sheet"); |
423 | 0 | return; |
424 | 0 | } |
425 | 0 | if (aApplicable) { |
426 | 0 | InsertSheetIntoAuthorData(size_t(index), aSheet); |
427 | 0 | } else { |
428 | 0 | MOZ_ASSERT(mServoStyles); |
429 | 0 | if (mStyleRuleMap) { |
430 | 0 | mStyleRuleMap->SheetRemoved(aSheet); |
431 | 0 | } |
432 | 0 | Servo_AuthorStyles_RemoveStyleSheet(mServoStyles.get(), &aSheet); |
433 | 0 | ApplicableRulesChanged(); |
434 | 0 | } |
435 | 0 | } |
436 | | |
437 | | void |
438 | | ShadowRoot::RemoveSheet(StyleSheet* aSheet) |
439 | 0 | { |
440 | 0 | MOZ_ASSERT(aSheet); |
441 | 0 | RefPtr<StyleSheet> sheet = DocumentOrShadowRoot::RemoveSheet(*aSheet); |
442 | 0 | MOZ_ASSERT(sheet); |
443 | 0 | if (sheet->IsApplicable()) { |
444 | 0 | MOZ_ASSERT(mServoStyles); |
445 | 0 | if (mStyleRuleMap) { |
446 | 0 | mStyleRuleMap->SheetRemoved(*sheet); |
447 | 0 | } |
448 | 0 | Servo_AuthorStyles_RemoveStyleSheet(mServoStyles.get(), sheet); |
449 | 0 | ApplicableRulesChanged(); |
450 | 0 | } |
451 | 0 | } |
452 | | |
453 | | void |
454 | | ShadowRoot::AddToIdTable(Element* aElement, nsAtom* aId) |
455 | 0 | { |
456 | 0 | nsIdentifierMapEntry* entry = mIdentifierMap.PutEntry(aId); |
457 | 0 | if (entry) { |
458 | 0 | entry->AddIdElement(aElement); |
459 | 0 | } |
460 | 0 | } |
461 | | |
462 | | void |
463 | | ShadowRoot::RemoveFromIdTable(Element* aElement, nsAtom* aId) |
464 | 0 | { |
465 | 0 | nsIdentifierMapEntry* entry = mIdentifierMap.GetEntry(aId); |
466 | 0 | if (entry) { |
467 | 0 | entry->RemoveIdElement(aElement); |
468 | 0 | if (entry->IsEmpty()) { |
469 | 0 | mIdentifierMap.RemoveEntry(entry); |
470 | 0 | } |
471 | 0 | } |
472 | 0 | } |
473 | | |
474 | | void |
475 | | ShadowRoot::GetEventTargetParent(EventChainPreVisitor& aVisitor) |
476 | 0 | { |
477 | 0 | aVisitor.mCanHandle = true; |
478 | 0 | aVisitor.mRootOfClosedTree = IsClosed(); |
479 | 0 | // Inform that we're about to exit the current scope. |
480 | 0 | aVisitor.mRelatedTargetRetargetedInCurrentScope = false; |
481 | 0 |
|
482 | 0 | // https://dom.spec.whatwg.org/#ref-for-get-the-parent%E2%91%A6 |
483 | 0 | if (!aVisitor.mEvent->mFlags.mComposed) { |
484 | 0 | nsCOMPtr<nsIContent> originalTarget = |
485 | 0 | do_QueryInterface(aVisitor.mEvent->mOriginalTarget); |
486 | 0 | if (originalTarget->GetContainingShadow() == this) { |
487 | 0 | // If we do stop propagation, we still want to propagate |
488 | 0 | // the event to chrome (nsPIDOMWindow::GetParentTarget()). |
489 | 0 | // The load event is special in that we don't ever propagate it |
490 | 0 | // to chrome. |
491 | 0 | nsCOMPtr<nsPIDOMWindowOuter> win = OwnerDoc()->GetWindow(); |
492 | 0 | EventTarget* parentTarget = win && aVisitor.mEvent->mMessage != eLoad |
493 | 0 | ? win->GetParentTarget() : nullptr; |
494 | 0 |
|
495 | 0 | aVisitor.SetParentTarget(parentTarget, true); |
496 | 0 | return; |
497 | 0 | } |
498 | 0 | } |
499 | 0 |
|
500 | 0 | nsIContent* shadowHost = GetHost(); |
501 | 0 | aVisitor.SetParentTarget(shadowHost, false); |
502 | 0 |
|
503 | 0 | nsCOMPtr<nsIContent> content(do_QueryInterface(aVisitor.mEvent->mTarget)); |
504 | 0 | if (content && content->GetBindingParent() == shadowHost) { |
505 | 0 | aVisitor.mEventTargetAtParent = shadowHost; |
506 | 0 | } |
507 | 0 | } |
508 | | |
509 | | ShadowRoot::SlotAssignment |
510 | | ShadowRoot::SlotAssignmentFor(nsIContent* aContent) |
511 | 0 | { |
512 | 0 | nsAutoString slotName; |
513 | 0 | // Note that if slot attribute is missing, assign it to the first default |
514 | 0 | // slot, if exists. |
515 | 0 | if (aContent->IsElement()) { |
516 | 0 | aContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::slot, slotName); |
517 | 0 | } |
518 | 0 |
|
519 | 0 | nsTArray<HTMLSlotElement*>* slots = mSlotMap.Get(slotName); |
520 | 0 | if (!slots) { |
521 | 0 | return { }; |
522 | 0 | } |
523 | 0 | |
524 | 0 | HTMLSlotElement* slot = slots->ElementAt(0); |
525 | 0 | MOZ_ASSERT(slot); |
526 | 0 |
|
527 | 0 | // Find the appropriate position in the assigned node list for the |
528 | 0 | // newly assigned content. |
529 | 0 | const nsTArray<RefPtr<nsINode>>& assignedNodes = slot->AssignedNodes(); |
530 | 0 | nsIContent* currentContent = GetHost()->GetFirstChild(); |
531 | 0 | Maybe<uint32_t> insertionIndex; |
532 | 0 | for (uint32_t i = 0; i < assignedNodes.Length(); i++) { |
533 | 0 | // Seek through the host's explicit children until the |
534 | 0 | // assigned content is found. |
535 | 0 | while (currentContent && currentContent != assignedNodes[i]) { |
536 | 0 | if (currentContent == aContent) { |
537 | 0 | insertionIndex.emplace(i); |
538 | 0 | break; |
539 | 0 | } |
540 | 0 | |
541 | 0 | currentContent = currentContent->GetNextSibling(); |
542 | 0 | } |
543 | 0 |
|
544 | 0 | if (insertionIndex) { |
545 | 0 | break; |
546 | 0 | } |
547 | 0 | } |
548 | 0 |
|
549 | 0 | return { slot, insertionIndex }; |
550 | 0 | } |
551 | | |
552 | | void |
553 | | ShadowRoot::MaybeReassignElement(Element* aElement) |
554 | 0 | { |
555 | 0 | MOZ_ASSERT(aElement->GetParent() == GetHost()); |
556 | 0 | HTMLSlotElement* oldSlot = aElement->GetAssignedSlot(); |
557 | 0 | SlotAssignment assignment = SlotAssignmentFor(aElement); |
558 | 0 |
|
559 | 0 | if (assignment.mSlot == oldSlot) { |
560 | 0 | // Nothing to do here. |
561 | 0 | return; |
562 | 0 | } |
563 | 0 | |
564 | 0 | if (nsIDocument* doc = GetComposedDoc()) { |
565 | 0 | if (nsIPresShell* shell = doc->GetShell()) { |
566 | 0 | shell->SlotAssignmentWillChange(*aElement, oldSlot, assignment.mSlot); |
567 | 0 | } |
568 | 0 | } |
569 | 0 |
|
570 | 0 | if (oldSlot) { |
571 | 0 | oldSlot->RemoveAssignedNode(aElement); |
572 | 0 | oldSlot->EnqueueSlotChangeEvent(); |
573 | 0 | } |
574 | 0 |
|
575 | 0 | if (assignment.mSlot) { |
576 | 0 | if (assignment.mIndex) { |
577 | 0 | assignment.mSlot->InsertAssignedNode(*assignment.mIndex, aElement); |
578 | 0 | } else { |
579 | 0 | assignment.mSlot->AppendAssignedNode(aElement); |
580 | 0 | } |
581 | 0 | assignment.mSlot->EnqueueSlotChangeEvent(); |
582 | 0 | } |
583 | 0 |
|
584 | 0 | SlotStateChanged(oldSlot); |
585 | 0 | SlotStateChanged(assignment.mSlot); |
586 | 0 | } |
587 | | |
588 | | Element* |
589 | | ShadowRoot::GetActiveElement() |
590 | 0 | { |
591 | 0 | return GetRetargetedFocusedElement(); |
592 | 0 | } |
593 | | |
594 | | void |
595 | | ShadowRoot::GetInnerHTML(nsAString& aInnerHTML) |
596 | 0 | { |
597 | 0 | GetMarkup(false, aInnerHTML); |
598 | 0 | } |
599 | | |
600 | | void |
601 | | ShadowRoot::SetInnerHTML(const nsAString& aInnerHTML, ErrorResult& aError) |
602 | 0 | { |
603 | 0 | SetInnerHTMLInternal(aInnerHTML, aError); |
604 | 0 | } |
605 | | |
606 | | nsINode* |
607 | | ShadowRoot::ImportNodeAndAppendChildAt(nsINode& aParentNode, |
608 | | nsINode& aNode, |
609 | | bool aDeep, |
610 | | mozilla::ErrorResult& rv) |
611 | 0 | { |
612 | 0 | MOZ_ASSERT(mIsUAWidget); |
613 | 0 |
|
614 | 0 | if (!aParentNode.IsInUAWidget()) { |
615 | 0 | rv.Throw(NS_ERROR_INVALID_ARG); |
616 | 0 | return nullptr; |
617 | 0 | } |
618 | 0 | |
619 | 0 | RefPtr<nsINode> node = OwnerDoc()->ImportNode(aNode, aDeep, rv); |
620 | 0 | if (rv.Failed()) { |
621 | 0 | return nullptr; |
622 | 0 | } |
623 | 0 | |
624 | 0 | return aParentNode.AppendChild(*node, rv); |
625 | 0 | } |
626 | | |
627 | | nsINode* |
628 | | ShadowRoot::CreateElementAndAppendChildAt(nsINode& aParentNode, |
629 | | const nsAString& aTagName, |
630 | 0 | mozilla::ErrorResult& rv) { |
631 | 0 | MOZ_ASSERT(mIsUAWidget); |
632 | 0 | MOZ_ASSERT(OwnerDoc()); |
633 | 0 |
|
634 | 0 | if (!aParentNode.IsInUAWidget()) { |
635 | 0 | rv.Throw(NS_ERROR_INVALID_ARG); |
636 | 0 | return nullptr; |
637 | 0 | } |
638 | 0 | |
639 | 0 | // This option is not exposed to UA Widgets |
640 | 0 | ElementCreationOptionsOrString options; |
641 | 0 |
|
642 | 0 | RefPtr<nsINode> node = OwnerDoc()->CreateElement(aTagName, options, rv); |
643 | 0 | if (rv.Failed()) { |
644 | 0 | return nullptr; |
645 | 0 | } |
646 | 0 | |
647 | 0 | return aParentNode.AppendChild(*node, rv); |
648 | 0 | } |
649 | | |
650 | | void |
651 | | ShadowRoot::AttributeChanged(Element* aElement, |
652 | | int32_t aNameSpaceID, |
653 | | nsAtom* aAttribute, |
654 | | int32_t aModType, |
655 | | const nsAttrValue* aOldValue) |
656 | 0 | { |
657 | 0 | if (aNameSpaceID != kNameSpaceID_None || aAttribute != nsGkAtoms::slot) { |
658 | 0 | return; |
659 | 0 | } |
660 | 0 | |
661 | 0 | if (aElement->GetParent() != GetHost()) { |
662 | 0 | return; |
663 | 0 | } |
664 | 0 | |
665 | 0 | MaybeReassignElement(aElement); |
666 | 0 | } |
667 | | |
668 | | void |
669 | | ShadowRoot::ContentAppended(nsIContent* aFirstNewContent) |
670 | 0 | { |
671 | 0 | for (nsIContent* content = aFirstNewContent; |
672 | 0 | content; |
673 | 0 | content = content->GetNextSibling()) { |
674 | 0 | ContentInserted(content); |
675 | 0 | } |
676 | 0 | } |
677 | | |
678 | | void |
679 | | ShadowRoot::ContentInserted(nsIContent* aChild) |
680 | 0 | { |
681 | 0 | // Check to ensure that the child not an anonymous subtree root because |
682 | 0 | // even though its parent could be the host it may not be in the host's child |
683 | 0 | // list. |
684 | 0 | if (aChild->IsRootOfAnonymousSubtree()) { |
685 | 0 | return; |
686 | 0 | } |
687 | 0 | |
688 | 0 | if (!aChild->IsSlotable()) { |
689 | 0 | return; |
690 | 0 | } |
691 | 0 | |
692 | 0 | if (aChild->GetParent() == GetHost()) { |
693 | 0 | SlotAssignment assignment = SlotAssignmentFor(aChild); |
694 | 0 | if (!assignment.mSlot) { |
695 | 0 | return; |
696 | 0 | } |
697 | 0 | |
698 | 0 | // Fallback content will go away, let layout know. |
699 | 0 | if (assignment.mSlot->AssignedNodes().IsEmpty()) { |
700 | 0 | InvalidateStyleAndLayoutOnSubtree(assignment.mSlot); |
701 | 0 | } |
702 | 0 |
|
703 | 0 | if (assignment.mIndex) { |
704 | 0 | assignment.mSlot->InsertAssignedNode(*assignment.mIndex, aChild); |
705 | 0 | } else { |
706 | 0 | assignment.mSlot->AppendAssignedNode(aChild); |
707 | 0 | } |
708 | 0 | assignment.mSlot->EnqueueSlotChangeEvent(); |
709 | 0 |
|
710 | 0 | SlotStateChanged(assignment.mSlot); |
711 | 0 | return; |
712 | 0 | } |
713 | 0 |
|
714 | 0 | // If parent's root is a shadow root, and parent is a slot whose assigned |
715 | 0 | // nodes is the empty list, then run signal a slot change for parent. |
716 | 0 | HTMLSlotElement* slot = HTMLSlotElement::FromNodeOrNull(aChild->GetParent()); |
717 | 0 | if (slot && slot->GetContainingShadow() == this && |
718 | 0 | slot->AssignedNodes().IsEmpty()) { |
719 | 0 | slot->EnqueueSlotChangeEvent(); |
720 | 0 | } |
721 | 0 | } |
722 | | |
723 | | void |
724 | | ShadowRoot::ContentRemoved(nsIContent* aChild, nsIContent* aPreviousSibling) |
725 | 0 | { |
726 | 0 | // Check to ensure that the child not an anonymous subtree root because |
727 | 0 | // even though its parent could be the host it may not be in the host's child |
728 | 0 | // list. |
729 | 0 | if (aChild->IsRootOfAnonymousSubtree()) { |
730 | 0 | return; |
731 | 0 | } |
732 | 0 | |
733 | 0 | if (!aChild->IsSlotable()) { |
734 | 0 | return; |
735 | 0 | } |
736 | 0 | |
737 | 0 | if (aChild->GetParent() == GetHost()) { |
738 | 0 | if (HTMLSlotElement* slot = aChild->GetAssignedSlot()) { |
739 | 0 | // If the slot is going to start showing fallback content, we need to tell |
740 | 0 | // layout about it. |
741 | 0 | if (slot->AssignedNodes().Length() == 1) { |
742 | 0 | InvalidateStyleAndLayoutOnSubtree(slot); |
743 | 0 | } |
744 | 0 | slot->RemoveAssignedNode(aChild); |
745 | 0 | slot->EnqueueSlotChangeEvent(); |
746 | 0 | } |
747 | 0 | return; |
748 | 0 | } |
749 | 0 |
|
750 | 0 | // If parent's root is a shadow root, and parent is a slot whose assigned |
751 | 0 | // nodes is the empty list, then run signal a slot change for parent. |
752 | 0 | HTMLSlotElement* slot = HTMLSlotElement::FromNodeOrNull(aChild->GetParent()); |
753 | 0 | if (slot && slot->GetContainingShadow() == this && |
754 | 0 | slot->AssignedNodes().IsEmpty()) { |
755 | 0 | slot->EnqueueSlotChangeEvent(); |
756 | 0 | } |
757 | 0 | } |
758 | | |
759 | | ServoStyleRuleMap& |
760 | | ShadowRoot::ServoStyleRuleMap() |
761 | 0 | { |
762 | 0 | if (!mStyleRuleMap) { |
763 | 0 | mStyleRuleMap = MakeUnique<mozilla::ServoStyleRuleMap>(); |
764 | 0 | } |
765 | 0 | mStyleRuleMap->EnsureTable(*this); |
766 | 0 | return *mStyleRuleMap; |
767 | 0 | } |
768 | | |
769 | | nsresult |
770 | | ShadowRoot::Clone(dom::NodeInfo* aNodeInfo, nsINode** aResult) const |
771 | 0 | { |
772 | 0 | *aResult = nullptr; |
773 | 0 | return NS_ERROR_DOM_NOT_SUPPORTED_ERR; |
774 | 0 | } |