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 /*
8 * Base class for all element classes and DocumentFragment.
9 */
10
11 #include "mozilla/ArrayUtils.h"
12 #include "mozilla/Likely.h"
13 #include "mozilla/MemoryReporting.h"
14 #include "mozilla/StaticPtr.h"
15
16 #include "mozilla/dom/FragmentOrElement.h"
17 #include "DOMIntersectionObserver.h"
18 #include "mozilla/AsyncEventDispatcher.h"
19 #include "mozilla/DeclarationBlock.h"
20 #include "mozilla/EffectSet.h"
21 #include "mozilla/EventDispatcher.h"
22 #include "mozilla/EventListenerManager.h"
23 #include "mozilla/EventStates.h"
24 #include "mozilla/HTMLEditor.h"
25 #include "mozilla/PresShell.h"
26 #include "mozilla/RestyleManager.h"
27 #include "mozilla/TextEditor.h"
28 #include "mozilla/TouchEvents.h"
29 #include "mozilla/URLExtraData.h"
30 #include "mozilla/dom/Attr.h"
31 #include "nsDOMAttributeMap.h"
32 #include "nsAtom.h"
33 #include "mozilla/dom/NodeInfo.h"
34 #include "mozilla/dom/Event.h"
35 #include "mozilla/dom/ScriptLoader.h"
36 #include "mozilla/dom/TouchEvent.h"
37 #include "mozilla/dom/CustomElementRegistry.h"
38 #include "mozilla/dom/Document.h"
39 #include "mozilla/dom/DocumentInlines.h"
40 #include "nsIControllers.h"
41 #include "nsIDocumentEncoder.h"
42 #include "nsFocusManager.h"
43 #include "nsIScriptGlobalObject.h"
44 #include "nsNetUtil.h"
45 #include "nsIFrame.h"
46 #include "nsIAnonymousContentCreator.h"
47 #include "nsPresContext.h"
48 #include "nsStyleConsts.h"
49 #include "nsString.h"
50 #include "nsUnicharUtils.h"
51 #include "nsDOMCID.h"
52 #include "nsDOMCSSAttrDeclaration.h"
53 #include "nsNameSpaceManager.h"
54 #include "nsContentList.h"
55 #include "nsDOMTokenList.h"
56 #include "nsError.h"
57 #include "nsDOMString.h"
58 #include "nsXULElement.h"
59 #include "mozilla/InternalMutationEvent.h"
60 #include "mozilla/MouseEvents.h"
61 #include "nsAttrValueOrString.h"
62 #include "nsQueryObject.h"
63 #include "nsFrameSelection.h"
64 #ifdef DEBUG
65 # include "nsRange.h"
66 #endif
67
68 #include "nsFrameLoader.h"
69 #include "nsPIDOMWindow.h"
70 #include "nsLayoutUtils.h"
71 #include "nsGkAtoms.h"
72 #include "nsContentUtils.h"
73 #include "nsTextFragment.h"
74 #include "nsContentCID.h"
75 #include "nsWindowSizes.h"
76
77 #include "nsIWidget.h"
78
79 #include "nsNodeInfoManager.h"
80 #include "nsGenericHTMLElement.h"
81 #include "nsContentCreatorFunctions.h"
82 #include "nsView.h"
83 #include "nsViewManager.h"
84 #include "nsIScrollableFrame.h"
85 #include "ChildIterator.h"
86 #include "nsTextNode.h"
87 #include "mozilla/dom/NodeListBinding.h"
88
89 #include "nsCCUncollectableMarker.h"
90
91 #include "mozAutoDocUpdate.h"
92
93 #include "mozilla/Sprintf.h"
94 #include "nsDOMMutationObserver.h"
95 #include "nsWrapperCacheInlines.h"
96 #include "nsCycleCollector.h"
97 #include "xpcpublic.h"
98 #include "mozilla/Telemetry.h"
99
100 #include "mozilla/CORSMode.h"
101
102 #include "mozilla/dom/ShadowRoot.h"
103 #include "mozilla/dom/HTMLSlotElement.h"
104 #include "mozilla/dom/HTMLTemplateElement.h"
105 #include "mozilla/dom/SVGUseElement.h"
106
107 #include "nsStyledElement.h"
108 #include "nsIContentInlines.h"
109 #include "nsChildContentList.h"
110 #include "mozilla/BloomFilter.h"
111
112 #include "NodeUbiReporting.h"
113
114 using namespace mozilla;
115 using namespace mozilla::dom;
116
117 int32_t nsIContent::sTabFocusModel = eTabFocus_any;
118 bool nsIContent::sTabFocusModelAppliesToXUL = false;
119 uint64_t nsMutationGuard::sGeneration = 0;
120
121 NS_IMPL_CYCLE_COLLECTION_CLASS(nsIContent)
122
123 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsIContent)
124 MOZ_ASSERT_UNREACHABLE("Our subclasses don't call us");
125 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
126
127 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsIContent)
128 MOZ_ASSERT_UNREACHABLE("Our subclasses don't call us");
129 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
130
NS_INTERFACE_MAP_BEGIN(nsIContent)131 NS_INTERFACE_MAP_BEGIN(nsIContent)
132 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
133 // Don't bother to QI to cycle collection, because our CC impl is
134 // not doing anything anyway.
135 NS_INTERFACE_MAP_ENTRY(nsIContent)
136 NS_INTERFACE_MAP_ENTRY(nsINode)
137 NS_INTERFACE_MAP_ENTRY(mozilla::dom::EventTarget)
138 NS_INTERFACE_MAP_ENTRY_TEAROFF(nsISupportsWeakReference,
139 new nsNodeSupportsWeakRefTearoff(this))
140 // DOM bindings depend on the identity pointer being the
141 // same as nsINode (which nsIContent inherits).
142 NS_INTERFACE_MAP_ENTRY(nsISupports)
143 NS_INTERFACE_MAP_END
144
145 NS_IMPL_MAIN_THREAD_ONLY_CYCLE_COLLECTING_ADDREF(nsIContent)
146
147 NS_IMPL_DOMARENA_DESTROY(nsIContent)
148
149 NS_IMPL_MAIN_THREAD_ONLY_CYCLE_COLLECTING_RELEASE_WITH_LAST_RELEASE_AND_DESTROY(
150 nsIContent, LastRelease(), Destroy())
151
152 nsIContent* nsIContent::FindFirstNonChromeOnlyAccessContent() const {
153 // This handles also nested native anonymous content.
154 for (const nsIContent* content = this; content;
155 content = content->GetChromeOnlyAccessSubtreeRootParent()) {
156 if (!content->ChromeOnlyAccess()) {
157 // Oops, this function signature allows casting const to
158 // non-const. (Then again, so does GetFirstChild()->GetParent().)
159 return const_cast<nsIContent*>(content);
160 }
161 }
162 return nullptr;
163 }
164
165 // https://dom.spec.whatwg.org/#dom-slotable-assignedslot
GetAssignedSlotByMode() const166 HTMLSlotElement* nsIContent::GetAssignedSlotByMode() const {
167 /**
168 * Get slotable's assigned slot for the result of
169 * find a slot with open flag UNSET [1].
170 *
171 * [1] https://dom.spec.whatwg.org/#assign-a-slot
172 */
173 HTMLSlotElement* slot = GetAssignedSlot();
174 if (!slot) {
175 return nullptr;
176 }
177
178 MOZ_ASSERT(GetParent());
179 MOZ_ASSERT(GetParent()->GetShadowRoot());
180
181 /**
182 * Additional check for open flag SET:
183 * If slotable’s parent’s shadow root's mode is not "open",
184 * then return null.
185 */
186 if (GetParent()->GetShadowRoot()->IsClosed()) {
187 return nullptr;
188 }
189
190 return slot;
191 }
192
GetDesiredIMEState()193 nsIContent::IMEState nsIContent::GetDesiredIMEState() {
194 if (!IsEditable()) {
195 // Check for the special case where we're dealing with elements which don't
196 // have the editable flag set, but are readwrite (such as text controls).
197 if (!IsElement() ||
198 !AsElement()->State().HasState(NS_EVENT_STATE_READWRITE)) {
199 return IMEState(IMEEnabled::Disabled);
200 }
201 }
202 // NOTE: The content for independent editors (e.g., input[type=text],
203 // textarea) must override this method, so, we don't need to worry about
204 // that here.
205 nsIContent* editableAncestor = GetEditingHost();
206
207 // This is in another editable content, use the result of it.
208 if (editableAncestor && editableAncestor != this) {
209 return editableAncestor->GetDesiredIMEState();
210 }
211 Document* doc = GetComposedDoc();
212 if (!doc) {
213 return IMEState(IMEEnabled::Disabled);
214 }
215 nsPresContext* pc = doc->GetPresContext();
216 if (!pc) {
217 return IMEState(IMEEnabled::Disabled);
218 }
219 HTMLEditor* htmlEditor = nsContentUtils::GetHTMLEditor(pc);
220 if (!htmlEditor) {
221 return IMEState(IMEEnabled::Disabled);
222 }
223 IMEState state;
224 htmlEditor->GetPreferredIMEState(&state);
225 return state;
226 }
227
HasIndependentSelection() const228 bool nsIContent::HasIndependentSelection() const {
229 nsIFrame* frame = GetPrimaryFrame();
230 return (frame && frame->GetStateBits() & NS_FRAME_INDEPENDENT_SELECTION);
231 }
232
GetEditingHost()233 dom::Element* nsIContent::GetEditingHost() {
234 // If this isn't editable, return nullptr.
235 if (!IsEditable()) {
236 return nullptr;
237 }
238
239 Document* doc = GetComposedDoc();
240 if (!doc) {
241 return nullptr;
242 }
243
244 // If this is in designMode, we should return <body>
245 if (IsInDesignMode() && !IsInShadowTree()) {
246 return doc->GetBodyElement();
247 }
248
249 dom::Element* editableParentElement = nullptr;
250 for (dom::Element* parent = GetParentElement();
251 parent && parent->HasFlag(NODE_IS_EDITABLE);
252 parent = editableParentElement->GetParentElement()) {
253 editableParentElement = parent;
254 }
255 return editableParentElement ? editableParentElement
256 : dom::Element::FromNode(this);
257 }
258
LookupNamespaceURIInternal(const nsAString & aNamespacePrefix,nsAString & aNamespaceURI) const259 nsresult nsIContent::LookupNamespaceURIInternal(
260 const nsAString& aNamespacePrefix, nsAString& aNamespaceURI) const {
261 if (aNamespacePrefix.EqualsLiteral("xml")) {
262 // Special-case for xml prefix
263 aNamespaceURI.AssignLiteral("http://www.w3.org/XML/1998/namespace");
264 return NS_OK;
265 }
266
267 if (aNamespacePrefix.EqualsLiteral("xmlns")) {
268 // Special-case for xmlns prefix
269 aNamespaceURI.AssignLiteral("http://www.w3.org/2000/xmlns/");
270 return NS_OK;
271 }
272
273 RefPtr<nsAtom> name;
274 if (!aNamespacePrefix.IsEmpty()) {
275 name = NS_Atomize(aNamespacePrefix);
276 NS_ENSURE_TRUE(name, NS_ERROR_OUT_OF_MEMORY);
277 } else {
278 name = nsGkAtoms::xmlns;
279 }
280 // Trace up the content parent chain looking for the namespace
281 // declaration that declares aNamespacePrefix.
282 for (Element* element = GetAsElementOrParentElement(); element;
283 element = element->GetParentElement()) {
284 if (element->GetAttr(kNameSpaceID_XMLNS, name, aNamespaceURI)) {
285 return NS_OK;
286 }
287 }
288 return NS_ERROR_FAILURE;
289 }
290
GetLang() const291 nsAtom* nsIContent::GetLang() const {
292 for (const Element* element = GetAsElementOrParentElement(); element;
293 element = element->GetParentElement()) {
294 if (!element->GetAttrCount()) {
295 continue;
296 }
297
298 // xml:lang has precedence over lang on HTML elements (see
299 // XHTML1 section C.7).
300 const nsAttrValue* attr =
301 element->GetParsedAttr(nsGkAtoms::lang, kNameSpaceID_XML);
302 if (!attr && element->SupportsLangAttr()) {
303 attr = element->GetParsedAttr(nsGkAtoms::lang);
304 }
305 if (attr) {
306 MOZ_ASSERT(attr->Type() == nsAttrValue::eAtom);
307 MOZ_ASSERT(attr->GetAtomValue());
308 return attr->GetAtomValue();
309 }
310 }
311
312 return nullptr;
313 }
314
GetBaseURI(bool aTryUseXHRDocBaseURI) const315 nsIURI* nsIContent::GetBaseURI(bool aTryUseXHRDocBaseURI) const {
316 if (SVGUseElement* use = GetContainingSVGUseShadowHost()) {
317 if (URLExtraData* data = use->GetContentURLData()) {
318 return data->BaseURI();
319 }
320 }
321
322 return OwnerDoc()->GetBaseURI(aTryUseXHRDocBaseURI);
323 }
324
GetBaseURIForStyleAttr() const325 nsIURI* nsIContent::GetBaseURIForStyleAttr() const {
326 if (SVGUseElement* use = GetContainingSVGUseShadowHost()) {
327 if (URLExtraData* data = use->GetContentURLData()) {
328 return data->BaseURI();
329 }
330 }
331 // This also ignores the case that SVG inside XBL binding.
332 // But it is probably fine.
333 return OwnerDoc()->GetDocBaseURI();
334 }
335
GetURLDataForStyleAttr(nsIPrincipal * aSubjectPrincipal) const336 already_AddRefed<URLExtraData> nsIContent::GetURLDataForStyleAttr(
337 nsIPrincipal* aSubjectPrincipal) const {
338 if (SVGUseElement* use = GetContainingSVGUseShadowHost()) {
339 if (URLExtraData* data = use->GetContentURLData()) {
340 return do_AddRef(data);
341 }
342 }
343 if (aSubjectPrincipal && aSubjectPrincipal != NodePrincipal()) {
344 // TODO: Cache this?
345 nsCOMPtr<nsIReferrerInfo> referrerInfo =
346 ReferrerInfo::CreateForInternalCSSResources(OwnerDoc());
347 return MakeAndAddRef<URLExtraData>(OwnerDoc()->GetDocBaseURI(),
348 referrerInfo, aSubjectPrincipal);
349 }
350 // This also ignores the case that SVG inside XBL binding.
351 // But it is probably fine.
352 return do_AddRef(OwnerDoc()->DefaultStyleAttrURLData());
353 }
354
ConstructUbiNode(void * storage)355 void nsIContent::ConstructUbiNode(void* storage) {
356 JS::ubi::Concrete<nsIContent>::construct(storage, this);
357 }
358
359 //----------------------------------------------------------------------
360
GetJSObjectChild(nsWrapperCache * aCache)361 static inline JSObject* GetJSObjectChild(nsWrapperCache* aCache) {
362 return aCache->PreservingWrapper() ? aCache->GetWrapperPreserveColor()
363 : nullptr;
364 }
365
NeedsScriptTraverse(nsINode * aNode)366 static bool NeedsScriptTraverse(nsINode* aNode) {
367 return aNode->PreservingWrapper() && aNode->GetWrapperPreserveColor() &&
368 !aNode->HasKnownLiveWrapperAndDoesNotNeedTracing(aNode);
369 }
370
371 //----------------------------------------------------------------------
372
373 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsAttrChildContentList)
374 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsAttrChildContentList)
375
376 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(nsAttrChildContentList, mNode)
377
378 // If the wrapper is known-live, the list can't be part of a garbage cycle.
379 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(nsAttrChildContentList)
380 return tmp->HasKnownLiveWrapper();
381 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
382
383 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(nsAttrChildContentList)
384 return tmp->HasKnownLiveWrapperAndDoesNotNeedTracing(tmp);
385 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
386
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(nsAttrChildContentList)387 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(nsAttrChildContentList)
388 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
389
390 NS_INTERFACE_TABLE_HEAD(nsAttrChildContentList)
391 NS_WRAPPERCACHE_INTERFACE_TABLE_ENTRY
392 NS_INTERFACE_TABLE(nsAttrChildContentList, nsINodeList)
393 NS_INTERFACE_TABLE_TO_MAP_SEGUE_CYCLE_COLLECTION(nsAttrChildContentList)
394 NS_INTERFACE_MAP_END
395
396 JSObject* nsAttrChildContentList::WrapObject(
397 JSContext* cx, JS::Handle<JSObject*> aGivenProto) {
398 return NodeList_Binding::Wrap(cx, this, aGivenProto);
399 }
400
Length()401 uint32_t nsAttrChildContentList::Length() {
402 return mNode ? mNode->GetChildCount() : 0;
403 }
404
Item(uint32_t aIndex)405 nsIContent* nsAttrChildContentList::Item(uint32_t aIndex) {
406 if (mNode) {
407 return mNode->GetChildAt_Deprecated(aIndex);
408 }
409
410 return nullptr;
411 }
412
IndexOf(nsIContent * aContent)413 int32_t nsAttrChildContentList::IndexOf(nsIContent* aContent) {
414 if (mNode) {
415 return mNode->ComputeIndexOf_Deprecated(aContent);
416 }
417
418 return -1;
419 }
420
421 //----------------------------------------------------------------------
Length()422 uint32_t nsParentNodeChildContentList::Length() {
423 if (!mIsCacheValid && !ValidateCache()) {
424 return 0;
425 }
426
427 MOZ_ASSERT(mIsCacheValid);
428
429 return mCachedChildArray.Length();
430 }
431
Item(uint32_t aIndex)432 nsIContent* nsParentNodeChildContentList::Item(uint32_t aIndex) {
433 if (!mIsCacheValid && !ValidateCache()) {
434 return nullptr;
435 }
436
437 MOZ_ASSERT(mIsCacheValid);
438
439 return mCachedChildArray.SafeElementAt(aIndex, nullptr);
440 }
441
IndexOf(nsIContent * aContent)442 int32_t nsParentNodeChildContentList::IndexOf(nsIContent* aContent) {
443 if (!mIsCacheValid && !ValidateCache()) {
444 return -1;
445 }
446
447 MOZ_ASSERT(mIsCacheValid);
448
449 return mCachedChildArray.IndexOf(aContent);
450 }
451
ValidateCache()452 bool nsParentNodeChildContentList::ValidateCache() {
453 MOZ_ASSERT(!mIsCacheValid);
454 MOZ_ASSERT(mCachedChildArray.IsEmpty());
455
456 nsINode* parent = GetParentObject();
457 if (!parent) {
458 return false;
459 }
460
461 for (nsIContent* node = parent->GetFirstChild(); node;
462 node = node->GetNextSibling()) {
463 mCachedChildArray.AppendElement(node);
464 }
465 mIsCacheValid = true;
466
467 return true;
468 }
469
470 //----------------------------------------------------------------------
471
Children()472 nsIHTMLCollection* FragmentOrElement::Children() {
473 nsDOMSlots* slots = DOMSlots();
474
475 if (!slots->mChildrenList) {
476 slots->mChildrenList =
477 new nsContentList(this, kNameSpaceID_Wildcard, nsGkAtoms::_asterisk,
478 nsGkAtoms::_asterisk, false);
479 }
480
481 return slots->mChildrenList;
482 }
483
484 //----------------------------------------------------------------------
485
NS_IMPL_CYCLE_COLLECTION(nsNodeSupportsWeakRefTearoff,mNode)486 NS_IMPL_CYCLE_COLLECTION(nsNodeSupportsWeakRefTearoff, mNode)
487
488 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsNodeSupportsWeakRefTearoff)
489 NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
490 NS_INTERFACE_MAP_END_AGGREGATED(mNode)
491
492 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsNodeSupportsWeakRefTearoff)
493 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsNodeSupportsWeakRefTearoff)
494
495 NS_IMETHODIMP
496 nsNodeSupportsWeakRefTearoff::GetWeakReference(
497 nsIWeakReference** aInstancePtr) {
498 nsINode::nsSlots* slots = mNode->Slots();
499 if (!slots->mWeakReference) {
500 slots->mWeakReference = new nsNodeWeakReference(mNode);
501 }
502
503 NS_ADDREF(*aInstancePtr = slots->mWeakReference);
504
505 return NS_OK;
506 }
507
508 //----------------------------------------------------------------------
509
510 static const size_t MaxDOMSlotSizeAllowed =
511 #ifdef HAVE_64BIT_BUILD
512 128;
513 #else
514 64;
515 #endif
516
517 static_assert(sizeof(nsINode::nsSlots) <= MaxDOMSlotSizeAllowed,
518 "DOM slots cannot be grown without consideration");
519 static_assert(sizeof(FragmentOrElement::nsDOMSlots) <= MaxDOMSlotSizeAllowed,
520 "DOM slots cannot be grown without consideration");
521
UnlinkExtendedSlots()522 void nsIContent::nsExtendedContentSlots::UnlinkExtendedSlots() {
523 mContainingShadow = nullptr;
524 mAssignedSlot = nullptr;
525 }
526
TraverseExtendedSlots(nsCycleCollectionTraversalCallback & aCb)527 void nsIContent::nsExtendedContentSlots::TraverseExtendedSlots(
528 nsCycleCollectionTraversalCallback& aCb) {
529 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCb, "mExtendedSlots->mContainingShadow");
530 aCb.NoteXPCOMChild(NS_ISUPPORTS_CAST(nsIContent*, mContainingShadow));
531
532 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCb, "mExtendedSlots->mAssignedSlot");
533 aCb.NoteXPCOMChild(NS_ISUPPORTS_CAST(nsIContent*, mAssignedSlot.get()));
534 }
535
536 nsIContent::nsExtendedContentSlots::nsExtendedContentSlots() = default;
537
~nsExtendedContentSlots()538 nsIContent::nsExtendedContentSlots::~nsExtendedContentSlots() {
539 MOZ_ASSERT(!mManualSlotAssignment);
540 }
541
SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const542 size_t nsIContent::nsExtendedContentSlots::SizeOfExcludingThis(
543 MallocSizeOf aMallocSizeOf) const {
544 // For now, nothing to measure here. We don't actually own any of our
545 // members.
546 return 0;
547 }
548
nsDOMSlots()549 FragmentOrElement::nsDOMSlots::nsDOMSlots()
550 : nsIContent::nsContentSlots(), mDataset(nullptr) {
551 MOZ_COUNT_CTOR(nsDOMSlots);
552 }
553
~nsDOMSlots()554 FragmentOrElement::nsDOMSlots::~nsDOMSlots() {
555 MOZ_COUNT_DTOR(nsDOMSlots);
556
557 if (mAttributeMap) {
558 mAttributeMap->DropReference();
559 }
560 }
561
Traverse(nsCycleCollectionTraversalCallback & aCb)562 void FragmentOrElement::nsDOMSlots::Traverse(
563 nsCycleCollectionTraversalCallback& aCb) {
564 nsIContent::nsContentSlots::Traverse(aCb);
565
566 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCb, "mSlots->mStyle");
567 aCb.NoteXPCOMChild(mStyle.get());
568
569 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCb, "mSlots->mAttributeMap");
570 aCb.NoteXPCOMChild(mAttributeMap.get());
571
572 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCb, "mSlots->mChildrenList");
573 aCb.NoteXPCOMChild(NS_ISUPPORTS_CAST(nsINodeList*, mChildrenList));
574
575 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCb, "mSlots->mClassList");
576 aCb.NoteXPCOMChild(mClassList.get());
577
578 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCb, "mSlots->mPart");
579 aCb.NoteXPCOMChild(mPart.get());
580 }
581
Unlink()582 void FragmentOrElement::nsDOMSlots::Unlink() {
583 nsIContent::nsContentSlots::Unlink();
584 mStyle = nullptr;
585 if (mAttributeMap) {
586 mAttributeMap->DropReference();
587 mAttributeMap = nullptr;
588 }
589 mChildrenList = nullptr;
590 mClassList = nullptr;
591 mPart = nullptr;
592 }
593
SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const594 size_t FragmentOrElement::nsDOMSlots::SizeOfIncludingThis(
595 MallocSizeOf aMallocSizeOf) const {
596 size_t n = aMallocSizeOf(this);
597
598 nsExtendedContentSlots* extendedSlots = GetExtendedContentSlots();
599 if (extendedSlots) {
600 if (OwnsExtendedSlots()) {
601 n += aMallocSizeOf(extendedSlots);
602 }
603
604 n += extendedSlots->SizeOfExcludingThis(aMallocSizeOf);
605 }
606
607 if (mAttributeMap) {
608 n += mAttributeMap->SizeOfIncludingThis(aMallocSizeOf);
609 }
610
611 if (mChildrenList) {
612 n += mChildrenList->SizeOfIncludingThis(aMallocSizeOf);
613 }
614
615 // Measurement of the following members may be added later if DMD finds it is
616 // worthwhile:
617 // - Superclass members (nsINode::nsSlots)
618 // - mStyle
619 // - mDataSet
620 // - mClassList
621
622 // The following member are not measured:
623 // - mControllers: because it is non-owning
624 return n;
625 }
626
627 FragmentOrElement::nsExtendedDOMSlots::nsExtendedDOMSlots() = default;
628
629 FragmentOrElement::nsExtendedDOMSlots::~nsExtendedDOMSlots() = default;
630
UnlinkExtendedSlots()631 void FragmentOrElement::nsExtendedDOMSlots::UnlinkExtendedSlots() {
632 nsIContent::nsExtendedContentSlots::UnlinkExtendedSlots();
633
634 // Don't clear mXBLBinding, it'll be done in
635 // BindingManager::RemovedFromDocument from FragmentOrElement::Unlink.
636 //
637 // mShadowRoot will similarly be cleared explicitly from
638 // FragmentOrElement::Unlink.
639 mSMILOverrideStyle = nullptr;
640 mControllers = nullptr;
641 mLabelsList = nullptr;
642 if (mCustomElementData) {
643 mCustomElementData->Unlink();
644 mCustomElementData = nullptr;
645 }
646 }
647
TraverseExtendedSlots(nsCycleCollectionTraversalCallback & aCb)648 void FragmentOrElement::nsExtendedDOMSlots::TraverseExtendedSlots(
649 nsCycleCollectionTraversalCallback& aCb) {
650 nsIContent::nsExtendedContentSlots::TraverseExtendedSlots(aCb);
651
652 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCb, "mExtendedSlots->mSMILOverrideStyle");
653 aCb.NoteXPCOMChild(mSMILOverrideStyle.get());
654
655 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCb, "mExtendedSlots->mControllers");
656 aCb.NoteXPCOMChild(mControllers);
657
658 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCb, "mExtendedSlots->mLabelsList");
659 aCb.NoteXPCOMChild(NS_ISUPPORTS_CAST(nsINodeList*, mLabelsList));
660
661 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCb, "mExtendedSlots->mShadowRoot");
662 aCb.NoteXPCOMChild(NS_ISUPPORTS_CAST(nsIContent*, mShadowRoot));
663
664 if (mCustomElementData) {
665 mCustomElementData->Traverse(aCb);
666 }
667 }
668
SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const669 size_t FragmentOrElement::nsExtendedDOMSlots::SizeOfExcludingThis(
670 MallocSizeOf aMallocSizeOf) const {
671 size_t n =
672 nsIContent::nsExtendedContentSlots::SizeOfExcludingThis(aMallocSizeOf);
673
674 // We own mSMILOverrideStyle but there seems to be no memory reporting on CSS
675 // declarations? At least report the memory the declaration takes up
676 // directly.
677 if (mSMILOverrideStyle) {
678 n += aMallocSizeOf(mSMILOverrideStyle);
679 }
680
681 // We don't really own mSMILOverrideStyleDeclaration. mSMILOverrideStyle owns
682 // it.
683
684 // We don't seem to have memory reporting for nsXULControllers. At least
685 // report the memory it's using directly.
686 if (mControllers) {
687 n += aMallocSizeOf(mControllers);
688 }
689
690 if (mLabelsList) {
691 n += mLabelsList->SizeOfIncludingThis(aMallocSizeOf);
692 }
693
694 // mShadowRoot should be handled during normal DOM tree memory reporting, just
695 // like kids, siblings, etc.
696
697 if (mCustomElementData) {
698 n += mCustomElementData->SizeOfIncludingThis(aMallocSizeOf);
699 }
700
701 return n;
702 }
703
FragmentOrElement(already_AddRefed<mozilla::dom::NodeInfo> && aNodeInfo)704 FragmentOrElement::FragmentOrElement(
705 already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)
706 : nsIContent(std::move(aNodeInfo)) {}
707
~FragmentOrElement()708 FragmentOrElement::~FragmentOrElement() {
709 MOZ_ASSERT(!IsInUncomposedDoc(),
710 "Please remove this from the document properly");
711 if (GetParent()) {
712 NS_RELEASE(mParent);
713 }
714 }
715
GetChildren(uint32_t aFilter)716 already_AddRefed<nsINodeList> FragmentOrElement::GetChildren(uint32_t aFilter) {
717 RefPtr<nsSimpleContentList> list = new nsSimpleContentList(this);
718 AllChildrenIterator iter(this, aFilter);
719 while (nsIContent* kid = iter.GetNextChild()) {
720 list->AppendElement(kid);
721 }
722
723 return list.forget();
724 }
725
FindChromeAccessOnlySubtreeOwner(nsINode * aNode)726 static nsINode* FindChromeAccessOnlySubtreeOwner(nsINode* aNode) {
727 if (!aNode->ChromeOnlyAccess()) {
728 return aNode;
729 }
730
731 while (aNode && !aNode->IsRootOfChromeAccessOnlySubtree()) {
732 aNode = aNode->GetParentNode();
733 }
734
735 return aNode ? aNode->GetParentOrShadowHostNode() : nullptr;
736 }
737
FindChromeAccessOnlySubtreeOwner(EventTarget * aTarget)738 already_AddRefed<nsINode> FindChromeAccessOnlySubtreeOwner(
739 EventTarget* aTarget) {
740 nsCOMPtr<nsINode> node = nsINode::FromEventTargetOrNull(aTarget);
741 if (!node || !node->ChromeOnlyAccess()) {
742 return node.forget();
743 }
744
745 node = FindChromeAccessOnlySubtreeOwner(node);
746 return node.forget();
747 }
748
GetEventTargetParent(EventChainPreVisitor & aVisitor)749 void nsIContent::GetEventTargetParent(EventChainPreVisitor& aVisitor) {
750 // FIXME! Document how this event retargeting works, Bug 329124.
751 aVisitor.mCanHandle = true;
752 aVisitor.mMayHaveListenerManager = HasListenerManager();
753
754 if (IsInShadowTree()) {
755 aVisitor.mItemInShadowTree = true;
756 }
757
758 // Don't propagate mouseover and mouseout events when mouse is moving
759 // inside chrome access only content.
760 bool isAnonForEvents = IsRootOfChromeAccessOnlySubtree();
761 aVisitor.mRootOfClosedTree = isAnonForEvents;
762 if ((aVisitor.mEvent->mMessage == eMouseOver ||
763 aVisitor.mEvent->mMessage == eMouseOut ||
764 aVisitor.mEvent->mMessage == ePointerOver ||
765 aVisitor.mEvent->mMessage == ePointerOut) &&
766 // Check if we should stop event propagation when event has just been
767 // dispatched or when we're about to propagate from
768 // chrome access only subtree or if we are about to propagate out of
769 // a shadow root to a shadow root host.
770 ((this == aVisitor.mEvent->mOriginalTarget && !ChromeOnlyAccess()) ||
771 isAnonForEvents)) {
772 nsCOMPtr<nsIContent> relatedTarget = nsIContent::FromEventTargetOrNull(
773 aVisitor.mEvent->AsMouseEvent()->mRelatedTarget);
774 if (relatedTarget && relatedTarget->OwnerDoc() == OwnerDoc()) {
775 // If current target is anonymous for events or we know that related
776 // target is descendant of an element which is anonymous for events,
777 // we may want to stop event propagation.
778 // If this is the original target, aVisitor.mRelatedTargetIsInAnon
779 // must be updated.
780 if (isAnonForEvents || aVisitor.mRelatedTargetIsInAnon ||
781 (aVisitor.mEvent->mOriginalTarget == this &&
782 (aVisitor.mRelatedTargetIsInAnon =
783 relatedTarget->ChromeOnlyAccess()))) {
784 nsINode* anonOwner = FindChromeAccessOnlySubtreeOwner(this);
785 if (anonOwner) {
786 nsINode* anonOwnerRelated =
787 FindChromeAccessOnlySubtreeOwner(relatedTarget);
788 if (anonOwnerRelated) {
789 // Note, anonOwnerRelated may still be inside some other
790 // native anonymous subtree. The case where anonOwner is still
791 // inside native anonymous subtree will be handled when event
792 // propagates up in the DOM tree.
793 while (anonOwner != anonOwnerRelated &&
794 anonOwnerRelated->ChromeOnlyAccess()) {
795 anonOwnerRelated =
796 FindChromeAccessOnlySubtreeOwner(anonOwnerRelated);
797 }
798 if (anonOwner == anonOwnerRelated) {
799 #ifdef DEBUG_smaug
800 nsCOMPtr<nsIContent> originalTarget =
801 nsIContent::FromEventTargetOrNull(
802 aVisitor.mEvent->mOriginalTarget);
803 nsAutoString ot, ct, rt;
804 if (originalTarget) {
805 originalTarget->NodeInfo()->NameAtom()->ToString(ot);
806 }
807 NodeInfo()->NameAtom()->ToString(ct);
808 relatedTarget->NodeInfo()->NameAtom()->ToString(rt);
809 printf(
810 "Stopping %s propagation:"
811 "\n\toriginalTarget=%s \n\tcurrentTarget=%s %s"
812 "\n\trelatedTarget=%s %s \n%s",
813 (aVisitor.mEvent->mMessage == eMouseOver) ? "mouseover"
814 : "mouseout",
815 NS_ConvertUTF16toUTF8(ot).get(),
816 NS_ConvertUTF16toUTF8(ct).get(),
817 isAnonForEvents
818 ? "(is native anonymous)"
819 : (ChromeOnlyAccess() ? "(is in native anonymous subtree)"
820 : ""),
821 NS_ConvertUTF16toUTF8(rt).get(),
822 relatedTarget->ChromeOnlyAccess()
823 ? "(is in native anonymous subtree)"
824 : "",
825 (originalTarget &&
826 relatedTarget->FindFirstNonChromeOnlyAccessContent() ==
827 originalTarget->FindFirstNonChromeOnlyAccessContent())
828 ? ""
829 : "Wrong event propagation!?!\n");
830 #endif
831 aVisitor.SetParentTarget(nullptr, false);
832 // Event should not propagate to non-anon content.
833 aVisitor.mCanHandle = isAnonForEvents;
834 return;
835 }
836 }
837 }
838 }
839 }
840 }
841
842 // Event parent is the assigned slot, if node is assigned, or node's parent
843 // otherwise.
844 HTMLSlotElement* slot = GetAssignedSlot();
845 nsIContent* parent = slot ? slot : GetParent();
846
847 // Event may need to be retargeted if this is the root of a native
848 // anonymous content subtree or event is dispatched somewhere inside XBL.
849 if (isAnonForEvents) {
850 #ifdef DEBUG
851 // If a DOM event is explicitly dispatched using node.dispatchEvent(), then
852 // all the events are allowed even in the native anonymous content..
853 nsCOMPtr<nsIContent> t =
854 nsIContent::FromEventTargetOrNull(aVisitor.mEvent->mOriginalTarget);
855 NS_ASSERTION(!t || !t->ChromeOnlyAccess() ||
856 aVisitor.mEvent->mClass != eMutationEventClass ||
857 aVisitor.mDOMEvent,
858 "Mutation event dispatched in native anonymous content!?!");
859 #endif
860 aVisitor.mEventTargetAtParent = parent;
861 } else if (parent && aVisitor.mOriginalTargetIsInAnon) {
862 nsCOMPtr<nsIContent> content(
863 nsIContent::FromEventTargetOrNull(aVisitor.mEvent->mTarget));
864 if (content &&
865 content->GetClosestNativeAnonymousSubtreeRootParent() == parent) {
866 aVisitor.mEventTargetAtParent = parent;
867 }
868 }
869
870 if (!aVisitor.mEvent->mFlags.mComposedInNativeAnonymousContent &&
871 IsRootOfNativeAnonymousSubtree() && OwnerDoc()->GetWindow()) {
872 aVisitor.SetParentTarget(OwnerDoc()->GetWindow()->GetParentTarget(), true);
873 } else if (parent) {
874 aVisitor.SetParentTarget(parent, false);
875 if (slot) {
876 ShadowRoot* root = slot->GetContainingShadow();
877 if (root && root->IsClosed()) {
878 aVisitor.mParentIsSlotInClosedTree = true;
879 }
880 }
881 } else {
882 aVisitor.SetParentTarget(GetComposedDoc(), false);
883 }
884
885 if (!ChromeOnlyAccess() && !aVisitor.mRelatedTargetRetargetedInCurrentScope) {
886 // We don't support Shadow DOM in native anonymous content yet.
887 aVisitor.mRelatedTargetRetargetedInCurrentScope = true;
888 if (aVisitor.mEvent->mOriginalRelatedTarget) {
889 // https://dom.spec.whatwg.org/#concept-event-dispatch
890 // Step 3.
891 // "Let relatedTarget be the result of retargeting event's relatedTarget
892 // against target if event's relatedTarget is non-null, and null
893 // otherwise."
894 //
895 // This is a bit complicated because the event might be from native
896 // anonymous content, but we need to deal with non-native anonymous
897 // content there.
898 bool initialTarget = this == aVisitor.mEvent->mOriginalTarget;
899 nsCOMPtr<nsINode> originalTargetAsNode;
900 // Use of mOriginalTargetIsInAnon is an optimization here.
901 if (!initialTarget && aVisitor.mOriginalTargetIsInAnon) {
902 originalTargetAsNode =
903 FindChromeAccessOnlySubtreeOwner(aVisitor.mEvent->mOriginalTarget);
904 initialTarget = originalTargetAsNode == this;
905 }
906 if (initialTarget) {
907 nsCOMPtr<nsINode> relatedTargetAsNode =
908 FindChromeAccessOnlySubtreeOwner(
909 aVisitor.mEvent->mOriginalRelatedTarget);
910 if (!originalTargetAsNode) {
911 originalTargetAsNode =
912 nsINode::FromEventTargetOrNull(aVisitor.mEvent->mOriginalTarget);
913 }
914
915 if (relatedTargetAsNode && originalTargetAsNode) {
916 nsINode* retargetedRelatedTarget = nsContentUtils::Retarget(
917 relatedTargetAsNode, originalTargetAsNode);
918 if (originalTargetAsNode == retargetedRelatedTarget &&
919 retargetedRelatedTarget != relatedTargetAsNode) {
920 // Step 4.
921 // "If target is relatedTarget and target is not event's
922 // relatedTarget, then return true."
923 aVisitor.IgnoreCurrentTargetBecauseOfShadowDOMRetargeting();
924 // Old code relies on mTarget to point to the first element which
925 // was not added to the event target chain because of mCanHandle
926 // being false, but in Shadow DOM case mTarget really should
927 // point to a node in Shadow DOM.
928 aVisitor.mEvent->mTarget = aVisitor.mTargetInKnownToBeHandledScope;
929 return;
930 }
931
932 // Part of step 5. Retargeting target has happened already higher
933 // up in this method.
934 // "Append to an event path with event, target, targetOverride,
935 // relatedTarget, and false."
936 aVisitor.mRetargetedRelatedTarget = retargetedRelatedTarget;
937 }
938 } else {
939 nsCOMPtr<nsINode> relatedTargetAsNode =
940 FindChromeAccessOnlySubtreeOwner(
941 aVisitor.mEvent->mOriginalRelatedTarget);
942 if (relatedTargetAsNode) {
943 // Step 11.3.
944 // "Let relatedTarget be the result of retargeting event's
945 // relatedTarget against parent if event's relatedTarget is non-null,
946 // and null otherwise.".
947 nsINode* retargetedRelatedTarget =
948 nsContentUtils::Retarget(relatedTargetAsNode, this);
949 nsCOMPtr<nsINode> targetInKnownToBeHandledScope =
950 FindChromeAccessOnlySubtreeOwner(
951 aVisitor.mTargetInKnownToBeHandledScope);
952 // If aVisitor.mTargetInKnownToBeHandledScope wasn't nsINode,
953 // targetInKnownToBeHandledScope will be null. This may happen when
954 // dispatching event to Window object in a content page and
955 // propagating the event to a chrome Element.
956 if (targetInKnownToBeHandledScope &&
957 IsShadowIncludingInclusiveDescendantOf(
958 targetInKnownToBeHandledScope->SubtreeRoot())) {
959 // Part of step 11.4.
960 // "If target's root is a shadow-including inclusive ancestor of
961 // parent, then"
962 // "...Append to an event path with event, parent, null,
963 // relatedTarget, " and slot-in-closed-tree."
964 aVisitor.mRetargetedRelatedTarget = retargetedRelatedTarget;
965 } else if (this == retargetedRelatedTarget) {
966 // Step 11.5
967 // "Otherwise, if parent and relatedTarget are identical, then set
968 // parent to null."
969 aVisitor.IgnoreCurrentTargetBecauseOfShadowDOMRetargeting();
970 // Old code relies on mTarget to point to the first element which
971 // was not added to the event target chain because of mCanHandle
972 // being false, but in Shadow DOM case mTarget really should
973 // point to a node in Shadow DOM.
974 aVisitor.mEvent->mTarget = aVisitor.mTargetInKnownToBeHandledScope;
975 return;
976 } else if (targetInKnownToBeHandledScope) {
977 // Note, if targetInKnownToBeHandledScope is null,
978 // mTargetInKnownToBeHandledScope could be Window object in content
979 // page and we're in chrome document in the same process.
980
981 // Step 11.6
982 aVisitor.mRetargetedRelatedTarget = retargetedRelatedTarget;
983 }
984 }
985 }
986 }
987
988 if (aVisitor.mEvent->mClass == eTouchEventClass) {
989 // Retarget touch objects.
990 MOZ_ASSERT(!aVisitor.mRetargetedTouchTargets.isSome());
991 aVisitor.mRetargetedTouchTargets.emplace();
992 WidgetTouchEvent* touchEvent = aVisitor.mEvent->AsTouchEvent();
993 WidgetTouchEvent::TouchArray& touches = touchEvent->mTouches;
994 for (uint32_t i = 0; i < touches.Length(); ++i) {
995 Touch* touch = touches[i];
996 EventTarget* originalTarget = touch->mOriginalTarget;
997 EventTarget* touchTarget = originalTarget;
998 nsCOMPtr<nsINode> targetAsNode =
999 nsINode::FromEventTargetOrNull(originalTarget);
1000 if (targetAsNode) {
1001 EventTarget* retargeted =
1002 nsContentUtils::Retarget(targetAsNode, this);
1003 if (retargeted) {
1004 touchTarget = retargeted;
1005 }
1006 }
1007 aVisitor.mRetargetedTouchTargets->AppendElement(touchTarget);
1008 touch->mTarget = touchTarget;
1009 }
1010 MOZ_ASSERT(aVisitor.mRetargetedTouchTargets->Length() ==
1011 touches.Length());
1012 }
1013 }
1014
1015 if (slot) {
1016 // Inform that we're about to exit the current scope.
1017 aVisitor.mRelatedTargetRetargetedInCurrentScope = false;
1018 }
1019 }
1020
IsFocusable(int32_t * aTabIndex,bool aWithMouse)1021 bool nsIContent::IsFocusable(int32_t* aTabIndex, bool aWithMouse) {
1022 bool focusable = IsFocusableInternal(aTabIndex, aWithMouse);
1023 // Ensure that the return value and aTabIndex are consistent in the case
1024 // we're in userfocusignored context.
1025 if (focusable || (aTabIndex && *aTabIndex != -1)) {
1026 return focusable;
1027 }
1028 return false;
1029 }
1030
IsFocusableInternal(int32_t * aTabIndex,bool aWithMouse)1031 bool nsIContent::IsFocusableInternal(int32_t* aTabIndex, bool aWithMouse) {
1032 if (aTabIndex) {
1033 *aTabIndex = -1; // Default, not tabbable
1034 }
1035 return false;
1036 }
1037
IsLink(nsIURI ** aURI) const1038 bool FragmentOrElement::IsLink(nsIURI** aURI) const {
1039 *aURI = nullptr;
1040 return false;
1041 }
1042
SetAssignedSlot(HTMLSlotElement * aSlot)1043 void nsIContent::SetAssignedSlot(HTMLSlotElement* aSlot) {
1044 MOZ_ASSERT(aSlot || GetExistingExtendedContentSlots());
1045 ExtendedContentSlots()->mAssignedSlot = aSlot;
1046 }
1047
1048 #ifdef MOZ_DOM_LIST
Dump()1049 void nsIContent::Dump() { List(); }
1050 #endif
1051
GetTextContentInternal(nsAString & aTextContent,OOMReporter & aError)1052 void FragmentOrElement::GetTextContentInternal(nsAString& aTextContent,
1053 OOMReporter& aError) {
1054 if (!nsContentUtils::GetNodeTextContent(this, true, aTextContent, fallible)) {
1055 aError.ReportOOM();
1056 }
1057 }
1058
SetTextContentInternal(const nsAString & aTextContent,nsIPrincipal * aSubjectPrincipal,ErrorResult & aError)1059 void FragmentOrElement::SetTextContentInternal(const nsAString& aTextContent,
1060 nsIPrincipal* aSubjectPrincipal,
1061 ErrorResult& aError) {
1062 aError = nsContentUtils::SetNodeTextContent(this, aTextContent, false);
1063 }
1064
DestroyContent()1065 void FragmentOrElement::DestroyContent() {
1066 // Drop any servo data. We do this before the RemovedFromDocument call below
1067 // so that it doesn't need to try to keep the style state sane when shuffling
1068 // around the flattened tree.
1069 //
1070 // TODO(emilio): I suspect this can be asserted against instead, with a bit of
1071 // effort to avoid calling Document::Destroy with a shell...
1072 if (IsElement()) {
1073 AsElement()->ClearServoData();
1074 }
1075
1076 #ifdef DEBUG
1077 uint32_t oldChildCount = GetChildCount();
1078 #endif
1079
1080 for (nsIContent* child = GetFirstChild(); child;
1081 child = child->GetNextSibling()) {
1082 child->DestroyContent();
1083 MOZ_ASSERT(child->GetParent() == this,
1084 "Mutating the tree during XBL destructors is evil");
1085 }
1086
1087 MOZ_ASSERT(oldChildCount == GetChildCount(),
1088 "Mutating the tree during XBL destructors is evil");
1089
1090 if (ShadowRoot* shadowRoot = GetShadowRoot()) {
1091 shadowRoot->DestroyContent();
1092 }
1093 }
1094
SaveSubtreeState()1095 void FragmentOrElement::SaveSubtreeState() {
1096 for (nsIContent* child = GetFirstChild(); child;
1097 child = child->GetNextSibling()) {
1098 child->SaveSubtreeState();
1099 }
1100
1101 // FIXME(bug 1469277): Pretty sure this wants to dig into shadow trees as
1102 // well.
1103 }
1104
1105 //----------------------------------------------------------------------
1106
1107 // Generic DOMNode implementations
1108
FireNodeInserted(Document * aDoc,nsINode * aParent,nsTArray<nsCOMPtr<nsIContent>> & aNodes)1109 void FragmentOrElement::FireNodeInserted(
1110 Document* aDoc, nsINode* aParent, nsTArray<nsCOMPtr<nsIContent>>& aNodes) {
1111 uint32_t count = aNodes.Length();
1112 for (uint32_t i = 0; i < count; ++i) {
1113 nsIContent* childContent = aNodes[i];
1114
1115 if (nsContentUtils::HasMutationListeners(
1116 childContent, NS_EVENT_BITS_MUTATION_NODEINSERTED, aParent)) {
1117 InternalMutationEvent mutation(true, eLegacyNodeInserted);
1118 mutation.mRelatedNode = aParent;
1119
1120 mozAutoSubtreeModified subtree(aDoc, aParent);
1121 (new AsyncEventDispatcher(childContent, mutation))->RunDOMEventWhenSafe();
1122 }
1123 }
1124 }
1125
1126 //----------------------------------------------------------------------
1127
1128 // nsISupports implementation
1129
1130 #define SUBTREE_UNBINDINGS_PER_RUNNABLE 500
1131
1132 class ContentUnbinder : public Runnable {
1133 public:
ContentUnbinder()1134 ContentUnbinder() : Runnable("ContentUnbinder") { mLast = this; }
1135
~ContentUnbinder()1136 ~ContentUnbinder() { Run(); }
1137
UnbindSubtree(nsIContent * aNode)1138 void UnbindSubtree(nsIContent* aNode) {
1139 if (aNode->NodeType() != nsINode::ELEMENT_NODE &&
1140 aNode->NodeType() != nsINode::DOCUMENT_FRAGMENT_NODE) {
1141 return;
1142 }
1143 FragmentOrElement* container = static_cast<FragmentOrElement*>(aNode);
1144 if (container->HasChildren()) {
1145 // Invalidate cached array of child nodes
1146 container->InvalidateChildNodes();
1147
1148 while (container->HasChildren()) {
1149 // Hold a strong ref to the node when we remove it, because we may be
1150 // the last reference to it. We need to call DisconnectChild()
1151 // before calling UnbindFromTree, since this last can notify various
1152 // observers and they should really see consistent
1153 // tree state.
1154 // If this code changes, change the corresponding code in
1155 // FragmentOrElement's and Document's unlink impls.
1156 nsCOMPtr<nsIContent> child = container->GetLastChild();
1157 container->DisconnectChild(child);
1158 UnbindSubtree(child);
1159 child->UnbindFromTree();
1160 }
1161 }
1162 }
1163
Run()1164 NS_IMETHOD Run() override {
1165 nsAutoScriptBlocker scriptBlocker;
1166 uint32_t len = mSubtreeRoots.Length();
1167 if (len) {
1168 for (uint32_t i = 0; i < len; ++i) {
1169 UnbindSubtree(mSubtreeRoots[i]);
1170 }
1171 mSubtreeRoots.Clear();
1172 }
1173 nsCycleCollector_dispatchDeferredDeletion();
1174 if (this == sContentUnbinder) {
1175 sContentUnbinder = nullptr;
1176 if (mNext) {
1177 RefPtr<ContentUnbinder> next;
1178 next.swap(mNext);
1179 sContentUnbinder = next;
1180 next->mLast = mLast;
1181 mLast = nullptr;
1182 NS_DispatchToCurrentThreadQueue(next.forget(),
1183 EventQueuePriority::Idle);
1184 }
1185 }
1186 return NS_OK;
1187 }
1188
UnbindAll()1189 static void UnbindAll() {
1190 RefPtr<ContentUnbinder> ub = sContentUnbinder;
1191 sContentUnbinder = nullptr;
1192 while (ub) {
1193 ub->Run();
1194 ub = ub->mNext;
1195 }
1196 }
1197
Append(nsIContent * aSubtreeRoot)1198 static void Append(nsIContent* aSubtreeRoot) {
1199 if (!sContentUnbinder) {
1200 sContentUnbinder = new ContentUnbinder();
1201 nsCOMPtr<nsIRunnable> e = sContentUnbinder;
1202 NS_DispatchToCurrentThreadQueue(e.forget(), EventQueuePriority::Idle);
1203 }
1204
1205 if (sContentUnbinder->mLast->mSubtreeRoots.Length() >=
1206 SUBTREE_UNBINDINGS_PER_RUNNABLE) {
1207 sContentUnbinder->mLast->mNext = new ContentUnbinder();
1208 sContentUnbinder->mLast = sContentUnbinder->mLast->mNext;
1209 }
1210 sContentUnbinder->mLast->mSubtreeRoots.AppendElement(aSubtreeRoot);
1211 }
1212
1213 private:
1214 AutoTArray<nsCOMPtr<nsIContent>, SUBTREE_UNBINDINGS_PER_RUNNABLE>
1215 mSubtreeRoots;
1216 RefPtr<ContentUnbinder> mNext;
1217 ContentUnbinder* mLast;
1218 static ContentUnbinder* sContentUnbinder;
1219 };
1220
1221 ContentUnbinder* ContentUnbinder::sContentUnbinder = nullptr;
1222
ClearContentUnbinder()1223 void FragmentOrElement::ClearContentUnbinder() { ContentUnbinder::UnbindAll(); }
1224
1225 NS_IMPL_CYCLE_COLLECTION_CLASS(FragmentOrElement)
1226
1227 // We purposefully don't UNLINK_BEGIN_INHERITED here.
1228 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(FragmentOrElement)
1229 nsIContent::Unlink(tmp);
1230
1231 // The XBL binding is removed by RemoveFromBindingManagerRunnable
1232 // which is dispatched in UnbindFromTree.
1233
1234 if (tmp->HasProperties()) {
1235 if (tmp->IsElement()) {
1236 Element* elem = tmp->AsElement();
1237 elem->UnlinkIntersectionObservers();
1238 }
1239
1240 if (tmp->IsHTMLElement() || tmp->IsSVGElement()) {
1241 nsStaticAtom* const* props =
1242 Element::HTMLSVGPropertiesToTraverseAndUnlink();
1243 for (uint32_t i = 0; props[i]; ++i) {
1244 tmp->RemoveProperty(props[i]);
1245 }
1246 }
1247
1248 if (tmp->MayHaveAnimations()) {
1249 nsAtom** effectProps = EffectSet::GetEffectSetPropertyAtoms();
1250 for (uint32_t i = 0; effectProps[i]; ++i) {
1251 tmp->RemoveProperty(effectProps[i]);
1252 }
1253 }
1254 }
1255
1256 // Unlink child content (and unbind our subtree).
1257 if (tmp->UnoptimizableCCNode() || !nsCCUncollectableMarker::sGeneration) {
1258 // Don't allow script to run while we're unbinding everything.
1259 nsAutoScriptBlocker scriptBlocker;
1260 while (tmp->HasChildren()) {
1261 // Hold a strong ref to the node when we remove it, because we may be
1262 // the last reference to it.
1263 // If this code changes, change the corresponding code in Document's
1264 // unlink impl and ContentUnbinder::UnbindSubtree.
1265 nsCOMPtr<nsIContent> child = tmp->GetLastChild();
1266 tmp->DisconnectChild(child);
1267 child->UnbindFromTree();
1268 }
1269 } else if (!tmp->GetParent() && tmp->HasChildren()) {
1270 ContentUnbinder::Append(tmp);
1271 } /* else {
1272 The subtree root will end up to a ContentUnbinder, and that will
1273 unbind the child nodes.
1274 } */
1275
1276 if (ShadowRoot* shadowRoot = tmp->GetShadowRoot()) {
1277 shadowRoot->Unbind();
1278 tmp->ExtendedDOMSlots()->mShadowRoot = nullptr;
1279 }
1280
1281 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
1282
NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(FragmentOrElement)1283 NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(FragmentOrElement)
1284
1285 void FragmentOrElement::MarkNodeChildren(nsINode* aNode) {
1286 JSObject* o = GetJSObjectChild(aNode);
1287 if (o) {
1288 JS::ExposeObjectToActiveJS(o);
1289 }
1290
1291 EventListenerManager* elm = aNode->GetExistingListenerManager();
1292 if (elm) {
1293 elm->MarkForCC();
1294 }
1295 }
1296
FindOptimizableSubtreeRoot(nsINode * aNode)1297 nsINode* FindOptimizableSubtreeRoot(nsINode* aNode) {
1298 nsINode* p;
1299 while ((p = aNode->GetParentNode())) {
1300 if (aNode->UnoptimizableCCNode()) {
1301 return nullptr;
1302 }
1303 aNode = p;
1304 }
1305
1306 if (aNode->UnoptimizableCCNode()) {
1307 return nullptr;
1308 }
1309 return aNode;
1310 }
1311
1312 StaticAutoPtr<nsTHashSet<nsINode*>> gCCBlackMarkedNodes;
1313
ClearBlackMarkedNodes()1314 static void ClearBlackMarkedNodes() {
1315 if (!gCCBlackMarkedNodes) {
1316 return;
1317 }
1318 for (nsINode* n : *gCCBlackMarkedNodes) {
1319 n->SetCCMarkedRoot(false);
1320 n->SetInCCBlackTree(false);
1321 }
1322 gCCBlackMarkedNodes = nullptr;
1323 }
1324
1325 // static
RemoveBlackMarkedNode(nsINode * aNode)1326 void FragmentOrElement::RemoveBlackMarkedNode(nsINode* aNode) {
1327 if (!gCCBlackMarkedNodes) {
1328 return;
1329 }
1330 gCCBlackMarkedNodes->Remove(aNode);
1331 }
1332
IsCertainlyAliveNode(nsINode * aNode,Document * aDoc)1333 static bool IsCertainlyAliveNode(nsINode* aNode, Document* aDoc) {
1334 MOZ_ASSERT(aNode->GetComposedDoc() == aDoc);
1335
1336 // Marked to be in-CC-generation or if the document is an svg image that's
1337 // being kept alive by the image cache. (Note that an svg image's internal
1338 // SVG document will receive an OnPageHide() call when it gets purged from
1339 // the image cache; hence, we use IsVisible() as a hint that the document is
1340 // actively being kept alive by the cache.)
1341 return nsCCUncollectableMarker::InGeneration(aDoc->GetMarkedCCGeneration()) ||
1342 (nsCCUncollectableMarker::sGeneration && aDoc->IsBeingUsedAsImage() &&
1343 aDoc->IsVisible());
1344 }
1345
1346 // static
CanSkipInCC(nsINode * aNode)1347 bool FragmentOrElement::CanSkipInCC(nsINode* aNode) {
1348 // Don't try to optimize anything during shutdown.
1349 if (nsCCUncollectableMarker::sGeneration == 0) {
1350 return false;
1351 }
1352
1353 Document* currentDoc = aNode->GetComposedDoc();
1354 if (currentDoc && IsCertainlyAliveNode(aNode, currentDoc)) {
1355 return !NeedsScriptTraverse(aNode);
1356 }
1357
1358 // Bail out early if aNode is somewhere in anonymous content,
1359 // or otherwise unusual.
1360 if (aNode->UnoptimizableCCNode()) {
1361 return false;
1362 }
1363
1364 nsINode* root = currentDoc ? static_cast<nsINode*>(currentDoc)
1365 : FindOptimizableSubtreeRoot(aNode);
1366 if (!root) {
1367 return false;
1368 }
1369
1370 // Subtree has been traversed already.
1371 if (root->CCMarkedRoot()) {
1372 return root->InCCBlackTree() && !NeedsScriptTraverse(aNode);
1373 }
1374
1375 if (!gCCBlackMarkedNodes) {
1376 gCCBlackMarkedNodes = new nsTHashSet<nsINode*>(1020);
1377 }
1378
1379 // nodesToUnpurple contains nodes which will be removed
1380 // from the purple buffer if the DOM tree is known-live.
1381 AutoTArray<nsIContent*, 1020> nodesToUnpurple;
1382 // grayNodes need script traverse, so they aren't removed from
1383 // the purple buffer, but are marked to be in known-live subtree so that
1384 // traverse is faster.
1385 AutoTArray<nsINode*, 1020> grayNodes;
1386
1387 bool foundLiveWrapper = root->HasKnownLiveWrapper();
1388 if (root != currentDoc) {
1389 currentDoc = nullptr;
1390 if (NeedsScriptTraverse(root)) {
1391 grayNodes.AppendElement(root);
1392 } else if (static_cast<nsIContent*>(root)->IsPurple()) {
1393 nodesToUnpurple.AppendElement(static_cast<nsIContent*>(root));
1394 }
1395 }
1396
1397 // Traverse the subtree and check if we could know without CC
1398 // that it is known-live.
1399 // Note, this traverse is non-virtual and inline, so it should be a lot faster
1400 // than CC's generic traverse.
1401 for (nsIContent* node = root->GetFirstChild(); node;
1402 node = node->GetNextNode(root)) {
1403 foundLiveWrapper = foundLiveWrapper || node->HasKnownLiveWrapper();
1404 if (foundLiveWrapper && currentDoc) {
1405 // If we can mark the whole document known-live, no need to optimize
1406 // so much, since when the next purple node in the document will be
1407 // handled, it is fast to check that currentDoc is in CCGeneration.
1408 break;
1409 }
1410 if (NeedsScriptTraverse(node)) {
1411 // Gray nodes need real CC traverse.
1412 grayNodes.AppendElement(node);
1413 } else if (node->IsPurple()) {
1414 nodesToUnpurple.AppendElement(node);
1415 }
1416 }
1417
1418 root->SetCCMarkedRoot(true);
1419 root->SetInCCBlackTree(foundLiveWrapper);
1420 gCCBlackMarkedNodes->Insert(root);
1421
1422 if (!foundLiveWrapper) {
1423 return false;
1424 }
1425
1426 if (currentDoc) {
1427 // Special case documents. If we know the document is known-live,
1428 // we can mark the document to be in CCGeneration.
1429 currentDoc->MarkUncollectableForCCGeneration(
1430 nsCCUncollectableMarker::sGeneration);
1431 } else {
1432 for (uint32_t i = 0; i < grayNodes.Length(); ++i) {
1433 nsINode* node = grayNodes[i];
1434 node->SetInCCBlackTree(true);
1435 gCCBlackMarkedNodes->Insert(node);
1436 }
1437 }
1438
1439 // Subtree is known-live, we can remove non-gray purple nodes from
1440 // purple buffer.
1441 for (uint32_t i = 0; i < nodesToUnpurple.Length(); ++i) {
1442 nsIContent* purple = nodesToUnpurple[i];
1443 // Can't remove currently handled purple node.
1444 if (purple != aNode) {
1445 purple->RemovePurple();
1446 }
1447 }
1448 return !NeedsScriptTraverse(aNode);
1449 }
1450
1451 AutoTArray<nsINode*, 1020>* gPurpleRoots = nullptr;
1452 AutoTArray<nsIContent*, 1020>* gNodesToUnbind = nullptr;
1453
ClearCycleCollectorCleanupData()1454 void ClearCycleCollectorCleanupData() {
1455 if (gPurpleRoots) {
1456 uint32_t len = gPurpleRoots->Length();
1457 for (uint32_t i = 0; i < len; ++i) {
1458 nsINode* n = gPurpleRoots->ElementAt(i);
1459 n->SetIsPurpleRoot(false);
1460 }
1461 delete gPurpleRoots;
1462 gPurpleRoots = nullptr;
1463 }
1464 if (gNodesToUnbind) {
1465 uint32_t len = gNodesToUnbind->Length();
1466 for (uint32_t i = 0; i < len; ++i) {
1467 nsIContent* c = gNodesToUnbind->ElementAt(i);
1468 c->SetIsPurpleRoot(false);
1469 ContentUnbinder::Append(c);
1470 }
1471 delete gNodesToUnbind;
1472 gNodesToUnbind = nullptr;
1473 }
1474 }
1475
ShouldClearPurple(nsIContent * aContent)1476 static bool ShouldClearPurple(nsIContent* aContent) {
1477 MOZ_ASSERT(aContent);
1478 if (aContent->IsPurple()) {
1479 return true;
1480 }
1481
1482 JSObject* o = GetJSObjectChild(aContent);
1483 if (o && JS::ObjectIsMarkedGray(o)) {
1484 return true;
1485 }
1486
1487 if (aContent->HasListenerManager()) {
1488 return true;
1489 }
1490
1491 return aContent->HasProperties();
1492 }
1493
1494 // If aNode is not optimizable, but is an element
1495 // with a frame in a document which has currently active presshell,
1496 // we can act as if it was optimizable. When the primary frame dies, aNode
1497 // will end up to the purple buffer because of the refcount change.
NodeHasActiveFrame(Document * aCurrentDoc,nsINode * aNode)1498 bool NodeHasActiveFrame(Document* aCurrentDoc, nsINode* aNode) {
1499 return aCurrentDoc->GetPresShell() && aNode->IsElement() &&
1500 aNode->AsElement()->GetPrimaryFrame();
1501 }
1502
1503 // CanSkip checks if aNode is known-live, and if it is, returns true. If aNode
1504 // is in a known-live DOM tree, CanSkip may also remove other objects from
1505 // purple buffer and unmark event listeners and user data. If the root of the
1506 // DOM tree is a document, less optimizations are done since checking the
1507 // liveness of the current document is usually fast and we don't want slow down
1508 // such common cases.
CanSkip(nsINode * aNode,bool aRemovingAllowed)1509 bool FragmentOrElement::CanSkip(nsINode* aNode, bool aRemovingAllowed) {
1510 // Don't try to optimize anything during shutdown.
1511 if (nsCCUncollectableMarker::sGeneration == 0) {
1512 return false;
1513 }
1514
1515 bool unoptimizable = aNode->UnoptimizableCCNode();
1516 Document* currentDoc = aNode->GetComposedDoc();
1517 if (currentDoc && IsCertainlyAliveNode(aNode, currentDoc) &&
1518 (!unoptimizable || NodeHasActiveFrame(currentDoc, aNode))) {
1519 MarkNodeChildren(aNode);
1520 return true;
1521 }
1522
1523 if (unoptimizable) {
1524 return false;
1525 }
1526
1527 nsINode* root = currentDoc ? static_cast<nsINode*>(currentDoc)
1528 : FindOptimizableSubtreeRoot(aNode);
1529 if (!root) {
1530 return false;
1531 }
1532
1533 // Subtree has been traversed already, and aNode has
1534 // been handled in a way that doesn't require revisiting it.
1535 if (root->IsPurpleRoot()) {
1536 return false;
1537 }
1538
1539 // nodesToClear contains nodes which are either purple or
1540 // gray.
1541 AutoTArray<nsIContent*, 1020> nodesToClear;
1542
1543 bool foundLiveWrapper = root->HasKnownLiveWrapper();
1544 bool domOnlyCycle = false;
1545 if (root != currentDoc) {
1546 currentDoc = nullptr;
1547 if (!foundLiveWrapper) {
1548 domOnlyCycle = static_cast<nsIContent*>(root)->OwnedOnlyByTheDOMTree();
1549 }
1550 if (ShouldClearPurple(static_cast<nsIContent*>(root))) {
1551 nodesToClear.AppendElement(static_cast<nsIContent*>(root));
1552 }
1553 }
1554
1555 // Traverse the subtree and check if we could know without CC
1556 // that it is known-live.
1557 // Note, this traverse is non-virtual and inline, so it should be a lot faster
1558 // than CC's generic traverse.
1559 for (nsIContent* node = root->GetFirstChild(); node;
1560 node = node->GetNextNode(root)) {
1561 foundLiveWrapper = foundLiveWrapper || node->HasKnownLiveWrapper();
1562 if (foundLiveWrapper) {
1563 domOnlyCycle = false;
1564 if (currentDoc) {
1565 // If we can mark the whole document live, no need to optimize
1566 // so much, since when the next purple node in the document will be
1567 // handled, it is fast to check that the currentDoc is in CCGeneration.
1568 break;
1569 }
1570 // No need to put stuff to the nodesToClear array, if we can clear it
1571 // already here.
1572 if (node->IsPurple() && (node != aNode || aRemovingAllowed)) {
1573 node->RemovePurple();
1574 }
1575 MarkNodeChildren(node);
1576 } else {
1577 domOnlyCycle = domOnlyCycle && node->OwnedOnlyByTheDOMTree();
1578 if (ShouldClearPurple(node)) {
1579 // Collect interesting nodes which we can clear if we find that
1580 // they are kept alive in a known-live tree or are in a DOM-only cycle.
1581 nodesToClear.AppendElement(node);
1582 }
1583 }
1584 }
1585
1586 if (!currentDoc || !foundLiveWrapper) {
1587 root->SetIsPurpleRoot(true);
1588 if (domOnlyCycle) {
1589 if (!gNodesToUnbind) {
1590 gNodesToUnbind = new AutoTArray<nsIContent*, 1020>();
1591 }
1592 gNodesToUnbind->AppendElement(static_cast<nsIContent*>(root));
1593 for (uint32_t i = 0; i < nodesToClear.Length(); ++i) {
1594 nsIContent* n = nodesToClear[i];
1595 if ((n != aNode || aRemovingAllowed) && n->IsPurple()) {
1596 n->RemovePurple();
1597 }
1598 }
1599 return true;
1600 } else {
1601 if (!gPurpleRoots) {
1602 gPurpleRoots = new AutoTArray<nsINode*, 1020>();
1603 }
1604 gPurpleRoots->AppendElement(root);
1605 }
1606 }
1607
1608 if (!foundLiveWrapper) {
1609 return false;
1610 }
1611
1612 if (currentDoc) {
1613 // Special case documents. If we know the document is known-live,
1614 // we can mark the document to be in CCGeneration.
1615 currentDoc->MarkUncollectableForCCGeneration(
1616 nsCCUncollectableMarker::sGeneration);
1617 MarkNodeChildren(currentDoc);
1618 }
1619
1620 // Subtree is known-live, so we can remove purple nodes from
1621 // purple buffer and mark stuff that to be certainly alive.
1622 for (uint32_t i = 0; i < nodesToClear.Length(); ++i) {
1623 nsIContent* n = nodesToClear[i];
1624 MarkNodeChildren(n);
1625 // Can't remove currently handled purple node,
1626 // unless aRemovingAllowed is true.
1627 if ((n != aNode || aRemovingAllowed) && n->IsPurple()) {
1628 n->RemovePurple();
1629 }
1630 }
1631 return true;
1632 }
1633
CanSkipThis(nsINode * aNode)1634 bool FragmentOrElement::CanSkipThis(nsINode* aNode) {
1635 if (nsCCUncollectableMarker::sGeneration == 0) {
1636 return false;
1637 }
1638 if (aNode->HasKnownLiveWrapper()) {
1639 return true;
1640 }
1641 Document* c = aNode->GetComposedDoc();
1642 return ((c && IsCertainlyAliveNode(aNode, c)) || aNode->InCCBlackTree()) &&
1643 !NeedsScriptTraverse(aNode);
1644 }
1645
InitCCCallbacks()1646 void FragmentOrElement::InitCCCallbacks() {
1647 nsCycleCollector_setForgetSkippableCallback(ClearCycleCollectorCleanupData);
1648 nsCycleCollector_setBeforeUnlinkCallback(ClearBlackMarkedNodes);
1649 }
1650
1651 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(FragmentOrElement)
1652 return FragmentOrElement::CanSkip(tmp, aRemovingAllowed);
1653 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
1654
1655 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(FragmentOrElement)
1656 return FragmentOrElement::CanSkipInCC(tmp);
1657 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
1658
1659 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(FragmentOrElement)
1660 return FragmentOrElement::CanSkipThis(tmp);
1661 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
1662
1663 // We purposefully don't TRAVERSE_BEGIN_INHERITED here. All the bits
1664 // we should traverse should be added here or in nsINode::Traverse.
1665 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(FragmentOrElement)
1666 if (MOZ_UNLIKELY(cb.WantDebugInfo())) {
1667 char name[512];
1668 uint32_t nsid = tmp->GetNameSpaceID();
1669 nsAtomCString localName(tmp->NodeInfo()->NameAtom());
1670 nsAutoCString uri;
1671 if (tmp->OwnerDoc()->GetDocumentURI()) {
1672 uri = tmp->OwnerDoc()->GetDocumentURI()->GetSpecOrDefault();
1673 }
1674
1675 nsAutoString id;
1676 nsAtom* idAtom = tmp->GetID();
1677 if (idAtom) {
1678 id.AppendLiteral(" id='");
1679 id.Append(nsDependentAtomString(idAtom));
1680 id.Append('\'');
1681 }
1682
1683 nsAutoString classes;
1684 const nsAttrValue* classAttrValue =
1685 tmp->IsElement() ? tmp->AsElement()->GetClasses() : nullptr;
1686 if (classAttrValue) {
1687 classes.AppendLiteral(" class='");
1688 nsAutoString classString;
1689 classAttrValue->ToString(classString);
1690 classString.ReplaceChar(char16_t('\n'), char16_t(' '));
1691 classes.Append(classString);
1692 classes.Append('\'');
1693 }
1694
1695 nsAutoCString orphan;
1696 if (!tmp->IsInComposedDoc()) {
1697 orphan.AppendLiteral(" (orphan)");
1698 }
1699
1700 const char* nsuri = nsNameSpaceManager::GetNameSpaceDisplayName(nsid);
1701 SprintfLiteral(name, "FragmentOrElement %s %s%s%s%s %s", nsuri,
1702 localName.get(), NS_ConvertUTF16toUTF8(id).get(),
1703 NS_ConvertUTF16toUTF8(classes).get(), orphan.get(),
1704 uri.get());
1705 cb.DescribeRefCountedNode(tmp->mRefCnt.get(), name);
1706 } else {
1707 NS_IMPL_CYCLE_COLLECTION_DESCRIBE(FragmentOrElement, tmp->mRefCnt.get())
1708 }
1709
1710 if (!nsIContent::Traverse(tmp, cb)) {
1711 return NS_SUCCESS_INTERRUPTED_TRAVERSE;
1712 }
1713
1714 // Check that whenever we have effect properties, MayHaveAnimations is set.
1715 #ifdef DEBUG
1716 nsAtom** effectProps = EffectSet::GetEffectSetPropertyAtoms();
1717 for (uint32_t i = 0; effectProps[i]; ++i) {
1718 MOZ_ASSERT_IF(tmp->GetProperty(effectProps[i]), tmp->MayHaveAnimations());
1719 }
1720 #endif
1721
1722 if (tmp->HasProperties()) {
1723 if (tmp->IsElement()) {
1724 Element* elem = tmp->AsElement();
1725 IntersectionObserverList* observers =
1726 static_cast<IntersectionObserverList*>(
1727 elem->GetProperty(nsGkAtoms::intersectionobserverlist));
1728 if (observers) {
1729 for (DOMIntersectionObserver* observer : observers->Keys()) {
1730 cb.NoteXPCOMChild(observer);
1731 }
1732 }
1733 }
1734 if (tmp->IsHTMLElement() || tmp->IsSVGElement()) {
1735 nsStaticAtom* const* props =
1736 Element::HTMLSVGPropertiesToTraverseAndUnlink();
1737 for (uint32_t i = 0; props[i]; ++i) {
1738 nsISupports* property =
1739 static_cast<nsISupports*>(tmp->GetProperty(props[i]));
1740 cb.NoteXPCOMChild(property);
1741 }
1742 }
1743 if (tmp->MayHaveAnimations()) {
1744 nsAtom** effectProps = EffectSet::GetEffectSetPropertyAtoms();
1745 for (uint32_t i = 0; effectProps[i]; ++i) {
1746 EffectSet* effectSet =
1747 static_cast<EffectSet*>(tmp->GetProperty(effectProps[i]));
1748 if (effectSet) {
1749 effectSet->Traverse(cb);
1750 }
1751 }
1752 }
1753 }
1754
1755 // Traverse attribute names.
1756 if (tmp->IsElement()) {
1757 Element* element = tmp->AsElement();
1758 uint32_t i;
1759 uint32_t attrs = element->GetAttrCount();
1760 for (i = 0; i < attrs; i++) {
1761 const nsAttrName* name = element->GetUnsafeAttrNameAt(i);
1762 if (!name->IsAtom()) {
1763 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mAttrs[i]->NodeInfo()");
1764 cb.NoteNativeChild(name->NodeInfo(),
1765 NS_CYCLE_COLLECTION_PARTICIPANT(NodeInfo));
1766 }
1767 }
1768 }
1769 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
1770
NS_INTERFACE_MAP_BEGIN(FragmentOrElement)1771 NS_INTERFACE_MAP_BEGIN(FragmentOrElement)
1772 NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(FragmentOrElement)
1773 NS_INTERFACE_MAP_END_INHERITING(nsIContent)
1774
1775 //----------------------------------------------------------------------
1776
1777 const nsTextFragment* FragmentOrElement::GetText() { return nullptr; }
1778
TextLength() const1779 uint32_t FragmentOrElement::TextLength() const {
1780 // We can remove this assertion if it turns out to be useful to be able
1781 // to depend on this returning 0
1782 MOZ_ASSERT_UNREACHABLE("called FragmentOrElement::TextLength");
1783
1784 return 0;
1785 }
1786
TextIsOnlyWhitespace()1787 bool FragmentOrElement::TextIsOnlyWhitespace() { return false; }
1788
ThreadSafeTextIsOnlyWhitespace() const1789 bool FragmentOrElement::ThreadSafeTextIsOnlyWhitespace() const { return false; }
1790
IsVoidTag(nsAtom * aTag)1791 static inline bool IsVoidTag(nsAtom* aTag) {
1792 static const nsAtom* voidElements[] = {
1793 nsGkAtoms::area, nsGkAtoms::base, nsGkAtoms::basefont,
1794 nsGkAtoms::bgsound, nsGkAtoms::br, nsGkAtoms::col,
1795 nsGkAtoms::embed, nsGkAtoms::frame, nsGkAtoms::hr,
1796 nsGkAtoms::img, nsGkAtoms::input, nsGkAtoms::keygen,
1797 nsGkAtoms::link, nsGkAtoms::meta, nsGkAtoms::param,
1798 nsGkAtoms::source, nsGkAtoms::track, nsGkAtoms::wbr};
1799
1800 static mozilla::BitBloomFilter<12, nsAtom> sFilter;
1801 static bool sInitialized = false;
1802 if (!sInitialized) {
1803 sInitialized = true;
1804 for (uint32_t i = 0; i < ArrayLength(voidElements); ++i) {
1805 sFilter.add(voidElements[i]);
1806 }
1807 }
1808
1809 if (sFilter.mightContain(aTag)) {
1810 for (uint32_t i = 0; i < ArrayLength(voidElements); ++i) {
1811 if (aTag == voidElements[i]) {
1812 return true;
1813 }
1814 }
1815 }
1816 return false;
1817 }
1818
1819 /* static */
IsHTMLVoid(nsAtom * aLocalName)1820 bool FragmentOrElement::IsHTMLVoid(nsAtom* aLocalName) {
1821 return aLocalName && IsVoidTag(aLocalName);
1822 }
1823
GetMarkup(bool aIncludeSelf,nsAString & aMarkup)1824 void FragmentOrElement::GetMarkup(bool aIncludeSelf, nsAString& aMarkup) {
1825 aMarkup.Truncate();
1826
1827 Document* doc = OwnerDoc();
1828 if (IsInHTMLDocument()) {
1829 nsContentUtils::SerializeNodeToMarkup(this, !aIncludeSelf, aMarkup);
1830 return;
1831 }
1832
1833 nsAutoString contentType;
1834 doc->GetContentType(contentType);
1835 bool tryToCacheEncoder = !aIncludeSelf;
1836
1837 nsCOMPtr<nsIDocumentEncoder> docEncoder = doc->GetCachedEncoder();
1838 if (!docEncoder) {
1839 docEncoder = do_createDocumentEncoder(
1840 PromiseFlatCString(NS_ConvertUTF16toUTF8(contentType)).get());
1841 }
1842 if (!docEncoder) {
1843 // This could be some type for which we create a synthetic document. Try
1844 // again as XML
1845 contentType.AssignLiteral("application/xml");
1846 docEncoder = do_createDocumentEncoder("application/xml");
1847 // Don't try to cache the encoder since it would point to a different
1848 // contentType once it has been reinitialized.
1849 tryToCacheEncoder = false;
1850 }
1851
1852 NS_ENSURE_TRUE_VOID(docEncoder);
1853
1854 uint32_t flags = nsIDocumentEncoder::OutputEncodeBasicEntities |
1855 // Output DOM-standard newlines
1856 nsIDocumentEncoder::OutputLFLineBreak |
1857 // Don't do linebreaking that's not present in
1858 // the source
1859 nsIDocumentEncoder::OutputRaw |
1860 // Only check for mozdirty when necessary (bug 599983)
1861 nsIDocumentEncoder::OutputIgnoreMozDirty;
1862
1863 if (IsEditable()) {
1864 nsCOMPtr<Element> elem = do_QueryInterface(this);
1865 TextEditor* textEditor = elem ? elem->GetTextEditorInternal() : nullptr;
1866 if (textEditor && textEditor->OutputsMozDirty()) {
1867 flags &= ~nsIDocumentEncoder::OutputIgnoreMozDirty;
1868 }
1869 }
1870
1871 DebugOnly<nsresult> rv = docEncoder->NativeInit(doc, contentType, flags);
1872 MOZ_ASSERT(NS_SUCCEEDED(rv));
1873
1874 if (aIncludeSelf) {
1875 docEncoder->SetNode(this);
1876 } else {
1877 docEncoder->SetContainerNode(this);
1878 }
1879 rv = docEncoder->EncodeToString(aMarkup);
1880 MOZ_ASSERT(NS_SUCCEEDED(rv));
1881 if (tryToCacheEncoder) {
1882 doc->SetCachedEncoder(docEncoder.forget());
1883 }
1884 }
1885
ContainsMarkup(const nsAString & aStr)1886 static bool ContainsMarkup(const nsAString& aStr) {
1887 // Note: we can't use FindCharInSet because null is one of the characters we
1888 // want to search for.
1889 const char16_t* start = aStr.BeginReading();
1890 const char16_t* end = aStr.EndReading();
1891
1892 while (start != end) {
1893 char16_t c = *start;
1894 if (c == char16_t('<') || c == char16_t('&') || c == char16_t('\r') ||
1895 c == char16_t('\0')) {
1896 return true;
1897 }
1898 ++start;
1899 }
1900
1901 return false;
1902 }
1903
SetInnerHTMLInternal(const nsAString & aInnerHTML,ErrorResult & aError)1904 void FragmentOrElement::SetInnerHTMLInternal(const nsAString& aInnerHTML,
1905 ErrorResult& aError) {
1906 FragmentOrElement* target = this;
1907 // Handle template case.
1908 if (target->IsTemplateElement()) {
1909 DocumentFragment* frag =
1910 static_cast<HTMLTemplateElement*>(target)->Content();
1911 MOZ_ASSERT(frag);
1912 target = frag;
1913 }
1914
1915 // Fast-path for strings with no markup. Limit this to short strings, to
1916 // avoid ContainsMarkup taking too long. The choice for 100 is based on
1917 // gut feeling.
1918 //
1919 // Don't do this for elements with a weird parser insertion mode, for
1920 // instance setting innerHTML = "" on a <html> element should add the
1921 // optional <head> and <body> elements.
1922 if (!target->HasWeirdParserInsertionMode() && aInnerHTML.Length() < 100 &&
1923 !ContainsMarkup(aInnerHTML)) {
1924 aError = nsContentUtils::SetNodeTextContent(target, aInnerHTML, false);
1925 return;
1926 }
1927
1928 Document* doc = target->OwnerDoc();
1929
1930 // Batch possible DOMSubtreeModified events.
1931 mozAutoSubtreeModified subtree(doc, nullptr);
1932
1933 target->FireNodeRemovedForChildren();
1934
1935 // Needed when innerHTML is used in combination with contenteditable
1936 mozAutoDocUpdate updateBatch(doc, true);
1937
1938 // Remove childnodes.
1939 nsAutoMutationBatch mb(target, true, false);
1940 while (target->HasChildren()) {
1941 target->RemoveChildNode(target->GetFirstChild(), true);
1942 }
1943 mb.RemovalDone();
1944
1945 nsAutoScriptLoaderDisabler sld(doc);
1946
1947 FragmentOrElement* parseContext = this;
1948 if (ShadowRoot* shadowRoot = ShadowRoot::FromNode(this)) {
1949 // Fix up the context to be the host of the ShadowRoot. See
1950 // https://w3c.github.io/DOM-Parsing/#dom-innerhtml-innerhtml setter step 1.
1951 parseContext = shadowRoot->GetHost();
1952 }
1953
1954 if (doc->IsHTMLDocument()) {
1955 nsAtom* contextLocalName = parseContext->NodeInfo()->NameAtom();
1956 int32_t contextNameSpaceID = parseContext->GetNameSpaceID();
1957
1958 int32_t oldChildCount = target->GetChildCount();
1959 aError = nsContentUtils::ParseFragmentHTML(
1960 aInnerHTML, target, contextLocalName, contextNameSpaceID,
1961 doc->GetCompatibilityMode() == eCompatibility_NavQuirks, true);
1962 mb.NodesAdded();
1963 // HTML5 parser has notified, but not fired mutation events.
1964 nsContentUtils::FireMutationEventsForDirectParsing(doc, target,
1965 oldChildCount);
1966 } else {
1967 RefPtr<DocumentFragment> df = nsContentUtils::CreateContextualFragment(
1968 parseContext, aInnerHTML, true, aError);
1969 if (!aError.Failed()) {
1970 // Suppress assertion about node removal mutation events that can't have
1971 // listeners anyway, because no one has had the chance to register
1972 // mutation listeners on the fragment that comes from the parser.
1973 nsAutoScriptBlockerSuppressNodeRemoved scriptBlocker;
1974
1975 target->AppendChild(*df, aError);
1976 mb.NodesAdded();
1977 }
1978 }
1979 }
1980
FireNodeRemovedForChildren()1981 void FragmentOrElement::FireNodeRemovedForChildren() {
1982 Document* doc = OwnerDoc();
1983 // Optimize the common case
1984 if (!nsContentUtils::HasMutationListeners(
1985 doc, NS_EVENT_BITS_MUTATION_NODEREMOVED)) {
1986 return;
1987 }
1988
1989 nsCOMPtr<nsINode> child;
1990 for (child = GetFirstChild(); child && child->GetParentNode() == this;
1991 child = child->GetNextSibling()) {
1992 nsContentUtils::MaybeFireNodeRemoved(child, this);
1993 }
1994 }
1995
AddSizeOfExcludingThis(nsWindowSizes & aSizes,size_t * aNodeSize) const1996 void FragmentOrElement::AddSizeOfExcludingThis(nsWindowSizes& aSizes,
1997 size_t* aNodeSize) const {
1998 nsIContent::AddSizeOfExcludingThis(aSizes, aNodeSize);
1999
2000 nsDOMSlots* slots = GetExistingDOMSlots();
2001 if (slots) {
2002 *aNodeSize += slots->SizeOfIncludingThis(aSizes.mState.mMallocSizeOf);
2003 }
2004 }
2005