diff options
Diffstat (limited to '')
-rw-r--r-- | dom/base/DocumentOrShadowRoot.cpp | 853 |
1 files changed, 853 insertions, 0 deletions
diff --git a/dom/base/DocumentOrShadowRoot.cpp b/dom/base/DocumentOrShadowRoot.cpp new file mode 100644 index 0000000000..39a1a5762e --- /dev/null +++ b/dom/base/DocumentOrShadowRoot.cpp @@ -0,0 +1,853 @@ +/* -*- 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<RefPtr<StyleSheet>>& 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<StyleSheet> 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<ShadowRoot&>(AsNode()).RemoveSheetFromStyles(aSheet); + } +} + +// https://wicg.github.io/construct-stylesheets/#dom-documentorshadowroot-adoptedstylesheets +void DocumentOrShadowRoot::SetAdoptedStyleSheets( + const Sequence<OwningNonNull<StyleSheet>>& aAdoptedStyleSheets, + ErrorResult& aRv) { + Document& doc = *AsNode().OwnerDoc(); + for (const OwningNonNull<StyleSheet>& 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<OwningNonNull<StyleSheet>> list; + if (!list.SetCapacity(mAdoptedStyleSheets.Length(), fallible)) { + return; + } + + Document& ownerDoc = *AsNode().OwnerDoc(); + const Document& sourceDoc = *aSource.AsNode().OwnerDoc(); + auto* clonedSheetMap = static_cast<Document::AdoptedStyleSheetCloneCache*>( + sourceDoc.GetProperty(nsGkAtoms::adoptedsheetclones)); + MOZ_ASSERT(clonedSheetMap); + + for (const StyleSheet* sheet : aSource.mAdoptedStyleSheets) { + RefPtr<StyleSheet> clone = clonedSheetMap->LookupForAdd(sheet).OrInsert( + [&] { return sheet->CloneAdoptedSheet(ownerDoc); }); + MOZ_ASSERT(clone); + MOZ_DIAGNOSTIC_ASSERT(clone->ConstructorDocumentMatches(ownerDoc)); + DebugOnly<bool> 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<nsContentList> DocumentOrShadowRoot::GetElementsByTagNameNS( + const nsAString& aNamespaceURI, const nsAString& aLocalName) { + ErrorResult rv; + RefPtr<nsContentList> list = + GetElementsByTagNameNS(aNamespaceURI, aLocalName, rv); + if (rv.Failed()) { + return nullptr; + } + return list.forget(); +} + +already_AddRefed<nsContentList> 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<nsContentList> 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<Element> 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 <typename NodeOrElement> +NodeOrElement* CastTo(nsIContent* aContent); + +template <> +Element* CastTo<Element>(nsIContent* aContent) { + return aContent->AsElement(); +} + +template <> +nsINode* CastTo<nsINode>(nsIContent* aContent) { + return aContent; +} + +template <typename NodeOrElement> +static void QueryNodesFromRect(DocumentOrShadowRoot& aRoot, const nsRect& aRect, + FrameForPointOptions aOptions, + FlushLayout aShouldFlushLayout, + Multiple aMultiple, ViewportType aViewportType, + nsTArray<RefPtr<NodeOrElement>>& aNodes) { + static_assert(std::is_same<nsINode, NodeOrElement>::value || + std::is_same<Element, NodeOrElement>::value, + "Should returning nodes or elements"); + + constexpr bool returningElements = + std::is_same<Element, NodeOrElement>::value; + + nsCOMPtr<Document> 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<nsIFrame*, 8> 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<NodeOrElement>(content)); + if (aMultiple == Multiple::No) { + return; + } + } + } +} + +template <typename NodeOrElement> +static void QueryNodesFromPoint(DocumentOrShadowRoot& aRoot, float aX, float aY, + FrameForPointOptions aOptions, + FlushLayout aShouldFlushLayout, + Multiple aMultiple, ViewportType aViewportType, + nsTArray<RefPtr<NodeOrElement>>& 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<RefPtr<Element>>& aElements) { + QueryNodesFromPoint(*this, aX, aY, {}, FlushLayout::Yes, Multiple::Yes, + ViewportType::Layout, aElements); +} + +void DocumentOrShadowRoot::NodesFromPoint(float aX, float aY, + nsTArray<RefPtr<nsINode>>& aNodes) { + QueryNodesFromPoint(*this, aX, aY, {}, FlushLayout::Yes, Multiple::Yes, + ViewportType::Layout, aNodes); +} + +nsINode* DocumentOrShadowRoot::NodeFromPoint(float aX, float aY) { + AutoTArray<RefPtr<nsINode>, 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<FrameForPointOption> options; + if (aIgnoreRootScrollFrame) { + options += FrameForPointOption::IgnoreRootScrollFrame; + } + + auto flush = aFlushLayout ? FlushLayout::Yes : FlushLayout::No; + + AutoTArray<RefPtr<Element>, 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<RefPtr<nsINode>>& 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<HTMLInputElement> mSelectedRadioButton; + nsCOMArray<nsIFormControl> mRadioButtons; + uint32_t mRequiredRadioCount; + bool mGroupSuffersFromValueMissing; +}; + +void DocumentOrShadowRoot::GetAnimations( + nsTArray<RefPtr<Animation>>& 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<nsIContent> child = AsNode().GetFirstChild(); child; + child = child->GetNextSibling()) { + if (RefPtr<Element> element = Element::FromNode(child)) { + nsTArray<RefPtr<Animation>> result; + element->GetAnimationsWithoutFlush(options, result); + aAnimations.AppendElements(std::move(result)); + } + } + + aAnimations.Sort(AnimationPtrComparator<RefPtr<Animation>>()); +} + +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<HTMLInputElement> 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<HTMLInputElement> radio; + do { + if (aPrevious) { + if (--index < 0) { + index = numRadios - 1; + } + } else if (++index >= numRadios) { + index = 0; + } + NS_ASSERTION( + static_cast<nsGenericHTMLFormElement*>(radioGroup->mRadioButtons[index]) + ->IsHTMLElement(nsGkAtoms::input), + "mRadioButtons holding a non-radio button"); + radio = static_cast<HTMLInputElement*>(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<RefPtr<StyleSheet>>& 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.<origin>[i]"); + cb.NoteXPCOMChild(&aSheet); + } +} + +void DocumentOrShadowRoot::TraverseStyleSheets( + nsTArray<RefPtr<StyleSheet>>& 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<RefPtr<StyleSheet>>& 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 |