/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "DocumentOrShadowRoot.h" #include "mozilla/AnimationComparator.h" #include "mozilla/EventStateManager.h" #include "mozilla/PresShell.h" #include "mozilla/dom/AnimatableBinding.h" #include "mozilla/dom/Document.h" #include "mozilla/dom/HTMLInputElement.h" #include "mozilla/dom/ShadowRoot.h" #include "mozilla/dom/StyleSheetList.h" #include "nsTHashtable.h" #include "nsFocusManager.h" #include "nsIRadioVisitor.h" #include "nsIFormControl.h" #include "nsLayoutUtils.h" #include "nsSVGUtils.h" #include "nsWindowSizes.h" namespace mozilla { namespace dom { DocumentOrShadowRoot::DocumentOrShadowRoot(ShadowRoot* aShadowRoot) : mAsNode(aShadowRoot), mKind(Kind::ShadowRoot) { MOZ_ASSERT(mAsNode); } DocumentOrShadowRoot::DocumentOrShadowRoot(Document* aDoc) : mAsNode(aDoc), mKind(Kind::Document) { MOZ_ASSERT(mAsNode); } void DocumentOrShadowRoot::AddSizeOfOwnedSheetArrayExcludingThis( nsWindowSizes& aSizes, const nsTArray>& aSheets) const { size_t n = 0; n += aSheets.ShallowSizeOfExcludingThis(aSizes.mState.mMallocSizeOf); for (StyleSheet* sheet : aSheets) { if (!sheet->GetAssociatedDocumentOrShadowRoot()) { // Avoid over-reporting shared sheets. continue; } n += sheet->SizeOfIncludingThis(aSizes.mState.mMallocSizeOf); } if (mKind == Kind::ShadowRoot) { aSizes.mLayoutShadowDomStyleSheetsSize += n; } else { aSizes.mLayoutStyleSheetsSize += n; } } void DocumentOrShadowRoot::AddSizeOfExcludingThis(nsWindowSizes& aSizes) const { AddSizeOfOwnedSheetArrayExcludingThis(aSizes, mStyleSheets); aSizes.mDOMOtherSize += mIdentifierMap.SizeOfExcludingThis(aSizes.mState.mMallocSizeOf); } DocumentOrShadowRoot::~DocumentOrShadowRoot() { for (StyleSheet* sheet : mStyleSheets) { sheet->ClearAssociatedDocumentOrShadowRoot(); } } StyleSheetList* DocumentOrShadowRoot::StyleSheets() { if (!mDOMStyleSheets) { mDOMStyleSheets = new StyleSheetList(*this); } return mDOMStyleSheets; } void DocumentOrShadowRoot::InsertSheetAt(size_t aIndex, StyleSheet& aSheet) { aSheet.SetAssociatedDocumentOrShadowRoot( this, StyleSheet::OwnedByDocumentOrShadowRoot); mStyleSheets.InsertElementAt(aIndex, &aSheet); } void DocumentOrShadowRoot::InsertAdoptedSheetAt(size_t aIndex, StyleSheet& aSheet) { mAdoptedStyleSheets.InsertElementAt(aIndex, &aSheet); } already_AddRefed DocumentOrShadowRoot::RemoveSheet( StyleSheet& aSheet) { auto index = mStyleSheets.IndexOf(&aSheet); if (index == mStyleSheets.NoIndex) { return nullptr; } RefPtr sheet = std::move(mStyleSheets[index]); mStyleSheets.RemoveElementAt(index); sheet->ClearAssociatedDocumentOrShadowRoot(); return sheet.forget(); } // https://wicg.github.io/construct-stylesheets/#dom-documentorshadowroot-adoptedstylesheets void DocumentOrShadowRoot::EnsureAdoptedSheetsAreValid( const Sequence>& aAdoptedStyleSheets, ErrorResult& aRv) { nsTHashtable> set( aAdoptedStyleSheets.Length()); for (const OwningNonNull& sheet : aAdoptedStyleSheets) { // 2.1 Check if all sheets are constructed, else throw NotAllowedError if (!sheet->IsConstructed()) { return aRv.ThrowNotAllowedError( "Each adopted style sheet must be created through the Constructable " "StyleSheets API"); } // 2.2 Check if all sheets' constructor documents match the // DocumentOrShadowRoot's node document, else throw NotAlloweError if (!sheet->ConstructorDocumentMatches(AsNode().OwnerDoc())) { return aRv.ThrowNotAllowedError( "Each adopted style sheet's constructor document must match the " "document or shadow root's node document"); } // FIXME(nordzilla): This is temporary code to disallow duplicate sheets. // This exists to ensure that the fuzzers aren't blocked. // This code will eventually be removed. // https://bugzilla.mozilla.org/show_bug.cgi?id=1617302 if (!set.EnsureInserted(sheet.get())) { return aRv.ThrowNotAllowedError( "Temporarily disallowing duplicate stylesheets."); } } } Element* DocumentOrShadowRoot::GetElementById(const nsAString& aElementId) { if (MOZ_UNLIKELY(aElementId.IsEmpty())) { nsContentUtils::ReportEmptyGetElementByIdArg(AsNode().OwnerDoc()); return nullptr; } if (IdentifierMapEntry* entry = mIdentifierMap.GetEntry(aElementId)) { if (Element* el = entry->GetIdElement()) { return el; } } return nullptr; } already_AddRefed DocumentOrShadowRoot::GetElementsByTagNameNS( const nsAString& aNamespaceURI, const nsAString& aLocalName) { ErrorResult rv; RefPtr list = GetElementsByTagNameNS(aNamespaceURI, aLocalName, rv); if (rv.Failed()) { return nullptr; } return list.forget(); } already_AddRefed DocumentOrShadowRoot::GetElementsByTagNameNS( const nsAString& aNamespaceURI, const nsAString& aLocalName, ErrorResult& aResult) { int32_t nameSpaceId = kNameSpaceID_Wildcard; if (!aNamespaceURI.EqualsLiteral("*")) { aResult = nsContentUtils::NameSpaceManager()->RegisterNameSpace( aNamespaceURI, nameSpaceId); if (aResult.Failed()) { return nullptr; } } NS_ASSERTION(nameSpaceId != kNameSpaceID_Unknown, "Unexpected namespace ID!"); return NS_GetContentList(&AsNode(), nameSpaceId, aLocalName); } already_AddRefed DocumentOrShadowRoot::GetElementsByClassName( const nsAString& aClasses) { return nsContentUtils::GetElementsByClassName(&AsNode(), aClasses); } nsIContent* DocumentOrShadowRoot::Retarget(nsIContent* aContent) const { for (nsIContent* cur = aContent; cur; cur = cur->GetContainingShadowHost()) { if (cur->SubtreeRoot() == &AsNode()) { return cur; } } return nullptr; } Element* DocumentOrShadowRoot::GetRetargetedFocusedElement() { if (nsCOMPtr window = AsNode().OwnerDoc()->GetWindow()) { nsCOMPtr focusedWindow; nsIContent* focusedContent = nsFocusManager::GetFocusedDescendant( window, nsFocusManager::eOnlyCurrentWindow, getter_AddRefs(focusedWindow)); // be safe and make sure the element is from this document if (focusedContent && focusedContent->OwnerDoc() == AsNode().OwnerDoc()) { if (focusedContent->ChromeOnlyAccess()) { focusedContent = focusedContent->FindFirstNonChromeOnlyAccessContent(); } if (focusedContent) { if (nsIContent* retarget = Retarget(focusedContent)) { return retarget->AsElement(); } } } } return nullptr; } Element* DocumentOrShadowRoot::GetPointerLockElement() { nsCOMPtr pointerLockedElement = do_QueryReferent(EventStateManager::sPointerLockedElement); if (!pointerLockedElement) { return nullptr; } nsIContent* retargetedPointerLockedElement = Retarget(pointerLockedElement); return retargetedPointerLockedElement && retargetedPointerLockedElement->IsElement() ? retargetedPointerLockedElement->AsElement() : nullptr; } Element* DocumentOrShadowRoot::GetFullscreenElement() { if (!AsNode().IsInComposedDoc()) { return nullptr; } Element* element = AsNode().OwnerDoc()->FullscreenStackTop(); NS_ASSERTION(!element || element->State().HasState(NS_EVENT_STATE_FULLSCREEN), "Fullscreen element should have fullscreen styles applied"); nsIContent* retargeted = Retarget(element); if (retargeted && retargeted->IsElement()) { return retargeted->AsElement(); } return nullptr; } namespace { using FrameForPointOption = nsLayoutUtils::FrameForPointOption; // Whether only one node or multiple nodes is requested. enum class Multiple { No, Yes, }; // Whether we should flush layout or not. enum class FlushLayout { No, Yes, }; template NodeOrElement* CastTo(nsIContent* aContent); template <> Element* CastTo(nsIContent* aContent) { return aContent->AsElement(); } template <> nsINode* CastTo(nsIContent* aContent) { return aContent; } template static void QueryNodesFromRect(DocumentOrShadowRoot& aRoot, const nsRect& aRect, EnumSet aOptions, FlushLayout aShouldFlushLayout, Multiple aMultiple, nsTArray>& aNodes) { static_assert(std::is_same::value || std::is_same::value, "Should returning nodes or elements"); constexpr bool returningElements = std::is_same::value; nsCOMPtr doc = aRoot.AsNode().OwnerDoc(); // Make sure the layout information we get is up-to-date, and // ensure we get a root frame (for everything but XUL) if (aShouldFlushLayout == FlushLayout::Yes) { doc->FlushPendingNotifications(FlushType::Layout); } PresShell* presShell = doc->GetPresShell(); if (!presShell) { return; } nsIFrame* rootFrame = presShell->GetRootFrame(); // XUL docs, unlike HTML, have no frame tree until everything's done loading if (!rootFrame) { return; // return null to premature XUL callers as a reminder to wait } aOptions += FrameForPointOption::IgnorePaintSuppression; aOptions += FrameForPointOption::IgnoreCrossDoc; AutoTArray frames; nsLayoutUtils::GetFramesForArea(rootFrame, aRect, frames, aOptions); for (nsIFrame* frame : frames) { nsIContent* content = doc->GetContentInThisDocument(frame); if (!content) { continue; } if (returningElements && !content->IsElement()) { // If this helper is called via ElementsFromPoint, we need to make sure // our frame is an element. Otherwise return whatever the top frame is // even if it isn't the top-painted element. // SVG 'text' element's SVGTextFrame doesn't respond to hit-testing, so // if 'content' is a child of such an element then we need to manually // defer to the parent here. if (aMultiple == Multiple::Yes && !nsSVGUtils::IsInSVGTextSubtree(frame)) { continue; } content = content->GetParent(); if (ShadowRoot* shadow = ShadowRoot::FromNodeOrNull(content)) { content = shadow->Host(); } } // XXXsmaug There is plenty of unspec'ed behavior here // https://github.com/w3c/webcomponents/issues/735 // https://github.com/w3c/webcomponents/issues/736 content = aRoot.Retarget(content); if (content && content != aNodes.SafeLastElement(nullptr)) { aNodes.AppendElement(CastTo(content)); if (aMultiple == Multiple::No) { return; } } } } template static void QueryNodesFromPoint(DocumentOrShadowRoot& aRoot, float aX, float aY, EnumSet aOptions, FlushLayout aShouldFlushLayout, Multiple aMultiple, nsTArray>& aNodes) { // As per the spec, we return null if either coord is negative. if (!aOptions.contains(FrameForPointOption::IgnoreRootScrollFrame) && (aX < 0 || aY < 0)) { return; } nscoord x = nsPresContext::CSSPixelsToAppUnits(aX); nscoord y = nsPresContext::CSSPixelsToAppUnits(aY); nsPoint pt(x, y); QueryNodesFromRect(aRoot, nsRect(pt, nsSize(1, 1)), aOptions, aShouldFlushLayout, aMultiple, aNodes); } } // namespace Element* DocumentOrShadowRoot::ElementFromPoint(float aX, float aY) { return ElementFromPointHelper(aX, aY, false, true); } void DocumentOrShadowRoot::ElementsFromPoint( float aX, float aY, nsTArray>& aElements) { QueryNodesFromPoint(*this, aX, aY, {}, FlushLayout::Yes, Multiple::Yes, aElements); } void DocumentOrShadowRoot::NodesFromPoint(float aX, float aY, nsTArray>& aNodes) { QueryNodesFromPoint(*this, aX, aY, {}, FlushLayout::Yes, Multiple::Yes, aNodes); } nsINode* DocumentOrShadowRoot::NodeFromPoint(float aX, float aY) { AutoTArray, 1> nodes; QueryNodesFromPoint(*this, aX, aY, {}, FlushLayout::Yes, Multiple::No, nodes); return nodes.SafeElementAt(0); } Element* DocumentOrShadowRoot::ElementFromPointHelper( float aX, float aY, bool aIgnoreRootScrollFrame, bool aFlushLayout) { EnumSet options; if (aIgnoreRootScrollFrame) { options += FrameForPointOption::IgnoreRootScrollFrame; } auto flush = aFlushLayout ? FlushLayout::Yes : FlushLayout::No; AutoTArray, 1> elements; QueryNodesFromPoint(*this, aX, aY, options, flush, Multiple::No, elements); return elements.SafeElementAt(0); } void DocumentOrShadowRoot::NodesFromRect(float aX, float aY, float aTopSize, float aRightSize, float aBottomSize, float aLeftSize, bool aIgnoreRootScrollFrame, bool aFlushLayout, bool aOnlyVisible, nsTArray>& aReturn) { // Following the same behavior of elementFromPoint, // we don't return anything if either coord is negative if (!aIgnoreRootScrollFrame && (aX < 0 || aY < 0)) { return; } nscoord x = nsPresContext::CSSPixelsToAppUnits(aX - aLeftSize); nscoord y = nsPresContext::CSSPixelsToAppUnits(aY - aTopSize); nscoord w = nsPresContext::CSSPixelsToAppUnits(aLeftSize + aRightSize) + 1; nscoord h = nsPresContext::CSSPixelsToAppUnits(aTopSize + aBottomSize) + 1; nsRect rect(x, y, w, h); EnumSet options; if (aIgnoreRootScrollFrame) { options += FrameForPointOption::IgnoreRootScrollFrame; } if (aOnlyVisible) { options += FrameForPointOption::OnlyVisible; } auto flush = aFlushLayout ? FlushLayout::Yes : FlushLayout::No; QueryNodesFromRect(*this, rect, options, flush, Multiple::Yes, aReturn); } Element* DocumentOrShadowRoot::AddIDTargetObserver(nsAtom* aID, IDTargetObserver aObserver, void* aData, bool aForImage) { nsDependentAtomString id(aID); if (!CheckGetElementByIdArg(id)) { return nullptr; } IdentifierMapEntry* entry = mIdentifierMap.PutEntry(aID); NS_ENSURE_TRUE(entry, nullptr); entry->AddContentChangeCallback(aObserver, aData, aForImage); return aForImage ? entry->GetImageIdElement() : entry->GetIdElement(); } void DocumentOrShadowRoot::RemoveIDTargetObserver(nsAtom* aID, IDTargetObserver aObserver, void* aData, bool aForImage) { nsDependentAtomString id(aID); if (!CheckGetElementByIdArg(id)) { return; } IdentifierMapEntry* entry = mIdentifierMap.GetEntry(aID); if (!entry) { return; } entry->RemoveContentChangeCallback(aObserver, aData, aForImage); } Element* DocumentOrShadowRoot::LookupImageElement(const nsAString& aId) { if (aId.IsEmpty()) { return nullptr; } IdentifierMapEntry* entry = mIdentifierMap.GetEntry(aId); return entry ? entry->GetImageIdElement() : nullptr; } void DocumentOrShadowRoot::ReportEmptyGetElementByIdArg() { nsContentUtils::ReportEmptyGetElementByIdArg(AsNode().OwnerDoc()); } /** * A struct that holds all the information about a radio group. */ struct nsRadioGroupStruct { nsRadioGroupStruct() : mRequiredRadioCount(0), mGroupSuffersFromValueMissing(false) {} /** * A strong pointer to the currently selected radio button. */ RefPtr mSelectedRadioButton; nsCOMArray mRadioButtons; uint32_t mRequiredRadioCount; bool mGroupSuffersFromValueMissing; }; void DocumentOrShadowRoot::GetAnimations( nsTArray>& aAnimations) { // As with Element::GetAnimations we initially flush style here. // This should ensure that there are no subsequent changes to the tree // structure while iterating over the children below. if (Document* doc = AsNode().GetComposedDoc()) { doc->FlushPendingNotifications( ChangesToFlush(FlushType::Style, false /* flush animations */)); } GetAnimationsOptions options; options.mSubtree = true; for (RefPtr child = AsNode().GetFirstChild(); child; child = child->GetNextSibling()) { if (RefPtr element = Element::FromNode(child)) { nsTArray> result; element->GetAnimations(options, result, Element::Flush::No); aAnimations.AppendElements(std::move(result)); } } aAnimations.Sort(AnimationPtrComparator>()); } nsresult DocumentOrShadowRoot::WalkRadioGroup(const nsAString& aName, nsIRadioVisitor* aVisitor, bool aFlushContent) { nsRadioGroupStruct* radioGroup = GetOrCreateRadioGroup(aName); for (int i = 0; i < radioGroup->mRadioButtons.Count(); i++) { if (!aVisitor->Visit(radioGroup->mRadioButtons[i])) { return NS_OK; } } return NS_OK; } void DocumentOrShadowRoot::SetCurrentRadioButton(const nsAString& aName, HTMLInputElement* aRadio) { nsRadioGroupStruct* radioGroup = GetOrCreateRadioGroup(aName); radioGroup->mSelectedRadioButton = aRadio; } HTMLInputElement* DocumentOrShadowRoot::GetCurrentRadioButton( const nsAString& aName) { return GetOrCreateRadioGroup(aName)->mSelectedRadioButton; } nsresult DocumentOrShadowRoot::GetNextRadioButton( const nsAString& aName, const bool aPrevious, HTMLInputElement* aFocusedRadio, HTMLInputElement** aRadioOut) { // XXX Can we combine the HTML radio button method impls of // Document and nsHTMLFormControl? // XXX Why is HTML radio button stuff in Document, as // opposed to nsHTMLDocument? *aRadioOut = nullptr; nsRadioGroupStruct* radioGroup = GetOrCreateRadioGroup(aName); // Return the radio button relative to the focused radio button. // If no radio is focused, get the radio relative to the selected one. RefPtr currentRadio; if (aFocusedRadio) { currentRadio = aFocusedRadio; } else { currentRadio = radioGroup->mSelectedRadioButton; if (!currentRadio) { return NS_ERROR_FAILURE; } } int32_t index = radioGroup->mRadioButtons.IndexOf(currentRadio); if (index < 0) { return NS_ERROR_FAILURE; } int32_t numRadios = radioGroup->mRadioButtons.Count(); RefPtr radio; do { if (aPrevious) { if (--index < 0) { index = numRadios - 1; } } else if (++index >= numRadios) { index = 0; } NS_ASSERTION( static_cast(radioGroup->mRadioButtons[index]) ->IsHTMLElement(nsGkAtoms::input), "mRadioButtons holding a non-radio button"); radio = static_cast(radioGroup->mRadioButtons[index]); } while (radio->Disabled() && radio != currentRadio); radio.forget(aRadioOut); return NS_OK; } void DocumentOrShadowRoot::AddToRadioGroup(const nsAString& aName, HTMLInputElement* aRadio) { nsRadioGroupStruct* radioGroup = GetOrCreateRadioGroup(aName); radioGroup->mRadioButtons.AppendObject(aRadio); if (aRadio->IsRequired()) { radioGroup->mRequiredRadioCount++; } } void DocumentOrShadowRoot::RemoveFromRadioGroup(const nsAString& aName, HTMLInputElement* aRadio) { nsRadioGroupStruct* radioGroup = GetOrCreateRadioGroup(aName); radioGroup->mRadioButtons.RemoveObject(aRadio); if (aRadio->IsRequired()) { NS_ASSERTION(radioGroup->mRequiredRadioCount != 0, "mRequiredRadioCount about to wrap below 0!"); radioGroup->mRequiredRadioCount--; } } uint32_t DocumentOrShadowRoot::GetRequiredRadioCount( const nsAString& aName) const { nsRadioGroupStruct* radioGroup = GetRadioGroup(aName); return radioGroup ? radioGroup->mRequiredRadioCount : 0; } void DocumentOrShadowRoot::RadioRequiredWillChange(const nsAString& aName, bool aRequiredAdded) { nsRadioGroupStruct* radioGroup = GetOrCreateRadioGroup(aName); if (aRequiredAdded) { radioGroup->mRequiredRadioCount++; } else { NS_ASSERTION(radioGroup->mRequiredRadioCount != 0, "mRequiredRadioCount about to wrap below 0!"); radioGroup->mRequiredRadioCount--; } } bool DocumentOrShadowRoot::GetValueMissingState(const nsAString& aName) const { nsRadioGroupStruct* radioGroup = GetRadioGroup(aName); return radioGroup && radioGroup->mGroupSuffersFromValueMissing; } void DocumentOrShadowRoot::SetValueMissingState(const nsAString& aName, bool aValue) { nsRadioGroupStruct* radioGroup = GetOrCreateRadioGroup(aName); radioGroup->mGroupSuffersFromValueMissing = aValue; } nsRadioGroupStruct* DocumentOrShadowRoot::GetRadioGroup( const nsAString& aName) const { nsRadioGroupStruct* radioGroup = nullptr; mRadioGroups.Get(aName, &radioGroup); return radioGroup; } nsRadioGroupStruct* DocumentOrShadowRoot::GetOrCreateRadioGroup( const nsAString& aName) { return mRadioGroups.LookupForAdd(aName) .OrInsert([]() { return new nsRadioGroupStruct(); }) .get(); } int32_t DocumentOrShadowRoot::StyleOrderIndexOfSheet( const StyleSheet& aSheet) const { if (aSheet.IsConstructed()) { int32_t index = mAdoptedStyleSheets.IndexOf(&aSheet); return (index < 0) ? index : index + SheetCount(); } return mStyleSheets.IndexOf(&aSheet); } void DocumentOrShadowRoot::GetAdoptedStyleSheets( nsTArray>& aAdoptedStyleSheets) const { aAdoptedStyleSheets = mAdoptedStyleSheets; } void DocumentOrShadowRoot::Traverse(DocumentOrShadowRoot* tmp, nsCycleCollectionTraversalCallback& cb) { NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStyleSheets) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDOMStyleSheets) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAdoptedStyleSheets) auto NoteSheets = [tmp, &cb = cb](nsTArray>& sheetList) { for (StyleSheet* sheet : sheetList) { if (!sheet->IsApplicable()) { continue; } // The style set or mServoStyles keep more references to it if the sheet // is applicable. if (tmp->mKind == Kind::ShadowRoot) { NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mServoStyles->sheets[i]"); cb.NoteXPCOMChild(sheet); } else if (tmp->AsNode().AsDocument()->StyleSetFilled()) { NS_CYCLE_COLLECTION_NOTE_EDGE_NAME( cb, "mStyleSet->mRawSet.stylist.stylesheets.author[i]"); cb.NoteXPCOMChild(sheet); } } }; NoteSheets(tmp->mStyleSheets); NoteSheets(tmp->mAdoptedStyleSheets); for (auto iter = tmp->mIdentifierMap.ConstIter(); !iter.Done(); iter.Next()) { iter.Get()->Traverse(&cb); } for (auto iter = tmp->mRadioGroups.Iter(); !iter.Done(); iter.Next()) { nsRadioGroupStruct* radioGroup = iter.UserData(); NS_CYCLE_COLLECTION_NOTE_EDGE_NAME( cb, "mRadioGroups entry->mSelectedRadioButton"); cb.NoteXPCOMChild(ToSupports(radioGroup->mSelectedRadioButton)); uint32_t i, count = radioGroup->mRadioButtons.Count(); for (i = 0; i < count; ++i) { NS_CYCLE_COLLECTION_NOTE_EDGE_NAME( cb, "mRadioGroups entry->mRadioButtons[i]"); cb.NoteXPCOMChild(radioGroup->mRadioButtons[i]); } } } void DocumentOrShadowRoot::Unlink(DocumentOrShadowRoot* tmp) { NS_IMPL_CYCLE_COLLECTION_UNLINK(mDOMStyleSheets); for (RefPtr& sheet : tmp->mAdoptedStyleSheets) { sheet->RemoveAdopter(*tmp); } NS_IMPL_CYCLE_COLLECTION_UNLINK(mAdoptedStyleSheets); tmp->mIdentifierMap.Clear(); tmp->mRadioGroups.Clear(); } } // namespace dom } // namespace mozilla