/* -*- 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/StaticPtr.h" #include "nsError.h" #include "nsIFrame.h" #include "nsIFrameInlines.h" #include "mozilla/ComputedStyle.h" #include "nsIScrollableFrame.h" #include "nsContentUtils.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 "nsIDocument.h" #include "nsCSSProps.h" #include "nsCSSPseudoElements.h" #include "mozilla/EffectSet.h" #include "mozilla/IntegerRange.h" #include "mozilla/ServoStyleSet.h" #include "mozilla/RestyleManager.h" #include "imgIRequest.h" #include "nsLayoutUtils.h" #include "nsCSSKeywords.h" #include "nsStyleCoord.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" using namespace mozilla; using namespace mozilla::dom; #if defined(DEBUG_bzbarsky) || defined(DEBUG_caillon) #define DEBUG_ComputedDOMStyle #endif /* * 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, nsIDocument* aDocument, nsComputedDOMStyle::StyleType aStyleType) { RefPtr computedStyle = new nsComputedDOMStyle(aElement, aPseudoElt, aDocument, aStyleType); return computedStyle.forget(); } static nsDOMCSSValueList* GetROCSSValueList(bool aCommaDelimited) { return new nsDOMCSSValueList(aCommaDelimited); } template already_AddRefed GetBackgroundList(T nsStyleImageLayers::Layer::* aMember, uint32_t nsStyleImageLayers::* aCount, const nsStyleImageLayers& aLayers, const nsCSSKTableEntry aTable[]) { RefPtr valueList = GetROCSSValueList(true); for (uint32_t i = 0, i_end = aLayers.*aCount; i < i_end; ++i) { RefPtr val = new nsROCSSPrimitiveValue; val->SetIdent(nsCSSProps::ValueToKeywordEnum(aLayers.mLayers[i].*aMember, aTable)); valueList->AppendCSSValue(val.forget()); } return valueList.forget(); } // Whether aDocument needs to restyle for aElement static bool DocumentNeedsRestyle( const nsIDocument* aDocument, Element* aElement, nsAtom* aPseudo) { nsIPresShell* shell = aDocument->GetShell(); if (!shell) { return true; } nsPresContext* presContext = shell->GetPresContext(); MOZ_ASSERT(presContext); // 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 = shell->StyleSet(); if (styleSet->StyleSheetsHaveChanged()) { return true; } // 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) { if (aPseudo == nsCSSPseudoElements::before()) { if (EffectSet::GetEffectSet(aElement, CSSPseudoElementType::before)) { return true; } } else if (aPseudo == nsCSSPseudoElements::after()) { if (EffectSet::GetEffectSet(aElement, CSSPseudoElementType::after)) { 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() && !aDocument->GetServoRestyleRoot()) { return false; } // Then if there is a restyle root, we check if the root is an ancestor of // this content. If it is not, then we don't need to restyle immediately. // Note this is different from Gecko: we only check if any ancestor needs // to restyle _itself_, not descendants, since dirty descendants can be // another subtree. return restyleManager->HasPendingRestyleAncestor(aElement); } /** * 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. typedef already_AddRefed (nsComputedDOMStyle::*ComputeMethod)(); nsCSSPropertyID mProperty; ComputeMethod mGetter; bool IsLayoutFlushNeeded() const { return nsCSSProps::PropHasFlags(mProperty, CSSPropFlags::GetCSNeedsLayoutFlush); } bool IsEnabled() const { return nsCSSProps::IsEnabled(mProperty, CSSEnabledState::eForAllContent); } }; // This generated file includes definition of kEntries which is typed // Entry[] and used below, so this #include has to be put here. #include "nsComputedDOMStyleGenerated.cpp" /** * Returns the number of properties that should be exposed on an * nsComputedDOMStyle, ecxluding any disabled properties. */ uint32_t Length() { Update(); return mExposedPropertyCount; } /** * 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) { Update(); for (uint32_t i = 0; i < mExposedPropertyCount; i++) { const Entry* entry = &kEntries[EntryIndex(i)]; if (entry->mProperty == aPropID) { return entry; } } return nullptr; } /** * Records that mIndexMap needs updating, due to prefs changing that could * affect the set of properties exposed on an nsComputedDOMStyle. */ void MarkDirty() { mExposedPropertyCount = 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 mExposedPropertyCount; /** * A map of indexes on the nsComputedDOMStyle object to indexes into kEntries. */ uint32_t mIndexMap[ArrayLength(kEntries)]; private: /** * Returns whether mExposedPropertyCount and mIndexMap are out of date. */ bool IsDirty() { return mExposedPropertyCount == 0; } /** * Updates mExposedPropertyCount 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 < mExposedPropertyCount); return mIndexMap[aIndex]; } }; constexpr ComputedStyleMap::Entry ComputedStyleMap::kEntries[ArrayLength(kEntries)]; void ComputedStyleMap::Update() { if (!IsDirty()) { return; } uint32_t index = 0; for (uint32_t i = 0; i < ArrayLength(kEntries); i++) { if (kEntries[i].IsEnabled()) { mIndexMap[index++] = i; } } mExposedPropertyCount = index; } nsComputedDOMStyle::nsComputedDOMStyle(dom::Element* aElement, const nsAString& aPseudoElt, nsIDocument* aDocument, StyleType aStyleType) : mDocumentWeak(nullptr) , mOuterFrame(nullptr) , mInnerFrame(nullptr) , mPresShell(nullptr) , mStyleType(aStyleType) , mComputedStyleGeneration(0) , mExposeVisitedStyle(false) , mResolvedComputedStyle(false) #ifdef DEBUG , mFlushedPendingReflows(false) #endif { 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; mPseudo = nsCSSPseudoElements::GetPseudoAtom(aPseudoElt); } nsComputedDOMStyle::~nsComputedDOMStyle() { MOZ_ASSERT(!mResolvedComputedStyle, "Should have called ClearComputedStyle() during last release."); } NS_IMPL_CYCLE_COLLECTION_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 NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(nsComputedDOMStyle) NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(nsComputedDOMStyle) return tmp->HasKnownLiveWrapper(); NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(nsComputedDOMStyle) return tmp->HasKnownLiveWrapper(); NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(nsComputedDOMStyle) 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, nsAString& aValue) { // This is mostly to avoid code duplication with GetPropertyCSSValue(); if // perf ever becomes an issue here (doubtful), we can look into changing // this. return GetPropertyValue( NS_ConvertASCIItoUTF16(nsCSSProps::GetStringValue(aPropID)), aValue); } nsresult nsComputedDOMStyle::SetPropertyValue(const nsCSSPropertyID aPropID, const nsAString& aValue, nsIPrincipal* aSubjectPrincipal) { return NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR; } void nsComputedDOMStyle::GetCssText(nsAString& aCssText) { aCssText.Truncate(); } void nsComputedDOMStyle::SetCssText(const nsAString& aCssText, nsIPrincipal* aSubjectPrincipal, ErrorResult& aRv) { aRv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); } uint32_t nsComputedDOMStyle::Length() { // Make sure we have up to date style so that we can include custom // properties. UpdateCurrentStyleSources(false); 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 nsAString& aPropertyName, nsAString& aReturn) { aReturn.Truncate(); nsCSSPropertyID prop = nsCSSProps::LookupProperty(aPropertyName); const ComputedStyleMap::Entry* entry = nullptr; if (prop != eCSSPropertyExtra_variable) { entry = GetComputedStyleMap()->FindEntryForProperty(prop); if (!entry) { return NS_OK; } } const bool layoutFlushIsNeeded = entry && entry->IsLayoutFlushNeeded(); UpdateCurrentStyleSources(layoutFlushIsNeeded); if (!mComputedStyle) { return NS_OK; } auto cleanup = mozilla::MakeScopeExit([&] { ClearCurrentStyleSources(); }); if (!entry) { MOZ_ASSERT(nsCSSProps::IsCustomPropertyName(aPropertyName)); const nsAString& name = Substring(aPropertyName, CSS_CUSTOM_NAME_PREFIX_LENGTH); Servo_GetCustomPropertyValue(mComputedStyle, &name, &aReturn); return NS_OK; } if (nsCSSProps::PropHasFlags(prop, CSSPropFlags::IsLogical)) { MOZ_ASSERT(entry); MOZ_ASSERT(entry->mGetter == &nsComputedDOMStyle::DummyGetter); prop = Servo_ResolveLogicalProperty(prop, mComputedStyle); entry = GetComputedStyleMap()->FindEntryForProperty(prop); MOZ_ASSERT(layoutFlushIsNeeded == entry->IsLayoutFlushNeeded(), "Logical and physical property don't agree on whether layout is " "needed"); } if (!nsCSSProps::PropHasFlags(prop, CSSPropFlags::SerializedByServo)) { if (RefPtr value = (this->*entry->mGetter)()) { ErrorResult rv; nsString text; value->GetCssText(text, rv); aReturn.Assign(text); return rv.StealNSResult(); } return NS_OK; } MOZ_ASSERT(entry->mGetter == &nsComputedDOMStyle::DummyGetter); Servo_GetPropertyValue(mComputedStyle, prop, &aReturn); return NS_OK; } /* static */ already_AddRefed nsComputedDOMStyle::GetComputedStyle(Element* aElement, nsAtom* aPseudo, StyleType aStyleType) { if (nsIDocument* 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 mozilla::ComputedStyle* aStyle) { MOZ_ASSERT(aStyle); // TODO(emilio): We may want to avoid re-resolving pseudo-element styles // more often. return aStyle->HasPseudoElementData() && !aStyle->GetPseudo(); } static inline CSSPseudoElementType GetPseudoType(nsAtom* aPseudo) { if (!aPseudo) { return CSSPseudoElementType::NotPseudo; } // FIXME(emilio, bug 1433439): The eIgnoreEnabledState thing is dubious. return nsCSSPseudoElements::GetPseudoType( aPseudo, CSSEnabledState::eIgnoreEnabledState); } already_AddRefed nsComputedDOMStyle::DoGetComputedStyleNoFlush(Element* aElement, nsAtom* aPseudo, nsIPresShell* 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. nsIPresShell* presShell = nsContentUtils::GetPresShellForContent(aElement); bool inDocWithShell = true; if (!presShell) { inDocWithShell = false; presShell = aPresShell; if (!presShell) { return nullptr; } } CSSPseudoElementType pseudoType = GetPseudoType(aPseudo); if (aPseudo && pseudoType >= CSSPseudoElementType::Count) { return nullptr; } if (aElement->IsInNativeAnonymousSubtree() && !aElement->IsInComposedDoc()) { // Normal web content can't access NAC, but Accessibility, DevTools and // Editor use this same API and this may get called for anonymous content. // Computing the style of a pseudo-element that doesn't have a parent doesn't // really make sense. 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 == eAll && !aElement->IsHTMLElement(nsGkAtoms::area)) { Element* element = nullptr; if (aPseudo == nsCSSPseudoElements::before()) { element = nsLayoutUtils::GetBeforePseudo(aElement); } else if (aPseudo == nsCSSPseudoElements::after()) { element = nsLayoutUtils::GetAfterPseudo(aElement); } else if (!aPseudo) { element = aElement; } if (element) { if (nsIFrame* styleFrame = nsLayoutUtils::GetStyleFrame(element)) { ComputedStyle* result = styleFrame->Style(); // Don't use the style if it was influenced by pseudo-elements, // since then it's not the primary style for this element / pseudo. if (!MustReresolveStyle(result)) { RefPtr ret = result; return ret.forget(); } } } } // 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 == eDefaultOnly ? StyleRuleInclusion::DefaultOnly : StyleRuleInclusion::All; RefPtr result = styleSet->ResolveStyleLazily(aElement, pseudoType, rules); return result.forget(); } already_AddRefed nsComputedDOMStyle::GetUnanimatedComputedStyleNoFlush(Element* aElement, nsAtom* aPseudo) { RefPtr style = GetComputedStyleNoFlush(aElement, aPseudo); if (!style) { return nullptr; } CSSPseudoElementType pseudoType = GetPseudoType(aPseudo); nsIPresShell* shell = aElement->OwnerDoc()->GetShell(); MOZ_ASSERT(shell, "How in the world did we get a style a few lines above?"); Element* elementOrPseudoElement = EffectCompositor::GetElementToRestyle(aElement, pseudoType); if (!elementOrPseudoElement) { return nullptr; } return shell->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) { nsAutoCString spec; nsresult rv = aURI.GetSpec(spec); if (NS_FAILED(rv)) { return; } aURLs.AppendElement(NS_ConvertUTF8toUTF16(spec)); } static void AddImageURL(const css::URLValue& aURL, nsTArray& aURLs) { if (aURL.IsLocalRef()) { return; } if (nsIURI* uri = aURL.GetURI()) { AddImageURL(*uri, aURLs); } } static void AddImageURL(const nsStyleImageRequest& aRequest, nsTArray& aURLs) { if (auto* value = aRequest.GetImageValue()) { AddImageURL(*value, aURLs); } } static void AddImageURL(const nsStyleImage& aImage, nsTArray& aURLs) { if (auto* urlValue = aImage.GetURLValue()) { AddImageURL(*urlValue, aURLs); } } static void AddImageURL(const StyleShapeSource& aShapeSource, nsTArray& aURLs) { switch (aShapeSource.GetType()) { case StyleShapeSourceType::URL: AddImageURL(aShapeSource.URL(), aURLs); break; case StyleShapeSourceType::Image: AddImageURL(aShapeSource.ShapeImage(), aURLs); break; default: break; } } static void AddImageURLs(const nsStyleImageLayers& aLayers, nsTArray& aURLs) { for (auto i : IntegerRange(aLayers.mLayers.Length())) { AddImageURL(aLayers.mLayers[i].mImage, aURLs); } } // FIXME(stylo-everywhere): This should be `const ComputedStyle&`. static void CollectImageURLsForProperty(nsCSSPropertyID aProp, ComputedStyle& aStyle, nsTArray& aURLs) { if (nsCSSProps::IsShorthand(aProp)) { CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(p, aProp, CSSEnabledState::eForAllContent) { CollectImageURLsForProperty(*p, aStyle, aURLs); } return; } switch (aProp) { case eCSSProperty_cursor: for (auto& image : aStyle.StyleUI()->mCursorImages) { AddImageURL(*image.mImage, 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: if (nsStyleImageRequest* image = aStyle.StyleList()->mListStyleImage) { AddImageURL(*image, 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 nsAString& aPropertyName, nsTArray& aImageURLs, mozilla::ErrorResult& aRv) { nsCSSPropertyID prop = nsCSSProps::LookupProperty(aPropertyName); if (prop == eCSSProperty_UNKNOWN) { aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR); return; } UpdateCurrentStyleSources(false); 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"); } nsIDocument* 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; } void nsComputedDOMStyle::SetFrameComputedStyle(mozilla::ComputedStyle* aStyle, uint64_t aGeneration) { ClearComputedStyle(); mComputedStyle = aStyle; mComputedStyleGeneration = aGeneration; } bool nsComputedDOMStyle::NeedsToFlush(nsIDocument* aDocument) const { // If mElement is not in the same document, we could do some checks to know if // there are some pending restyles can be ignored across documents (since we // will use the caller document's style), but it can be complicated and should // be an edge case, so we just don't bother to do the optimization in this // case. // // FIXME(emilio): This is likely to want GetComposedDoc() instead of // OwnerDoc(). if (aDocument != mElement->OwnerDoc()) { return true; } if (DocumentNeedsRestyle(aDocument, mElement, mPseudo)) { return true; } // 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 (nsIDocument* parentDocument = aDocument->GetParentDocument()) { Element* element = parentDocument->FindContentForSubDocument(aDocument); if (DocumentNeedsRestyle(parentDocument, element, nullptr)) { return true; } aDocument = parentDocument; } return false; } void nsComputedDOMStyle::UpdateCurrentStyleSources(bool aNeedsLayoutFlush) { nsCOMPtr document = do_QueryReferent(mDocumentWeak); if (!document) { ClearComputedStyle(); return; } // TODO(emilio): We may want to handle a few special-cases here: // // * https://github.com/w3c/csswg-drafts/issues/1964 // * https://github.com/w3c/csswg-drafts/issues/1548 // If the property we are computing relies on layout, then we must flush. const bool needsToFlush = aNeedsLayoutFlush || NeedsToFlush(document); if (needsToFlush) { // Flush _before_ getting the presshell, since that could create a new // presshell. Also note that we want to flush the style on the document // we're computing style in, not on the document mElement is in -- the two // may be different. document->FlushPendingNotifications( aNeedsLayoutFlush ? FlushType::Layout : FlushType::Style); } #ifdef DEBUG mFlushedPendingReflows = aNeedsLayoutFlush; #endif nsCOMPtr presShellForContent = nsContentUtils::GetPresShellForContent(mElement); if (presShellForContent && presShellForContent->GetDocument() != document) { presShellForContent->GetDocument()->FlushPendingNotifications(FlushType::Style); if (presShellForContent->IsDestroying()) { presShellForContent = nullptr; } } mPresShell = document->GetShell(); 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. // As for Gecko, GetUndisplayedRestyleGeneration is effectively equal to // GetRestyleGeneration, since the generation is incremented whenever we // process restyles. uint64_t currentGeneration = mPresShell->GetPresContext()->GetUndisplayedRestyleGeneration(); if (mComputedStyle) { // We can't rely on the undisplayed restyle generation if mElement is // out-of-document, since that generation is not incremented for DOM changes // on out-of-document elements. // // So we always need to update the style to ensure it it up-to-date. if (mComputedStyleGeneration == currentGeneration && mElement->IsInComposedDoc()) { // Our cached style is still valid. return; } // We've processed some restyles, so the cached style might be out of date. 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 == eAll && !mElement->IsHTMLElement(nsGkAtoms::area)) { mOuterFrame = nullptr; if (!mPseudo) { mOuterFrame = mElement->GetPrimaryFrame(); } else if (mPseudo == nsCSSPseudoElements::before() || mPseudo == nsCSSPseudoElements::after()) { nsAtom* property = mPseudo == nsCSSPseudoElements::before() ? nsGkAtoms::beforePseudoProperty : nsGkAtoms::afterPseudoProperty; auto* pseudo = static_cast(mElement->GetProperty(property)); mOuterFrame = pseudo ? pseudo->GetPrimaryFrame() : nullptr; } mInnerFrame = mOuterFrame; if (mOuterFrame) { LayoutFrameType type = mOuterFrame->Type(); if (type == LayoutFrameType::TableWrapper) { // If the frame is a table wrapper frame then we should get the style // from the inner table frame. mInnerFrame = mOuterFrame->PrincipalChildList().FirstChild(); NS_ASSERTION(mInnerFrame, "table wrapper must have an inner"); NS_ASSERTION(!mInnerFrame->GetNextSibling(), "table wrapper frames should have just one child, " "the inner table"); } SetFrameComputedStyle(mInnerFrame->Style(), currentGeneration); NS_ASSERTION(mComputedStyle, "Frame without style?"); } } if (!mComputedStyle || MustReresolveStyle(mComputedStyle)) { // Need to resolve a style. RefPtr resolvedComputedStyle = DoGetComputedStyleNoFlush( mElement, mPseudo, presShellForContent ? presShellForContent.get() : 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(!needsToFlush || currentGeneration == mPresShell->GetPresContext()->GetUndisplayedRestyleGeneration(), "why should we have flushed style again?"); SetResolvedComputedStyle(std::move(resolvedComputedStyle), currentGeneration); NS_ASSERTION(mPseudo || !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 (ComputedStyle* 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; } NS_IMETHODIMP nsComputedDOMStyle::RemoveProperty(const nsAString& aPropertyName, nsAString& aReturn) { return NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR; } void nsComputedDOMStyle::GetPropertyPriority(const nsAString& aPropertyName, nsAString& aReturn) { aReturn.Truncate(); } NS_IMETHODIMP nsComputedDOMStyle::SetProperty(const nsAString& aPropertyName, const nsAString& aValue, const nsAString& aPriority, nsIPrincipal* aSubjectPrincipal) { return NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR; } void nsComputedDOMStyle::IndexedGetter(uint32_t aIndex, bool& aFound, nsAString& aPropName) { ComputedStyleMap* map = GetComputedStyleMap(); uint32_t length = map->Length(); if (aIndex < length) { aFound = true; CopyASCIItoUTF16(nsCSSProps::GetStringValue(map->PropertyAt(aIndex)), aPropName); return; } // Custom properties are exposed with indexed properties just after all // of the built-in properties. UpdateCurrentStyleSources(false); if (!mComputedStyle) { aFound = false; return; } uint32_t count = Servo_GetCustomPropertiesCount(mComputedStyle); const uint32_t index = aIndex - length; if (index < count) { aFound = true; nsString varName; Servo_GetCustomPropertyNameAt(mComputedStyle, index, &varName); aPropName.AssignLiteral("--"); aPropName.Append(varName); } else { aFound = false; } ClearCurrentStyleSources(); } // Property getters... already_AddRefed nsComputedDOMStyle::DoGetBinding() { RefPtr val = new nsROCSSPrimitiveValue; const nsStyleDisplay* display = StyleDisplay(); if (display->mBinding && display->mBinding->GetURI()) { val->SetURI(display->mBinding->GetURI()); } else { val->SetIdent(eCSSKeyword_none); } return val.forget(); } already_AddRefed nsComputedDOMStyle::DoGetBottom() { return GetOffsetWidthFor(eSideBottom); } void nsComputedDOMStyle::SetToRGBAColor(nsROCSSPrimitiveValue* aValue, nscolor aColor) { nsAutoString string; const bool hasAlpha = NS_GET_A(aColor) != 255; if (hasAlpha) { string.AppendLiteral("rgba("); } else { string.AppendLiteral("rgb("); } string.AppendInt(NS_GET_R(aColor)); string.AppendLiteral(", "); string.AppendInt(NS_GET_G(aColor)); string.AppendLiteral(", "); string.AppendInt(NS_GET_B(aColor)); if (hasAlpha) { string.AppendLiteral(", "); float alpha = nsStyleUtil::ColorComponentToFloat(NS_GET_A(aColor)); nsStyleUtil::AppendCSSNumber(alpha, string); } string.AppendLiteral(")"); aValue->SetString(string); } void nsComputedDOMStyle::SetValueFromComplexColor(nsROCSSPrimitiveValue* aValue, const StyleComplexColor& aColor) { SetToRGBAColor(aValue, aColor.CalcColor(mComputedStyle)); } already_AddRefed nsComputedDOMStyle::DoGetColor() { RefPtr val = new nsROCSSPrimitiveValue; SetToRGBAColor(val, StyleColor()->mColor); return val.forget(); } already_AddRefed nsComputedDOMStyle::DoGetColumnCount() { RefPtr val = new nsROCSSPrimitiveValue; const nsStyleColumn* column = StyleColumn(); if (column->mColumnCount == nsStyleColumn::kColumnCountAuto) { val->SetIdent(eCSSKeyword_auto); } else { val->SetNumber(column->mColumnCount); } return val.forget(); } already_AddRefed nsComputedDOMStyle::DoGetColumnWidth() { RefPtr val = new nsROCSSPrimitiveValue; // XXX fix the auto case. When we actually have a column frame, I think // we should return the computed column width. SetValueToCoord(val, StyleColumn()->mColumnWidth, true); return val.forget(); } already_AddRefed nsComputedDOMStyle::DoGetColumnRuleWidth() { RefPtr val = new nsROCSSPrimitiveValue; val->SetAppUnits(StyleColumn()->GetComputedColumnRuleWidth()); return val.forget(); } /* 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 nsStyleDisplay* display = StyleDisplay(); RefPtr width = new nsROCSSPrimitiveValue; SetValueToCoord(width, display->mTransformOrigin[0], false, &nsComputedDOMStyle::GetFrameBoundsWidthForTransform); valueList->AppendCSSValue(width.forget()); RefPtr height = new nsROCSSPrimitiveValue; SetValueToCoord(height, display->mTransformOrigin[1], false, &nsComputedDOMStyle::GetFrameBoundsHeightForTransform); valueList->AppendCSSValue(height.forget()); if (display->mTransformOrigin[2].GetUnit() != eStyleUnit_Coord || display->mTransformOrigin[2].GetCoordValue() != 0) { RefPtr depth = new nsROCSSPrimitiveValue; SetValueToCoord(depth, display->mTransformOrigin[2], false, nullptr); 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 nsStyleDisplay* display = StyleDisplay(); RefPtr width = new nsROCSSPrimitiveValue; SetValueToCoord(width, display->mPerspectiveOrigin[0], false, &nsComputedDOMStyle::GetFrameBoundsWidthForTransform); valueList->AppendCSSValue(width.forget()); RefPtr height = new nsROCSSPrimitiveValue; SetValueToCoord(height, display->mPerspectiveOrigin[1], false, &nsComputedDOMStyle::GetFrameBoundsHeightForTransform); valueList->AppendCSSValue(height.forget()); return valueList.forget(); } already_AddRefed nsComputedDOMStyle::DoGetPerspective() { RefPtr val = new nsROCSSPrimitiveValue; SetValueToCoord(val, StyleDisplay()->mChildPerspective, false); return val.forget(); } already_AddRefed nsComputedDOMStyle::DoGetTransform() { const nsStyleDisplay* display = StyleDisplay(); return GetTransformValue(display->mSpecifiedTransform); } static already_AddRefed ReadIndividualTransformValue(nsCSSValueSharedList* aList, const std::function& aCallback) { if (!aList) { RefPtr val = new nsROCSSPrimitiveValue; val->SetIdent(eCSSKeyword_none); return val.forget(); } nsAutoString result; const nsCSSValue::Array* data = aList->mHead->mValue.GetArrayValue(); aCallback(data, result); RefPtr val = new nsROCSSPrimitiveValue; val->SetString(result); return val.forget(); } already_AddRefed nsComputedDOMStyle::DoGetTranslate() { typedef nsStyleTransformMatrix::TransformReferenceBox TransformReferenceBox; RefPtr self(this); return ReadIndividualTransformValue(StyleDisplay()->mSpecifiedTranslate, [self](const nsCSSValue::Array* aData, nsString& aResult) { TransformReferenceBox refBox(self->mInnerFrame, nsSize(0, 0)); // Even though the spec doesn't say to resolve percentage values, Blink // and Edge do and so until that is clarified we do as well: // // https://github.com/w3c/csswg-drafts/issues/2124 switch (nsStyleTransformMatrix::TransformFunctionOf(aData)) { /* translate : */ case eCSSKeyword_translatex: { MOZ_ASSERT(aData->Count() == 2, "Invalid array!"); float tx = ProcessTranslatePart(aData->Item(1), &refBox, &TransformReferenceBox::Width); aResult.AppendFloat(tx); aResult.AppendLiteral("px"); break; } /* translate : */ case eCSSKeyword_translate: { MOZ_ASSERT(aData->Count() == 3, "Invalid array!"); float tx = ProcessTranslatePart(aData->Item(1), &refBox, &TransformReferenceBox::Width); aResult.AppendFloat(tx); aResult.AppendLiteral("px"); float ty = ProcessTranslatePart(aData->Item(2), &refBox, &TransformReferenceBox::Height); if (ty != 0) { aResult.AppendLiteral(" "); aResult.AppendFloat(ty); aResult.AppendLiteral("px"); } break; } /* translate : */ case eCSSKeyword_translate3d: { MOZ_ASSERT(aData->Count() == 4, "Invalid array!"); float tx = ProcessTranslatePart(aData->Item(1), &refBox, &TransformReferenceBox::Width); aResult.AppendFloat(tx); aResult.AppendLiteral("px"); float ty = ProcessTranslatePart(aData->Item(2), &refBox, &TransformReferenceBox::Height); float tz = ProcessTranslatePart(aData->Item(3), &refBox, nullptr); if (ty != 0. || tz != 0.) { aResult.AppendLiteral(" "); aResult.AppendFloat(ty); aResult.AppendLiteral("px"); } if (tz != 0.) { aResult.AppendLiteral(" "); aResult.AppendFloat(tz); aResult.AppendLiteral("px"); } break; } default: MOZ_ASSERT_UNREACHABLE("Unexpected CSS keyword."); } }); } already_AddRefed nsComputedDOMStyle::DoGetRotate() { return ReadIndividualTransformValue(StyleDisplay()->mSpecifiedRotate, [](const nsCSSValue::Array* aData, nsString& aResult) { switch (nsStyleTransformMatrix::TransformFunctionOf(aData)) { /* rotate : */ case eCSSKeyword_rotate: { MOZ_ASSERT(aData->Count() == 2, "Invalid array!"); float theta = aData->Item(1).GetAngleValueInDegrees(); aResult.AppendFloat(theta); aResult.AppendLiteral("deg"); break; } /* rotate : */ case eCSSKeyword_rotate3d: { MOZ_ASSERT(aData->Count() == 5, "Invalid array!"); float rx = aData->Item(1).GetFloatValue(); float ry = aData->Item(2).GetFloatValue(); float rz = aData->Item(3).GetFloatValue(); if (rx != 0. || ry != 0. || rz != 1.) { aResult.AppendFloat(rx); aResult.AppendLiteral(" "); aResult.AppendFloat(ry); aResult.AppendLiteral(" "); aResult.AppendFloat(rz); aResult.AppendLiteral(" "); } float theta = aData->Item(4).GetAngleValueInDegrees(); aResult.AppendFloat(theta); aResult.AppendLiteral("deg"); break; } default: MOZ_ASSERT_UNREACHABLE("Unexpected CSS keyword."); } }); } /* static */ already_AddRefed nsComputedDOMStyle::MatrixToCSSValue(const mozilla::gfx::Matrix4x4& matrix) { bool is3D = !matrix.Is2D(); nsAutoString resultString(NS_LITERAL_STRING("matrix")); 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::DoGetOsxFontSmoothing() { if (nsContentUtils::ShouldResistFingerprinting( mPresShell->GetPresContext()->GetDocShell())) return nullptr; RefPtr val = new nsROCSSPrimitiveValue; val->SetIdent(nsCSSProps::ValueToKeywordEnum(StyleFont()->mFont.smoothing, nsCSSProps::kFontSmoothingKTable)); return val.forget(); } // return a value *only* for valid longhand values from CSS 2.1, either // normal or small-caps only already_AddRefed nsComputedDOMStyle::DoGetFontVariant() { const nsFont& f = StyleFont()->mFont; // if any of the other font-variant subproperties other than // font-variant-caps are not normal then can't calculate a computed value if (f.variantAlternates || f.variantEastAsian || f.variantLigatures || f.variantNumeric || f.variantPosition) { return nullptr; } nsCSSKeyword keyword; switch (f.variantCaps) { case 0: keyword = eCSSKeyword_normal; break; case NS_FONT_VARIANT_CAPS_SMALLCAPS: keyword = eCSSKeyword_small_caps; break; default: return nullptr; } RefPtr val = new nsROCSSPrimitiveValue; val->SetIdent(keyword); return val.forget(); } static void SetValueToCalc(const nsStyleCoord::CalcValue* aCalc, nsROCSSPrimitiveValue* aValue) { RefPtr val = new nsROCSSPrimitiveValue; nsAutoString tmp, result; result.AppendLiteral("calc("); val->SetAppUnits(aCalc->mLength); val->GetCssText(tmp); result.Append(tmp); if (aCalc->mHasPercent) { result.AppendLiteral(" + "); val->SetPercent(aCalc->mPercent); val->GetCssText(tmp); result.Append(tmp); } result.Append(')'); aValue->SetString(result); // not really SetString } 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::SetValueToPositionCoord( const Position::Coord& aCoord, nsROCSSPrimitiveValue* aValue) { if (!aCoord.mHasPercent) { MOZ_ASSERT(aCoord.mPercent == 0.0f, "Shouldn't have mPercent!"); aValue->SetAppUnits(aCoord.mLength); } else if (aCoord.mLength == 0) { aValue->SetPercent(aCoord.mPercent); } else { SetValueToCalc(&aCoord, aValue); } } void nsComputedDOMStyle::SetValueToPosition( const Position& aPosition, nsDOMCSSValueList* aValueList) { RefPtr valX = new nsROCSSPrimitiveValue; SetValueToPositionCoord(aPosition.mXPosition, valX); aValueList->AppendCSSValue(valX.forget()); RefPtr valY = new nsROCSSPrimitiveValue; SetValueToPositionCoord(aPosition.mYPosition, valY); aValueList->AppendCSSValue(valY.forget()); } void nsComputedDOMStyle::SetValueToURLValue(const css::URLValue* aURL, nsROCSSPrimitiveValue* aValue) { if (!aURL) { aValue->SetIdent(eCSSKeyword_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. nsAutoString source; aURL->GetSourceString(source); nsAutoString url; url.AppendLiteral(u"url("); nsStyleUtil::AppendEscapedCSSString(source, url, '"'); url.Append(')'); aValue->SetString(url); } already_AddRefed nsComputedDOMStyle::DoGetBackgroundPosition() { const nsStyleImageLayers& layers = StyleBackground()->mImage; return DoGetImageLayerPosition(layers); } already_AddRefed nsComputedDOMStyle::DoGetGridTemplateAreas() { const css::GridTemplateAreasValue* areas = StylePosition()->mGridTemplateAreas; if (!areas) { RefPtr val = new nsROCSSPrimitiveValue; val->SetIdent(eCSSKeyword_none); return val.forget(); } MOZ_ASSERT(!areas->mTemplates.IsEmpty(), "Unexpected empty array in GridTemplateAreasValue"); RefPtr valueList = GetROCSSValueList(false); for (uint32_t i = 0; i < areas->mTemplates.Length(); i++) { nsAutoString str; nsStyleUtil::AppendEscapedCSSString(areas->mTemplates[i], str); RefPtr val = new nsROCSSPrimitiveValue; val->SetString(str); valueList->AppendCSSValue(val.forget()); } return valueList.forget(); } void nsComputedDOMStyle::AppendGridLineNames(nsString& aResult, const nsTArray& aLineNames) { uint32_t numLines = aLineNames.Length(); if (numLines == 0) { return; } for (uint32_t i = 0;;) { nsStyleUtil::AppendEscapedCSSIdent(aLineNames[i], aResult); if (++i == numLines) { break; } aResult.Append(' '); } } void nsComputedDOMStyle::AppendGridLineNames(nsDOMCSSValueList* aValueList, const nsTArray& aLineNames, bool aSuppressEmptyList) { if (aLineNames.IsEmpty() && aSuppressEmptyList) { return; } RefPtr val = new nsROCSSPrimitiveValue; nsAutoString lineNamesString; lineNamesString.Assign('['); AppendGridLineNames(lineNamesString, aLineNames); lineNamesString.Append(']'); val->SetString(lineNamesString); aValueList->AppendCSSValue(val.forget()); } void nsComputedDOMStyle::AppendGridLineNames(nsDOMCSSValueList* aValueList, const nsTArray& aLineNames1, const nsTArray& aLineNames2) { if (aLineNames1.IsEmpty() && aLineNames2.IsEmpty()) { return; } RefPtr val = new nsROCSSPrimitiveValue; nsAutoString lineNamesString; lineNamesString.Assign('['); if (!aLineNames1.IsEmpty()) { AppendGridLineNames(lineNamesString, aLineNames1); } if (!aLineNames2.IsEmpty()) { if (!aLineNames1.IsEmpty()) { lineNamesString.Append(' '); } AppendGridLineNames(lineNamesString, aLineNames2); } lineNamesString.Append(']'); val->SetString(lineNamesString); aValueList->AppendCSSValue(val.forget()); } already_AddRefed nsComputedDOMStyle::GetGridTrackSize(const nsStyleCoord& aMinValue, const nsStyleCoord& aMaxValue) { if (aMinValue.GetUnit() == eStyleUnit_None) { // A fit-content() function. RefPtr val = new nsROCSSPrimitiveValue; nsAutoString argumentStr, fitContentStr; fitContentStr.AppendLiteral("fit-content("); MOZ_ASSERT(aMaxValue.IsCoordPercentCalcUnit(), "unexpected unit for fit-content() argument value"); SetValueToCoord(val, aMaxValue, true); val->GetCssText(argumentStr); fitContentStr.Append(argumentStr); fitContentStr.Append(char16_t(')')); val->SetString(fitContentStr); return val.forget(); } if (aMinValue == aMaxValue) { RefPtr val = new nsROCSSPrimitiveValue; SetValueToCoord(val, aMinValue, true, nullptr, nsCSSProps::kGridTrackBreadthKTable); return val.forget(); } // minmax(auto, ) is equivalent to (and is our internal representation // of) , and both compute to if (aMinValue.GetUnit() == eStyleUnit_Auto && aMaxValue.GetUnit() == eStyleUnit_FlexFraction) { RefPtr val = new nsROCSSPrimitiveValue; SetValueToCoord(val, aMaxValue, true); return val.forget(); } RefPtr val = new nsROCSSPrimitiveValue; nsAutoString argumentStr, minmaxStr; minmaxStr.AppendLiteral("minmax("); SetValueToCoord(val, aMinValue, true, nullptr, nsCSSProps::kGridTrackBreadthKTable); val->GetCssText(argumentStr); minmaxStr.Append(argumentStr); minmaxStr.AppendLiteral(", "); SetValueToCoord(val, aMaxValue, true, nullptr, nsCSSProps::kGridTrackBreadthKTable); val->GetCssText(argumentStr); minmaxStr.Append(argumentStr); minmaxStr.Append(char16_t(')')); val->SetString(minmaxStr); return val.forget(); } already_AddRefed nsComputedDOMStyle::GetGridTemplateColumnsRows( const nsStyleGridTemplate& aTrackList, const ComputedGridTrackInfo* aTrackInfo) { if (aTrackList.mIsSubgrid) { // XXX TODO: add support for repeat(auto-fill) for 'subgrid' (bug 1234311) NS_ASSERTION(aTrackList.mMinTrackSizingFunctions.IsEmpty() && aTrackList.mMaxTrackSizingFunctions.IsEmpty(), "Unexpected sizing functions with subgrid"); RefPtr valueList = GetROCSSValueList(false); RefPtr subgridKeyword = new nsROCSSPrimitiveValue; subgridKeyword->SetIdent(eCSSKeyword_subgrid); valueList->AppendCSSValue(subgridKeyword.forget()); for (uint32_t i = 0, len = aTrackList.mLineNameLists.Length(); ; ++i) { if (MOZ_UNLIKELY(aTrackList.IsRepeatAutoIndex(i))) { MOZ_ASSERT(aTrackList.mIsAutoFill, "subgrid can only have 'auto-fill'"); MOZ_ASSERT(aTrackList.mRepeatAutoLineNameListAfter.IsEmpty(), "mRepeatAutoLineNameListAfter isn't used for subgrid"); RefPtr start = new nsROCSSPrimitiveValue; start->SetString(NS_LITERAL_STRING("repeat(auto-fill,")); valueList->AppendCSSValue(start.forget()); AppendGridLineNames(valueList, aTrackList.mRepeatAutoLineNameListBefore, /*aSuppressEmptyList*/ false); RefPtr end = new nsROCSSPrimitiveValue; end->SetString(NS_LITERAL_STRING(")")); valueList->AppendCSSValue(end.forget()); } if (i == len) { break; } AppendGridLineNames(valueList, aTrackList.mLineNameLists[i], /*aSuppressEmptyList*/ false); } return valueList.forget(); } uint32_t numSizes = aTrackList.mMinTrackSizingFunctions.Length(); MOZ_ASSERT(aTrackList.mMaxTrackSizingFunctions.Length() == numSizes, "Different number of min and max track sizing functions"); if (aTrackInfo) { DebugOnly isAutoFill = aTrackList.HasRepeatAuto() && aTrackList.mIsAutoFill; DebugOnly isAutoFit = aTrackList.HasRepeatAuto() && !aTrackList.mIsAutoFill; DebugOnly numExplicitTracks = aTrackInfo->mNumExplicitTracks; MOZ_ASSERT(numExplicitTracks == numSizes || (isAutoFill && numExplicitTracks >= numSizes) || (isAutoFit && numExplicitTracks + 1 >= numSizes), "expected all explicit tracks (or possibly one less, if there's " "an 'auto-fit' track, since that can collapse away)"); numSizes = aTrackInfo->mSizes.Length(); } // An empty without repeats is represented as "none" in syntax. if (numSizes == 0 && !aTrackList.HasRepeatAuto()) { RefPtr val = new nsROCSSPrimitiveValue; val->SetIdent(eCSSKeyword_none); return val.forget(); } RefPtr valueList = GetROCSSValueList(false); if (aTrackInfo) { // 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. const nsTArray& trackSizes = aTrackInfo->mSizes; const uint32_t numExplicitTracks = aTrackInfo->mNumExplicitTracks; const uint32_t numLeadingImplicitTracks = aTrackInfo->mNumLeadingImplicitTracks; MOZ_ASSERT(numSizes >= numLeadingImplicitTracks + numExplicitTracks); // Add any leading implicit tracks. for (uint32_t i = 0; i < numLeadingImplicitTracks; ++i) { RefPtr val = new nsROCSSPrimitiveValue; val->SetAppUnits(trackSizes[i]); valueList->AppendCSSValue(val.forget()); } // Then add any explicit tracks and removed auto-fit tracks. if (numExplicitTracks || aTrackList.HasRepeatAuto()) { int32_t endOfRepeat = 0; // first index after any repeat() tracks int32_t offsetToLastRepeat = 0; if (aTrackList.HasRepeatAuto()) { // offsetToLastRepeat is -1 if all repeat(auto-fit) tracks are empty offsetToLastRepeat = numExplicitTracks + 1 - aTrackList.mLineNameLists.Length(); endOfRepeat = aTrackList.mRepeatAutoIndex + offsetToLastRepeat + 1; } uint32_t repeatIndex = 0; uint32_t numRepeatTracks = aTrackInfo->mRemovedRepeatTracks.Length(); enum LinePlacement { LinesPrecede, LinesFollow, LinesBetween }; auto AppendRemovedAutoFits = [this, aTrackInfo, &valueList, aTrackList, &repeatIndex, numRepeatTracks](LinePlacement aPlacement) { // Add in removed auto-fit tracks and lines here, if necessary bool atLeastOneTrackReported = false; while (repeatIndex < numRepeatTracks && aTrackInfo->mRemovedRepeatTracks[repeatIndex]) { if ((aPlacement == LinesPrecede) || ((aPlacement == LinesBetween) && atLeastOneTrackReported)) { // Precede it with the lines between repeats. AppendGridLineNames(valueList, aTrackList.mRepeatAutoLineNameListAfter, aTrackList.mRepeatAutoLineNameListBefore); } // Removed 'auto-fit' tracks are reported as 0px. RefPtr val = new nsROCSSPrimitiveValue; val->SetAppUnits(0); valueList->AppendCSSValue(val.forget()); atLeastOneTrackReported = true; if (aPlacement == LinesFollow) { // Follow it with the lines between repeats. AppendGridLineNames(valueList, aTrackList.mRepeatAutoLineNameListAfter, aTrackList.mRepeatAutoLineNameListBefore); } repeatIndex++; } repeatIndex++; }; for (int32_t i = 0;; i++) { if (aTrackList.HasRepeatAuto()) { if (i == aTrackList.mRepeatAutoIndex) { const nsTArray& lineNames = aTrackList.mLineNameLists[i]; if (i == endOfRepeat) { // All auto-fit tracks are empty, but we report them anyway. AppendGridLineNames(valueList, lineNames, aTrackList.mRepeatAutoLineNameListBefore); AppendRemovedAutoFits(LinesBetween); AppendGridLineNames(valueList, aTrackList.mRepeatAutoLineNameListAfter, aTrackList.mLineNameLists[i + 1]); } else { AppendGridLineNames(valueList, lineNames, aTrackList.mRepeatAutoLineNameListBefore); AppendRemovedAutoFits(LinesFollow); } } else if (i == endOfRepeat) { // Before appending the last line, finish off any removed auto-fits. AppendRemovedAutoFits(LinesPrecede); const nsTArray& lineNames = aTrackList.mLineNameLists[aTrackList.mRepeatAutoIndex + 1]; AppendGridLineNames(valueList, aTrackList.mRepeatAutoLineNameListAfter, lineNames); } else if (i > aTrackList.mRepeatAutoIndex && i < endOfRepeat) { AppendGridLineNames(valueList, aTrackList.mRepeatAutoLineNameListAfter, aTrackList.mRepeatAutoLineNameListBefore); AppendRemovedAutoFits(LinesFollow); } else { uint32_t j = i > endOfRepeat ? i - offsetToLastRepeat : i; const nsTArray& lineNames = aTrackList.mLineNameLists[j]; AppendGridLineNames(valueList, lineNames); } } else { const nsTArray& lineNames = aTrackList.mLineNameLists[i]; AppendGridLineNames(valueList, lineNames); } if (uint32_t(i) == numExplicitTracks) { break; } RefPtr val = new nsROCSSPrimitiveValue; val->SetAppUnits(trackSizes[i + numLeadingImplicitTracks]); valueList->AppendCSSValue(val.forget()); } } // Add any trailing implicit tracks. for (uint32_t i = numLeadingImplicitTracks + numExplicitTracks; i < numSizes; ++i) { RefPtr val = new nsROCSSPrimitiveValue; val->SetAppUnits(trackSizes[i]); valueList->AppendCSSValue(val.forget()); } } else { // We don't have a frame. So, we'll just return a serialization of // the tracks from the style (without resolved sizes). for (uint32_t i = 0;; i++) { const nsTArray& lineNames = aTrackList.mLineNameLists[i]; if (!lineNames.IsEmpty()) { AppendGridLineNames(valueList, lineNames); } if (i == numSizes) { break; } if (MOZ_UNLIKELY(aTrackList.IsRepeatAutoIndex(i))) { RefPtr start = new nsROCSSPrimitiveValue; start->SetString(aTrackList.mIsAutoFill ? NS_LITERAL_STRING("repeat(auto-fill,") : NS_LITERAL_STRING("repeat(auto-fit,")); valueList->AppendCSSValue(start.forget()); if (!aTrackList.mRepeatAutoLineNameListBefore.IsEmpty()) { AppendGridLineNames(valueList, aTrackList.mRepeatAutoLineNameListBefore); } valueList->AppendCSSValue( GetGridTrackSize(aTrackList.mMinTrackSizingFunctions[i], aTrackList.mMaxTrackSizingFunctions[i])); if (!aTrackList.mRepeatAutoLineNameListAfter.IsEmpty()) { AppendGridLineNames(valueList, aTrackList.mRepeatAutoLineNameListAfter); } RefPtr end = new nsROCSSPrimitiveValue; end->SetString(NS_LITERAL_STRING(")")); valueList->AppendCSSValue(end.forget()); } else { valueList->AppendCSSValue( GetGridTrackSize(aTrackList.mMinTrackSizingFunctions[i], aTrackList.mMaxTrackSizingFunctions[i])); } } } return valueList.forget(); } already_AddRefed nsComputedDOMStyle::DoGetGridAutoFlow() { nsAutoString str; nsStyleUtil::AppendBitmaskCSSValue(nsCSSProps::kGridAutoFlowKTable, StylePosition()->mGridAutoFlow, NS_STYLE_GRID_AUTO_FLOW_ROW, NS_STYLE_GRID_AUTO_FLOW_DENSE, str); RefPtr val = new nsROCSSPrimitiveValue; val->SetString(str); return val.forget(); } already_AddRefed nsComputedDOMStyle::DoGetGridAutoColumns() { return GetGridTrackSize(StylePosition()->mGridAutoColumnsMin, StylePosition()->mGridAutoColumnsMax); } already_AddRefed nsComputedDOMStyle::DoGetGridAutoRows() { return GetGridTrackSize(StylePosition()->mGridAutoRowsMin, StylePosition()->mGridAutoRowsMax); } already_AddRefed nsComputedDOMStyle::DoGetGridTemplateColumns() { const ComputedGridTrackInfo* info = nullptr; nsGridContainerFrame* gridFrame = nsGridContainerFrame::GetGridFrameWithComputedInfo(mInnerFrame); if (gridFrame) { info = gridFrame->GetComputedTemplateColumns(); } return GetGridTemplateColumnsRows( StylePosition()->GridTemplateColumns(), info); } already_AddRefed nsComputedDOMStyle::DoGetGridTemplateRows() { const ComputedGridTrackInfo* info = nullptr; nsGridContainerFrame* gridFrame = nsGridContainerFrame::GetGridFrameWithComputedInfo(mInnerFrame); if (gridFrame) { info = gridFrame->GetComputedTemplateRows(); } return GetGridTemplateColumnsRows( StylePosition()->GridTemplateRows(), info); } already_AddRefed nsComputedDOMStyle::GetGridLine(const nsStyleGridLine& aGridLine) { if (aGridLine.IsAuto()) { RefPtr val = new nsROCSSPrimitiveValue; val->SetIdent(eCSSKeyword_auto); return val.forget(); } RefPtr valueList = GetROCSSValueList(false); if (aGridLine.mHasSpan) { RefPtr span = new nsROCSSPrimitiveValue; span->SetIdent(eCSSKeyword_span); valueList->AppendCSSValue(span.forget()); } if (aGridLine.mInteger != 0) { RefPtr integer = new nsROCSSPrimitiveValue; integer->SetNumber(aGridLine.mInteger); valueList->AppendCSSValue(integer.forget()); } if (!aGridLine.mLineName.IsEmpty()) { RefPtr lineName = new nsROCSSPrimitiveValue; nsString escapedLineName; nsStyleUtil::AppendEscapedCSSIdent(aGridLine.mLineName, escapedLineName); lineName->SetString(escapedLineName); valueList->AppendCSSValue(lineName.forget()); } NS_ASSERTION(valueList->Length() > 0, "Should have appended at least one value"); return valueList.forget(); } already_AddRefed nsComputedDOMStyle::DoGetGridColumnStart() { return GetGridLine(StylePosition()->mGridColumnStart); } already_AddRefed nsComputedDOMStyle::DoGetGridColumnEnd() { return GetGridLine(StylePosition()->mGridColumnEnd); } already_AddRefed nsComputedDOMStyle::DoGetGridRowStart() { return GetGridLine(StylePosition()->mGridRowStart); } already_AddRefed nsComputedDOMStyle::DoGetGridRowEnd() { return GetGridLine(StylePosition()->mGridRowEnd); } already_AddRefed nsComputedDOMStyle::DoGetColumnGap() { RefPtr val = new nsROCSSPrimitiveValue; const auto& columnGap = StylePosition()->mColumnGap; if (columnGap.GetUnit() == eStyleUnit_Normal) { val->SetIdent(eCSSKeyword_normal); } else { SetValueToCoord(val, columnGap, true); } return val.forget(); } already_AddRefed nsComputedDOMStyle::DoGetRowGap() { RefPtr val = new nsROCSSPrimitiveValue; const auto& rowGap = StylePosition()->mRowGap; if (rowGap.GetUnit() == eStyleUnit_Normal) { val->SetIdent(eCSSKeyword_normal); } else { SetValueToCoord(val, rowGap, true); } return val.forget(); } 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::DoGetBorderTopStyle() { return GetBorderStyleFor(eSideTop); } already_AddRefed nsComputedDOMStyle::DoGetBorderBottomStyle() { return GetBorderStyleFor(eSideBottom); } already_AddRefed nsComputedDOMStyle::DoGetBorderLeftStyle() { return GetBorderStyleFor(eSideLeft); } already_AddRefed nsComputedDOMStyle::DoGetBorderRightStyle() { return GetBorderStyleFor(eSideRight); } already_AddRefed nsComputedDOMStyle::DoGetBorderBottomLeftRadius() { return GetEllipseRadii(StyleBorder()->mBorderRadius, eCornerBottomLeft); } already_AddRefed nsComputedDOMStyle::DoGetBorderBottomRightRadius() { return GetEllipseRadii(StyleBorder()->mBorderRadius, eCornerBottomRight); } already_AddRefed nsComputedDOMStyle::DoGetBorderTopLeftRadius() { return GetEllipseRadii(StyleBorder()->mBorderRadius, eCornerTopLeft); } already_AddRefed nsComputedDOMStyle::DoGetBorderTopRightRadius() { return GetEllipseRadii(StyleBorder()->mBorderRadius, eCornerTopRight); } 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::DoGetMarginTopWidth() { return GetMarginWidthFor(eSideTop); } already_AddRefed nsComputedDOMStyle::DoGetMarginBottomWidth() { return GetMarginWidthFor(eSideBottom); } already_AddRefed nsComputedDOMStyle::DoGetMarginLeftWidth() { return GetMarginWidthFor(eSideLeft); } already_AddRefed nsComputedDOMStyle::DoGetMarginRightWidth() { return GetMarginWidthFor(eSideRight); } already_AddRefed nsComputedDOMStyle::DoGetOverscrollBehaviorX() { RefPtr val = new nsROCSSPrimitiveValue; val->SetIdent( nsCSSProps::ValueToKeywordEnum(StyleDisplay()->mOverscrollBehaviorX, nsCSSProps::kOverscrollBehaviorKTable)); return val.forget(); } already_AddRefed nsComputedDOMStyle::DoGetOverscrollBehaviorY() { RefPtr val = new nsROCSSPrimitiveValue; val->SetIdent( nsCSSProps::ValueToKeywordEnum(StyleDisplay()->mOverscrollBehaviorY, nsCSSProps::kOverscrollBehaviorKTable)); return val.forget(); } already_AddRefed nsComputedDOMStyle::DoGetScrollSnapTypeX() { RefPtr val = new nsROCSSPrimitiveValue; val->SetIdent( nsCSSProps::ValueToKeywordEnum(StyleDisplay()->mScrollSnapTypeX, nsCSSProps::kScrollSnapTypeKTable)); return val.forget(); } already_AddRefed nsComputedDOMStyle::DoGetScrollSnapTypeY() { RefPtr val = new nsROCSSPrimitiveValue; val->SetIdent( nsCSSProps::ValueToKeywordEnum(StyleDisplay()->mScrollSnapTypeY, nsCSSProps::kScrollSnapTypeKTable)); return val.forget(); } already_AddRefed nsComputedDOMStyle::GetScrollSnapPoints(const nsStyleCoord& aCoord) { RefPtr val = new nsROCSSPrimitiveValue; if (aCoord.GetUnit() == eStyleUnit_None) { val->SetIdent(eCSSKeyword_none); } else { nsAutoString argumentString; SetCssTextToCoord(argumentString, aCoord, true); nsAutoString tmp; tmp.AppendLiteral("repeat("); tmp.Append(argumentString); tmp.Append(')'); val->SetString(tmp); } return val.forget(); } already_AddRefed nsComputedDOMStyle::DoGetScrollSnapPointsX() { return GetScrollSnapPoints(StyleDisplay()->mScrollSnapPointsX); } already_AddRefed nsComputedDOMStyle::DoGetScrollSnapPointsY() { return GetScrollSnapPoints(StyleDisplay()->mScrollSnapPointsY); } already_AddRefed nsComputedDOMStyle::DoGetScrollSnapDestination() { RefPtr valueList = GetROCSSValueList(false); SetValueToPosition(StyleDisplay()->mScrollSnapDestination, valueList); return valueList.forget(); } already_AddRefed nsComputedDOMStyle::DoGetScrollSnapCoordinate() { const nsStyleDisplay* sd = StyleDisplay(); if (sd->mScrollSnapCoordinate.IsEmpty()) { // Having no snap coordinates is interpreted as "none" RefPtr val = new nsROCSSPrimitiveValue; val->SetIdent(eCSSKeyword_none); return val.forget(); } else { RefPtr valueList = GetROCSSValueList(true); for (size_t i = 0, i_end = sd->mScrollSnapCoordinate.Length(); i < i_end; ++i) { RefPtr itemList = GetROCSSValueList(false); SetValueToPosition(sd->mScrollSnapCoordinate[i], itemList); valueList->AppendCSSValue(itemList.forget()); } return valueList.forget(); } } already_AddRefed nsComputedDOMStyle::DoGetScrollbarColor() { const nsStyleUI* ui = StyleUI(); MOZ_ASSERT(ui->mScrollbarFaceColor.IsAuto() == ui->mScrollbarTrackColor.IsAuto(), "Whether the two colors are auto should be identical"); if (ui->mScrollbarFaceColor.IsAuto()) { RefPtr val = new nsROCSSPrimitiveValue; val->SetIdent(eCSSKeyword_auto); return val.forget(); } RefPtr list = GetROCSSValueList(false); auto put = [this, &list](const StyleComplexColor& color) { RefPtr val = new nsROCSSPrimitiveValue; SetValueFromComplexColor(val, color); list->AppendCSSValue(val.forget()); }; put(ui->mScrollbarFaceColor); put(ui->mScrollbarTrackColor); return list.forget(); } already_AddRefed nsComputedDOMStyle::DoGetOutlineWidth() { RefPtr val = new nsROCSSPrimitiveValue; const nsStyleOutline* outline = StyleOutline(); nscoord width; if (outline->mOutlineStyle == NS_STYLE_BORDER_STYLE_NONE) { NS_ASSERTION(outline->GetOutlineWidth() == 0, "unexpected width"); width = 0; } else { width = outline->GetOutlineWidth(); } val->SetAppUnits(width); return val.forget(); } already_AddRefed nsComputedDOMStyle::DoGetOutlineStyle() { RefPtr val = new nsROCSSPrimitiveValue; val->SetIdent( nsCSSProps::ValueToKeywordEnum(StyleOutline()->mOutlineStyle, nsCSSProps::kOutlineStyleKTable)); return val.forget(); } already_AddRefed nsComputedDOMStyle::DoGetOutlineRadiusBottomLeft() { return GetEllipseRadii(StyleOutline()->mOutlineRadius, eCornerBottomLeft); } already_AddRefed nsComputedDOMStyle::DoGetOutlineRadiusBottomRight() { return GetEllipseRadii(StyleOutline()->mOutlineRadius, eCornerBottomRight); } already_AddRefed nsComputedDOMStyle::DoGetOutlineRadiusTopLeft() { return GetEllipseRadii(StyleOutline()->mOutlineRadius, eCornerTopLeft); } already_AddRefed nsComputedDOMStyle::DoGetOutlineRadiusTopRight() { return GetEllipseRadii(StyleOutline()->mOutlineRadius, eCornerTopRight); } already_AddRefed nsComputedDOMStyle::GetEllipseRadii(const nsStyleCorners& aRadius, Corner aFullCorner) { nsStyleCoord radiusX = aRadius.Get(FullToHalfCorner(aFullCorner, false)); nsStyleCoord radiusY = aRadius.Get(FullToHalfCorner(aFullCorner, true)); // for compatibility, return a single value if X and Y are equal if (radiusX == radiusY) { RefPtr val = new nsROCSSPrimitiveValue; SetValueToCoord(val, radiusX, true); return val.forget(); } RefPtr valueList = GetROCSSValueList(false); RefPtr valX = new nsROCSSPrimitiveValue; RefPtr valY = new nsROCSSPrimitiveValue; SetValueToCoord(valX, radiusX, true); SetValueToCoord(valY, radiusY, true); valueList->AppendCSSValue(valX.forget()); valueList->AppendCSSValue(valY.forget()); return valueList.forget(); } already_AddRefed nsComputedDOMStyle::GetCSSShadowArray(nsCSSShadowArray* aArray, bool aIsBoxShadow) { if (!aArray) { RefPtr val = new nsROCSSPrimitiveValue; val->SetIdent(eCSSKeyword_none); return val.forget(); } static nscoord nsCSSShadowItem::* const shadowValuesNoSpread[] = { &nsCSSShadowItem::mXOffset, &nsCSSShadowItem::mYOffset, &nsCSSShadowItem::mRadius }; static nscoord nsCSSShadowItem::* const shadowValuesWithSpread[] = { &nsCSSShadowItem::mXOffset, &nsCSSShadowItem::mYOffset, &nsCSSShadowItem::mRadius, &nsCSSShadowItem::mSpread }; nscoord nsCSSShadowItem::* const * shadowValues; uint32_t shadowValuesLength; if (aIsBoxShadow) { shadowValues = shadowValuesWithSpread; shadowValuesLength = ArrayLength(shadowValuesWithSpread); } else { shadowValues = shadowValuesNoSpread; shadowValuesLength = ArrayLength(shadowValuesNoSpread); } RefPtr valueList = GetROCSSValueList(true); for (nsCSSShadowItem *item = aArray->ShadowAt(0), *item_end = item + aArray->Length(); item < item_end; ++item) { RefPtr itemList = GetROCSSValueList(false); // Color is either the specified shadow color or the foreground color RefPtr val = new nsROCSSPrimitiveValue; SetValueFromComplexColor(val, item->mColor); itemList->AppendCSSValue(val.forget()); // Set the offsets, blur radius, and spread if available for (uint32_t i = 0; i < shadowValuesLength; ++i) { val = new nsROCSSPrimitiveValue; val->SetAppUnits(item->*(shadowValues[i])); itemList->AppendCSSValue(val.forget()); } if (item->mInset && aIsBoxShadow) { // This is an inset box-shadow val = new nsROCSSPrimitiveValue; val->SetIdent( nsCSSProps::ValueToKeywordEnum( uint8_t(StyleBoxShadowType::Inset), nsCSSProps::kBoxShadowTypeKTable)); itemList->AppendCSSValue(val.forget()); } valueList->AppendCSSValue(itemList.forget()); } return valueList.forget(); } already_AddRefed nsComputedDOMStyle::DoGetBoxShadow() { return GetCSSShadowArray(StyleEffects()->mBoxShadow, true); } already_AddRefed nsComputedDOMStyle::DoGetZIndex() { RefPtr val = new nsROCSSPrimitiveValue; SetValueToCoord(val, StylePosition()->mZIndex, false); return val.forget(); } already_AddRefed nsComputedDOMStyle::DoGetInitialLetter() { const nsStyleTextReset* textReset = StyleTextReset(); RefPtr val = new nsROCSSPrimitiveValue; if (textReset->mInitialLetterSink == 0) { val->SetIdent(eCSSKeyword_normal); return val.forget(); } else { RefPtr valueList = GetROCSSValueList(false); val->SetNumber(textReset->mInitialLetterSize); valueList->AppendCSSValue(val.forget()); RefPtr second = new nsROCSSPrimitiveValue; second->SetNumber(textReset->mInitialLetterSink); valueList->AppendCSSValue(second.forget()); return valueList.forget(); } } already_AddRefed nsComputedDOMStyle::DoGetLineHeight() { RefPtr val = new nsROCSSPrimitiveValue; nscoord lineHeight; if (GetLineHeightCoord(lineHeight)) { val->SetAppUnits(lineHeight); } else { SetValueToCoord(val, StyleText()->mLineHeight, true, nullptr, nsCSSProps::kLineHeightKTable); } return val.forget(); } already_AddRefed nsComputedDOMStyle::DoGetVerticalAlign() { RefPtr val = new nsROCSSPrimitiveValue; SetValueToCoord(val, StyleDisplay()->mVerticalAlign, false, nullptr, nsCSSProps::kVerticalAlignKTable); return val.forget(); } already_AddRefed nsComputedDOMStyle::DoGetTextDecoration() { const nsStyleTextReset* textReset = StyleTextReset(); bool isInitialStyle = textReset->mTextDecorationStyle == NS_STYLE_TEXT_DECORATION_STYLE_SOLID; StyleComplexColor color = textReset->mTextDecorationColor; if (isInitialStyle && color.IsCurrentColor()) { return DoGetTextDecorationLine(); } RefPtr valueList = GetROCSSValueList(false); valueList->AppendCSSValue(DoGetTextDecorationLine()); if (!isInitialStyle) { valueList->AppendCSSValue(DoGetTextDecorationStyle()); } if (!color.IsCurrentColor()) { valueList->AppendCSSValue(DoGetTextDecorationColor()); } return valueList.forget(); } already_AddRefed nsComputedDOMStyle::DoGetTextDecorationColor() { RefPtr val = new nsROCSSPrimitiveValue; SetValueFromComplexColor(val, StyleTextReset()->mTextDecorationColor); return val.forget(); } already_AddRefed nsComputedDOMStyle::DoGetTextDecorationLine() { RefPtr val = new nsROCSSPrimitiveValue; int32_t intValue = StyleTextReset()->mTextDecorationLine; if (NS_STYLE_TEXT_DECORATION_LINE_NONE == intValue) { val->SetIdent(eCSSKeyword_none); } else { nsAutoString decorationLineString; // Clear the OVERRIDE_ALL bits -- we don't want these to appear in // the computed style. intValue &= ~NS_STYLE_TEXT_DECORATION_LINE_OVERRIDE_ALL; nsStyleUtil::AppendBitmaskCSSValue(nsCSSProps::kTextDecorationLineKTable, intValue, NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE, NS_STYLE_TEXT_DECORATION_LINE_BLINK, decorationLineString); val->SetString(decorationLineString); } return val.forget(); } already_AddRefed nsComputedDOMStyle::DoGetTextDecorationStyle() { RefPtr val = new nsROCSSPrimitiveValue; val->SetIdent( nsCSSProps::ValueToKeywordEnum(StyleTextReset()->mTextDecorationStyle, nsCSSProps::kTextDecorationStyleKTable)); return val.forget(); } already_AddRefed nsComputedDOMStyle::DoGetTextEmphasisPosition() { auto position = StyleText()->mTextEmphasisPosition; MOZ_ASSERT(!(position & NS_STYLE_TEXT_EMPHASIS_POSITION_OVER) != !(position & NS_STYLE_TEXT_EMPHASIS_POSITION_UNDER)); RefPtr first = new nsROCSSPrimitiveValue; first->SetIdent((position & NS_STYLE_TEXT_EMPHASIS_POSITION_OVER) ? eCSSKeyword_over : eCSSKeyword_under); MOZ_ASSERT(!(position & NS_STYLE_TEXT_EMPHASIS_POSITION_LEFT) != !(position & NS_STYLE_TEXT_EMPHASIS_POSITION_RIGHT)); RefPtr second = new nsROCSSPrimitiveValue; second->SetIdent((position & NS_STYLE_TEXT_EMPHASIS_POSITION_LEFT) ? eCSSKeyword_left : eCSSKeyword_right); RefPtr valueList = GetROCSSValueList(false); valueList->AppendCSSValue(first.forget()); valueList->AppendCSSValue(second.forget()); return valueList.forget(); } already_AddRefed nsComputedDOMStyle::DoGetTextEmphasisStyle() { auto style = StyleText()->mTextEmphasisStyle; if (style == NS_STYLE_TEXT_EMPHASIS_STYLE_NONE) { RefPtr val = new nsROCSSPrimitiveValue; val->SetIdent(eCSSKeyword_none); return val.forget(); } if (style == NS_STYLE_TEXT_EMPHASIS_STYLE_STRING) { RefPtr val = new nsROCSSPrimitiveValue; nsAutoString tmp; nsStyleUtil::AppendEscapedCSSString( StyleText()->mTextEmphasisStyleString, tmp); val->SetString(tmp); return val.forget(); } RefPtr fillVal = new nsROCSSPrimitiveValue; if ((style & NS_STYLE_TEXT_EMPHASIS_STYLE_FILL_MASK) == NS_STYLE_TEXT_EMPHASIS_STYLE_FILLED) { fillVal->SetIdent(eCSSKeyword_filled); } else { MOZ_ASSERT((style & NS_STYLE_TEXT_EMPHASIS_STYLE_FILL_MASK) == NS_STYLE_TEXT_EMPHASIS_STYLE_OPEN); fillVal->SetIdent(eCSSKeyword_open); } RefPtr shapeVal = new nsROCSSPrimitiveValue; shapeVal->SetIdent(nsCSSProps::ValueToKeywordEnum( style & NS_STYLE_TEXT_EMPHASIS_STYLE_SHAPE_MASK, nsCSSProps::kTextEmphasisStyleShapeKTable)); RefPtr valueList = GetROCSSValueList(false); valueList->AppendCSSValue(fillVal.forget()); valueList->AppendCSSValue(shapeVal.forget()); return valueList.forget(); } already_AddRefed nsComputedDOMStyle::DoGetTextOverflow() { const nsStyleTextReset *style = StyleTextReset(); RefPtr first = new nsROCSSPrimitiveValue; const nsStyleTextOverflowSide *side = style->mTextOverflow.GetFirstValue(); if (side->mType == NS_STYLE_TEXT_OVERFLOW_STRING) { nsAutoString str; nsStyleUtil::AppendEscapedCSSString(side->mString, str); first->SetString(str); } else { first->SetIdent( nsCSSProps::ValueToKeywordEnum(side->mType, nsCSSProps::kTextOverflowKTable)); } side = style->mTextOverflow.GetSecondValue(); if (!side) { return first.forget(); } RefPtr second = new nsROCSSPrimitiveValue; if (side->mType == NS_STYLE_TEXT_OVERFLOW_STRING) { nsAutoString str; nsStyleUtil::AppendEscapedCSSString(side->mString, str); second->SetString(str); } else { second->SetIdent( nsCSSProps::ValueToKeywordEnum(side->mType, nsCSSProps::kTextOverflowKTable)); } RefPtr valueList = GetROCSSValueList(false); valueList->AppendCSSValue(first.forget()); valueList->AppendCSSValue(second.forget()); return valueList.forget(); } already_AddRefed nsComputedDOMStyle::DoGetTextShadow() { return GetCSSShadowArray(StyleText()->mTextShadow, false); } already_AddRefed nsComputedDOMStyle::DoGetTabSize() { RefPtr val = new nsROCSSPrimitiveValue; SetValueToCoord(val, StyleText()->mTabSize, true); return val.forget(); } already_AddRefed nsComputedDOMStyle::DoGetLetterSpacing() { RefPtr val = new nsROCSSPrimitiveValue; SetValueToCoord(val, StyleText()->mLetterSpacing, false); return val.forget(); } already_AddRefed nsComputedDOMStyle::DoGetWordSpacing() { RefPtr val = new nsROCSSPrimitiveValue; SetValueToCoord(val, StyleText()->mWordSpacing, false); return val.forget(); } already_AddRefed nsComputedDOMStyle::DoGetWebkitTextStrokeWidth() { RefPtr val = new nsROCSSPrimitiveValue; val->SetAppUnits(StyleText()->mWebkitTextStrokeWidth); return val.forget(); } static_assert(NS_STYLE_UNICODE_BIDI_NORMAL == 0, "unicode-bidi style constants not as expected"); already_AddRefed nsComputedDOMStyle::DoGetCaretColor() { RefPtr val = new nsROCSSPrimitiveValue; SetValueFromComplexColor(val, StyleUI()->mCaretColor); return val.forget(); } already_AddRefed nsComputedDOMStyle::DoGetCursor() { RefPtr valueList = GetROCSSValueList(true); const nsStyleUI *ui = StyleUI(); for (const nsCursorImage& item : ui->mCursorImages) { RefPtr itemList = GetROCSSValueList(false); RefPtr val = new nsROCSSPrimitiveValue; SetValueToURLValue(item.mImage->GetImageValue(), val); itemList->AppendCSSValue(val.forget()); if (item.mHaveHotspot) { RefPtr valX = new nsROCSSPrimitiveValue; RefPtr valY = new nsROCSSPrimitiveValue; valX->SetNumber(item.mHotspotX); valY->SetNumber(item.mHotspotY); itemList->AppendCSSValue(valX.forget()); itemList->AppendCSSValue(valY.forget()); } valueList->AppendCSSValue(itemList.forget()); } RefPtr val = new nsROCSSPrimitiveValue; val->SetIdent(nsCSSProps::ValueToKeywordEnum(ui->mCursor, nsCSSProps::kCursorKTable)); valueList->AppendCSSValue(val.forget()); return valueList.forget(); } already_AddRefed nsComputedDOMStyle::DoGetBoxFlex() { RefPtr val = new nsROCSSPrimitiveValue; val->SetNumber(StyleXUL()->mBoxFlex); return val.forget(); } /* Border image properties */ void nsComputedDOMStyle::AppendFourSideCoordValues(nsDOMCSSValueList* aList, const nsStyleSides& aValues) { const nsStyleCoord& top = aValues.Get(eSideTop); const nsStyleCoord& right = aValues.Get(eSideRight); const nsStyleCoord& bottom = aValues.Get(eSideBottom); const nsStyleCoord& left = aValues.Get(eSideLeft); auto appendValue = [this, aList](const nsStyleCoord& value) { RefPtr val = new nsROCSSPrimitiveValue; SetValueToCoord(val, value, true); aList->AppendCSSValue(val.forget()); }; appendValue(top); if (top != right || top != bottom || top != left) { appendValue(right); if (top != bottom || right != left) { appendValue(bottom); if (right != left) { appendValue(left); } } } } already_AddRefed nsComputedDOMStyle::DoGetBorderImageSlice() { const nsStyleBorder* border = StyleBorder(); RefPtr valueList = GetROCSSValueList(false); AppendFourSideCoordValues(valueList, border->mBorderImageSlice); // Fill keyword. if (NS_STYLE_BORDER_IMAGE_SLICE_FILL == border->mBorderImageFill) { RefPtr val = new nsROCSSPrimitiveValue; val->SetIdent(eCSSKeyword_fill); valueList->AppendCSSValue(val.forget()); } return valueList.forget(); } already_AddRefed nsComputedDOMStyle::DoGetBorderImageWidth() { const nsStyleBorder* border = StyleBorder(); RefPtr valueList = GetROCSSValueList(false); AppendFourSideCoordValues(valueList, border->mBorderImageWidth); return valueList.forget(); } already_AddRefed nsComputedDOMStyle::DoGetBorderImageOutset() { const nsStyleBorder* border = StyleBorder(); RefPtr valueList = GetROCSSValueList(false); AppendFourSideCoordValues(valueList, border->mBorderImageOutset); return valueList.forget(); } already_AddRefed nsComputedDOMStyle::DoGetFlexBasis() { RefPtr val = new nsROCSSPrimitiveValue; // XXXdholbert We could make this more automagic and resolve percentages // if we wanted, by passing in a PercentageBaseGetter instead of nullptr // below. Logic would go like this: // if (i'm a flex item) { // if (my flex container is horizontal) { // percentageBaseGetter = &nsComputedDOMStyle::GetCBContentWidth; // } else { // percentageBaseGetter = &nsComputedDOMStyle::GetCBContentHeight; // } // } SetValueToCoord(val, StylePosition()->mFlexBasis, true, nullptr, nsCSSProps::kFlexBasisKTable); return val.forget(); } already_AddRefed nsComputedDOMStyle::DoGetFlexGrow() { RefPtr val = new nsROCSSPrimitiveValue; val->SetNumber(StylePosition()->mFlexGrow); return val.forget(); } already_AddRefed nsComputedDOMStyle::DoGetFlexShrink() { RefPtr val = new nsROCSSPrimitiveValue; val->SetNumber(StylePosition()->mFlexShrink); return val.forget(); } already_AddRefed nsComputedDOMStyle::DoGetAlignContent() { RefPtr val = new nsROCSSPrimitiveValue; nsAutoString str; auto align = StylePosition()->mAlignContent; nsCSSValue::AppendAlignJustifyValueToString(align & NS_STYLE_ALIGN_ALL_BITS, str); auto fallback = align >> NS_STYLE_ALIGN_ALL_SHIFT; if (fallback) { str.Append(' '); nsCSSValue::AppendAlignJustifyValueToString(fallback, str); } val->SetString(str); return val.forget(); } already_AddRefed nsComputedDOMStyle::DoGetAlignItems() { RefPtr val = new nsROCSSPrimitiveValue; nsAutoString str; auto align = StylePosition()->mAlignItems; nsCSSValue::AppendAlignJustifyValueToString(align, str); val->SetString(str); return val.forget(); } already_AddRefed nsComputedDOMStyle::DoGetAlignSelf() { RefPtr val = new nsROCSSPrimitiveValue; nsAutoString str; auto align = StylePosition()->mAlignSelf; nsCSSValue::AppendAlignJustifyValueToString(align, str); val->SetString(str); return val.forget(); } already_AddRefed nsComputedDOMStyle::DoGetJustifyContent() { RefPtr val = new nsROCSSPrimitiveValue; nsAutoString str; auto justify = StylePosition()->mJustifyContent; nsCSSValue::AppendAlignJustifyValueToString(justify & NS_STYLE_JUSTIFY_ALL_BITS, str); auto fallback = justify >> NS_STYLE_JUSTIFY_ALL_SHIFT; if (fallback) { MOZ_ASSERT(nsCSSProps::ValueToKeywordEnum(fallback & ~NS_STYLE_JUSTIFY_FLAG_BITS, nsCSSProps::kAlignSelfPosition) != eCSSKeyword_UNKNOWN, "unknown fallback value"); str.Append(' '); nsCSSValue::AppendAlignJustifyValueToString(fallback, str); } val->SetString(str); return val.forget(); } already_AddRefed nsComputedDOMStyle::DoGetJustifyItems() { RefPtr val = new nsROCSSPrimitiveValue; nsAutoString str; auto justify = StylePosition()->mJustifyItems; nsCSSValue::AppendAlignJustifyValueToString(justify, str); val->SetString(str); return val.forget(); } already_AddRefed nsComputedDOMStyle::DoGetJustifySelf() { RefPtr val = new nsROCSSPrimitiveValue; nsAutoString str; auto justify = StylePosition()->mJustifySelf; nsCSSValue::AppendAlignJustifyValueToString(justify, str); val->SetString(str); return val.forget(); } already_AddRefed nsComputedDOMStyle::DoGetForceBrokenImageIcon() { RefPtr val = new nsROCSSPrimitiveValue; val->SetNumber(StyleUIReset()->mForceBrokenImageIcon); return val.forget(); } already_AddRefed nsComputedDOMStyle::DoGetDisplay() { RefPtr val = new nsROCSSPrimitiveValue; val->SetIdent(nsCSSProps::ValueToKeywordEnum(StyleDisplay()->mDisplay, nsCSSProps::kDisplayKTable)); return val.forget(); } already_AddRefed nsComputedDOMStyle::DoGetContain() { RefPtr val = new nsROCSSPrimitiveValue; int32_t mask = StyleDisplay()->mContain; if (mask == 0) { val->SetIdent(eCSSKeyword_none); } else if (mask & NS_STYLE_CONTAIN_STRICT) { NS_ASSERTION(mask == (NS_STYLE_CONTAIN_STRICT | NS_STYLE_CONTAIN_ALL_BITS), "contain: strict should imply contain: size layout style paint"); val->SetIdent(eCSSKeyword_strict); } else if (mask & NS_STYLE_CONTAIN_CONTENT) { NS_ASSERTION(mask == (NS_STYLE_CONTAIN_CONTENT | NS_STYLE_CONTAIN_CONTENT_BITS), "contain: content should imply contain: layout style paint"); val->SetIdent(eCSSKeyword_content); } else { nsAutoString valueStr; nsStyleUtil::AppendBitmaskCSSValue(nsCSSProps::kContainKTable, mask, NS_STYLE_CONTAIN_SIZE, NS_STYLE_CONTAIN_PAINT, valueStr); val->SetString(valueStr); } return val.forget(); } already_AddRefed nsComputedDOMStyle::DoGetWillChange() { const nsTArray>& willChange = StyleDisplay()->mWillChange; if (willChange.IsEmpty()) { RefPtr val = new nsROCSSPrimitiveValue; val->SetIdent(eCSSKeyword_auto); return val.forget(); } RefPtr valueList = GetROCSSValueList(true); for (const nsAtom* ident : willChange) { RefPtr property = new nsROCSSPrimitiveValue; property->SetString(nsDependentAtomString(ident)); valueList->AppendCSSValue(property.forget()); } return valueList.forget(); } already_AddRefed nsComputedDOMStyle::DoGetOverflow() { const nsStyleDisplay* display = StyleDisplay(); RefPtr overflowX = new nsROCSSPrimitiveValue; overflowX->SetIdent( nsCSSProps::ValueToKeywordEnum(display->mOverflowX, nsCSSProps::kOverflowKTable)); if (display->mOverflowX == display->mOverflowY) { return overflowX.forget(); } RefPtr valueList = GetROCSSValueList(false); valueList->AppendCSSValue(overflowX.forget()); RefPtr overflowY= new nsROCSSPrimitiveValue; overflowY->SetIdent( nsCSSProps::ValueToKeywordEnum(display->mOverflowY, nsCSSProps::kOverflowKTable)); valueList->AppendCSSValue(overflowY.forget()); return valueList.forget(); } already_AddRefed nsComputedDOMStyle::DoGetOverflowY() { RefPtr val = new nsROCSSPrimitiveValue; val->SetIdent( nsCSSProps::ValueToKeywordEnum(StyleDisplay()->mOverflowY, nsCSSProps::kOverflowSubKTable)); return val.forget(); } already_AddRefed nsComputedDOMStyle::DoGetOverflowClipBoxBlock() { RefPtr val = new nsROCSSPrimitiveValue; val->SetIdent( nsCSSProps::ValueToKeywordEnum(StyleDisplay()->mOverflowClipBoxBlock, nsCSSProps::kOverflowClipBoxKTable)); return val.forget(); } already_AddRefed nsComputedDOMStyle::DoGetOverflowClipBoxInline() { RefPtr val = new nsROCSSPrimitiveValue; val->SetIdent( nsCSSProps::ValueToKeywordEnum(StyleDisplay()->mOverflowClipBoxInline, nsCSSProps::kOverflowClipBoxKTable)); return val.forget(); } already_AddRefed nsComputedDOMStyle::DoGetTouchAction() { RefPtr val = new nsROCSSPrimitiveValue; int32_t intValue = StyleDisplay()->mTouchAction; // None and Auto and Manipulation values aren't allowed // to be in conjunction with other values. // But there are all checks in CSSParserImpl::ParseTouchAction nsAutoString valueStr; nsStyleUtil::AppendBitmaskCSSValue(nsCSSProps::kTouchActionKTable, intValue, NS_STYLE_TOUCH_ACTION_NONE, NS_STYLE_TOUCH_ACTION_MANIPULATION, valueStr); val->SetString(valueStr); return val.forget(); } already_AddRefed nsComputedDOMStyle::DoGetHeight() { RefPtr val = new nsROCSSPrimitiveValue; bool calcHeight = false; if (mInnerFrame) { calcHeight = true; const nsStyleDisplay* displayData = StyleDisplay(); if (displayData->mDisplay == mozilla::StyleDisplay::Inline && !(mInnerFrame->IsFrameOfType(nsIFrame::eReplaced)) && // An outer SVG frame should behave the same as eReplaced in this case !mInnerFrame->IsSVGOuterSVGFrame()) { calcHeight = false; } } if (calcHeight) { AssertFlushedPendingReflows(); nsMargin adjustedValues = GetAdjustedValuesForBoxSizing(); val->SetAppUnits(mInnerFrame->GetContentRect().height + adjustedValues.TopBottom()); } else { const nsStylePosition *positionData = StylePosition(); nscoord minHeight = StyleCoordToNSCoord(positionData->mMinHeight, &nsComputedDOMStyle::GetCBContentHeight, 0, true); nscoord maxHeight = StyleCoordToNSCoord(positionData->mMaxHeight, &nsComputedDOMStyle::GetCBContentHeight, nscoord_MAX, true); SetValueToCoord(val, positionData->mHeight, true, nullptr, nsCSSProps::kWidthKTable, minHeight, maxHeight); } return val.forget(); } already_AddRefed nsComputedDOMStyle::DoGetWidth() { RefPtr val = new nsROCSSPrimitiveValue; bool calcWidth = false; if (mInnerFrame) { calcWidth = true; const nsStyleDisplay *displayData = StyleDisplay(); if (displayData->mDisplay == mozilla::StyleDisplay::Inline && !(mInnerFrame->IsFrameOfType(nsIFrame::eReplaced)) && // An outer SVG frame should behave the same as eReplaced in this case !mInnerFrame->IsSVGOuterSVGFrame()) { calcWidth = false; } } if (calcWidth) { AssertFlushedPendingReflows(); nsMargin adjustedValues = GetAdjustedValuesForBoxSizing(); val->SetAppUnits(mInnerFrame->GetContentRect().width + adjustedValues.LeftRight()); } else { const nsStylePosition *positionData = StylePosition(); nscoord minWidth = StyleCoordToNSCoord(positionData->mMinWidth, &nsComputedDOMStyle::GetCBContentWidth, 0, true); nscoord maxWidth = StyleCoordToNSCoord(positionData->mMaxWidth, &nsComputedDOMStyle::GetCBContentWidth, nscoord_MAX, true); SetValueToCoord(val, positionData->mWidth, true, nullptr, nsCSSProps::kWidthKTable, minWidth, maxWidth); } return val.forget(); } already_AddRefed nsComputedDOMStyle::DoGetMaxHeight() { RefPtr val = new nsROCSSPrimitiveValue; SetValueToCoord(val, StylePosition()->mMaxHeight, true, nullptr, nsCSSProps::kWidthKTable); return val.forget(); } already_AddRefed nsComputedDOMStyle::DoGetMaxWidth() { RefPtr val = new nsROCSSPrimitiveValue; SetValueToCoord(val, StylePosition()->mMaxWidth, true, nullptr, nsCSSProps::kWidthKTable); 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; nsStyleCoord minHeight = StylePosition()->mMinHeight; if (eStyleUnit_Auto == minHeight.GetUnit() && !ShouldHonorMinSizeAutoInAxis(eAxisVertical)) { minHeight.SetCoordValue(0); } SetValueToCoord(val, minHeight, true, nullptr, nsCSSProps::kWidthKTable); return val.forget(); } already_AddRefed nsComputedDOMStyle::DoGetMinWidth() { RefPtr val = new nsROCSSPrimitiveValue; nsStyleCoord minWidth = StylePosition()->mMinWidth; if (eStyleUnit_Auto == minWidth.GetUnit() && !ShouldHonorMinSizeAutoInAxis(eAxisHorizontal)) { minWidth.SetCoordValue(0); } SetValueToCoord(val, minWidth, true, nullptr, nsCSSProps::kWidthKTable); return val.forget(); } already_AddRefed nsComputedDOMStyle::DoGetObjectPosition() { RefPtr valueList = GetROCSSValueList(false); SetValueToPosition(StylePosition()->mObjectPosition, valueList); return valueList.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(); AssertFlushedPendingReflows(); uint8_t position = display->mPosition; if (!mOuterFrame) { // GetRelativeOffset and GetAbsoluteOffset don't handle elements // without frames in any sensible way. GetStaticOffset, however, // is perfect for that case. position = NS_STYLE_POSITION_STATIC; } switch (position) { case NS_STYLE_POSITION_STATIC: return GetStaticOffset(aSide); case NS_STYLE_POSITION_RELATIVE: return GetRelativeOffset(aSide); case NS_STYLE_POSITION_STICKY: return GetStickyOffset(aSide); case NS_STYLE_POSITION_ABSOLUTE: case NS_STYLE_POSITION_FIXED: return GetAbsoluteOffset(aSide); default: NS_ERROR("Invalid position"); return nullptr; } } already_AddRefed nsComputedDOMStyle::GetAbsoluteOffset(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(); } } 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; } RefPtr val = new nsROCSSPrimitiveValue; val->SetAppUnits(offset); return val.forget(); } 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::GetRelativeOffset(mozilla::Side aSide) { RefPtr val = new nsROCSSPrimitiveValue; const nsStylePosition* positionData = StylePosition(); int32_t sign = 1; nsStyleCoord coord = positionData->mOffset.Get(aSide); NS_ASSERTION(coord.GetUnit() == eStyleUnit_Coord || coord.GetUnit() == eStyleUnit_Percent || coord.GetUnit() == eStyleUnit_Auto || coord.IsCalcUnit(), "Unexpected unit"); if (coord.GetUnit() == eStyleUnit_Auto) { coord = positionData->mOffset.Get(NS_OPPOSITE_SIDE(aSide)); sign = -1; } PercentageBaseGetter baseGetter; if (aSide == eSideLeft || aSide == eSideRight) { baseGetter = &nsComputedDOMStyle::GetCBContentWidth; } else { baseGetter = &nsComputedDOMStyle::GetCBContentHeight; } val->SetAppUnits(sign * StyleCoordToNSCoord(coord, baseGetter, 0, false)); return val.forget(); } already_AddRefed nsComputedDOMStyle::GetStickyOffset(mozilla::Side aSide) { RefPtr val = new nsROCSSPrimitiveValue; const nsStylePosition* positionData = StylePosition(); nsStyleCoord coord = positionData->mOffset.Get(aSide); NS_ASSERTION(coord.GetUnit() == eStyleUnit_Coord || coord.GetUnit() == eStyleUnit_Percent || coord.GetUnit() == eStyleUnit_Auto || coord.IsCalcUnit(), "Unexpected unit"); if (coord.GetUnit() == eStyleUnit_Auto) { val->SetIdent(eCSSKeyword_auto); return val.forget(); } PercentageBaseGetter baseGetter; if (aSide == eSideLeft || aSide == eSideRight) { baseGetter = &nsComputedDOMStyle::GetScrollFrameContentWidth; } else { baseGetter = &nsComputedDOMStyle::GetScrollFrameContentHeight; } val->SetAppUnits(StyleCoordToNSCoord(coord, baseGetter, 0, false)); return val.forget(); } already_AddRefed nsComputedDOMStyle::GetStaticOffset(mozilla::Side aSide) { RefPtr val = new nsROCSSPrimitiveValue; SetValueToCoord(val, StylePosition()->mOffset.Get(aSide), false); return val.forget(); } already_AddRefed nsComputedDOMStyle::GetPaddingWidthFor(mozilla::Side aSide) { RefPtr val = new nsROCSSPrimitiveValue; if (!mInnerFrame) { SetValueToCoord(val, StylePadding()->mPadding.Get(aSide), true); } else { AssertFlushedPendingReflows(); val->SetAppUnits(mInnerFrame->GetUsedPadding().Side(aSide)); } return val.forget(); } bool nsComputedDOMStyle::GetLineHeightCoord(nscoord& aCoord) { AssertFlushedPendingReflows(); nscoord blockHeight = NS_AUTOHEIGHT; if (StyleText()->mLineHeight.GetUnit() == eStyleUnit_Enumerated) { if (!mInnerFrame) return false; if (nsLayoutUtils::IsNonWrapperBlock(mInnerFrame)) { blockHeight = mInnerFrame->GetContentRect().height; } else { GetCBContentHeight(blockHeight); } } nsPresContext* presContext = mPresShell->GetPresContext(); // lie about font size inflation since we lie about font size (since // the inflation only applies to text) aCoord = ReflowInput::CalcLineHeight(mElement, mComputedStyle, presContext, blockHeight, 1.0f); // CalcLineHeight uses font->mFont.size, but we want to use // font->mSize as the font size. Adjust for that. Also adjust for // the text zoom, if any. const nsStyleFont* font = StyleFont(); float fCoord = float(aCoord); if (font->mAllowZoom) { fCoord /= presContext->EffectiveTextZoom(); } if (font->mFont.size != font->mSize) { fCoord = fCoord * (float(font->mSize) / float(font->mFont.size)); } aCoord = NSToCoordRound(fCoord); return true; } already_AddRefed nsComputedDOMStyle::GetBorderWidthFor(mozilla::Side aSide) { RefPtr val = new nsROCSSPrimitiveValue; nscoord width; if (mInnerFrame) { AssertFlushedPendingReflows(); width = mInnerFrame->GetUsedBorder().Side(aSide); } else { width = StyleBorder()->GetComputedBorderWidth(aSide); } val->SetAppUnits(width); return val.forget(); } already_AddRefed nsComputedDOMStyle::GetBorderColorFor(mozilla::Side aSide) { RefPtr val = new nsROCSSPrimitiveValue; SetValueFromComplexColor(val, StyleBorder()->BorderColorFor(aSide)); return val.forget(); } already_AddRefed nsComputedDOMStyle::GetMarginWidthFor(mozilla::Side aSide) { RefPtr val = new nsROCSSPrimitiveValue; if (!mInnerFrame) { SetValueToCoord(val, StyleMargin()->mMargin.Get(aSide), 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(); } already_AddRefed nsComputedDOMStyle::GetBorderStyleFor(mozilla::Side aSide) { RefPtr val = new nsROCSSPrimitiveValue; val->SetIdent( nsCSSProps::ValueToKeywordEnum(StyleBorder()->GetBorderStyle(aSide), nsCSSProps::kBorderStyleKTable)); return val.forget(); } void nsComputedDOMStyle::SetValueToCoord(nsROCSSPrimitiveValue* aValue, const nsStyleCoord& aCoord, bool aClampNegativeCalc, PercentageBaseGetter aPercentageBaseGetter, const KTableEntry aTable[], nscoord aMinAppUnits, nscoord aMaxAppUnits) { MOZ_ASSERT(aValue, "Must have a value to work with"); switch (aCoord.GetUnit()) { case eStyleUnit_Normal: aValue->SetIdent(eCSSKeyword_normal); break; case eStyleUnit_Auto: aValue->SetIdent(eCSSKeyword_auto); break; case eStyleUnit_Percent: { nscoord percentageBase; if (aPercentageBaseGetter && (this->*aPercentageBaseGetter)(percentageBase)) { nscoord val = NSCoordSaturatingMultiply(percentageBase, aCoord.GetPercentValue()); aValue->SetAppUnits(std::max(aMinAppUnits, std::min(val, aMaxAppUnits))); } else { aValue->SetPercent(aCoord.GetPercentValue()); } } break; case eStyleUnit_Factor: aValue->SetNumber(aCoord.GetFactorValue()); break; case eStyleUnit_Coord: { nscoord val = aCoord.GetCoordValue(); aValue->SetAppUnits(std::max(aMinAppUnits, std::min(val, aMaxAppUnits))); } break; case eStyleUnit_Integer: aValue->SetNumber(aCoord.GetIntValue()); break; case eStyleUnit_Enumerated: NS_ASSERTION(aTable, "Must have table to handle this case"); aValue->SetIdent(nsCSSProps::ValueToKeywordEnum(aCoord.GetIntValue(), aTable)); break; case eStyleUnit_None: aValue->SetIdent(eCSSKeyword_none); break; case eStyleUnit_Calc: nscoord percentageBase; if (!aCoord.CalcHasPercent()) { nscoord val = aCoord.ComputeCoordPercentCalc(0); if (aClampNegativeCalc && val < 0) { MOZ_ASSERT(aCoord.IsCalcUnit(), "parser should have rejected value"); val = 0; } aValue->SetAppUnits(std::max(aMinAppUnits, std::min(val, aMaxAppUnits))); } else if (aPercentageBaseGetter && (this->*aPercentageBaseGetter)(percentageBase)) { nscoord val = aCoord.ComputeCoordPercentCalc(percentageBase); if (aClampNegativeCalc && val < 0) { MOZ_ASSERT(aCoord.IsCalcUnit(), "parser should have rejected value"); val = 0; } aValue->SetAppUnits(std::max(aMinAppUnits, std::min(val, aMaxAppUnits))); } else { nsStyleCoord::Calc *calc = aCoord.GetCalcValue(); SetValueToCalc(calc, aValue); } break; case eStyleUnit_Degree: aValue->SetDegree(aCoord.GetAngleValue()); break; case eStyleUnit_FlexFraction: { nsAutoString tmpStr; nsStyleUtil::AppendCSSNumber(aCoord.GetFlexFractionValue(), tmpStr); tmpStr.AppendLiteral("fr"); aValue->SetString(tmpStr); break; } default: NS_ERROR("Can't handle this unit"); break; } } nscoord nsComputedDOMStyle::StyleCoordToNSCoord(const nsStyleCoord& aCoord, PercentageBaseGetter aPercentageBaseGetter, nscoord aDefaultValue, bool aClampNegativeCalc) { MOZ_ASSERT(aPercentageBaseGetter, "Must have a percentage base getter"); if (aCoord.GetUnit() == eStyleUnit_Coord) { return aCoord.GetCoordValue(); } if (aCoord.GetUnit() == eStyleUnit_Percent || aCoord.IsCalcUnit()) { nscoord percentageBase; if ((this->*aPercentageBaseGetter)(percentageBase)) { nscoord result = aCoord.ComputeCoordPercentCalc(percentageBase); if (aClampNegativeCalc && result < 0) { // It's expected that we can get a negative value here with calc(). // We can also get a negative value with a percentage value if // percentageBase is negative; this isn't expected, but can happen // when large length values overflow. NS_WARNING_ASSERTION( percentageBase >= 0, "percentage base value overflowed to become negative for a property " "that disallows negative values"); MOZ_ASSERT(aCoord.IsCalcUnit() || (aCoord.HasPercent() && percentageBase < 0), "parser should have rejected value"); result = 0; } return result; } // Fall through to returning aDefaultValue if we have no percentage base. } return aDefaultValue; } bool nsComputedDOMStyle::GetCBContentWidth(nscoord& aWidth) { if (!mOuterFrame) { return false; } AssertFlushedPendingReflows(); nsIFrame* container = mOuterFrame->GetContainingBlock(); aWidth = container->GetContentRect().width; return true; } bool nsComputedDOMStyle::GetCBContentHeight(nscoord& aHeight) { if (!mOuterFrame) { return false; } AssertFlushedPendingReflows(); nsIFrame* container = mOuterFrame->GetContainingBlock(); aHeight = container->GetContentRect().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; } bool nsComputedDOMStyle::GetFrameBoundsWidthForTransform(nscoord& aWidth) { // We need a frame to work with. if (!mInnerFrame) { return false; } AssertFlushedPendingReflows(); aWidth = nsStyleTransformMatrix::TransformReferenceBox(mInnerFrame).Width(); return true; } bool nsComputedDOMStyle::GetFrameBoundsHeightForTransform(nscoord& aHeight) { // We need a frame to work with. if (!mInnerFrame) { return false; } AssertFlushedPendingReflows(); aHeight = nsStyleTransformMatrix::TransformReferenceBox(mInnerFrame).Height(); return true; } already_AddRefed nsComputedDOMStyle::GetFallbackValue(const nsStyleSVGPaint* aPaint) { RefPtr fallback = new nsROCSSPrimitiveValue; if (aPaint->GetFallbackType() == eStyleSVGFallbackType_Color) { SetToRGBAColor(fallback, aPaint->GetFallbackColor(mComputedStyle)); } else { fallback->SetIdent(eCSSKeyword_none); } return fallback.forget(); } already_AddRefed nsComputedDOMStyle::GetSVGPaintFor(bool aFill) { RefPtr val = new nsROCSSPrimitiveValue; const nsStyleSVG* svg = StyleSVG(); const nsStyleSVGPaint* paint = aFill ? &svg->mFill : &svg->mStroke; nsAutoString paintString; switch (paint->Type()) { case eStyleSVGPaintType_None: val->SetIdent(eCSSKeyword_none); break; case eStyleSVGPaintType_Color: SetToRGBAColor(val, paint->GetColor(mComputedStyle)); break; case eStyleSVGPaintType_Server: { SetValueToURLValue(paint->GetPaintServer(), val); if (paint->GetFallbackType() != eStyleSVGFallbackType_NotSet) { RefPtr valueList = GetROCSSValueList(false); RefPtr fallback = GetFallbackValue(paint); valueList->AppendCSSValue(val.forget()); valueList->AppendCSSValue(fallback.forget()); return valueList.forget(); } break; } case eStyleSVGPaintType_ContextFill: case eStyleSVGPaintType_ContextStroke: { val->SetIdent(paint->Type() == eStyleSVGPaintType_ContextFill ? eCSSKeyword_context_fill : eCSSKeyword_context_stroke); if (paint->GetFallbackType() != eStyleSVGFallbackType_NotSet) { RefPtr valueList = GetROCSSValueList(false); RefPtr fallback = GetFallbackValue(paint); valueList->AppendCSSValue(val.forget()); valueList->AppendCSSValue(fallback.forget()); return valueList.forget(); } break; } } return val.forget(); } /* 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(nsCSSValueSharedList* aSpecifiedTransform) { /* If there are no transforms, then we should construct a single-element * entry and hand it back. */ if (!aSpecifiedTransform) { RefPtr val = new nsROCSSPrimitiveValue; /* Set it to "none." */ val->SetIdent(eCSSKeyword_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, nsSize(0, 0)); bool dummyBool; gfx::Matrix4x4 matrix = nsStyleTransformMatrix::ReadTransforms(aSpecifiedTransform->mHead, refBox, float(mozilla::AppUnitsPerCSSPixel()), &dummyBool); return MatrixToCSSValue(matrix); } already_AddRefed nsComputedDOMStyle::DoGetFill() { return GetSVGPaintFor(true); } already_AddRefed nsComputedDOMStyle::DoGetStroke() { return GetSVGPaintFor(false); } already_AddRefed nsComputedDOMStyle::DoGetMarkerEnd() { RefPtr val = new nsROCSSPrimitiveValue; SetValueToURLValue(StyleSVG()->mMarkerEnd, val); return val.forget(); } already_AddRefed nsComputedDOMStyle::DoGetMarkerMid() { RefPtr val = new nsROCSSPrimitiveValue; SetValueToURLValue(StyleSVG()->mMarkerMid, val); return val.forget(); } already_AddRefed nsComputedDOMStyle::DoGetMarkerStart() { RefPtr val = new nsROCSSPrimitiveValue; SetValueToURLValue(StyleSVG()->mMarkerStart, val); return val.forget(); } already_AddRefed nsComputedDOMStyle::DoGetStrokeDasharray() { const nsStyleSVG* svg = StyleSVG(); if (svg->mStrokeDasharray.IsEmpty()) { RefPtr val = new nsROCSSPrimitiveValue; val->SetIdent(eCSSKeyword_none); return val.forget(); } RefPtr valueList = GetROCSSValueList(true); for (uint32_t i = 0; i < svg->mStrokeDasharray.Length(); i++) { RefPtr dash = new nsROCSSPrimitiveValue; SetValueToCoord(dash, svg->mStrokeDasharray[i], true); valueList->AppendCSSValue(dash.forget()); } return valueList.forget(); } already_AddRefed nsComputedDOMStyle::DoGetStrokeDashoffset() { RefPtr val = new nsROCSSPrimitiveValue; SetValueToCoord(val, StyleSVG()->mStrokeDashoffset, false); return val.forget(); } already_AddRefed nsComputedDOMStyle::DoGetStrokeWidth() { RefPtr val = new nsROCSSPrimitiveValue; SetValueToCoord(val, StyleSVG()->mStrokeWidth, true); return val.forget(); } already_AddRefed nsComputedDOMStyle::DoGetFillOpacity() { RefPtr val = new nsROCSSPrimitiveValue; val->SetNumber(StyleSVG()->mFillOpacity); return val.forget(); } already_AddRefed nsComputedDOMStyle::DoGetStrokeMiterlimit() { RefPtr val = new nsROCSSPrimitiveValue; val->SetNumber(StyleSVG()->mStrokeMiterlimit); return val.forget(); } already_AddRefed nsComputedDOMStyle::DoGetStrokeOpacity() { RefPtr val = new nsROCSSPrimitiveValue; val->SetNumber(StyleSVG()->mStrokeOpacity); return val.forget(); } void nsComputedDOMStyle::BoxValuesToString(nsAString& aString, const nsTArray& aBoxValues, bool aClampNegativeCalc) { MOZ_ASSERT(aBoxValues.Length() == 4, "wrong number of box values"); nsAutoString value1, value2, value3, value4; SetCssTextToCoord(value1, aBoxValues[0], aClampNegativeCalc); SetCssTextToCoord(value2, aBoxValues[1], aClampNegativeCalc); SetCssTextToCoord(value3, aBoxValues[2], aClampNegativeCalc); SetCssTextToCoord(value4, aBoxValues[3], aClampNegativeCalc); // nsROCSSPrimitiveValue do not have binary comparison operators. // Compare string results instead. aString.Append(value1); if (value1 != value2 || value1 != value3 || value1 != value4) { aString.Append(' '); aString.Append(value2); if (value1 != value3 || value2 != value4) { aString.Append(' '); aString.Append(value3); if (value2 != value4) { aString.Append(' '); aString.Append(value4); } } } } void nsComputedDOMStyle::BasicShapeRadiiToString(nsAString& aCssText, const nsStyleCorners& aCorners) { nsTArray horizontal, vertical; nsAutoString horizontalString, verticalString; NS_FOR_CSS_FULL_CORNERS(corner) { horizontal.AppendElement( aCorners.Get(FullToHalfCorner(corner, false))); vertical.AppendElement( aCorners.Get(FullToHalfCorner(corner, true))); } BoxValuesToString(horizontalString, horizontal, true); BoxValuesToString(verticalString, vertical, true); aCssText.Append(horizontalString); if (horizontalString == verticalString) { return; } aCssText.AppendLiteral(" / "); aCssText.Append(verticalString); } already_AddRefed nsComputedDOMStyle::CreatePrimitiveValueForBasicShape( const UniquePtr& aStyleBasicShape) { MOZ_ASSERT(aStyleBasicShape, "Expect a valid basic shape pointer!"); StyleBasicShapeType type = aStyleBasicShape->GetShapeType(); // Shape function name and opening parenthesis. nsAutoString shapeFunctionString; AppendASCIItoUTF16(nsCSSKeywords::GetStringValue( aStyleBasicShape->GetShapeTypeName()), shapeFunctionString); shapeFunctionString.Append('('); switch (type) { case StyleBasicShapeType::Polygon: { bool hasEvenOdd = aStyleBasicShape->GetFillRule() == StyleFillRule::Evenodd; if (hasEvenOdd) { shapeFunctionString.AppendLiteral("evenodd"); } for (size_t i = 0; i < aStyleBasicShape->Coordinates().Length(); i += 2) { nsAutoString coordString; if (i > 0 || hasEvenOdd) { shapeFunctionString.AppendLiteral(", "); } SetCssTextToCoord(coordString, aStyleBasicShape->Coordinates()[i], false); shapeFunctionString.Append(coordString); shapeFunctionString.Append(' '); SetCssTextToCoord(coordString, aStyleBasicShape->Coordinates()[i + 1], false); shapeFunctionString.Append(coordString); } break; } case StyleBasicShapeType::Circle: case StyleBasicShapeType::Ellipse: { const nsTArray& radii = aStyleBasicShape->Coordinates(); MOZ_ASSERT(radii.Length() == (type == StyleBasicShapeType::Circle ? 1 : 2), "wrong number of radii"); for (size_t i = 0; i < radii.Length(); ++i) { nsAutoString radius; RefPtr value = new nsROCSSPrimitiveValue; bool clampNegativeCalc = true; SetValueToCoord(value, radii[i], clampNegativeCalc, nullptr, nsCSSProps::kShapeRadiusKTable); value->GetCssText(radius); shapeFunctionString.Append(radius); shapeFunctionString.Append(' '); } shapeFunctionString.AppendLiteral("at "); RefPtr position = GetROCSSValueList(false); nsAutoString positionString; SetValueToPosition(aStyleBasicShape->GetPosition(), position); position->GetCssText(positionString); shapeFunctionString.Append(positionString); break; } case StyleBasicShapeType::Inset: { BoxValuesToString(shapeFunctionString, aStyleBasicShape->Coordinates(), false); if (aStyleBasicShape->HasRadius()) { shapeFunctionString.AppendLiteral(" round "); nsAutoString radiiString; BasicShapeRadiiToString(radiiString, aStyleBasicShape->GetRadius()); shapeFunctionString.Append(radiiString); } break; } default: MOZ_ASSERT_UNREACHABLE("unexpected type"); } shapeFunctionString.Append(')'); RefPtr functionValue = new nsROCSSPrimitiveValue; functionValue->SetString(shapeFunctionString); return functionValue.forget(); } template already_AddRefed nsComputedDOMStyle::CreatePrimitiveValueForShapeSource( const UniquePtr& aStyleBasicShape, ReferenceBox aReferenceBox, const KTableEntry aBoxKeywordTable[]) { RefPtr valueList = GetROCSSValueList(false); if (aStyleBasicShape) { valueList->AppendCSSValue( CreatePrimitiveValueForBasicShape(aStyleBasicShape)); } if (aReferenceBox == ReferenceBox::NoBox) { return valueList.forget(); } RefPtr val = new nsROCSSPrimitiveValue; val->SetIdent(nsCSSProps::ValueToKeywordEnum(aReferenceBox, aBoxKeywordTable)); valueList->AppendCSSValue(val.forget()); return valueList.forget(); } void nsComputedDOMStyle::SetCssTextToCoord(nsAString& aCssText, const nsStyleCoord& aCoord, bool aClampNegativeCalc) { RefPtr value = new nsROCSSPrimitiveValue; SetValueToCoord(value, aCoord, aClampNegativeCalc); value->GetCssText(aCssText); } already_AddRefed nsComputedDOMStyle::CreatePrimitiveValueForStyleFilter( const nsStyleFilter& aStyleFilter) { RefPtr value = new nsROCSSPrimitiveValue; // Handle url(). if (aStyleFilter.GetType() == NS_STYLE_FILTER_URL) { MOZ_ASSERT(aStyleFilter.GetURL() && aStyleFilter.GetURL()->GetURI()); SetValueToURLValue(aStyleFilter.GetURL(), value); return value.forget(); } // Filter function name and opening parenthesis. nsAutoString filterFunctionString; AppendASCIItoUTF16( nsCSSProps::ValueToKeyword(aStyleFilter.GetType(), nsCSSProps::kFilterFunctionKTable), filterFunctionString); filterFunctionString.Append('('); nsAutoString argumentString; if (aStyleFilter.GetType() == NS_STYLE_FILTER_DROP_SHADOW) { // Handle drop-shadow() RefPtr shadowValue = GetCSSShadowArray(aStyleFilter.GetDropShadow(), false); ErrorResult dummy; shadowValue->GetCssText(argumentString, dummy); } else { // Filter function argument. SetCssTextToCoord(argumentString, aStyleFilter.GetFilterParameter(), true); } filterFunctionString.Append(argumentString); // Filter function closing parenthesis. filterFunctionString.Append(')'); value->SetString(filterFunctionString); return value.forget(); } already_AddRefed nsComputedDOMStyle::DoGetFilter() { const nsTArray& filters = StyleEffects()->mFilters; if (filters.IsEmpty()) { RefPtr value = new nsROCSSPrimitiveValue; value->SetIdent(eCSSKeyword_none); return value.forget(); } RefPtr valueList = GetROCSSValueList(false); for(uint32_t i = 0; i < filters.Length(); i++) { RefPtr value = CreatePrimitiveValueForStyleFilter(filters[i]); valueList->AppendCSSValue(value.forget()); } return valueList.forget(); } already_AddRefed nsComputedDOMStyle::DoGetMask() { const nsStyleSVGReset* svg = StyleSVGReset(); const nsStyleImageLayers::Layer& firstLayer = svg->mMask.mLayers[0]; // Mask is now a shorthand, but it used to be a longhand, so that we // need to support computed style for the cases where it used to be // a longhand. if (svg->mMask.mImageCount > 1 || firstLayer.mClip != StyleGeometryBox::BorderBox || firstLayer.mOrigin != StyleGeometryBox::BorderBox || firstLayer.mComposite != NS_STYLE_MASK_COMPOSITE_ADD || firstLayer.mMaskMode != NS_STYLE_MASK_MODE_MATCH_SOURCE || !nsStyleImageLayers::IsInitialPositionForLayerType( firstLayer.mPosition, nsStyleImageLayers::LayerType::Mask) || !firstLayer.mRepeat.IsInitialValue() || !firstLayer.mSize.IsInitialValue() || !(firstLayer.mImage.GetType() == eStyleImageType_Null || firstLayer.mImage.GetType() == eStyleImageType_Image || firstLayer.mImage.GetType() == eStyleImageType_URL)) { return nullptr; } RefPtr val = new nsROCSSPrimitiveValue; SetValueToURLValue(firstLayer.mImage.GetURLValue(), val); return val.forget(); } already_AddRefed nsComputedDOMStyle::DoGetMaskPosition() { const nsStyleImageLayers& layers = StyleSVGReset()->mMask; return DoGetImageLayerPosition(layers); } already_AddRefed nsComputedDOMStyle::DoGetPaintOrder() { RefPtr val = new nsROCSSPrimitiveValue; nsAutoString string; uint8_t paintOrder = StyleSVG()->mPaintOrder; nsStyleUtil::AppendPaintOrderValue(paintOrder, string); val->SetString(string); return val.forget(); } already_AddRefed nsComputedDOMStyle::DoGetTransitionDelay() { const nsStyleDisplay* display = StyleDisplay(); RefPtr valueList = GetROCSSValueList(true); MOZ_ASSERT(display->mTransitionDelayCount > 0, "first item must be explicit"); uint32_t i = 0; do { const StyleTransition *transition = &display->mTransitions[i]; RefPtr delay = new nsROCSSPrimitiveValue; delay->SetTime((float)transition->GetDelay() / (float)PR_MSEC_PER_SEC); valueList->AppendCSSValue(delay.forget()); } while (++i < display->mTransitionDelayCount); return valueList.forget(); } already_AddRefed nsComputedDOMStyle::DoGetTransitionDuration() { const nsStyleDisplay* display = StyleDisplay(); RefPtr valueList = GetROCSSValueList(true); MOZ_ASSERT(display->mTransitionDurationCount > 0, "first item must be explicit"); uint32_t i = 0; do { const StyleTransition *transition = &display->mTransitions[i]; RefPtr duration = new nsROCSSPrimitiveValue; duration->SetTime((float)transition->GetDuration() / (float)PR_MSEC_PER_SEC); valueList->AppendCSSValue(duration.forget()); } while (++i < display->mTransitionDurationCount); return valueList.forget(); } already_AddRefed nsComputedDOMStyle::DoGetTransitionProperty() { const nsStyleDisplay* display = StyleDisplay(); RefPtr valueList = GetROCSSValueList(true); MOZ_ASSERT(display->mTransitionPropertyCount > 0, "first item must be explicit"); uint32_t i = 0; do { const StyleTransition *transition = &display->mTransitions[i]; RefPtr property = new nsROCSSPrimitiveValue; nsCSSPropertyID cssprop = transition->GetProperty(); if (cssprop == eCSSPropertyExtra_all_properties) property->SetIdent(eCSSKeyword_all); else if (cssprop == eCSSPropertyExtra_no_properties) property->SetIdent(eCSSKeyword_none); else if (cssprop == eCSSProperty_UNKNOWN || cssprop == eCSSPropertyExtra_variable) { nsAutoString escaped; nsStyleUtil::AppendEscapedCSSIdent( nsDependentAtomString(transition->GetUnknownProperty()), escaped); property->SetString(escaped); // really want SetIdent } else property->SetString(nsCSSProps::GetStringValue(cssprop)); valueList->AppendCSSValue(property.forget()); } while (++i < display->mTransitionPropertyCount); return valueList.forget(); } already_AddRefed nsComputedDOMStyle::DoGetAnimationName() { const nsStyleDisplay* display = StyleDisplay(); RefPtr valueList = GetROCSSValueList(true); MOZ_ASSERT(display->mAnimationNameCount > 0, "first item must be explicit"); uint32_t i = 0; do { const StyleAnimation *animation = &display->mAnimations[i]; RefPtr property = new nsROCSSPrimitiveValue; nsAtom* name = animation->GetName(); if (name == nsGkAtoms::_empty) { property->SetIdent(eCSSKeyword_none); } else { nsDependentAtomString nameStr(name); nsAutoString escaped; nsStyleUtil::AppendEscapedCSSIdent(nameStr, escaped); property->SetString(escaped); // really want SetIdent } valueList->AppendCSSValue(property.forget()); } while (++i < display->mAnimationNameCount); return valueList.forget(); } already_AddRefed nsComputedDOMStyle::DoGetAnimationDelay() { const nsStyleDisplay* display = StyleDisplay(); RefPtr valueList = GetROCSSValueList(true); MOZ_ASSERT(display->mAnimationDelayCount > 0, "first item must be explicit"); uint32_t i = 0; do { const StyleAnimation *animation = &display->mAnimations[i]; RefPtr delay = new nsROCSSPrimitiveValue; delay->SetTime((float)animation->GetDelay() / (float)PR_MSEC_PER_SEC); valueList->AppendCSSValue(delay.forget()); } while (++i < display->mAnimationDelayCount); return valueList.forget(); } already_AddRefed nsComputedDOMStyle::DoGetAnimationDuration() { const nsStyleDisplay* display = StyleDisplay(); RefPtr valueList = GetROCSSValueList(true); MOZ_ASSERT(display->mAnimationDurationCount > 0, "first item must be explicit"); uint32_t i = 0; do { const StyleAnimation *animation = &display->mAnimations[i]; RefPtr duration = new nsROCSSPrimitiveValue; duration->SetTime((float)animation->GetDuration() / (float)PR_MSEC_PER_SEC); valueList->AppendCSSValue(duration.forget()); } while (++i < display->mAnimationDurationCount); return valueList.forget(); } already_AddRefed nsComputedDOMStyle::DoGetAnimationIterationCount() { const nsStyleDisplay* display = StyleDisplay(); RefPtr valueList = GetROCSSValueList(true); MOZ_ASSERT(display->mAnimationIterationCountCount > 0, "first item must be explicit"); uint32_t i = 0; do { const StyleAnimation *animation = &display->mAnimations[i]; RefPtr iterationCount = new nsROCSSPrimitiveValue; float f = animation->GetIterationCount(); if (f == PositiveInfinity()) { iterationCount->SetIdent(eCSSKeyword_infinite); } else { iterationCount->SetNumber(f); } valueList->AppendCSSValue(iterationCount.forget()); } while (++i < display->mAnimationIterationCountCount); return valueList.forget(); } already_AddRefed nsComputedDOMStyle::DummyGetter() { MOZ_CRASH("DummyGetter is not supposed to be invoked"); } static void MarkComputedStyleMapDirty(const char* aPref, ComputedStyleMap* aData) { aData->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(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; }