/* -*- 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/SVGUtils.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 "nsWindowSizes.h" namespace mozilla::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); mStyleSheets.InsertElementAt(aIndex, &aSheet); } void DocumentOrShadowRoot::RemoveStyleSheet(StyleSheet& aSheet) { auto index = mStyleSheets.IndexOf(&aSheet); if (index == mStyleSheets.NoIndex) { // We should only hit this case if we are unlinking // in which case mStyleSheets should be cleared. MOZ_ASSERT(mKind != Kind::Document || AsNode().AsDocument()->InUnlinkOrDeletion()); MOZ_ASSERT(mStyleSheets.IsEmpty()); return; } RefPtr sheet = std::move(mStyleSheets[index]); mStyleSheets.RemoveElementAt(index); RemoveSheetFromStylesIfApplicable(*sheet); sheet->ClearAssociatedDocumentOrShadowRoot(); } void DocumentOrShadowRoot::RemoveSheetFromStylesIfApplicable( StyleSheet& aSheet) { if (!aSheet.IsApplicable()) { return; } if (mKind == Kind::Document) { AsNode().AsDocument()->RemoveStyleSheetFromStyleSets(aSheet); } else { MOZ_ASSERT(AsNode().IsShadowRoot()); static_cast(AsNode()).RemoveSheetFromStyles(aSheet); } } // https://wicg.github.io/construct-stylesheets/#dom-documentorshadowroot-adoptedstylesheets void DocumentOrShadowRoot::SetAdoptedStyleSheets( const Sequence>& aAdoptedStyleSheets, ErrorResult& aRv) { Document& doc = *AsNode().OwnerDoc(); 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(doc)) { return aRv.ThrowNotAllowedError( "Each adopted style sheet's constructor document must match the " "document or shadow root's node document"); } } auto* shadow = ShadowRoot::FromNode(AsNode()); MOZ_ASSERT((mKind == Kind::ShadowRoot) == !!shadow); StyleSheetSet set(aAdoptedStyleSheets.Length()); size_t commonPrefix = 0; // Find the index at which the new array differs from the old array. // We don't want to do extra work for the sheets that both arrays have. size_t min = std::min(aAdoptedStyleSheets.Length(), mAdoptedStyleSheets.Length()); for (size_t i = 0; i < min; ++i) { if (aAdoptedStyleSheets[i] != mAdoptedStyleSheets[i]) { break; } ++commonPrefix; set.PutEntry(mAdoptedStyleSheets[i]); } // Try to truncate the sheets to a common prefix. // If the prefix contains duplicates of sheets that we are removing, // we are just going to re-build everything from scratch. if (commonPrefix != mAdoptedStyleSheets.Length()) { StyleSheetSet removedSet(mAdoptedStyleSheets.Length() - commonPrefix); for (size_t i = mAdoptedStyleSheets.Length(); i != commonPrefix; --i) { StyleSheet* sheetToRemove = mAdoptedStyleSheets.ElementAt(i - 1); if (MOZ_UNLIKELY(set.Contains(sheetToRemove))) { // Fixing duplicate sheets would require insertions/removals from the // style set. We may as well just rebuild the whole thing from scratch. set.Clear(); // Note that setting this to zero means we'll continue the loop until // all the sheets are cleared. commonPrefix = 0; } if (MOZ_LIKELY(removedSet.EnsureInserted(sheetToRemove))) { RemoveSheetFromStylesIfApplicable(*sheetToRemove); sheetToRemove->RemoveAdopter(*this); } } mAdoptedStyleSheets.TruncateLength(commonPrefix); } // 3. Set the adopted style sheets to the new sheets mAdoptedStyleSheets.SetCapacity(aAdoptedStyleSheets.Length()); // Only add sheets that are not already in the common prefix. for (const auto& sheet : Span(aAdoptedStyleSheets).From(commonPrefix)) { if (MOZ_UNLIKELY(!set.EnsureInserted(sheet))) { // The idea is that this case is rare, so we pay the price of removing the // old sheet from the styles and append it later rather than the other way // around. RemoveSheetFromStylesIfApplicable(*sheet); } else { sheet->AddAdopter(*this); } mAdoptedStyleSheets.AppendElement(sheet); if (sheet->IsApplicable()) { if (mKind == Kind::Document) { doc.AddStyleSheetToStyleSets(*sheet); } else { shadow->InsertSheetIntoAuthorData(mAdoptedStyleSheets.Length() - 1, *sheet, mAdoptedStyleSheets); } } } } void DocumentOrShadowRoot::ClearAdoptedStyleSheets() { EnumerateUniqueAdoptedStyleSheetsBackToFront([&](StyleSheet& aSheet) { RemoveSheetFromStylesIfApplicable(aSheet); aSheet.RemoveAdopter(*this); }); mAdoptedStyleSheets.Clear(); } void DocumentOrShadowRoot::CloneAdoptedSheetsFrom( const DocumentOrShadowRoot& aSource) { if (!aSource.AdoptedSheetCount()) { return; } Sequence> list; if (!list.SetCapacity(mAdoptedStyleSheets.Length(), fallible)) { return; } Document& ownerDoc = *AsNode().OwnerDoc(); const Document& sourceDoc = *aSource.AsNode().OwnerDoc(); auto* clonedSheetMap = static_cast( sourceDoc.GetProperty(nsGkAtoms::adoptedsheetclones)); MOZ_ASSERT(clonedSheetMap); for (const StyleSheet* sheet : aSource.mAdoptedStyleSheets) { RefPtr clone = clonedSheetMap->LookupForAdd(sheet).OrInsert( [&] { return sheet->CloneAdoptedSheet(ownerDoc); }); MOZ_ASSERT(clone); MOZ_DIAGNOSTIC_ASSERT(clone->ConstructorDocumentMatches(ownerDoc)); DebugOnly succeeded = list.AppendElement(std::move(clone), fallible); MOZ_ASSERT(succeeded); } ErrorResult rv; SetAdoptedStyleSheets(list, rv); MOZ_ASSERT(!rv.Failed()); } 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() { auto* content = AsNode().OwnerDoc()->GetUnretargetedFocusedContent(); if (!content) { return nullptr; } if (nsIContent* retarget = Retarget(content)) { 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()->GetUnretargetedFullScreenElement(); 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; using FrameForPointOptions = nsLayoutUtils::FrameForPointOptions; // 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, FrameForPointOptions aOptions, FlushLayout aShouldFlushLayout, Multiple aMultiple, ViewportType aViewportType, 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.mBits += FrameForPointOption::IgnorePaintSuppression; aOptions.mBits += FrameForPointOption::IgnoreCrossDoc; AutoTArray frames; nsLayoutUtils::GetFramesForArea({rootFrame, aViewportType}, 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 && !SVGUtils::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, FrameForPointOptions aOptions, FlushLayout aShouldFlushLayout, Multiple aMultiple, ViewportType aViewportType, nsTArray>& aNodes) { // As per the spec, we return null if either coord is negative. if (!aOptions.mBits.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, aViewportType, aNodes); } } // namespace Element* DocumentOrShadowRoot::ElementFromPoint(float aX, float aY) { return ElementFromPointHelper(aX, aY, false, true, ViewportType::Layout); } void DocumentOrShadowRoot::ElementsFromPoint( float aX, float aY, nsTArray>& aElements) { QueryNodesFromPoint(*this, aX, aY, {}, FlushLayout::Yes, Multiple::Yes, ViewportType::Layout, aElements); } void DocumentOrShadowRoot::NodesFromPoint(float aX, float aY, nsTArray>& aNodes) { QueryNodesFromPoint(*this, aX, aY, {}, FlushLayout::Yes, Multiple::Yes, ViewportType::Layout, aNodes); } nsINode* DocumentOrShadowRoot::NodeFromPoint(float aX, float aY) { AutoTArray, 1> nodes; QueryNodesFromPoint(*this, aX, aY, {}, FlushLayout::Yes, Multiple::No, ViewportType::Layout, nodes); return nodes.SafeElementAt(0); } Element* DocumentOrShadowRoot::ElementFromPointHelper( float aX, float aY, bool aIgnoreRootScrollFrame, bool aFlushLayout, ViewportType aViewportType) { 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, aViewportType, 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, float aVisibleThreshold, 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); FrameForPointOptions options; if (aIgnoreRootScrollFrame) { options.mBits += FrameForPointOption::IgnoreRootScrollFrame; } if (aOnlyVisible) { options.mBits += FrameForPointOption::OnlyVisible; options.mVisibleThreshold = aVisibleThreshold; } auto flush = aFlushLayout ? FlushLayout::Yes : FlushLayout::No; QueryNodesFromRect(*this, rect, options, flush, Multiple::Yes, ViewportType::Layout, 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->GetAnimationsWithoutFlush(options, result); 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()) { // NOTE: constructable sheets can have duplicates, so we need to start // looking from behind. int32_t index = mAdoptedStyleSheets.LastIndexOf(&aSheet); return (index < 0) ? index : index + SheetCount(); } return mStyleSheets.IndexOf(&aSheet); } void DocumentOrShadowRoot::GetAdoptedStyleSheets( nsTArray>& aAdoptedStyleSheets) const { aAdoptedStyleSheets = mAdoptedStyleSheets.Clone(); } void DocumentOrShadowRoot::TraverseSheetRefInStylesIfApplicable( StyleSheet& aSheet, nsCycleCollectionTraversalCallback& cb) { if (!aSheet.IsApplicable()) { return; } if (mKind == Kind::ShadowRoot) { NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mServoStyles->sheets[i]"); cb.NoteXPCOMChild(&aSheet); } else if (AsNode().AsDocument()->StyleSetFilled()) { NS_CYCLE_COLLECTION_NOTE_EDGE_NAME( cb, "mStyleSet->mRawSet.stylist.stylesheets.[i]"); cb.NoteXPCOMChild(&aSheet); } } void DocumentOrShadowRoot::TraverseStyleSheets( nsTArray>& aSheets, const char* aEdgeName, nsCycleCollectionTraversalCallback& cb) { MOZ_ASSERT(aEdgeName); MOZ_ASSERT(&aSheets != &mAdoptedStyleSheets); for (StyleSheet* sheet : aSheets) { NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, aEdgeName); cb.NoteXPCOMChild(sheet); TraverseSheetRefInStylesIfApplicable(*sheet, cb); } } void DocumentOrShadowRoot::Traverse(DocumentOrShadowRoot* tmp, nsCycleCollectionTraversalCallback& cb) { NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDOMStyleSheets) tmp->TraverseStyleSheets(tmp->mStyleSheets, "mStyleSheets[i]", cb); tmp->EnumerateUniqueAdoptedStyleSheetsBackToFront([&](StyleSheet& aSheet) { tmp->TraverseSheetRefInStylesIfApplicable(aSheet, cb); }); NS_IMPL_CYCLE_COLLECTION_TRAVERSE(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::UnlinkStyleSheets( nsTArray>& aSheets) { MOZ_ASSERT(&aSheets != &mAdoptedStyleSheets); for (StyleSheet* sheet : aSheets) { sheet->ClearAssociatedDocumentOrShadowRoot(); RemoveSheetFromStylesIfApplicable(*sheet); } aSheets.Clear(); } void DocumentOrShadowRoot::Unlink(DocumentOrShadowRoot* tmp) { NS_IMPL_CYCLE_COLLECTION_UNLINK(mDOMStyleSheets); tmp->UnlinkStyleSheets(tmp->mStyleSheets); tmp->EnumerateUniqueAdoptedStyleSheetsBackToFront([&](StyleSheet& aSheet) { aSheet.RemoveAdopter(*tmp); tmp->RemoveSheetFromStylesIfApplicable(aSheet); }); NS_IMPL_CYCLE_COLLECTION_UNLINK(mAdoptedStyleSheets); tmp->mIdentifierMap.Clear(); tmp->mRadioGroups.Clear(); } } // namespace mozilla::dom