/* -*- 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 #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. */ already_AddRefed NS_NewComputedDOMStyle( dom::Element* aElement, const nsAString& aPseudoElt, Document* aDocument, nsComputedDOMStyle::StyleType aStyleType, mozilla::ErrorResult&) { auto [pseudo, functionalPseudoParameter] = nsCSSPseudoElements::ParsePseudoElement(aPseudoElt, CSSEnabledState::ForAllContent); auto returnEmpty = nsComputedDOMStyle::AlwaysReturnEmptyStyle::No; if (!pseudo) { if (!aPseudoElt.IsEmpty() && aPseudoElt.First() == u':') { returnEmpty = nsComputedDOMStyle::AlwaysReturnEmptyStyle::Yes; } pseudo.emplace(PseudoStyleType::NotPseudo); } RefPtr computedStyle = new nsComputedDOMStyle(aElement, *pseudo, functionalPseudoParameter, 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 (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, nsAtom* aFunctionalPseudoParameter, Document* aDocument, StyleType aStyleType, AlwaysReturnEmptyStyle aAlwaysEmpty) : mDocumentWeak(nullptr), mOuterFrame(nullptr), mInnerFrame(nullptr), mPresShell(nullptr), mPseudo(aPseudo), mFunctionalPseudoParameter(aFunctionalPseudoParameter), 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 = aDocument; mElement = aElement; SetEnabledCallbacks(nsIMutationObserver::kParentChainChanged); } 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_CYCLE_COLLECTING_ADDREF(nsComputedDOMStyle) NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_LAST_RELEASE(nsComputedDOMStyle, ClearComputedStyle()) void 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; } void nsComputedDOMStyle::GetPropertyValue(const nsACString& aPropertyName, nsACString& aReturn) { nsCSSPropertyID prop = nsCSSProps::LookupProperty(aPropertyName); GetPropertyValue(prop, aPropertyName, aReturn); } void 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; } } UpdateCurrentStyleSources(aPropID); if (!mComputedStyle) { return; } 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, mPresShell->StyleSet()->RawData(), &name, &aReturn); return; } if (nsCSSProps::PropHasFlags(aPropID, CSSPropFlags::IsLogical)) { MOZ_ASSERT(entry); MOZ_ASSERT(entry->mGetter == &nsComputedDOMStyle::DummyGetter); DebugOnly 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 value = (this->*entry->mGetter)()) { nsAutoString text; value->GetCssText(text); CopyUTF16toUTF8(text, aReturn); } return; } MOZ_ASSERT(entry->mGetter == &nsComputedDOMStyle::DummyGetter); Servo_GetResolvedValue(mComputedStyle, aPropID, mPresShell->StyleSet()->RawData(), mElement, &aReturn); } /* static */ already_AddRefed nsComputedDOMStyle::GetComputedStyle( Element* aElement, PseudoStyleType aPseudo, nsAtom* aFunctionalPseudoParameter, StyleType aStyleType) { if (Document* doc = aElement->GetComposedDoc()) { doc->FlushPendingNotifications(FlushType::Style); } return GetComputedStyleNoFlush(aElement, aPseudo, aFunctionalPseudoParameter, 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 nsComputedDOMStyle::DoGetComputedStyleNoFlush( const Element* aElement, PseudoStyleType aPseudo, nsAtom* aFunctionalPseudoParameter, 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 (!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 result = styleSet->ResolveStyleLazily( *aElement, aPseudo, aFunctionalPseudoParameter, rules); return result.forget(); } already_AddRefed nsComputedDOMStyle::GetUnanimatedComputedStyleNoFlush( Element* aElement, PseudoStyleType aPseudo, nsAtom* aFunctionalPseudoParameter) { RefPtr style = GetComputedStyleNoFlush(aElement, aPseudo, aFunctionalPseudoParameter); 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& 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& aURLs) { if (aURL.IsLocalRef()) { return; } if (nsIURI* uri = aURL.GetURI()) { AddImageURL(*uri, aURLs); } } static void AddImageURL(const StyleImage& aImage, nsTArray& aURLs) { if (auto* urlValue = aImage.GetImageRequestURLValue()) { AddImageURL(*urlValue, aURLs); } } static void AddImageURL(const StyleShapeOutside& aShapeOutside, nsTArray& aURLs) { if (aShapeOutside.IsImage()) { AddImageURL(aShapeOutside.AsImage(), aURLs); } } static void AddImageURL(const StyleClipPath& aClipPath, nsTArray& aURLs) { if (aClipPath.IsUrl()) { AddImageURL(aClipPath.AsUrl(), aURLs); } } static void AddImageURLs(const nsStyleImageLayers& aLayers, nsTArray& aURLs) { for (auto i : IntegerRange(aLayers.mLayers.Length())) { AddImageURL(aLayers.mLayers[i].mImage, aURLs); } } static void CollectImageURLsForProperty(nsCSSPropertyID aProp, const ComputedStyle& aStyle, nsTArray& 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; } } float nsComputedDOMStyle::UsedFontSize() { UpdateCurrentStyleSources(eCSSProperty_font_size); if (!mComputedStyle) { return -1.0; } return mComputedStyle->StyleFont()->mFont.size.ToCSSPixels(); } void nsComputedDOMStyle::GetCSSImageURLs(const nsACString& aPropertyName, nsTArray& 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&& 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->IsReplaced() && !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->StyleFont()->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; } } bool nsComputedDOMStyle::NeedsToFlushLayoutForContainerQuery() const { const auto* outerFrame = GetOuterFrame(); if (!outerFrame) { return false; } const auto* innerFrame = nsLayoutUtils::GetStyleFrame(outerFrame); MOZ_ASSERT(innerFrame, "No valid inner frame?"); // It's possible that potential containers are styled but not yet reflowed, // i.e. They don't have a correct size, which makes any container query // evaluation against them invalid. return innerFrame->HasUnreflowedContainerQueryAncestor(); } void nsComputedDOMStyle::Flush(Document& aDocument, FlushType aFlushType) { MOZ_ASSERT(mElement->IsInComposedDoc()); MOZ_ASSERT(mDocumentWeak == &aDocument); if (MOZ_UNLIKELY(&aDocument != mElement->OwnerDoc())) { aDocument.FlushPendingNotifications(aFlushType); } // This performs the flush, and also guarantees that content-visibility: // hidden elements get laid out, if needed. mElement->GetPrimaryFrame(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(mElement->GetProperty(property)); return pseudo ? pseudo->GetPrimaryFrame() : nullptr; } void nsComputedDOMStyle::UpdateCurrentStyleSources(nsCSSPropertyID aPropID) { nsCOMPtr document(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 didFlush = false; if (NeedsToFlushStyle(aPropID)) { didFlush = true; // We look at the frame in NeedsToFlushLayout, so flush frames, not only // styles. Flush(*document, FlushType::Frames); } const bool needsToFlushLayoutForProp = NeedsToFlushLayout(aPropID); if (needsToFlushLayoutForProp || NeedsToFlushLayoutForContainerQuery()) { MOZ_ASSERT_IF(needsToFlushLayoutForProp, 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 resolvedComputedStyle = DoGetComputedStyleNoFlush( mElement, mPseudo, mFunctionalPseudoParameter, 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 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 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 valueList = GetROCSSValueList(false); /* Now, get the values. */ const auto& origin = StyleDisplay()->mTransformOrigin; RefPtr width = new nsROCSSPrimitiveValue; auto position = MaybeResolvePositionForTransform( origin.horizontal, origin.vertical, mInnerFrame); SetValueToPosition(position, valueList); if (!origin.depth.IsZero()) { RefPtr 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 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 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 nsComputedDOMStyle::DoGetTransform() { const nsStyleDisplay* display = StyleDisplay(); return GetTransformValue(display->mTransform); } /* static */ already_AddRefed 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 val = new nsROCSSPrimitiveValue; val->SetString(resultString); return val.forget(); } already_AddRefed nsComputedDOMStyle::DoGetMozOsxFontSmoothing() { if (nsContentUtils::ShouldResistFingerprinting( mPresShell->GetPresContext()->GetDocShell(), RFPTarget::DOMStyleOsxFontSmoothing)) { return nullptr; } nsAutoCString result; mComputedStyle->GetComputedPropertyValue(eCSSProperty__moz_osx_font_smoothing, result); RefPtr val = new nsROCSSPrimitiveValue; val->SetString(result); return val.forget(); } already_AddRefed 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 valueList = GetROCSSValueList(true); for (uint32_t i = 0, i_end = aLayers.mPositionXCount; i < i_end; ++i) { RefPtr 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 valX = new nsROCSSPrimitiveValue; SetValueToLengthPercentage(valX, aPosition.horizontal, false); aValueList->AppendCSSValue(valX.forget()); RefPtr valY = new nsROCSSPrimitiveValue; SetValueToLengthPercentage(valY, aPosition.vertical, false); aValueList->AppendCSSValue(valY.forget()); } enum class Brackets { No, Yes }; static void AppendGridLineNames(nsACString& aResult, Span 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 aLineNames, bool aSuppressEmptyList = true) { if (aLineNames.IsEmpty() && aSuppressEmptyList) { return; } RefPtr val = new nsROCSSPrimitiveValue; nsAutoCString lineNamesString; AppendGridLineNames(lineNamesString, aLineNames, Brackets::Yes); val->SetString(lineNamesString); aValueList->AppendCSSValue(val.forget()); } static void AppendGridLineNames(nsDOMCSSValueList* aValueList, Span aLineNames1, Span aLineNames2) { if (aLineNames1.IsEmpty() && aLineNames2.IsEmpty()) { return; } RefPtr 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 nsComputedDOMStyle::GetGridTrackBreadth( const StyleTrackBreadth& aBreadth) { RefPtr val = new nsROCSSPrimitiveValue; SetValueToTrackBreadth(val, aBreadth); return val.forget(); } already_AddRefed nsComputedDOMStyle::GetGridTrackSize( const StyleTrackSize& aTrackSize) { if (aTrackSize.IsFitContent()) { // A fit-content() function. RefPtr 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()); const auto& min = aTrackSize.AsMinmax()._0; const auto& max = aTrackSize.AsMinmax()._1; if (min == max) { return GetGridTrackBreadth(min); } // minmax(auto, ) is equivalent to (and is our internal representation // of) , and both compute to if (min.IsAuto() && max.IsFr()) { return GetGridTrackBreadth(max); } nsAutoString argumentStr, minmaxStr; minmaxStr.AppendLiteral("minmax("); { RefPtr argValue = GetGridTrackBreadth(min); argValue->GetCssText(argumentStr); minmaxStr.Append(argumentStr); argumentStr.Truncate(); } minmaxStr.AppendLiteral(", "); { RefPtr argValue = GetGridTrackBreadth(max); argValue->GetCssText(argumentStr); minmaxStr.Append(argumentStr); } minmaxStr.Append(char16_t(')')); RefPtr val = new nsROCSSPrimitiveValue; val->SetString(minmaxStr); return val.forget(); } already_AddRefed nsComputedDOMStyle::GetGridTemplateColumnsRows( const StyleGridTemplateComponent& aTrackList, const ComputedGridTrackInfo& aTrackInfo) { if (aTrackInfo.mIsMasonry) { RefPtr val = new nsROCSSPrimitiveValue; val->SetString("masonry"); return val.forget(); } if (aTrackInfo.mIsSubgrid) { RefPtr valueList = GetROCSSValueList(false); RefPtr 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 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& 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 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(, 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 valueList = GetROCSSValueList(false); // Add any leading implicit tracks. if (serializeImplicit) { for (uint32_t i = 0; i < numLeadingImplicitTracks; ++i) { RefPtr 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 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 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 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 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 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 val = new nsROCSSPrimitiveValue; val->SetAppUnits(trackSizes[i]); valueList->AppendCSSValue(val.forget()); } } return valueList.forget(); } already_AddRefed 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 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 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 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 nsComputedDOMStyle::DoGetPaddingTop() { return GetPaddingWidthFor(eSideTop); } already_AddRefed nsComputedDOMStyle::DoGetPaddingBottom() { return GetPaddingWidthFor(eSideBottom); } already_AddRefed nsComputedDOMStyle::DoGetPaddingLeft() { return GetPaddingWidthFor(eSideLeft); } already_AddRefed nsComputedDOMStyle::DoGetPaddingRight() { return GetPaddingWidthFor(eSideRight); } already_AddRefed nsComputedDOMStyle::DoGetBorderSpacing() { RefPtr valueList = GetROCSSValueList(false); RefPtr xSpacing = new nsROCSSPrimitiveValue; RefPtr 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 nsComputedDOMStyle::DoGetBorderTopWidth() { return GetBorderWidthFor(eSideTop); } already_AddRefed nsComputedDOMStyle::DoGetBorderBottomWidth() { return GetBorderWidthFor(eSideBottom); } already_AddRefed nsComputedDOMStyle::DoGetBorderLeftWidth() { return GetBorderWidthFor(eSideLeft); } already_AddRefed nsComputedDOMStyle::DoGetBorderRightWidth() { return GetBorderWidthFor(eSideRight); } already_AddRefed nsComputedDOMStyle::DoGetMarginTop() { return GetMarginFor(eSideTop); } already_AddRefed nsComputedDOMStyle::DoGetMarginBottom() { return GetMarginFor(eSideBottom); } already_AddRefed nsComputedDOMStyle::DoGetMarginLeft() { return GetMarginFor(eSideLeft); } already_AddRefed nsComputedDOMStyle::DoGetMarginRight() { return GetMarginFor(eSideRight); } already_AddRefed nsComputedDOMStyle::DoGetHeight() { RefPtr 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 nsComputedDOMStyle::DoGetWidth() { RefPtr 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 nsComputedDOMStyle::DoGetMaxHeight() { RefPtr val = new nsROCSSPrimitiveValue; SetValueToMaxSize(val, StylePosition()->mMaxHeight); return val.forget(); } already_AddRefed nsComputedDOMStyle::DoGetMaxWidth() { RefPtr 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 nsComputedDOMStyle::DoGetMinHeight() { RefPtr 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 nsComputedDOMStyle::DoGetMinWidth() { RefPtr 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 nsComputedDOMStyle::DoGetLeft() { return GetOffsetWidthFor(eSideLeft); } already_AddRefed nsComputedDOMStyle::DoGetRight() { return GetOffsetWidthFor(eSideRight); } already_AddRefed nsComputedDOMStyle::DoGetTop() { return GetOffsetWidthFor(eSideTop); } already_AddRefed 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 nsComputedDOMStyle::GetNonStaticPositionOffset( mozilla::Side aSide, bool aResolveAuto, PercentageBaseGetter aWidthGetter, PercentageBaseGetter aHeightGetter) { RefPtr 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 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 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 nsComputedDOMStyle::GetStaticOffset( mozilla::Side aSide) { RefPtr val = new nsROCSSPrimitiveValue; SetValueToLengthPercentageOrAuto(val, StylePosition()->mOffset.Get(aSide), false); return val.forget(); } already_AddRefed nsComputedDOMStyle::GetPaddingWidthFor( mozilla::Side aSide) { RefPtr 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 nsComputedDOMStyle::GetBorderWidthFor( mozilla::Side aSide) { RefPtr 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 nsComputedDOMStyle::GetMarginFor(Side aSide) { RefPtr 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); 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 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 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 nsComputedDOMStyle::DummyGetter() { MOZ_CRASH("DummyGetter is not supposed to be invoked"); } static void MarkComputedStyleMapDirty(const char* aPref, void* aMap) { static_cast(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> 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 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(std::move(prefs)); Preferences::RegisterCallbacks(MarkComputedStyleMapDirty, gCallbackPrefs->Elements(), GetComputedStyleMap()); } /* static */ void nsComputedDOMStyle::UnregisterPrefChangeCallbacks() { if (!gCallbackPrefs) { return; } Preferences::UnregisterCallbacks(MarkComputedStyleMapDirty, gCallbackPrefs->Elements(), GetComputedStyleMap()); gCallbackPrefs = nullptr; }