diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
commit | 6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch) | |
tree | a68f146d7fa01f0134297619fbe7e33db084e0aa /layout/style/nsComputedDOMStyle.cpp | |
parent | Initial commit. (diff) | |
download | thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip |
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'layout/style/nsComputedDOMStyle.cpp')
-rw-r--r-- | layout/style/nsComputedDOMStyle.cpp | 2474 |
1 files changed, 2474 insertions, 0 deletions
diff --git a/layout/style/nsComputedDOMStyle.cpp b/layout/style/nsComputedDOMStyle.cpp new file mode 100644 index 0000000000..9744f6de73 --- /dev/null +++ b/layout/style/nsComputedDOMStyle.cpp @@ -0,0 +1,2474 @@ +/* -*- 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/. */ + +/* DOM object returned from element.getComputedStyle() */ + +#include "nsComputedDOMStyle.h" + +#include "mozilla/ArrayUtils.h" +#include "mozilla/FloatingPoint.h" +#include "mozilla/FontPropertyTypes.h" +#include "mozilla/Preferences.h" +#include "mozilla/PresShell.h" +#include "mozilla/PresShellInlines.h" +#include "mozilla/ScopeExit.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/StaticPrefs_layout.h" + +#include "nsError.h" +#include "nsIFrame.h" +#include "nsIFrameInlines.h" +#include "mozilla/ComputedStyle.h" +#include "nsIScrollableFrame.h" +#include "nsContentUtils.h" +#include "nsDocShell.h" +#include "nsIContent.h" +#include "nsStyleConsts.h" + +#include "nsDOMCSSValueList.h" +#include "nsFlexContainerFrame.h" +#include "nsGridContainerFrame.h" +#include "nsGkAtoms.h" +#include "mozilla/ReflowInput.h" +#include "nsStyleUtil.h" +#include "nsStyleStructInlines.h" +#include "nsROCSSPrimitiveValue.h" + +#include "nsPresContext.h" +#include "mozilla/dom/Document.h" + +#include "nsCSSProps.h" +#include "nsCSSPseudoElements.h" +#include "mozilla/EffectSet.h" +#include "mozilla/IntegerRange.h" +#include "mozilla/ServoStyleSet.h" +#include "mozilla/RestyleManager.h" +#include "mozilla/ViewportFrame.h" +#include "nsLayoutUtils.h" +#include "nsDisplayList.h" +#include "nsDOMCSSDeclaration.h" +#include "nsStyleTransformMatrix.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/ElementInlines.h" +#include "prtime.h" +#include "nsWrapperCacheInlines.h" +#include "mozilla/AppUnits.h" +#include <algorithm> +#include "mozilla/ComputedStyleInlines.h" +#include "nsPrintfCString.h" + +using namespace mozilla; +using namespace mozilla::dom; + +/* + * This is the implementation of the readonly CSSStyleDeclaration that is + * returned by the getComputedStyle() function. + */ + +static bool ShouldReturnEmptyStyleForInvalidPseudoElement( + const nsAString& aPseudoElt) { + // Old behavior is historical. New behavior is as discussed in + // https://github.com/w3c/csswg-drafts/issues/6501. + if (!StaticPrefs:: + layout_css_computed_style_new_invalid_pseudo_element_behavior()) { + if (aPseudoElt.Length() < 2) { + return false; + } + const char16_t* chars = aPseudoElt.BeginReading(); + return chars[0] == u':' && chars[1] == u':'; + } + return !aPseudoElt.IsEmpty() && aPseudoElt.First() == u':'; +} + +already_AddRefed<nsComputedDOMStyle> NS_NewComputedDOMStyle( + dom::Element* aElement, const nsAString& aPseudoElt, Document* aDocument, + nsComputedDOMStyle::StyleType aStyleType, mozilla::ErrorResult&) { + Maybe<PseudoStyleType> pseudo = nsCSSPseudoElements::GetPseudoType( + aPseudoElt, CSSEnabledState::ForAllContent); + auto returnEmpty = nsComputedDOMStyle::AlwaysReturnEmptyStyle::No; + if (!pseudo) { + if (ShouldReturnEmptyStyleForInvalidPseudoElement(aPseudoElt)) { + returnEmpty = nsComputedDOMStyle::AlwaysReturnEmptyStyle::Yes; + } + pseudo.emplace(PseudoStyleType::NotPseudo); + } + RefPtr<nsComputedDOMStyle> computedStyle = new nsComputedDOMStyle( + aElement, *pseudo, aDocument, aStyleType, returnEmpty); + return computedStyle.forget(); +} + +static nsDOMCSSValueList* GetROCSSValueList(bool aCommaDelimited) { + return new nsDOMCSSValueList(aCommaDelimited); +} + +static const Element* GetRenderedElement(const Element* aElement, + PseudoStyleType aPseudo) { + if (aPseudo == PseudoStyleType::NotPseudo) { + return aElement; + } + if (aPseudo == PseudoStyleType::before) { + return nsLayoutUtils::GetBeforePseudo(aElement); + } + if (aPseudo == PseudoStyleType::after) { + return nsLayoutUtils::GetAfterPseudo(aElement); + } + if (aPseudo == PseudoStyleType::marker) { + return nsLayoutUtils::GetMarkerPseudo(aElement); + } + return nullptr; +} + +// Whether aDocument needs to restyle for aElement +static bool ElementNeedsRestyle(Element* aElement, PseudoStyleType aPseudo, + bool aMayNeedToFlushLayout) { + const Document* doc = aElement->GetComposedDoc(); + if (!doc) { + // If the element is out of the document we don't return styles for it, so + // nothing to do. + return false; + } + + PresShell* presShell = doc->GetPresShell(); + if (!presShell) { + // If there's no pres-shell we'll just either mint a new style from our + // caller document, or return no styles, so nothing to do (unless our owner + // element needs to get restyled, which could cause us to gain a pres shell, + // but the caller checks that). + return false; + } + + // Unfortunately we don't know if the sheet change affects mElement or not, so + // just assume it will and that we need to flush normally. + ServoStyleSet* styleSet = presShell->StyleSet(); + if (styleSet->StyleSheetsHaveChanged()) { + return true; + } + + nsPresContext* presContext = presShell->GetPresContext(); + MOZ_ASSERT(presContext); + + // Pending media query updates can definitely change style on the element. For + // example, if you change the zoom factor and then call getComputedStyle, you + // should be able to observe the style with the new media queries. + // + // TODO(emilio): Does this need to also check the user font set? (it affects + // ch / ex units). + if (presContext->HasPendingMediaQueryUpdates()) { + // So gotta flush. + return true; + } + + // If the pseudo-element is animating, make sure to flush. + if (aElement->MayHaveAnimations() && aPseudo != PseudoStyleType::NotPseudo && + AnimationUtils::IsSupportedPseudoForAnimations(aPseudo)) { + if (EffectSet::Get(aElement, aPseudo)) { + return true; + } + } + + // For Servo, we need to process the restyle-hint-invalidations first, to + // expand LaterSiblings hint, so that we can look whether ancestors need + // restyling. + RestyleManager* restyleManager = presContext->RestyleManager(); + restyleManager->ProcessAllPendingAttributeAndStateInvalidations(); + + if (!presContext->EffectCompositor()->HasPendingStyleUpdates() && + !doc->GetServoRestyleRoot()) { + return false; + } + + // If there's a pseudo, we need to prefer that element, as the pseudo itself + // may have explicit restyles. + const Element* styledElement = GetRenderedElement(aElement, aPseudo); + // Try to skip the restyle otherwise. + return Servo_HasPendingRestyleAncestor( + styledElement ? styledElement : aElement, aMayNeedToFlushLayout); +} + +/** + * An object that represents the ordered set of properties that are exposed on + * an nsComputedDOMStyle object and how their computed values can be obtained. + */ +struct ComputedStyleMap { + friend class nsComputedDOMStyle; + + struct Entry { + // Create a pointer-to-member-function type. + using ComputeMethod = already_AddRefed<CSSValue> (nsComputedDOMStyle::*)(); + + nsCSSPropertyID mProperty; + + // Whether the property can ever be exposed in getComputedStyle(). For + // example, @page descriptors implemented as CSS properties or other + // internal properties, would have this flag set to `false`. + bool mCanBeExposed = false; + + ComputeMethod mGetter = nullptr; + + bool IsEnumerable() const { + return IsEnabled() && !nsCSSProps::IsShorthand(mProperty); + } + + bool IsEnabled() const { + if (!mCanBeExposed || + !nsCSSProps::IsEnabled(mProperty, CSSEnabledState::ForAllContent)) { + return false; + } + if (nsCSSProps::IsShorthand(mProperty) && + !StaticPrefs::layout_css_computed_style_shorthands()) { + return nsCSSProps::PropHasFlags( + mProperty, CSSPropFlags::ShorthandUnconditionallyExposedOnGetCS); + } + return true; + } + }; + + // This generated file includes definition of kEntries which is typed + // Entry[] and used below, so this #include has to be put here. +#include "nsComputedDOMStyleGenerated.inc" + + /** + * Returns the number of properties that should be exposed on an + * nsComputedDOMStyle, ecxluding any disabled properties. + */ + uint32_t Length() { + Update(); + return mEnumerablePropertyCount; + } + + /** + * Returns the property at the given index in the list of properties + * that should be exposed on an nsComputedDOMStyle, excluding any + * disabled properties. + */ + nsCSSPropertyID PropertyAt(uint32_t aIndex) { + Update(); + return kEntries[EntryIndex(aIndex)].mProperty; + } + + /** + * Searches for and returns the computed style map entry for the given + * property, or nullptr if the property is not exposed on nsComputedDOMStyle + * or is currently disabled. + */ + const Entry* FindEntryForProperty(nsCSSPropertyID aPropID) { + if (size_t(aPropID) >= ArrayLength(kEntryIndices)) { + MOZ_ASSERT(aPropID == eCSSProperty_UNKNOWN); + return nullptr; + } + MOZ_ASSERT(kEntryIndices[aPropID] < ArrayLength(kEntries)); + const auto& entry = kEntries[kEntryIndices[aPropID]]; + if (!entry.IsEnabled()) { + return nullptr; + } + return &entry; + } + + /** + * Records that mIndexMap needs updating, due to prefs changing that could + * affect the set of properties exposed on an nsComputedDOMStyle. + */ + void MarkDirty() { mEnumerablePropertyCount = 0; } + + // The member variables are public so that we can use an initializer in + // nsComputedDOMStyle::GetComputedStyleMap. Use the member functions + // above to get information from this object. + + /** + * The number of properties that should be exposed on an nsComputedDOMStyle. + * This will be less than eComputedStyleProperty_COUNT if some property + * prefs are disabled. A value of 0 indicates that it and mIndexMap are out + * of date. + */ + uint32_t mEnumerablePropertyCount = 0; + + /** + * A map of indexes on the nsComputedDOMStyle object to indexes into kEntries. + */ + uint32_t mIndexMap[ArrayLength(kEntries)]; + + private: + /** + * Returns whether mEnumerablePropertyCount and mIndexMap are out of date. + */ + bool IsDirty() { return mEnumerablePropertyCount == 0; } + + /** + * Updates mEnumerablePropertyCount and mIndexMap to take into account + * properties whose prefs are currently disabled. + */ + void Update(); + + /** + * Maps an nsComputedDOMStyle indexed getter index to an index into kEntries. + */ + uint32_t EntryIndex(uint32_t aIndex) const { + MOZ_ASSERT(aIndex < mEnumerablePropertyCount); + return mIndexMap[aIndex]; + } +}; + +constexpr ComputedStyleMap::Entry + ComputedStyleMap::kEntries[ArrayLength(kEntries)]; + +constexpr size_t ComputedStyleMap::kEntryIndices[ArrayLength(kEntries)]; + +void ComputedStyleMap::Update() { + if (!IsDirty()) { + return; + } + + uint32_t index = 0; + for (uint32_t i = 0; i < ArrayLength(kEntries); i++) { + if (kEntries[i].IsEnumerable()) { + mIndexMap[index++] = i; + } + } + mEnumerablePropertyCount = index; +} + +nsComputedDOMStyle::nsComputedDOMStyle(dom::Element* aElement, + PseudoStyleType aPseudo, + Document* aDocument, + StyleType aStyleType, + AlwaysReturnEmptyStyle aAlwaysEmpty) + : mDocumentWeak(nullptr), + mOuterFrame(nullptr), + mInnerFrame(nullptr), + mPresShell(nullptr), + mPseudo(aPseudo), + mStyleType(aStyleType), + mAlwaysReturnEmpty(aAlwaysEmpty) { + MOZ_ASSERT(aElement); + MOZ_ASSERT(aDocument); + // TODO(emilio, bug 548397, https://github.com/w3c/csswg-drafts/issues/2403): + // Should use aElement->OwnerDoc() instead. + mDocumentWeak = do_GetWeakReference(aDocument); + mElement = aElement; +} + +nsComputedDOMStyle::~nsComputedDOMStyle() { + MOZ_ASSERT(!mResolvedComputedStyle, + "Should have called ClearComputedStyle() during last release."); +} + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(nsComputedDOMStyle) + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsComputedDOMStyle) + tmp->ClearComputedStyle(); // remove observer before clearing mElement + NS_IMPL_CYCLE_COLLECTION_UNLINK(mElement) + NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsComputedDOMStyle) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mElement) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +// We can skip the nsComputedDOMStyle if it has no wrapper and its +// element is skippable, because it will have no outgoing edges, so +// it can't be part of a cycle. + +NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(nsComputedDOMStyle) + if (!tmp->GetWrapperPreserveColor()) { + return !tmp->mElement || + mozilla::dom::FragmentOrElement::CanSkip(tmp->mElement, true); + } + return tmp->HasKnownLiveWrapper(); +NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END + +NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(nsComputedDOMStyle) + if (!tmp->GetWrapperPreserveColor()) { + return !tmp->mElement || + mozilla::dom::FragmentOrElement::CanSkipInCC(tmp->mElement); + } + return tmp->HasKnownLiveWrapper(); +NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END + +NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(nsComputedDOMStyle) + if (!tmp->GetWrapperPreserveColor()) { + return !tmp->mElement || + mozilla::dom::FragmentOrElement::CanSkipThis(tmp->mElement); + } + return tmp->HasKnownLiveWrapper(); +NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END + +// QueryInterface implementation for nsComputedDOMStyle +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsComputedDOMStyle) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(nsIMutationObserver) +NS_INTERFACE_MAP_END_INHERITING(nsDOMCSSDeclaration) + +NS_IMPL_MAIN_THREAD_ONLY_CYCLE_COLLECTING_ADDREF(nsComputedDOMStyle) +NS_IMPL_MAIN_THREAD_ONLY_CYCLE_COLLECTING_RELEASE_WITH_LAST_RELEASE( + nsComputedDOMStyle, ClearComputedStyle()) + +nsresult nsComputedDOMStyle::GetPropertyValue(const nsCSSPropertyID aPropID, + nsACString& aValue) { + return GetPropertyValue(aPropID, EmptyCString(), aValue); +} + +void nsComputedDOMStyle::SetPropertyValue(const nsCSSPropertyID aPropID, + const nsACString& aValue, + nsIPrincipal* aSubjectPrincipal, + ErrorResult& aRv) { + aRv.ThrowNoModificationAllowedError(nsPrintfCString( + "Can't set value for property '%s' in computed style", + PromiseFlatCString(nsCSSProps::GetStringValue(aPropID)).get())); +} + +void nsComputedDOMStyle::GetCssText(nsACString& aCssText) { + aCssText.Truncate(); +} + +void nsComputedDOMStyle::SetCssText(const nsACString& aCssText, + nsIPrincipal* aSubjectPrincipal, + ErrorResult& aRv) { + aRv.ThrowNoModificationAllowedError("Can't set cssText on computed style"); +} + +uint32_t nsComputedDOMStyle::Length() { + // Make sure we have up to date style so that we can include custom + // properties. + UpdateCurrentStyleSources(eCSSPropertyExtra_variable); + if (!mComputedStyle) { + return 0; + } + + uint32_t length = GetComputedStyleMap()->Length() + + Servo_GetCustomPropertiesCount(mComputedStyle); + + ClearCurrentStyleSources(); + + return length; +} + +css::Rule* nsComputedDOMStyle::GetParentRule() { return nullptr; } + +NS_IMETHODIMP +nsComputedDOMStyle::GetPropertyValue(const nsACString& aPropertyName, + nsACString& aReturn) { + nsCSSPropertyID prop = nsCSSProps::LookupProperty(aPropertyName); + return GetPropertyValue(prop, aPropertyName, aReturn); +} + +nsresult nsComputedDOMStyle::GetPropertyValue( + nsCSSPropertyID aPropID, const nsACString& aMaybeCustomPropertyName, + nsACString& aReturn) { + MOZ_ASSERT(aReturn.IsEmpty()); + + const ComputedStyleMap::Entry* entry = nullptr; + if (aPropID != eCSSPropertyExtra_variable) { + entry = GetComputedStyleMap()->FindEntryForProperty(aPropID); + if (!entry) { + return NS_OK; + } + } + + UpdateCurrentStyleSources(aPropID); + if (!mComputedStyle) { + return NS_OK; + } + + auto cleanup = mozilla::MakeScopeExit([&] { ClearCurrentStyleSources(); }); + + if (!entry) { + MOZ_ASSERT(nsCSSProps::IsCustomPropertyName(aMaybeCustomPropertyName)); + const nsACString& name = + Substring(aMaybeCustomPropertyName, CSS_CUSTOM_NAME_PREFIX_LENGTH); + Servo_GetCustomPropertyValue(mComputedStyle, &name, &aReturn); + return NS_OK; + } + + if (nsCSSProps::PropHasFlags(aPropID, CSSPropFlags::IsLogical)) { + MOZ_ASSERT(entry); + MOZ_ASSERT(entry->mGetter == &nsComputedDOMStyle::DummyGetter); + + DebugOnly<nsCSSPropertyID> logicalProp = aPropID; + + aPropID = Servo_ResolveLogicalProperty(aPropID, mComputedStyle); + entry = GetComputedStyleMap()->FindEntryForProperty(aPropID); + + MOZ_ASSERT(NeedsToFlushLayout(logicalProp) == NeedsToFlushLayout(aPropID), + "Logical and physical property don't agree on whether layout is " + "needed"); + } + + if (!nsCSSProps::PropHasFlags(aPropID, CSSPropFlags::SerializedByServo)) { + if (RefPtr<CSSValue> value = (this->*entry->mGetter)()) { + ErrorResult rv; + nsAutoString text; + value->GetCssText(text, rv); + CopyUTF16toUTF8(text, aReturn); + return rv.StealNSResult(); + } + return NS_OK; + } + + MOZ_ASSERT(entry->mGetter == &nsComputedDOMStyle::DummyGetter); + Servo_GetResolvedValue(mComputedStyle, aPropID, + mPresShell->StyleSet()->RawData(), mElement, &aReturn); + return NS_OK; +} + +/* static */ +already_AddRefed<const ComputedStyle> nsComputedDOMStyle::GetComputedStyle( + Element* aElement, PseudoStyleType aPseudo, StyleType aStyleType) { + if (Document* doc = aElement->GetComposedDoc()) { + doc->FlushPendingNotifications(FlushType::Style); + } + return GetComputedStyleNoFlush(aElement, aPseudo, aStyleType); +} + +/** + * The following function checks whether we need to explicitly resolve the style + * again, even though we have a style coming from the frame. + * + * This basically checks whether the style is or may be under a ::first-line or + * ::first-letter frame, in which case we can't return the frame style, and we + * need to resolve it. See bug 505515. + */ +static bool MustReresolveStyle(const ComputedStyle* aStyle) { + MOZ_ASSERT(aStyle); + + // TODO(emilio): We may want to avoid re-resolving pseudo-element styles + // more often. + return aStyle->HasPseudoElementData() && !aStyle->IsPseudoElement(); +} + +static bool IsInFlatTree(const Element& aElement) { + const auto* topmost = &aElement; + while (true) { + if (topmost->HasServoData()) { + // If we have styled this element then we know it's in the flat tree. + return true; + } + const Element* parent = topmost->GetFlattenedTreeParentElement(); + if (!parent) { + break; + } + topmost = parent; + } + auto* root = topmost->GetFlattenedTreeParentNode(); + return root && root->IsDocument(); +} + +already_AddRefed<const ComputedStyle> +nsComputedDOMStyle::DoGetComputedStyleNoFlush(const Element* aElement, + PseudoStyleType aPseudo, + PresShell* aPresShell, + StyleType aStyleType) { + MOZ_ASSERT(aElement, "NULL element"); + + // If the content has a pres shell, we must use it. Otherwise we'd + // potentially mix rule trees by using the wrong pres shell's style + // set. Using the pres shell from the content also means that any + // content that's actually *in* a document will get the style from the + // correct document. + PresShell* presShell = nsContentUtils::GetPresShellForContent(aElement); + bool inDocWithShell = true; + if (!presShell) { + inDocWithShell = false; + presShell = aPresShell; + if (!presShell) { + return nullptr; + } + } + + MOZ_ASSERT(aPseudo == PseudoStyleType::NotPseudo || + PseudoStyle::IsPseudoElement(aPseudo)); + if (!aElement->IsInComposedDoc()) { + // Don't return styles for disconnected elements, that makes no sense. This + // can only happen with a non-null presShell for cross-document calls. + return nullptr; + } + + if (!StaticPrefs::layout_css_computed_style_styles_outside_flat_tree() && + !IsInFlatTree(*aElement)) { + return nullptr; + } + + // XXX the !aElement->IsHTMLElement(nsGkAtoms::area) + // check is needed due to bug 135040 (to avoid using + // mPrimaryFrame). Remove it once that's fixed. + if (inDocWithShell && aStyleType == StyleType::All && + !aElement->IsHTMLElement(nsGkAtoms::area)) { + if (const Element* element = GetRenderedElement(aElement, aPseudo)) { + if (element->HasServoData()) { + const ComputedStyle* result = + Servo_Element_GetMaybeOutOfDateStyle(element); + return do_AddRef(result); + } + } + } + + // No frame has been created, or we have a pseudo, or we're looking + // for the default style, so resolve the style ourselves. + ServoStyleSet* styleSet = presShell->StyleSet(); + + StyleRuleInclusion rules = aStyleType == StyleType::DefaultOnly + ? StyleRuleInclusion::DefaultOnly + : StyleRuleInclusion::All; + RefPtr<ComputedStyle> result = + styleSet->ResolveStyleLazily(*aElement, aPseudo, rules); + return result.forget(); +} + +already_AddRefed<const ComputedStyle> +nsComputedDOMStyle::GetUnanimatedComputedStyleNoFlush(Element* aElement, + PseudoStyleType aPseudo) { + RefPtr<const ComputedStyle> style = + GetComputedStyleNoFlush(aElement, aPseudo); + if (!style) { + return nullptr; + } + + PresShell* presShell = aElement->OwnerDoc()->GetPresShell(); + MOZ_ASSERT(presShell, + "How in the world did we get a style a few lines above?"); + + Element* elementOrPseudoElement = + AnimationUtils::GetElementForRestyle(aElement, aPseudo); + if (!elementOrPseudoElement) { + return nullptr; + } + + return presShell->StyleSet()->GetBaseContextForElement(elementOrPseudoElement, + style); +} + +nsMargin nsComputedDOMStyle::GetAdjustedValuesForBoxSizing() { + // We want the width/height of whatever parts 'width' or 'height' controls, + // which can be different depending on the value of the 'box-sizing' property. + const nsStylePosition* stylePos = StylePosition(); + + nsMargin adjustment; + if (stylePos->mBoxSizing == StyleBoxSizing::Border) { + adjustment = mInnerFrame->GetUsedBorderAndPadding(); + } + + return adjustment; +} + +static void AddImageURL(nsIURI& aURI, nsTArray<nsCString>& aURLs) { + nsCString spec; + nsresult rv = aURI.GetSpec(spec); + if (NS_FAILED(rv)) { + return; + } + + aURLs.AppendElement(std::move(spec)); +} + +static void AddImageURL(const StyleComputedUrl& aURL, + nsTArray<nsCString>& aURLs) { + if (aURL.IsLocalRef()) { + return; + } + + if (nsIURI* uri = aURL.GetURI()) { + AddImageURL(*uri, aURLs); + } +} + +static void AddImageURL(const StyleImage& aImage, nsTArray<nsCString>& aURLs) { + if (auto* urlValue = aImage.GetImageRequestURLValue()) { + AddImageURL(*urlValue, aURLs); + } +} + +static void AddImageURL(const StyleShapeOutside& aShapeOutside, + nsTArray<nsCString>& aURLs) { + if (aShapeOutside.IsImage()) { + AddImageURL(aShapeOutside.AsImage(), aURLs); + } +} + +static void AddImageURL(const StyleClipPath& aClipPath, + nsTArray<nsCString>& aURLs) { + if (aClipPath.IsUrl()) { + AddImageURL(aClipPath.AsUrl(), aURLs); + } +} + +static void AddImageURLs(const nsStyleImageLayers& aLayers, + nsTArray<nsCString>& aURLs) { + for (auto i : IntegerRange(aLayers.mLayers.Length())) { + AddImageURL(aLayers.mLayers[i].mImage, aURLs); + } +} + +static void CollectImageURLsForProperty(nsCSSPropertyID aProp, + const ComputedStyle& aStyle, + nsTArray<nsCString>& aURLs) { + if (nsCSSProps::IsShorthand(aProp)) { + CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(p, aProp, + CSSEnabledState::ForAllContent) { + CollectImageURLsForProperty(*p, aStyle, aURLs); + } + return; + } + + switch (aProp) { + case eCSSProperty_cursor: + for (auto& image : aStyle.StyleUI()->Cursor().images.AsSpan()) { + AddImageURL(image.image, aURLs); + } + break; + case eCSSProperty_background_image: + AddImageURLs(aStyle.StyleBackground()->mImage, aURLs); + break; + case eCSSProperty_mask_clip: + AddImageURLs(aStyle.StyleSVGReset()->mMask, aURLs); + break; + case eCSSProperty_list_style_image: { + const auto& image = aStyle.StyleList()->mListStyleImage; + if (image.IsUrl()) { + AddImageURL(image.AsUrl(), aURLs); + } + break; + } + case eCSSProperty_border_image_source: + AddImageURL(aStyle.StyleBorder()->mBorderImageSource, aURLs); + break; + case eCSSProperty_clip_path: + AddImageURL(aStyle.StyleSVGReset()->mClipPath, aURLs); + break; + case eCSSProperty_shape_outside: + AddImageURL(aStyle.StyleDisplay()->mShapeOutside, aURLs); + break; + default: + break; + } +} + +void nsComputedDOMStyle::GetCSSImageURLs(const nsACString& aPropertyName, + nsTArray<nsCString>& aImageURLs, + mozilla::ErrorResult& aRv) { + nsCSSPropertyID prop = nsCSSProps::LookupProperty(aPropertyName); + if (prop == eCSSProperty_UNKNOWN) { + // Note: not using nsPrintfCString here in case aPropertyName contains + // nulls. + aRv.ThrowSyntaxError("Invalid property name '"_ns + aPropertyName + "'"_ns); + return; + } + + UpdateCurrentStyleSources(prop); + + if (!mComputedStyle) { + return; + } + + CollectImageURLsForProperty(prop, *mComputedStyle, aImageURLs); + ClearCurrentStyleSources(); +} + +// nsDOMCSSDeclaration abstract methods which should never be called +// on a nsComputedDOMStyle object, but must be defined to avoid +// compile errors. +DeclarationBlock* nsComputedDOMStyle::GetOrCreateCSSDeclaration( + Operation aOperation, DeclarationBlock** aCreated) { + MOZ_CRASH("called nsComputedDOMStyle::GetCSSDeclaration"); +} + +nsresult nsComputedDOMStyle::SetCSSDeclaration(DeclarationBlock*, + MutationClosureData*) { + MOZ_CRASH("called nsComputedDOMStyle::SetCSSDeclaration"); +} + +Document* nsComputedDOMStyle::DocToUpdate() { + MOZ_CRASH("called nsComputedDOMStyle::DocToUpdate"); +} + +nsDOMCSSDeclaration::ParsingEnvironment +nsComputedDOMStyle::GetParsingEnvironment( + nsIPrincipal* aSubjectPrincipal) const { + MOZ_CRASH("called nsComputedDOMStyle::GetParsingEnvironment"); +} + +void nsComputedDOMStyle::ClearComputedStyle() { + if (mResolvedComputedStyle) { + mResolvedComputedStyle = false; + mElement->RemoveMutationObserver(this); + } + mComputedStyle = nullptr; +} + +void nsComputedDOMStyle::SetResolvedComputedStyle( + RefPtr<const ComputedStyle>&& aContext, uint64_t aGeneration) { + if (!mResolvedComputedStyle) { + mResolvedComputedStyle = true; + mElement->AddMutationObserver(this); + } + mComputedStyle = aContext; + mComputedStyleGeneration = aGeneration; + mPresShellId = mPresShell->GetPresShellId(); +} + +void nsComputedDOMStyle::SetFrameComputedStyle(mozilla::ComputedStyle* aStyle, + uint64_t aGeneration) { + ClearComputedStyle(); + mComputedStyle = aStyle; + mComputedStyleGeneration = aGeneration; + mPresShellId = mPresShell->GetPresShellId(); +} + +static bool MayNeedToFlushLayout(nsCSSPropertyID aPropID) { + switch (aPropID) { + case eCSSProperty_width: + case eCSSProperty_height: + case eCSSProperty_block_size: + case eCSSProperty_inline_size: + case eCSSProperty_line_height: + case eCSSProperty_grid_template_rows: + case eCSSProperty_grid_template_columns: + case eCSSProperty_perspective_origin: + case eCSSProperty_transform_origin: + case eCSSProperty_transform: + case eCSSProperty_border_top_width: + case eCSSProperty_border_bottom_width: + case eCSSProperty_border_left_width: + case eCSSProperty_border_right_width: + case eCSSProperty_border_block_start_width: + case eCSSProperty_border_block_end_width: + case eCSSProperty_border_inline_start_width: + case eCSSProperty_border_inline_end_width: + case eCSSProperty_top: + case eCSSProperty_right: + case eCSSProperty_bottom: + case eCSSProperty_left: + case eCSSProperty_inset_block_start: + case eCSSProperty_inset_block_end: + case eCSSProperty_inset_inline_start: + case eCSSProperty_inset_inline_end: + case eCSSProperty_padding_top: + case eCSSProperty_padding_right: + case eCSSProperty_padding_bottom: + case eCSSProperty_padding_left: + case eCSSProperty_padding_block_start: + case eCSSProperty_padding_block_end: + case eCSSProperty_padding_inline_start: + case eCSSProperty_padding_inline_end: + case eCSSProperty_margin_top: + case eCSSProperty_margin_right: + case eCSSProperty_margin_bottom: + case eCSSProperty_margin_left: + case eCSSProperty_margin_block_start: + case eCSSProperty_margin_block_end: + case eCSSProperty_margin_inline_start: + case eCSSProperty_margin_inline_end: + return true; + default: + return false; + } +} + +bool nsComputedDOMStyle::NeedsToFlushStyle(nsCSSPropertyID aPropID) const { + bool mayNeedToFlushLayout = MayNeedToFlushLayout(aPropID); + + // We always compute styles from the element's owner document. + if (ElementNeedsRestyle(mElement, mPseudo, mayNeedToFlushLayout)) { + return true; + } + + Document* doc = mElement->OwnerDoc(); + // If parent document is there, also needs to check if there is some change + // that needs to flush this document (e.g. size change for iframe). + while (doc->StyleOrLayoutObservablyDependsOnParentDocumentLayout()) { + if (Element* element = doc->GetEmbedderElement()) { + if (ElementNeedsRestyle(element, PseudoStyleType::NotPseudo, + mayNeedToFlushLayout)) { + return true; + } + } + + doc = doc->GetInProcessParentDocument(); + } + + return false; +} + +static bool IsNonReplacedInline(nsIFrame* aFrame) { + // FIXME: this should be IsInlineInsideStyle() since width/height + // doesn't apply to ruby boxes. + return aFrame->StyleDisplay()->IsInlineFlow() && + !aFrame->IsFrameOfType(nsIFrame::eReplaced) && + !aFrame->IsFieldSetFrame() && !aFrame->IsBlockFrame() && + !aFrame->IsScrollFrame() && !aFrame->IsColumnSetWrapperFrame(); +} + +static Side SideForPaddingOrMarginOrInsetProperty(nsCSSPropertyID aPropID) { + switch (aPropID) { + case eCSSProperty_top: + case eCSSProperty_margin_top: + case eCSSProperty_padding_top: + return eSideTop; + case eCSSProperty_right: + case eCSSProperty_margin_right: + case eCSSProperty_padding_right: + return eSideRight; + case eCSSProperty_bottom: + case eCSSProperty_margin_bottom: + case eCSSProperty_padding_bottom: + return eSideBottom; + case eCSSProperty_left: + case eCSSProperty_margin_left: + case eCSSProperty_padding_left: + return eSideLeft; + default: + MOZ_ASSERT_UNREACHABLE("Unexpected property"); + return eSideTop; + } +} + +static bool PaddingNeedsUsedValue(const LengthPercentage& aValue, + const ComputedStyle& aStyle) { + return !aValue.ConvertsToLength() || aStyle.StyleDisplay()->HasAppearance(); +} + +bool nsComputedDOMStyle::NeedsToFlushLayout(nsCSSPropertyID aPropID) const { + MOZ_ASSERT(aPropID != eCSSProperty_UNKNOWN); + if (aPropID == eCSSPropertyExtra_variable) { + return false; + } + nsIFrame* outerFrame = GetOuterFrame(); + if (!outerFrame) { + return false; + } + nsIFrame* frame = nsLayoutUtils::GetStyleFrame(outerFrame); + auto* style = frame->Style(); + if (nsCSSProps::PropHasFlags(aPropID, CSSPropFlags::IsLogical)) { + aPropID = Servo_ResolveLogicalProperty(aPropID, style); + } + + switch (aPropID) { + case eCSSProperty_width: + case eCSSProperty_height: + return !IsNonReplacedInline(frame); + case eCSSProperty_line_height: + return frame->StyleText()->mLineHeight.IsMozBlockHeight(); + case eCSSProperty_grid_template_rows: + case eCSSProperty_grid_template_columns: + return !!nsGridContainerFrame::GetGridContainerFrame(frame); + case eCSSProperty_perspective_origin: + return style->StyleDisplay()->mPerspectiveOrigin.HasPercent(); + case eCSSProperty_transform_origin: + return style->StyleDisplay()->mTransformOrigin.HasPercent(); + case eCSSProperty_transform: + return style->StyleDisplay()->mTransform.HasPercent(); + case eCSSProperty_border_top_width: + case eCSSProperty_border_bottom_width: + case eCSSProperty_border_left_width: + case eCSSProperty_border_right_width: + // FIXME(emilio): This should return false per spec (bug 1551000), but + // themed borders don't make that easy, so for now flush for that case. + // + // TODO(emilio): If we make GetUsedBorder() stop returning 0 for an + // unreflowed frame, or something of that sort, then we can stop flushing + // layout for themed frames. + return style->StyleDisplay()->HasAppearance(); + case eCSSProperty_top: + case eCSSProperty_right: + case eCSSProperty_bottom: + case eCSSProperty_left: + // Doing better than this is actually hard. + return style->StyleDisplay()->mPosition != StylePositionProperty::Static; + case eCSSProperty_padding_top: + case eCSSProperty_padding_right: + case eCSSProperty_padding_bottom: + case eCSSProperty_padding_left: { + Side side = SideForPaddingOrMarginOrInsetProperty(aPropID); + // Theming can override used padding, sigh. + // + // TODO(emilio): If we make GetUsedPadding() stop returning 0 for an + // unreflowed frame, or something of that sort, then we can stop flushing + // layout for themed frames. + return PaddingNeedsUsedValue(style->StylePadding()->mPadding.Get(side), + *style); + } + case eCSSProperty_margin_top: + case eCSSProperty_margin_right: + case eCSSProperty_margin_bottom: + case eCSSProperty_margin_left: { + // NOTE(emilio): This is dubious, but matches other browsers. + // See https://github.com/w3c/csswg-drafts/issues/2328 + Side side = SideForPaddingOrMarginOrInsetProperty(aPropID); + return !style->StyleMargin()->mMargin.Get(side).ConvertsToLength(); + } + default: + return false; + } +} + +void nsComputedDOMStyle::Flush(Document& aDocument, FlushType aFlushType) { + MOZ_ASSERT(mElement->IsInComposedDoc()); + +#ifdef DEBUG + { + nsCOMPtr<Document> document = do_QueryReferent(mDocumentWeak); + MOZ_ASSERT(document == &aDocument); + } +#endif + + aDocument.FlushPendingNotifications(aFlushType); + if (MOZ_UNLIKELY(&aDocument != mElement->OwnerDoc())) { + mElement->OwnerDoc()->FlushPendingNotifications(aFlushType); + } +} + +nsIFrame* nsComputedDOMStyle::GetOuterFrame() const { + if (mPseudo == PseudoStyleType::NotPseudo) { + return mElement->GetPrimaryFrame(); + } + nsAtom* property = nullptr; + if (mPseudo == PseudoStyleType::before) { + property = nsGkAtoms::beforePseudoProperty; + } else if (mPseudo == PseudoStyleType::after) { + property = nsGkAtoms::afterPseudoProperty; + } else if (mPseudo == PseudoStyleType::marker) { + property = nsGkAtoms::markerPseudoProperty; + } + if (!property) { + return nullptr; + } + auto* pseudo = static_cast<Element*>(mElement->GetProperty(property)); + return pseudo ? pseudo->GetPrimaryFrame() : nullptr; +} + +void nsComputedDOMStyle::UpdateCurrentStyleSources(nsCSSPropertyID aPropID) { + nsCOMPtr<Document> document = do_QueryReferent(mDocumentWeak); + if (!document) { + ClearComputedStyle(); + return; + } + + // We don't return styles for disconnected elements anymore, so don't go + // through the trouble of flushing or what not. + // + // TODO(emilio): We may want to return earlier for elements outside of the + // flat tree too: https://github.com/w3c/csswg-drafts/issues/1964 + if (!mElement->IsInComposedDoc()) { + ClearComputedStyle(); + return; + } + + if (mAlwaysReturnEmpty == AlwaysReturnEmptyStyle::Yes) { + ClearComputedStyle(); + return; + } + + DebugOnly<bool> didFlush = false; + if (NeedsToFlushStyle(aPropID)) { + didFlush = true; + // We look at the frame in NeedsToFlushLayout, so flush frames, not only + // styles. + Flush(*document, FlushType::Frames); + } + + if (NeedsToFlushLayout(aPropID)) { + MOZ_ASSERT(MayNeedToFlushLayout(aPropID)); + didFlush = true; + Flush(*document, FlushType::Layout); +#ifdef DEBUG + mFlushedPendingReflows = true; +#endif + } else { +#ifdef DEBUG + mFlushedPendingReflows = false; +#endif + } + + mPresShell = document->GetPresShell(); + if (!mPresShell || !mPresShell->GetPresContext()) { + ClearComputedStyle(); + return; + } + + // We need to use GetUndisplayedRestyleGeneration instead of + // GetRestyleGeneration, because the caching of mComputedStyle is an + // optimization that is useful only for displayed elements. + // For undisplayed elements we need to take into account any DOM changes that + // might cause a restyle, because Servo will not increase the generation for + // undisplayed elements. + uint64_t currentGeneration = + mPresShell->GetPresContext()->GetUndisplayedRestyleGeneration(); + + if (mComputedStyle && mComputedStyleGeneration == currentGeneration && + mPresShellId == mPresShell->GetPresShellId()) { + // Our cached style is still valid. + return; + } + + mComputedStyle = nullptr; + + // XXX the !mElement->IsHTMLElement(nsGkAtoms::area) check is needed due to + // bug 135040 (to avoid using mPrimaryFrame). Remove it once that's fixed. + if (mStyleType == StyleType::All && + !mElement->IsHTMLElement(nsGkAtoms::area)) { + mOuterFrame = GetOuterFrame(); + mInnerFrame = mOuterFrame; + if (mOuterFrame) { + mInnerFrame = nsLayoutUtils::GetStyleFrame(mOuterFrame); + SetFrameComputedStyle(mInnerFrame->Style(), currentGeneration); + NS_ASSERTION(mComputedStyle, "Frame without style?"); + } + } + + if (!mComputedStyle || MustReresolveStyle(mComputedStyle)) { + PresShell* presShellForContent = mElement->OwnerDoc()->GetPresShell(); + // Need to resolve a style. + RefPtr<const ComputedStyle> resolvedComputedStyle = + DoGetComputedStyleNoFlush( + mElement, mPseudo, + presShellForContent ? presShellForContent : mPresShell, mStyleType); + if (!resolvedComputedStyle) { + ClearComputedStyle(); + return; + } + + // No need to re-get the generation, even though GetComputedStyle + // will flush, since we flushed style at the top of this function. + // We don't need to check this if we only flushed the parent. + NS_ASSERTION( + !didFlush || + currentGeneration == + mPresShell->GetPresContext()->GetUndisplayedRestyleGeneration(), + "why should we have flushed style again?"); + + SetResolvedComputedStyle(std::move(resolvedComputedStyle), + currentGeneration); + NS_ASSERTION(mPseudo != PseudoStyleType::NotPseudo || + !mComputedStyle->HasPseudoElementData(), + "should not have pseudo-element data"); + } + + // mExposeVisitedStyle is set to true only by testing APIs that + // require chrome privilege. + MOZ_ASSERT(!mExposeVisitedStyle || nsContentUtils::IsCallerChrome(), + "mExposeVisitedStyle set incorrectly"); + if (mExposeVisitedStyle && mComputedStyle->RelevantLinkVisited()) { + if (const auto* styleIfVisited = mComputedStyle->GetStyleIfVisited()) { + mComputedStyle = styleIfVisited; + } + } +} + +void nsComputedDOMStyle::ClearCurrentStyleSources() { + // Release the current style if we got it off the frame. + // + // For a style we resolved, keep it around so that we can re-use it next time + // this object is queried, but not if it-s a re-resolved style because we were + // inside a pseudo-element. + if (!mResolvedComputedStyle || mOuterFrame) { + ClearComputedStyle(); + } + + mOuterFrame = nullptr; + mInnerFrame = nullptr; + mPresShell = nullptr; +} + +void nsComputedDOMStyle::RemoveProperty(const nsACString& aPropertyName, + nsACString& aReturn, ErrorResult& aRv) { + // Note: not using nsPrintfCString here in case aPropertyName contains + // nulls. + aRv.ThrowNoModificationAllowedError("Can't remove property '"_ns + + aPropertyName + + "' from computed style"_ns); +} + +void nsComputedDOMStyle::GetPropertyPriority(const nsACString& aPropertyName, + nsACString& aReturn) { + aReturn.Truncate(); +} + +void nsComputedDOMStyle::SetProperty(const nsACString& aPropertyName, + const nsACString& aValue, + const nsACString& aPriority, + nsIPrincipal* aSubjectPrincipal, + ErrorResult& aRv) { + // Note: not using nsPrintfCString here in case aPropertyName contains + // nulls. + aRv.ThrowNoModificationAllowedError("Can't set value for property '"_ns + + aPropertyName + "' in computed style"_ns); +} + +void nsComputedDOMStyle::IndexedGetter(uint32_t aIndex, bool& aFound, + nsACString& aPropName) { + ComputedStyleMap* map = GetComputedStyleMap(); + uint32_t length = map->Length(); + + if (aIndex < length) { + aFound = true; + aPropName.Assign(nsCSSProps::GetStringValue(map->PropertyAt(aIndex))); + return; + } + + // Custom properties are exposed with indexed properties just after all + // of the built-in properties. + UpdateCurrentStyleSources(eCSSPropertyExtra_variable); + if (!mComputedStyle) { + aFound = false; + return; + } + + uint32_t count = Servo_GetCustomPropertiesCount(mComputedStyle); + + const uint32_t index = aIndex - length; + if (index < count) { + aFound = true; + aPropName.AssignLiteral("--"); + if (nsAtom* atom = Servo_GetCustomPropertyNameAt(mComputedStyle, index)) { + aPropName.Append(nsAtomCString(atom)); + } + } else { + aFound = false; + } + + ClearCurrentStyleSources(); +} + +// Property getters... + +already_AddRefed<CSSValue> nsComputedDOMStyle::DoGetBottom() { + return GetOffsetWidthFor(eSideBottom); +} + +static Position MaybeResolvePositionForTransform(const LengthPercentage& aX, + const LengthPercentage& aY, + nsIFrame* aInnerFrame) { + if (!aInnerFrame) { + return {aX, aY}; + } + nsStyleTransformMatrix::TransformReferenceBox refBox(aInnerFrame); + CSSPoint p = nsStyleTransformMatrix::Convert2DPosition(aX, aY, refBox); + return {LengthPercentage::FromPixels(p.x), LengthPercentage::FromPixels(p.y)}; +} + +/* Convert the stored representation into a list of two values and then hand + * it back. + */ +already_AddRefed<CSSValue> nsComputedDOMStyle::DoGetTransformOrigin() { + /* We need to build up a list of two values. We'll call them + * width and height. + */ + + /* Store things as a value list */ + RefPtr<nsDOMCSSValueList> valueList = GetROCSSValueList(false); + + /* Now, get the values. */ + const auto& origin = StyleDisplay()->mTransformOrigin; + + RefPtr<nsROCSSPrimitiveValue> width = new nsROCSSPrimitiveValue; + auto position = MaybeResolvePositionForTransform( + origin.horizontal, origin.vertical, mInnerFrame); + SetValueToPosition(position, valueList); + if (!origin.depth.IsZero()) { + RefPtr<nsROCSSPrimitiveValue> depth = new nsROCSSPrimitiveValue; + depth->SetPixels(origin.depth.ToCSSPixels()); + valueList->AppendCSSValue(depth.forget()); + } + return valueList.forget(); +} + +/* Convert the stored representation into a list of two values and then hand + * it back. + */ +already_AddRefed<CSSValue> nsComputedDOMStyle::DoGetPerspectiveOrigin() { + /* We need to build up a list of two values. We'll call them + * width and height. + */ + + /* Store things as a value list */ + RefPtr<nsDOMCSSValueList> valueList = GetROCSSValueList(false); + + /* Now, get the values. */ + const auto& origin = StyleDisplay()->mPerspectiveOrigin; + + auto position = MaybeResolvePositionForTransform( + origin.horizontal, origin.vertical, mInnerFrame); + SetValueToPosition(position, valueList); + return valueList.forget(); +} + +already_AddRefed<CSSValue> nsComputedDOMStyle::DoGetTransform() { + const nsStyleDisplay* display = StyleDisplay(); + return GetTransformValue(display->mTransform); +} + +/* static */ +already_AddRefed<nsROCSSPrimitiveValue> nsComputedDOMStyle::MatrixToCSSValue( + const mozilla::gfx::Matrix4x4& matrix) { + bool is3D = !matrix.Is2D(); + + nsAutoString resultString(u"matrix"_ns); + if (is3D) { + resultString.AppendLiteral("3d"); + } + + resultString.Append('('); + resultString.AppendFloat(matrix._11); + resultString.AppendLiteral(", "); + resultString.AppendFloat(matrix._12); + resultString.AppendLiteral(", "); + if (is3D) { + resultString.AppendFloat(matrix._13); + resultString.AppendLiteral(", "); + resultString.AppendFloat(matrix._14); + resultString.AppendLiteral(", "); + } + resultString.AppendFloat(matrix._21); + resultString.AppendLiteral(", "); + resultString.AppendFloat(matrix._22); + resultString.AppendLiteral(", "); + if (is3D) { + resultString.AppendFloat(matrix._23); + resultString.AppendLiteral(", "); + resultString.AppendFloat(matrix._24); + resultString.AppendLiteral(", "); + resultString.AppendFloat(matrix._31); + resultString.AppendLiteral(", "); + resultString.AppendFloat(matrix._32); + resultString.AppendLiteral(", "); + resultString.AppendFloat(matrix._33); + resultString.AppendLiteral(", "); + resultString.AppendFloat(matrix._34); + resultString.AppendLiteral(", "); + } + resultString.AppendFloat(matrix._41); + resultString.AppendLiteral(", "); + resultString.AppendFloat(matrix._42); + if (is3D) { + resultString.AppendLiteral(", "); + resultString.AppendFloat(matrix._43); + resultString.AppendLiteral(", "); + resultString.AppendFloat(matrix._44); + } + resultString.Append(')'); + + /* Create a value to hold our result. */ + RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue; + + val->SetString(resultString); + return val.forget(); +} + +already_AddRefed<CSSValue> nsComputedDOMStyle::DoGetMozOsxFontSmoothing() { + if (nsContentUtils::ShouldResistFingerprinting( + mPresShell->GetPresContext()->GetDocShell(), RFPTarget::Unknown)) { + return nullptr; + } + + nsAutoCString result; + mComputedStyle->GetComputedPropertyValue(eCSSProperty__moz_osx_font_smoothing, + result); + RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue; + val->SetString(result); + return val.forget(); +} + +already_AddRefed<CSSValue> nsComputedDOMStyle::DoGetImageLayerPosition( + const nsStyleImageLayers& aLayers) { + if (aLayers.mPositionXCount != aLayers.mPositionYCount) { + // No value to return. We can't express this combination of + // values as a shorthand. + return nullptr; + } + + RefPtr<nsDOMCSSValueList> valueList = GetROCSSValueList(true); + for (uint32_t i = 0, i_end = aLayers.mPositionXCount; i < i_end; ++i) { + RefPtr<nsDOMCSSValueList> itemList = GetROCSSValueList(false); + + SetValueToPosition(aLayers.mLayers[i].mPosition, itemList); + valueList->AppendCSSValue(itemList.forget()); + } + + return valueList.forget(); +} + +void nsComputedDOMStyle::SetValueToPosition(const Position& aPosition, + nsDOMCSSValueList* aValueList) { + RefPtr<nsROCSSPrimitiveValue> valX = new nsROCSSPrimitiveValue; + SetValueToLengthPercentage(valX, aPosition.horizontal, false); + aValueList->AppendCSSValue(valX.forget()); + + RefPtr<nsROCSSPrimitiveValue> valY = new nsROCSSPrimitiveValue; + SetValueToLengthPercentage(valY, aPosition.vertical, false); + aValueList->AppendCSSValue(valY.forget()); +} + +void nsComputedDOMStyle::SetValueToURLValue(const StyleComputedUrl* aURL, + nsROCSSPrimitiveValue* aValue) { + if (!aURL) { + aValue->SetString("none"); + return; + } + + // If we have a usable nsIURI in the URLValue, and the url() wasn't + // a fragment-only URL, serialize the nsIURI. + if (!aURL->IsLocalRef()) { + if (nsIURI* uri = aURL->GetURI()) { + aValue->SetURI(uri); + return; + } + } + + // Otherwise, serialize the specified URL value. + NS_ConvertUTF8toUTF16 source(aURL->SpecifiedSerialization()); + nsAutoString url; + url.AppendLiteral(u"url("); + nsStyleUtil::AppendEscapedCSSString(source, url, '"'); + url.Append(')'); + aValue->SetString(url); +} + +enum class Brackets { No, Yes }; + +static void AppendGridLineNames(nsACString& aResult, + Span<const StyleCustomIdent> aLineNames, + Brackets aBrackets) { + if (aLineNames.IsEmpty()) { + if (aBrackets == Brackets::Yes) { + aResult.AppendLiteral("[]"); + } + return; + } + uint32_t numLines = aLineNames.Length(); + if (aBrackets == Brackets::Yes) { + aResult.Append('['); + } + for (uint32_t i = 0;;) { + // TODO: Maybe use servo to do this and avoid the silly utf16->utf8 dance? + nsAutoString name; + nsStyleUtil::AppendEscapedCSSIdent( + nsDependentAtomString(aLineNames[i].AsAtom()), name); + AppendUTF16toUTF8(name, aResult); + + if (++i == numLines) { + break; + } + aResult.Append(' '); + } + if (aBrackets == Brackets::Yes) { + aResult.Append(']'); + } +} + +static void AppendGridLineNames(nsDOMCSSValueList* aValueList, + Span<const StyleCustomIdent> aLineNames, + bool aSuppressEmptyList = true) { + if (aLineNames.IsEmpty() && aSuppressEmptyList) { + return; + } + RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue; + nsAutoCString lineNamesString; + AppendGridLineNames(lineNamesString, aLineNames, Brackets::Yes); + val->SetString(lineNamesString); + aValueList->AppendCSSValue(val.forget()); +} + +static void AppendGridLineNames(nsDOMCSSValueList* aValueList, + Span<const StyleCustomIdent> aLineNames1, + Span<const StyleCustomIdent> aLineNames2) { + if (aLineNames1.IsEmpty() && aLineNames2.IsEmpty()) { + return; + } + RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue; + nsAutoCString lineNamesString; + lineNamesString.Assign('['); + if (!aLineNames1.IsEmpty()) { + AppendGridLineNames(lineNamesString, aLineNames1, Brackets::No); + } + if (!aLineNames2.IsEmpty()) { + if (!aLineNames1.IsEmpty()) { + lineNamesString.Append(' '); + } + AppendGridLineNames(lineNamesString, aLineNames2, Brackets::No); + } + lineNamesString.Append(']'); + val->SetString(lineNamesString); + aValueList->AppendCSSValue(val.forget()); +} + +void nsComputedDOMStyle::SetValueToTrackBreadth( + nsROCSSPrimitiveValue* aValue, const StyleTrackBreadth& aBreadth) { + using Tag = StyleTrackBreadth::Tag; + switch (aBreadth.tag) { + case Tag::MinContent: + return aValue->SetString("min-content"); + case Tag::MaxContent: + return aValue->SetString("max-content"); + case Tag::Auto: + return aValue->SetString("auto"); + case Tag::Breadth: + return SetValueToLengthPercentage(aValue, aBreadth.AsBreadth(), true); + case Tag::Fr: { + nsAutoString tmpStr; + nsStyleUtil::AppendCSSNumber(aBreadth.AsFr(), tmpStr); + tmpStr.AppendLiteral("fr"); + return aValue->SetString(tmpStr); + } + default: + MOZ_ASSERT_UNREACHABLE("Unknown breadth value"); + return; + } +} + +already_AddRefed<nsROCSSPrimitiveValue> nsComputedDOMStyle::GetGridTrackBreadth( + const StyleTrackBreadth& aBreadth) { + RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue; + SetValueToTrackBreadth(val, aBreadth); + return val.forget(); +} + +already_AddRefed<nsROCSSPrimitiveValue> nsComputedDOMStyle::GetGridTrackSize( + const StyleTrackSize& aTrackSize) { + if (aTrackSize.IsFitContent()) { + // A fit-content() function. + RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue; + MOZ_ASSERT(aTrackSize.AsFitContent().IsBreadth(), + "unexpected unit for fit-content() argument value"); + SetValueFromFitContentFunction(val, aTrackSize.AsFitContent().AsBreadth()); + return val.forget(); + } + + if (aTrackSize.IsBreadth()) { + return GetGridTrackBreadth(aTrackSize.AsBreadth()); + } + + MOZ_ASSERT(aTrackSize.IsMinmax()); + auto& min = aTrackSize.AsMinmax()._0; + auto& max = aTrackSize.AsMinmax()._1; + if (min == max) { + return GetGridTrackBreadth(min); + } + + // minmax(auto, <flex>) is equivalent to (and is our internal representation + // of) <flex>, and both compute to <flex> + if (min.IsAuto() && max.IsFr()) { + return GetGridTrackBreadth(max); + } + + nsAutoString argumentStr, minmaxStr; + minmaxStr.AppendLiteral("minmax("); + + { + RefPtr<nsROCSSPrimitiveValue> argValue = GetGridTrackBreadth(min); + argValue->GetCssText(argumentStr, IgnoreErrors()); + minmaxStr.Append(argumentStr); + argumentStr.Truncate(); + } + + minmaxStr.AppendLiteral(", "); + + { + RefPtr<nsROCSSPrimitiveValue> argValue = GetGridTrackBreadth(max); + argValue->GetCssText(argumentStr, IgnoreErrors()); + minmaxStr.Append(argumentStr); + } + + minmaxStr.Append(char16_t(')')); + RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue; + val->SetString(minmaxStr); + return val.forget(); +} + +already_AddRefed<CSSValue> nsComputedDOMStyle::GetGridTemplateColumnsRows( + const StyleGridTemplateComponent& aTrackList, + const ComputedGridTrackInfo& aTrackInfo) { + if (aTrackInfo.mIsMasonry) { + RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue; + val->SetString("masonry"); + return val.forget(); + } + + if (aTrackInfo.mIsSubgrid) { + RefPtr<nsDOMCSSValueList> valueList = GetROCSSValueList(false); + RefPtr<nsROCSSPrimitiveValue> subgridKeyword = new nsROCSSPrimitiveValue; + subgridKeyword->SetString("subgrid"); + valueList->AppendCSSValue(subgridKeyword.forget()); + for (const auto& lineNames : aTrackInfo.mResolvedLineNames) { + AppendGridLineNames(valueList, lineNames, /*aSuppressEmptyList*/ false); + } + uint32_t line = aTrackInfo.mResolvedLineNames.Length(); + uint32_t lastLine = aTrackInfo.mNumExplicitTracks + 1; + const Span<const StyleCustomIdent> empty; + for (; line < lastLine; ++line) { + AppendGridLineNames(valueList, empty, /*aSuppressEmptyList*/ false); + } + return valueList.forget(); + } + + const bool serializeImplicit = + StaticPrefs::layout_css_serialize_grid_implicit_tracks(); + + const nsTArray<nscoord>& trackSizes = aTrackInfo.mSizes; + const uint32_t numExplicitTracks = aTrackInfo.mNumExplicitTracks; + const uint32_t numLeadingImplicitTracks = + aTrackInfo.mNumLeadingImplicitTracks; + uint32_t numSizes = trackSizes.Length(); + MOZ_ASSERT(numSizes >= numLeadingImplicitTracks + numExplicitTracks); + + const bool hasTracksToSerialize = + serializeImplicit ? !!numSizes : !!numExplicitTracks; + const bool hasRepeatAuto = aTrackList.HasRepeatAuto(); + if (!hasTracksToSerialize && !hasRepeatAuto) { + RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue; + val->SetString("none"); + return val.forget(); + } + + // We've done layout on the grid and have resolved the sizes of its tracks, + // so we'll return those sizes here. The grid spec says we MAY use + // repeat(<positive-integer>, Npx) here for consecutive tracks with the same + // size, but that doesn't seem worth doing since even for repeat(auto-*) + // the resolved size might differ for the repeated tracks. + RefPtr<nsDOMCSSValueList> valueList = GetROCSSValueList(false); + + // Add any leading implicit tracks. + if (serializeImplicit) { + for (uint32_t i = 0; i < numLeadingImplicitTracks; ++i) { + RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue; + val->SetAppUnits(trackSizes[i]); + valueList->AppendCSSValue(val.forget()); + } + } + + if (hasRepeatAuto) { + const auto* const autoRepeatValue = aTrackList.GetRepeatAutoValue(); + const auto repeatLineNames = autoRepeatValue->line_names.AsSpan(); + MOZ_ASSERT(repeatLineNames.Length() >= 2); + // Number of tracks inside the repeat, not including any repetitions. + // Check that if we have truncated the number of tracks due to overflowing + // the maximum track limit then we also truncate this repeat count. + MOZ_ASSERT(repeatLineNames.Length() == + autoRepeatValue->track_sizes.len + 1); + // If we have truncated the first repetition of repeat tracks, then we + // can't index using autoRepeatValue->track_sizes.len, and + // aTrackInfo.mRemovedRepeatTracks.Length() will account for all repeat + // tracks that haven't been truncated. + const uint32_t numRepeatTracks = + std::min(aTrackInfo.mRemovedRepeatTracks.Length(), + autoRepeatValue->track_sizes.len); + MOZ_ASSERT(repeatLineNames.Length() >= numRepeatTracks + 1); + // The total of all tracks in all repetitions of the repeat. + const uint32_t totalNumRepeatTracks = + aTrackInfo.mRemovedRepeatTracks.Length(); + const uint32_t repeatStart = aTrackInfo.mRepeatFirstTrack; + // We need to skip over any track sizes which were resolved to 0 by + // collapsed tracks. Keep track of the iteration separately. + const auto explicitTrackSizeBegin = + trackSizes.cbegin() + numLeadingImplicitTracks; + const auto explicitTrackSizeEnd = + explicitTrackSizeBegin + numExplicitTracks; + auto trackSizeIter = explicitTrackSizeBegin; + // Write any leading explicit tracks before the repeat. + for (uint32_t i = 0; i < repeatStart; i++) { + AppendGridLineNames(valueList, aTrackInfo.mResolvedLineNames[i]); + RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue; + val->SetAppUnits(*trackSizeIter++); + valueList->AppendCSSValue(val.forget()); + } + auto lineNameIter = aTrackInfo.mResolvedLineNames.cbegin() + repeatStart; + // Write the track names at the start of the repeat, including the names + // at the end of the last non-repeat track. Unlike all later repeat line + // name lists, this one needs the resolved line name which includes both + // the last non-repeat line names and the leading repeat line names. + AppendGridLineNames(valueList, *lineNameIter++); + { + // Write out the first repeat value, checking for size zero (removed + // track). + const nscoord firstRepeatTrackSize = + (!aTrackInfo.mRemovedRepeatTracks[0]) ? *trackSizeIter++ : 0; + RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue; + val->SetAppUnits(firstRepeatTrackSize); + valueList->AppendCSSValue(val.forget()); + } + // Write the line names and track sizes inside the repeat, checking for + // removed tracks (size 0). + for (uint32_t i = 1; i < totalNumRepeatTracks; i++) { + const uint32_t repeatIndex = i % numRepeatTracks; + // If we are rolling over from one repetition to the next, include track + // names from both the end of the previous repeat and the start of the + // next. + if (repeatIndex == 0) { + AppendGridLineNames(valueList, + repeatLineNames[numRepeatTracks].AsSpan(), + repeatLineNames[0].AsSpan()); + } else { + AppendGridLineNames(valueList, repeatLineNames[repeatIndex].AsSpan()); + } + MOZ_ASSERT(aTrackInfo.mRemovedRepeatTracks[i] || + trackSizeIter != explicitTrackSizeEnd); + const nscoord repeatTrackSize = + (!aTrackInfo.mRemovedRepeatTracks[i]) ? *trackSizeIter++ : 0; + RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue; + val->SetAppUnits(repeatTrackSize); + valueList->AppendCSSValue(val.forget()); + } + // The resolved line names include a single repetition of the auto-repeat + // line names. Skip over those. + lineNameIter += numRepeatTracks - 1; + // Write out any more tracks after the repeat. + while (trackSizeIter != explicitTrackSizeEnd) { + AppendGridLineNames(valueList, *lineNameIter++); + RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue; + val->SetAppUnits(*trackSizeIter++); + valueList->AppendCSSValue(val.forget()); + } + // Write the final trailing line name. + AppendGridLineNames(valueList, *lineNameIter++); + } else if (numExplicitTracks > 0) { + // If there are explicit tracks but no repeat tracks, just serialize those. + for (uint32_t i = 0;; i++) { + AppendGridLineNames(valueList, aTrackInfo.mResolvedLineNames[i]); + if (i == numExplicitTracks) { + break; + } + RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue; + val->SetAppUnits(trackSizes[i + numLeadingImplicitTracks]); + valueList->AppendCSSValue(val.forget()); + } + } + // Add any trailing implicit tracks. + if (serializeImplicit) { + for (uint32_t i = numLeadingImplicitTracks + numExplicitTracks; + i < numSizes; ++i) { + RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue; + val->SetAppUnits(trackSizes[i]); + valueList->AppendCSSValue(val.forget()); + } + } + + return valueList.forget(); +} + +already_AddRefed<CSSValue> nsComputedDOMStyle::DoGetGridTemplateColumns() { + nsGridContainerFrame* gridFrame = + nsGridContainerFrame::GetGridFrameWithComputedInfo(mInnerFrame); + if (!gridFrame) { + // The element doesn't have a box - return the computed value. + // https://drafts.csswg.org/css-grid/#resolved-track-list + nsAutoCString string; + mComputedStyle->GetComputedPropertyValue(eCSSProperty_grid_template_columns, + string); + RefPtr<nsROCSSPrimitiveValue> value = new nsROCSSPrimitiveValue; + value->SetString(string); + return value.forget(); + } + + // GetGridFrameWithComputedInfo() above ensures that this returns non-null: + const ComputedGridTrackInfo* info = gridFrame->GetComputedTemplateColumns(); + return GetGridTemplateColumnsRows(StylePosition()->mGridTemplateColumns, + *info); +} + +already_AddRefed<CSSValue> nsComputedDOMStyle::DoGetGridTemplateRows() { + nsGridContainerFrame* gridFrame = + nsGridContainerFrame::GetGridFrameWithComputedInfo(mInnerFrame); + if (!gridFrame) { + // The element doesn't have a box - return the computed value. + // https://drafts.csswg.org/css-grid/#resolved-track-list + nsAutoCString string; + mComputedStyle->GetComputedPropertyValue(eCSSProperty_grid_template_rows, + string); + RefPtr<nsROCSSPrimitiveValue> value = new nsROCSSPrimitiveValue; + value->SetString(string); + return value.forget(); + } + + // GetGridFrameWithComputedInfo() above ensures that this returns non-null: + const ComputedGridTrackInfo* info = gridFrame->GetComputedTemplateRows(); + return GetGridTemplateColumnsRows(StylePosition()->mGridTemplateRows, *info); +} + +already_AddRefed<CSSValue> nsComputedDOMStyle::DoGetPaddingTop() { + return GetPaddingWidthFor(eSideTop); +} + +already_AddRefed<CSSValue> nsComputedDOMStyle::DoGetPaddingBottom() { + return GetPaddingWidthFor(eSideBottom); +} + +already_AddRefed<CSSValue> nsComputedDOMStyle::DoGetPaddingLeft() { + return GetPaddingWidthFor(eSideLeft); +} + +already_AddRefed<CSSValue> nsComputedDOMStyle::DoGetPaddingRight() { + return GetPaddingWidthFor(eSideRight); +} + +already_AddRefed<CSSValue> nsComputedDOMStyle::DoGetBorderSpacing() { + RefPtr<nsDOMCSSValueList> valueList = GetROCSSValueList(false); + + RefPtr<nsROCSSPrimitiveValue> xSpacing = new nsROCSSPrimitiveValue; + RefPtr<nsROCSSPrimitiveValue> ySpacing = new nsROCSSPrimitiveValue; + + const nsStyleTableBorder* border = StyleTableBorder(); + xSpacing->SetAppUnits(border->mBorderSpacingCol); + ySpacing->SetAppUnits(border->mBorderSpacingRow); + + valueList->AppendCSSValue(xSpacing.forget()); + valueList->AppendCSSValue(ySpacing.forget()); + + return valueList.forget(); +} + +already_AddRefed<CSSValue> nsComputedDOMStyle::DoGetBorderTopWidth() { + return GetBorderWidthFor(eSideTop); +} + +already_AddRefed<CSSValue> nsComputedDOMStyle::DoGetBorderBottomWidth() { + return GetBorderWidthFor(eSideBottom); +} + +already_AddRefed<CSSValue> nsComputedDOMStyle::DoGetBorderLeftWidth() { + return GetBorderWidthFor(eSideLeft); +} + +already_AddRefed<CSSValue> nsComputedDOMStyle::DoGetBorderRightWidth() { + return GetBorderWidthFor(eSideRight); +} + +already_AddRefed<CSSValue> nsComputedDOMStyle::DoGetMarginTop() { + return GetMarginFor(eSideTop); +} + +already_AddRefed<CSSValue> nsComputedDOMStyle::DoGetMarginBottom() { + return GetMarginFor(eSideBottom); +} + +already_AddRefed<CSSValue> nsComputedDOMStyle::DoGetMarginLeft() { + return GetMarginFor(eSideLeft); +} + +already_AddRefed<CSSValue> nsComputedDOMStyle::DoGetMarginRight() { + return GetMarginFor(eSideRight); +} + +already_AddRefed<CSSValue> nsComputedDOMStyle::DoGetHeight() { + RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue; + + if (mInnerFrame && !IsNonReplacedInline(mInnerFrame)) { + AssertFlushedPendingReflows(); + nsMargin adjustedValues = GetAdjustedValuesForBoxSizing(); + val->SetAppUnits(mInnerFrame->GetContentRect().height + + adjustedValues.TopBottom()); + } else { + SetValueToSize(val, StylePosition()->mHeight); + } + + return val.forget(); +} + +already_AddRefed<CSSValue> nsComputedDOMStyle::DoGetWidth() { + RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue; + + if (mInnerFrame && !IsNonReplacedInline(mInnerFrame)) { + AssertFlushedPendingReflows(); + nsMargin adjustedValues = GetAdjustedValuesForBoxSizing(); + val->SetAppUnits(mInnerFrame->GetContentRect().width + + adjustedValues.LeftRight()); + } else { + SetValueToSize(val, StylePosition()->mWidth); + } + + return val.forget(); +} + +already_AddRefed<CSSValue> nsComputedDOMStyle::DoGetMaxHeight() { + RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue; + SetValueToMaxSize(val, StylePosition()->mMaxHeight); + return val.forget(); +} + +already_AddRefed<CSSValue> nsComputedDOMStyle::DoGetMaxWidth() { + RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue; + SetValueToMaxSize(val, StylePosition()->mMaxWidth); + return val.forget(); +} + +/** + * This function indicates whether we should return "auto" as the + * getComputedStyle() result for the (default) "min-width: auto" and + * "min-height: auto" CSS values. + * + * As of this writing, the CSS Sizing draft spec says this "auto" value + * *always* computes to itself. However, for now, we only make it compute to + * itself for grid and flex items (the containers where "auto" has special + * significance), because those are the only areas where the CSSWG has actually + * resolved on this "computes-to-itself" behavior. For elements in other sorts + * of containers, this function returns false, which will make us resolve + * "auto" to 0. + */ +bool nsComputedDOMStyle::ShouldHonorMinSizeAutoInAxis(PhysicalAxis aAxis) { + return mOuterFrame && mOuterFrame->IsFlexOrGridItem(); +} + +already_AddRefed<CSSValue> nsComputedDOMStyle::DoGetMinHeight() { + RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue; + StyleSize minHeight = StylePosition()->mMinHeight; + + if (minHeight.IsAuto() && !ShouldHonorMinSizeAutoInAxis(eAxisVertical)) { + minHeight = StyleSize::LengthPercentage(LengthPercentage::Zero()); + } + + SetValueToSize(val, minHeight); + return val.forget(); +} + +already_AddRefed<CSSValue> nsComputedDOMStyle::DoGetMinWidth() { + RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue; + + StyleSize minWidth = StylePosition()->mMinWidth; + + if (minWidth.IsAuto() && !ShouldHonorMinSizeAutoInAxis(eAxisHorizontal)) { + minWidth = StyleSize::LengthPercentage(LengthPercentage::Zero()); + } + + SetValueToSize(val, minWidth); + return val.forget(); +} + +already_AddRefed<CSSValue> nsComputedDOMStyle::DoGetLeft() { + return GetOffsetWidthFor(eSideLeft); +} + +already_AddRefed<CSSValue> nsComputedDOMStyle::DoGetRight() { + return GetOffsetWidthFor(eSideRight); +} + +already_AddRefed<CSSValue> nsComputedDOMStyle::DoGetTop() { + return GetOffsetWidthFor(eSideTop); +} + +already_AddRefed<CSSValue> nsComputedDOMStyle::GetOffsetWidthFor( + mozilla::Side aSide) { + const nsStyleDisplay* display = StyleDisplay(); + + mozilla::StylePositionProperty position = display->mPosition; + if (!mOuterFrame) { + // GetNonStaticPositionOffset or GetAbsoluteOffset don't handle elements + // without frames in any sensible way. GetStaticOffset, however, is perfect + // for that case. + position = StylePositionProperty::Static; + } + + switch (position) { + case StylePositionProperty::Static: + return GetStaticOffset(aSide); + case StylePositionProperty::Sticky: + return GetNonStaticPositionOffset( + aSide, false, &nsComputedDOMStyle::GetScrollFrameContentWidth, + &nsComputedDOMStyle::GetScrollFrameContentHeight); + case StylePositionProperty::Absolute: + case StylePositionProperty::Fixed: + return GetAbsoluteOffset(aSide); + case StylePositionProperty::Relative: + return GetNonStaticPositionOffset( + aSide, true, &nsComputedDOMStyle::GetCBContentWidth, + &nsComputedDOMStyle::GetCBContentHeight); + default: + MOZ_ASSERT_UNREACHABLE("Invalid position"); + return nullptr; + } +} + +static_assert(eSideTop == 0 && eSideRight == 1 && eSideBottom == 2 && + eSideLeft == 3, + "box side constants not as expected for NS_OPPOSITE_SIDE"); +#define NS_OPPOSITE_SIDE(s_) mozilla::Side(((s_) + 2) & 3) + +already_AddRefed<CSSValue> nsComputedDOMStyle::GetNonStaticPositionOffset( + mozilla::Side aSide, bool aResolveAuto, PercentageBaseGetter aWidthGetter, + PercentageBaseGetter aHeightGetter) { + RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue; + + const nsStylePosition* positionData = StylePosition(); + int32_t sign = 1; + LengthPercentageOrAuto coord = positionData->mOffset.Get(aSide); + + if (coord.IsAuto()) { + if (!aResolveAuto) { + val->SetString("auto"); + return val.forget(); + } + coord = positionData->mOffset.Get(NS_OPPOSITE_SIDE(aSide)); + sign = -1; + } + if (!coord.IsLengthPercentage()) { + val->SetPixels(0.0f); + return val.forget(); + } + + auto& lp = coord.AsLengthPercentage(); + if (lp.ConvertsToLength()) { + val->SetPixels(sign * lp.ToLengthInCSSPixels()); + return val.forget(); + } + + PercentageBaseGetter baseGetter = (aSide == eSideLeft || aSide == eSideRight) + ? aWidthGetter + : aHeightGetter; + nscoord percentageBase; + if (!(this->*baseGetter)(percentageBase)) { + val->SetPixels(0.0f); + return val.forget(); + } + nscoord result = lp.Resolve(percentageBase); + val->SetAppUnits(sign * result); + return val.forget(); +} + +already_AddRefed<CSSValue> nsComputedDOMStyle::GetAbsoluteOffset( + mozilla::Side aSide) { + const auto& offset = StylePosition()->mOffset; + const auto& coord = offset.Get(aSide); + const auto& oppositeCoord = offset.Get(NS_OPPOSITE_SIDE(aSide)); + + if (coord.IsAuto() || oppositeCoord.IsAuto()) { + RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue; + val->SetAppUnits(GetUsedAbsoluteOffset(aSide)); + return val.forget(); + } + + return GetNonStaticPositionOffset( + aSide, false, &nsComputedDOMStyle::GetCBPaddingRectWidth, + &nsComputedDOMStyle::GetCBPaddingRectHeight); +} + +nscoord nsComputedDOMStyle::GetUsedAbsoluteOffset(mozilla::Side aSide) { + MOZ_ASSERT(mOuterFrame, "need a frame, so we can call GetContainingBlock()"); + + nsIFrame* container = mOuterFrame->GetContainingBlock(); + nsMargin margin = mOuterFrame->GetUsedMargin(); + nsMargin border = container->GetUsedBorder(); + nsMargin scrollbarSizes(0, 0, 0, 0); + nsRect rect = mOuterFrame->GetRect(); + nsRect containerRect = container->GetRect(); + + if (container->IsViewportFrame()) { + // For absolutely positioned frames scrollbars are taken into + // account by virtue of getting a containing block that does + // _not_ include the scrollbars. For fixed positioned frames, + // the containing block is the viewport, which _does_ include + // scrollbars. We have to do some extra work. + // the first child in the default frame list is what we want + nsIFrame* scrollingChild = container->PrincipalChildList().FirstChild(); + nsIScrollableFrame* scrollFrame = do_QueryFrame(scrollingChild); + if (scrollFrame) { + scrollbarSizes = scrollFrame->GetActualScrollbarSizes(); + } + + // The viewport size might have been expanded by the visual viewport or + // the minimum-scale size. + const ViewportFrame* viewportFrame = do_QueryFrame(container); + MOZ_ASSERT(viewportFrame); + containerRect.SizeTo( + viewportFrame->AdjustViewportSizeForFixedPosition(containerRect)); + } else if (container->IsGridContainerFrame() && + mOuterFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) { + containerRect = nsGridContainerFrame::GridItemCB(mOuterFrame); + rect.MoveBy(-containerRect.x, -containerRect.y); + } + + nscoord offset = 0; + switch (aSide) { + case eSideTop: + offset = rect.y - margin.top - border.top - scrollbarSizes.top; + + break; + case eSideRight: + offset = containerRect.width - rect.width - rect.x - margin.right - + border.right - scrollbarSizes.right; + + break; + case eSideBottom: + offset = containerRect.height - rect.height - rect.y - margin.bottom - + border.bottom - scrollbarSizes.bottom; + + break; + case eSideLeft: + offset = rect.x - margin.left - border.left - scrollbarSizes.left; + + break; + default: + NS_ERROR("Invalid side"); + break; + } + + return offset; +} + +already_AddRefed<CSSValue> nsComputedDOMStyle::GetStaticOffset( + mozilla::Side aSide) { + RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue; + SetValueToLengthPercentageOrAuto(val, StylePosition()->mOffset.Get(aSide), + false); + return val.forget(); +} + +already_AddRefed<CSSValue> nsComputedDOMStyle::GetPaddingWidthFor( + mozilla::Side aSide) { + RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue; + + auto& padding = StylePadding()->mPadding.Get(aSide); + if (!mInnerFrame || !PaddingNeedsUsedValue(padding, *mComputedStyle)) { + SetValueToLengthPercentage(val, padding, true); + } else { + AssertFlushedPendingReflows(); + val->SetAppUnits(mInnerFrame->GetUsedPadding().Side(aSide)); + } + + return val.forget(); +} + +already_AddRefed<CSSValue> nsComputedDOMStyle::GetBorderWidthFor( + mozilla::Side aSide) { + RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue; + + nscoord width; + if (mInnerFrame && mComputedStyle->StyleDisplay()->HasAppearance()) { + AssertFlushedPendingReflows(); + width = mInnerFrame->GetUsedBorder().Side(aSide); + } else { + width = StyleBorder()->GetComputedBorderWidth(aSide); + } + val->SetAppUnits(width); + + return val.forget(); +} + +already_AddRefed<CSSValue> nsComputedDOMStyle::GetMarginFor(Side aSide) { + RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue; + + auto& margin = StyleMargin()->mMargin.Get(aSide); + if (!mInnerFrame || margin.ConvertsToLength()) { + SetValueToLengthPercentageOrAuto(val, margin, false); + } else { + AssertFlushedPendingReflows(); + + // For tables, GetUsedMargin always returns an empty margin, so we + // should read the margin from the table wrapper frame instead. + val->SetAppUnits(mOuterFrame->GetUsedMargin().Side(aSide)); + NS_ASSERTION(mOuterFrame == mInnerFrame || + mInnerFrame->GetUsedMargin() == nsMargin(0, 0, 0, 0), + "Inner tables must have zero margins"); + } + + return val.forget(); +} + +static void SetValueToExtremumLength(nsROCSSPrimitiveValue* aValue, + nsIFrame::ExtremumLength aSize) { + switch (aSize) { + case nsIFrame::ExtremumLength::MaxContent: + return aValue->SetString("max-content"); + case nsIFrame::ExtremumLength::MinContent: + return aValue->SetString("min-content"); + case nsIFrame::ExtremumLength::MozAvailable: + return aValue->SetString("-moz-available"); + case nsIFrame::ExtremumLength::FitContent: + return aValue->SetString("fit-content"); + case nsIFrame::ExtremumLength::FitContentFunction: + MOZ_ASSERT_UNREACHABLE("fit-content() should be handled separately"); + } + MOZ_ASSERT_UNREACHABLE("Unknown extremum length?"); +} + +void nsComputedDOMStyle::SetValueFromFitContentFunction( + nsROCSSPrimitiveValue* aValue, const LengthPercentage& aLength) { + nsAutoString argumentStr; + SetValueToLengthPercentage(aValue, aLength, true); + aValue->GetCssText(argumentStr, IgnoreErrors()); + + nsAutoString fitContentStr; + fitContentStr.AppendLiteral("fit-content("); + fitContentStr.Append(argumentStr); + fitContentStr.Append(u')'); + aValue->SetString(fitContentStr); +} + +void nsComputedDOMStyle::SetValueToSize(nsROCSSPrimitiveValue* aValue, + const StyleSize& aSize) { + if (aSize.IsAuto()) { + return aValue->SetString("auto"); + } + if (aSize.IsFitContentFunction()) { + return SetValueFromFitContentFunction(aValue, aSize.AsFitContentFunction()); + } + if (auto length = nsIFrame::ToExtremumLength(aSize)) { + return SetValueToExtremumLength(aValue, *length); + } + MOZ_ASSERT(aSize.IsLengthPercentage()); + SetValueToLengthPercentage(aValue, aSize.AsLengthPercentage(), true); +} + +void nsComputedDOMStyle::SetValueToMaxSize(nsROCSSPrimitiveValue* aValue, + const StyleMaxSize& aSize) { + if (aSize.IsNone()) { + return aValue->SetString("none"); + } + if (aSize.IsFitContentFunction()) { + return SetValueFromFitContentFunction(aValue, aSize.AsFitContentFunction()); + } + if (auto length = nsIFrame::ToExtremumLength(aSize)) { + return SetValueToExtremumLength(aValue, *length); + } + MOZ_ASSERT(aSize.IsLengthPercentage()); + SetValueToLengthPercentage(aValue, aSize.AsLengthPercentage(), true); +} + +void nsComputedDOMStyle::SetValueToLengthPercentageOrAuto( + nsROCSSPrimitiveValue* aValue, const LengthPercentageOrAuto& aSize, + bool aClampNegativeCalc) { + if (aSize.IsAuto()) { + return aValue->SetString("auto"); + } + SetValueToLengthPercentage(aValue, aSize.AsLengthPercentage(), + aClampNegativeCalc); +} + +void nsComputedDOMStyle::SetValueToLengthPercentage( + nsROCSSPrimitiveValue* aValue, const mozilla::LengthPercentage& aLength, + bool aClampNegativeCalc) { + if (aLength.ConvertsToLength()) { + CSSCoord length = aLength.ToLengthInCSSPixels(); + if (aClampNegativeCalc) { + length = std::max(float(length), 0.0f); + } + return aValue->SetPixels(length); + } + if (aLength.ConvertsToPercentage()) { + float result = aLength.ToPercentage(); + if (aClampNegativeCalc) { + result = std::max(result, 0.0f); + } + return aValue->SetPercent(result); + } + + nsAutoCString result; + Servo_LengthPercentage_ToCss(&aLength, &result); + aValue->SetString(result); +} + +bool nsComputedDOMStyle::GetCBContentWidth(nscoord& aWidth) { + if (!mOuterFrame) { + return false; + } + + AssertFlushedPendingReflows(); + + aWidth = mOuterFrame->GetContainingBlock()->GetContentRect().width; + return true; +} + +bool nsComputedDOMStyle::GetCBContentHeight(nscoord& aHeight) { + if (!mOuterFrame) { + return false; + } + + AssertFlushedPendingReflows(); + + aHeight = mOuterFrame->GetContainingBlock()->GetContentRect().height; + return true; +} + +bool nsComputedDOMStyle::GetCBPaddingRectWidth(nscoord& aWidth) { + if (!mOuterFrame) { + return false; + } + + AssertFlushedPendingReflows(); + + aWidth = mOuterFrame->GetContainingBlock()->GetPaddingRect().width; + return true; +} + +bool nsComputedDOMStyle::GetCBPaddingRectHeight(nscoord& aHeight) { + if (!mOuterFrame) { + return false; + } + + AssertFlushedPendingReflows(); + + aHeight = mOuterFrame->GetContainingBlock()->GetPaddingRect().height; + return true; +} + +bool nsComputedDOMStyle::GetScrollFrameContentWidth(nscoord& aWidth) { + if (!mOuterFrame) { + return false; + } + + AssertFlushedPendingReflows(); + + nsIScrollableFrame* scrollableFrame = + nsLayoutUtils::GetNearestScrollableFrame( + mOuterFrame->GetParent(), + nsLayoutUtils::SCROLLABLE_SAME_DOC | + nsLayoutUtils::SCROLLABLE_INCLUDE_HIDDEN); + + if (!scrollableFrame) { + return false; + } + aWidth = + scrollableFrame->GetScrolledFrame()->GetContentRectRelativeToSelf().width; + return true; +} + +bool nsComputedDOMStyle::GetScrollFrameContentHeight(nscoord& aHeight) { + if (!mOuterFrame) { + return false; + } + + AssertFlushedPendingReflows(); + + nsIScrollableFrame* scrollableFrame = + nsLayoutUtils::GetNearestScrollableFrame( + mOuterFrame->GetParent(), + nsLayoutUtils::SCROLLABLE_SAME_DOC | + nsLayoutUtils::SCROLLABLE_INCLUDE_HIDDEN); + + if (!scrollableFrame) { + return false; + } + aHeight = scrollableFrame->GetScrolledFrame() + ->GetContentRectRelativeToSelf() + .height; + return true; +} + +bool nsComputedDOMStyle::GetFrameBorderRectWidth(nscoord& aWidth) { + if (!mInnerFrame) { + return false; + } + + AssertFlushedPendingReflows(); + + aWidth = mInnerFrame->GetSize().width; + return true; +} + +bool nsComputedDOMStyle::GetFrameBorderRectHeight(nscoord& aHeight) { + if (!mInnerFrame) { + return false; + } + + AssertFlushedPendingReflows(); + + aHeight = mInnerFrame->GetSize().height; + return true; +} + +/* If the property is "none", hand back "none" wrapped in a value. + * Otherwise, compute the aggregate transform matrix and hands it back in a + * "matrix" wrapper. + */ +already_AddRefed<CSSValue> nsComputedDOMStyle::GetTransformValue( + const StyleTransform& aTransform) { + /* If there are no transforms, then we should construct a single-element + * entry and hand it back. + */ + if (aTransform.IsNone()) { + RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue; + val->SetString("none"); + return val.forget(); + } + + /* Otherwise, we need to compute the current value of the transform matrix, + * store it in a string, and hand it back to the caller. + */ + + /* Use the inner frame for the reference box. If we don't have an inner + * frame we use empty dimensions to allow us to continue (and percentage + * values in the transform will simply give broken results). + * TODO: There is no good way for us to represent the case where there's no + * frame, which is problematic. The reason is that when we have percentage + * transforms, there are a total of four stored matrix entries that influence + * the transform based on the size of the element. However, this poses a + * problem, because only two of these values can be explicitly referenced + * using the named transforms. Until a real solution is found, we'll just + * use this approach. + */ + nsStyleTransformMatrix::TransformReferenceBox refBox(mInnerFrame, nsRect()); + gfx::Matrix4x4 matrix = nsStyleTransformMatrix::ReadTransforms( + aTransform, refBox, float(mozilla::AppUnitsPerCSSPixel())); + + return MatrixToCSSValue(matrix); +} + +already_AddRefed<CSSValue> nsComputedDOMStyle::DummyGetter() { + MOZ_CRASH("DummyGetter is not supposed to be invoked"); +} + +static void MarkComputedStyleMapDirty(const char* aPref, void* aMap) { + static_cast<ComputedStyleMap*>(aMap)->MarkDirty(); +} + +void nsComputedDOMStyle::ParentChainChanged(nsIContent* aContent) { + NS_ASSERTION(mElement == aContent, "didn't we register mElement?"); + NS_ASSERTION(mResolvedComputedStyle, + "should have only registered an observer when " + "mResolvedComputedStyle is true"); + + ClearComputedStyle(); +} + +/* static */ +ComputedStyleMap* nsComputedDOMStyle::GetComputedStyleMap() { + static ComputedStyleMap map{}; + return ↦ +} + +static StaticAutoPtr<nsTArray<const char*>> gCallbackPrefs; + +/* static */ +void nsComputedDOMStyle::RegisterPrefChangeCallbacks() { + // Note that this will register callbacks for all properties with prefs, not + // just those that are implemented on computed style objects, as it's not + // easy to grab specific property data from ServoCSSPropList.h based on the + // entries iterated in nsComputedDOMStylePropertyList.h. + + AutoTArray<const char*, 64> prefs; + for (const auto* p = nsCSSProps::kPropertyPrefTable; + p->mPropID != eCSSProperty_UNKNOWN; p++) { + // Many properties are controlled by the same preference, so de-duplicate + // them before adding observers. + // + // Note: This is done by pointer comparison, which works because the mPref + // members are string literals from the same same translation unit, and are + // therefore de-duplicated by the compiler. On the off chance that we wind + // up with some duplicates with different pointers, though, it's not a bit + // deal. + if (!prefs.ContainsSorted(p->mPref)) { + prefs.InsertElementSorted(p->mPref); + } + } + + prefs.AppendElement( + StaticPrefs::GetPrefName_layout_css_computed_style_shorthands()); + + prefs.AppendElement(nullptr); + + MOZ_ASSERT(!gCallbackPrefs); + gCallbackPrefs = new nsTArray<const char*>(std::move(prefs)); + + Preferences::RegisterCallbacks(MarkComputedStyleMapDirty, + gCallbackPrefs->Elements(), + GetComputedStyleMap()); +} + +/* static */ +void nsComputedDOMStyle::UnregisterPrefChangeCallbacks() { + if (!gCallbackPrefs) { + return; + } + + Preferences::UnregisterCallbacks(MarkComputedStyleMapDirty, + gCallbackPrefs->Elements(), + GetComputedStyleMap()); + gCallbackPrefs = nullptr; +} |