Source code

Revision control

Copy as Markdown

Other Tools

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* DOM object returned from element.getComputedStyle() */
#include "nsComputedDOMStyle.h"
#include "mozilla/ArrayUtils.h"
#include "mozilla/FloatingPoint.h"
#include "mozilla/FontPropertyTypes.h"
#include "mozilla/Preferences.h"
#include "mozilla/PresShell.h"
#include "mozilla/PresShellInlines.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/ScrollContainerFrame.h"
#include "mozilla/StaticPtr.h"
#include "mozilla/StaticPrefs_layout.h"
#include "nsError.h"
#include "nsIFrame.h"
#include "nsIFrameInlines.h"
#include "mozilla/ComputedStyle.h"
#include "nsContentUtils.h"
#include "nsDocShell.h"
#include "nsIContent.h"
#include "nsStyleConsts.h"
#include "nsDOMCSSValueList.h"
#include "nsFlexContainerFrame.h"
#include "nsGridContainerFrame.h"
#include "nsGkAtoms.h"
#include "mozilla/ReflowInput.h"
#include "nsStyleUtil.h"
#include "nsStyleStructInlines.h"
#include "nsROCSSPrimitiveValue.h"
#include "nsPresContext.h"
#include "mozilla/dom/Document.h"
#include "nsCSSProps.h"
#include "nsCSSPseudoElements.h"
#include "mozilla/EffectSet.h"
#include "mozilla/IntegerRange.h"
#include "mozilla/ServoStyleSet.h"
#include "mozilla/RestyleManager.h"
#include "mozilla/ViewportFrame.h"
#include "nsLayoutUtils.h"
#include "nsDisplayList.h"
#include "nsDOMCSSDeclaration.h"
#include "nsStyleTransformMatrix.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/ElementInlines.h"
#include "prtime.h"
#include "nsWrapperCacheInlines.h"
#include "mozilla/AppUnits.h"
#include <algorithm>
#include "mozilla/ComputedStyleInlines.h"
#include "nsPrintfCString.h"
using namespace mozilla;
using namespace mozilla::dom;
/*
* This is the implementation of the readonly CSSStyleDeclaration that is
* returned by the getComputedStyle() function.
*/
already_AddRefed<nsComputedDOMStyle> NS_NewComputedDOMStyle(
dom::Element* aElement, const nsAString& aPseudoElt, Document* aDocument,
nsComputedDOMStyle::StyleType aStyleType, mozilla::ErrorResult&) {
auto request = nsCSSPseudoElements::ParsePseudoElement(
aPseudoElt, CSSEnabledState::ForAllContent);
auto returnEmpty = nsComputedDOMStyle::AlwaysReturnEmptyStyle::No;
if (!request) {
if (!aPseudoElt.IsEmpty() && aPseudoElt.First() == u':') {
returnEmpty = nsComputedDOMStyle::AlwaysReturnEmptyStyle::Yes;
}
request.emplace(PseudoStyleRequest());
}
return MakeAndAddRef<nsComputedDOMStyle>(aElement, std::move(*request),
aDocument, aStyleType, returnEmpty);
}
static nsDOMCSSValueList* GetROCSSValueList(bool aCommaDelimited) {
return new nsDOMCSSValueList(aCommaDelimited);
}
// Whether aDocument needs to restyle for aElement
static bool ElementNeedsRestyle(Element* aElement,
const PseudoStyleRequest& aPseudo,
bool aMayNeedToFlushLayout) {
const Document* doc = aElement->GetComposedDoc();
if (!doc) {
// If the element is out of the document we don't return styles for it, so
// nothing to do.
return false;
}
PresShell* presShell = doc->GetPresShell();
if (!presShell) {
// If there's no pres-shell we'll just either mint a new style from our
// caller document, or return no styles, so nothing to do (unless our owner
// element needs to get restyled, which could cause us to gain a pres shell,
// but the caller checks that).
return false;
}
// Unfortunately we don't know if the sheet change affects mElement or not, so
// just assume it will and that we need to flush normally.
ServoStyleSet* styleSet = presShell->StyleSet();
if (styleSet->StyleSheetsHaveChanged()) {
return true;
}
nsPresContext* presContext = presShell->GetPresContext();
MOZ_ASSERT(presContext);
// Pending media query updates can definitely change style on the element. For
// example, if you change the zoom factor and then call getComputedStyle, you
// should be able to observe the style with the new media queries.
//
// TODO(emilio): Does this need to also check the user font set? (it affects
// ch / ex units).
if (presContext->HasPendingMediaQueryUpdates()) {
// So gotta flush.
return true;
}
// If the pseudo-element is animating, make sure to flush.
if (aElement->MayHaveAnimations() && !aPseudo.IsNotPseudo() &&
AnimationUtils::IsSupportedPseudoForAnimations(aPseudo)) {
if (EffectSet::Get(aElement, aPseudo)) {
return true;
}
}
// For Servo, we need to process the restyle-hint-invalidations first, to
// expand LaterSiblings hint, so that we can look whether ancestors need
// restyling.
RestyleManager* restyleManager = presContext->RestyleManager();
restyleManager->ProcessAllPendingAttributeAndStateInvalidations();
if (!presContext->EffectCompositor()->HasPendingStyleUpdates() &&
!doc->GetServoRestyleRoot()) {
return false;
}
// If there's a pseudo, we need to prefer that element, as the pseudo itself
// may have explicit restyles.
const Element* styledElement = aElement->GetPseudoElement(aPseudo);
// Try to skip the restyle otherwise.
return Servo_HasPendingRestyleAncestor(
styledElement ? styledElement : aElement, aMayNeedToFlushLayout);
}
/**
* An object that represents the ordered set of properties that are exposed on
* an nsComputedDOMStyle object and how their computed values can be obtained.
*/
struct ComputedStyleMap {
friend class nsComputedDOMStyle;
struct Entry {
// Create a pointer-to-member-function type.
using ComputeMethod = already_AddRefed<CSSValue> (nsComputedDOMStyle::*)();
nsCSSPropertyID mProperty;
// Whether the property can ever be exposed in getComputedStyle(). For
// example, @page descriptors implemented as CSS properties or other
// internal properties, would have this flag set to `false`.
bool mCanBeExposed = false;
ComputeMethod mGetter = nullptr;
bool IsEnumerable() const {
return IsEnabled() && !nsCSSProps::IsShorthand(mProperty);
}
bool IsEnabled() const {
return mCanBeExposed &&
nsCSSProps::IsEnabled(mProperty, CSSEnabledState::ForAllContent);
}
};
// This generated file includes definition of kEntries which is typed
// Entry[] and used below, so this #include has to be put here.
#include "nsComputedDOMStyleGenerated.inc"
/**
* Returns the number of properties that should be exposed on an
* nsComputedDOMStyle, ecxluding any disabled properties.
*/
uint32_t Length() {
Update();
return mEnumerablePropertyCount;
}
/**
* Returns the property at the given index in the list of properties
* that should be exposed on an nsComputedDOMStyle, excluding any
* disabled properties.
*/
nsCSSPropertyID PropertyAt(uint32_t aIndex) {
Update();
return kEntries[EntryIndex(aIndex)].mProperty;
}
/**
* Searches for and returns the computed style map entry for the given
* property, or nullptr if the property is not exposed on nsComputedDOMStyle
* or is currently disabled.
*/
const Entry* FindEntryForProperty(nsCSSPropertyID aPropID) {
if (size_t(aPropID) >= std::size(kEntryIndices)) {
MOZ_ASSERT(aPropID == eCSSProperty_UNKNOWN);
return nullptr;
}
MOZ_ASSERT(kEntryIndices[aPropID] < std::size(kEntries));
const auto& entry = kEntries[kEntryIndices[aPropID]];
if (!entry.IsEnabled()) {
return nullptr;
}
return &entry;
}
/**
* Records that mIndexMap needs updating, due to prefs changing that could
* affect the set of properties exposed on an nsComputedDOMStyle.
*/
void MarkDirty() { mEnumerablePropertyCount = 0; }
// The member variables are public so that we can use an initializer in
// nsComputedDOMStyle::GetComputedStyleMap. Use the member functions
// above to get information from this object.
/**
* The number of properties that should be exposed on an nsComputedDOMStyle.
* This will be less than eComputedStyleProperty_COUNT if some property
* prefs are disabled. A value of 0 indicates that it and mIndexMap are out
* of date.
*/
uint32_t mEnumerablePropertyCount = 0;
/**
* A map of indexes on the nsComputedDOMStyle object to indexes into kEntries.
*/
uint32_t mIndexMap[std::size(kEntries)];
private:
/**
* Returns whether mEnumerablePropertyCount and mIndexMap are out of date.
*/
bool IsDirty() { return mEnumerablePropertyCount == 0; }
/**
* Updates mEnumerablePropertyCount and mIndexMap to take into account
* properties whose prefs are currently disabled.
*/
void Update();
/**
* Maps an nsComputedDOMStyle indexed getter index to an index into kEntries.
*/
uint32_t EntryIndex(uint32_t aIndex) const {
MOZ_ASSERT(aIndex < mEnumerablePropertyCount);
return mIndexMap[aIndex];
}
};
constexpr ComputedStyleMap::Entry
ComputedStyleMap::kEntries[std::size(kEntries)];
constexpr size_t ComputedStyleMap::kEntryIndices[std::size(kEntries)];
void ComputedStyleMap::Update() {
if (!IsDirty()) {
return;
}
uint32_t index = 0;
for (uint32_t i = 0; i < std::size(kEntries); i++) {
if (kEntries[i].IsEnumerable()) {
mIndexMap[index++] = i;
}
}
mEnumerablePropertyCount = index;
}
nsComputedDOMStyle::nsComputedDOMStyle(dom::Element* aElement,
PseudoStyleRequest&& aPseudo,
Document* aDocument,
StyleType aStyleType,
AlwaysReturnEmptyStyle aAlwaysEmpty)
: mDocumentWeak(nullptr),
mOuterFrame(nullptr),
mInnerFrame(nullptr),
mPresShell(nullptr),
mPseudo(std::move(aPseudo)),
mStyleType(aStyleType),
mAlwaysReturnEmpty(aAlwaysEmpty) {
MOZ_ASSERT(aElement);
MOZ_ASSERT(aDocument);
// Should use aElement->OwnerDoc() instead.
mDocumentWeak = aDocument;
mElement = aElement;
SetEnabledCallbacks(nsIMutationObserver::kParentChainChanged);
}
nsComputedDOMStyle::~nsComputedDOMStyle() {
MOZ_ASSERT(!mResolvedComputedStyle,
"Should have called ClearComputedStyle() during last release.");
}
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(nsComputedDOMStyle)
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsComputedDOMStyle)
tmp->ClearComputedStyle(); // remove observer before clearing mElement
NS_IMPL_CYCLE_COLLECTION_UNLINK(mElement)
NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsComputedDOMStyle)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mElement)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
// We can skip the nsComputedDOMStyle if it has no wrapper and its
// element is skippable, because it will have no outgoing edges, so
// it can't be part of a cycle.
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(nsComputedDOMStyle)
if (!tmp->GetWrapperPreserveColor()) {
return !tmp->mElement ||
mozilla::dom::FragmentOrElement::CanSkip(tmp->mElement, true);
}
return tmp->HasKnownLiveWrapper();
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(nsComputedDOMStyle)
if (!tmp->GetWrapperPreserveColor()) {
return !tmp->mElement ||
mozilla::dom::FragmentOrElement::CanSkipInCC(tmp->mElement);
}
return tmp->HasKnownLiveWrapper();
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(nsComputedDOMStyle)
if (!tmp->GetWrapperPreserveColor()) {
return !tmp->mElement ||
mozilla::dom::FragmentOrElement::CanSkipThis(tmp->mElement);
}
return tmp->HasKnownLiveWrapper();
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
// QueryInterface implementation for nsComputedDOMStyle
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsComputedDOMStyle)
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
NS_INTERFACE_MAP_ENTRY(nsIMutationObserver)
NS_INTERFACE_MAP_END_INHERITING(nsDOMCSSDeclaration)
NS_IMPL_CYCLE_COLLECTING_ADDREF(nsComputedDOMStyle)
NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_LAST_RELEASE(nsComputedDOMStyle,
ClearComputedStyle())
void nsComputedDOMStyle::GetPropertyValue(const nsCSSPropertyID aPropID,
nsACString& aValue) {
return GetPropertyValue(aPropID, EmptyCString(), aValue);
}
void nsComputedDOMStyle::SetPropertyValue(const nsCSSPropertyID aPropID,
const nsACString& aValue,
nsIPrincipal* aSubjectPrincipal,
ErrorResult& aRv) {
aRv.ThrowNoModificationAllowedError(nsPrintfCString(
"Can't set value for property '%s' in computed style",
PromiseFlatCString(nsCSSProps::GetStringValue(aPropID)).get()));
}
void nsComputedDOMStyle::GetCssText(nsACString& aCssText) {
aCssText.Truncate();
}
void nsComputedDOMStyle::SetCssText(const nsACString& aCssText,
nsIPrincipal* aSubjectPrincipal,
ErrorResult& aRv) {
aRv.ThrowNoModificationAllowedError("Can't set cssText on computed style");
}
uint32_t nsComputedDOMStyle::Length() {
// Make sure we have up to date style so that we can include custom
// properties.
UpdateCurrentStyleSources(eCSSPropertyExtra_variable);
if (!mComputedStyle) {
return 0;
}
uint32_t length = GetComputedStyleMap()->Length() +
Servo_GetCustomPropertiesCount(mComputedStyle);
ClearCurrentStyleSources();
return length;
}
css::Rule* nsComputedDOMStyle::GetParentRule() { return nullptr; }
void nsComputedDOMStyle::GetPropertyValue(const nsACString& aPropertyName,
nsACString& aReturn) {
nsCSSPropertyID prop = nsCSSProps::LookupProperty(aPropertyName);
GetPropertyValue(prop, aPropertyName, aReturn);
}
void nsComputedDOMStyle::GetPropertyValue(
nsCSSPropertyID aPropID, const nsACString& aMaybeCustomPropertyName,
nsACString& aReturn) {
MOZ_ASSERT(aReturn.IsEmpty());
const ComputedStyleMap::Entry* entry = nullptr;
if (aPropID != eCSSPropertyExtra_variable) {
entry = GetComputedStyleMap()->FindEntryForProperty(aPropID);
if (!entry) {
return;
}
}
UpdateCurrentStyleSources(aPropID);
if (!mComputedStyle) {
return;
}
auto cleanup = mozilla::MakeScopeExit([&] { ClearCurrentStyleSources(); });
if (!entry) {
MOZ_ASSERT(nsCSSProps::IsCustomPropertyName(aMaybeCustomPropertyName));
const nsACString& name =
Substring(aMaybeCustomPropertyName, CSS_CUSTOM_NAME_PREFIX_LENGTH);
Servo_GetCustomPropertyValue(mComputedStyle, &name,
mPresShell->StyleSet()->RawData(), &aReturn);
return;
}
if (nsCSSProps::PropHasFlags(aPropID, CSSPropFlags::IsLogical)) {
MOZ_ASSERT(entry);
MOZ_ASSERT(entry->mGetter == &nsComputedDOMStyle::DummyGetter);
DebugOnly<nsCSSPropertyID> logicalProp = aPropID;
aPropID = Servo_ResolveLogicalProperty(aPropID, mComputedStyle);
entry = GetComputedStyleMap()->FindEntryForProperty(aPropID);
MOZ_ASSERT(NeedsToFlushLayout(logicalProp) == NeedsToFlushLayout(aPropID),
"Logical and physical property don't agree on whether layout is "
"needed");
}
if (!nsCSSProps::PropHasFlags(aPropID, CSSPropFlags::SerializedByServo)) {
if (RefPtr<CSSValue> value = (this->*entry->mGetter)()) {
nsAutoString text;
value->GetCssText(text);
CopyUTF16toUTF8(text, aReturn);
}
return;
}
MOZ_ASSERT(entry->mGetter == &nsComputedDOMStyle::DummyGetter);
Servo_GetResolvedValue(mComputedStyle, aPropID,
mPresShell->StyleSet()->RawData(), mElement, &aReturn);
}
/* static */
already_AddRefed<const ComputedStyle> nsComputedDOMStyle::GetComputedStyle(
Element* aElement, const PseudoStyleRequest& aPseudo,
StyleType aStyleType) {
if (Document* doc = aElement->GetComposedDoc()) {
doc->FlushPendingNotifications(FlushType::Style);
}
return GetComputedStyleNoFlush(aElement, aPseudo, aStyleType);
}
/**
* The following function checks whether we need to explicitly resolve the style
* again, even though we have a style coming from the frame.
*
* This basically checks whether the style is or may be under a ::first-line or
* ::first-letter frame, in which case we can't return the frame style, and we
* need to resolve it. See bug 505515.
*/
static bool MustReresolveStyle(const ComputedStyle* aStyle) {
MOZ_ASSERT(aStyle);
// TODO(emilio): We may want to avoid re-resolving pseudo-element styles
// more often.
return aStyle->HasPseudoElementData() && !aStyle->IsPseudoElement();
}
static bool IsInFlatTree(const Element& aElement) {
const auto* topmost = &aElement;
while (true) {
if (topmost->HasServoData()) {
// If we have styled this element then we know it's in the flat tree.
return true;
}
const Element* parent = topmost->GetFlattenedTreeParentElement();
if (!parent) {
break;
}
topmost = parent;
}
auto* root = topmost->GetFlattenedTreeParentNode();
return root && root->IsDocument();
}
already_AddRefed<const ComputedStyle>
nsComputedDOMStyle::DoGetComputedStyleNoFlush(const Element* aElement,
const PseudoStyleRequest& aPseudo,
PresShell* aPresShell,
StyleType aStyleType) {
MOZ_ASSERT(aElement, "NULL element");
// If the content has a pres shell, we must use it. Otherwise we'd
// potentially mix rule trees by using the wrong pres shell's style
// set. Using the pres shell from the content also means that any
// content that's actually *in* a document will get the style from the
// correct document.
PresShell* presShell = nsContentUtils::GetPresShellForContent(aElement);
bool inDocWithShell = true;
if (!presShell) {
inDocWithShell = false;
presShell = aPresShell;
if (!presShell) {
return nullptr;
}
}
MOZ_ASSERT(aPseudo.IsPseudoElementOrNotPseudo());
if (!aElement->IsInComposedDoc()) {
// Don't return styles for disconnected elements, that makes no sense. This
// can only happen with a non-null presShell for cross-document calls.
return nullptr;
}
if (!IsInFlatTree(*aElement)) {
return nullptr;
}
// XXX the !aElement->IsHTMLElement(nsGkAtoms::area)
// check is needed due to bug 135040 (to avoid using
// mPrimaryFrame). Remove it once that's fixed.
if (inDocWithShell && aStyleType == StyleType::All &&
!aElement->IsHTMLElement(nsGkAtoms::area)) {
if (const Element* element = aElement->GetPseudoElement(aPseudo)) {
if (element->HasServoData()) {
const ComputedStyle* result =
Servo_Element_GetMaybeOutOfDateStyle(element);
return do_AddRef(result);
}
}
}
// No frame has been created, or we have a pseudo, or we're looking
// for the default style, so resolve the style ourselves.
ServoStyleSet* styleSet = presShell->StyleSet();
StyleRuleInclusion rules = aStyleType == StyleType::DefaultOnly
? StyleRuleInclusion::DefaultOnly
: StyleRuleInclusion::All;
RefPtr<ComputedStyle> result =
styleSet->ResolveStyleLazily(*aElement, aPseudo, rules);
return result.forget();
}
already_AddRefed<const ComputedStyle>
nsComputedDOMStyle::GetUnanimatedComputedStyleNoFlush(
Element* aElement, const PseudoStyleRequest& aPseudo) {
RefPtr<const ComputedStyle> style =
GetComputedStyleNoFlush(aElement, aPseudo);
if (!style) {
return nullptr;
}
PresShell* presShell = aElement->OwnerDoc()->GetPresShell();
MOZ_ASSERT(presShell,
"How in the world did we get a style a few lines above?");
Element* elementOrPseudoElement = aElement->GetPseudoElement(aPseudo);
if (!elementOrPseudoElement) {
return nullptr;
}
return presShell->StyleSet()->GetBaseContextForElement(elementOrPseudoElement,
style);
}
nsMargin nsComputedDOMStyle::GetAdjustedValuesForBoxSizing() {
// We want the width/height of whatever parts 'width' or 'height' controls,
// which can be different depending on the value of the 'box-sizing' property.
const nsStylePosition* stylePos = StylePosition();
nsMargin adjustment;
if (stylePos->mBoxSizing == StyleBoxSizing::Border) {
adjustment = mInnerFrame->GetUsedBorderAndPadding();
}
return adjustment;
}
static void AddImageURL(nsIURI& aURI, nsTArray<nsCString>& aURLs) {
nsCString spec;
nsresult rv = aURI.GetSpec(spec);
if (NS_FAILED(rv)) {
return;
}
aURLs.AppendElement(std::move(spec));
}
static void AddImageURL(const StyleComputedUrl& aURL,
nsTArray<nsCString>& aURLs) {
if (aURL.IsLocalRef()) {
return;
}
if (nsIURI* uri = aURL.GetURI()) {
AddImageURL(*uri, aURLs);
}
}
static void AddImageURL(const StyleImage& aImage, nsTArray<nsCString>& aURLs) {
if (auto* urlValue = aImage.GetImageRequestURLValue()) {
AddImageURL(*urlValue, aURLs);
}
}
static void AddImageURL(const StyleShapeOutside& aShapeOutside,
nsTArray<nsCString>& aURLs) {
if (aShapeOutside.IsImage()) {
AddImageURL(aShapeOutside.AsImage(), aURLs);
}
}
static void AddImageURL(const StyleClipPath& aClipPath,
nsTArray<nsCString>& aURLs) {
if (aClipPath.IsUrl()) {
AddImageURL(aClipPath.AsUrl(), aURLs);
}
}
static void AddImageURLs(const nsStyleImageLayers& aLayers,
nsTArray<nsCString>& aURLs) {
for (auto i : IntegerRange(aLayers.mLayers.Length())) {
AddImageURL(aLayers.mLayers[i].mImage, aURLs);
}
}
static void CollectImageURLsForProperty(nsCSSPropertyID aProp,
const ComputedStyle& aStyle,
nsTArray<nsCString>& aURLs) {
if (nsCSSProps::IsShorthand(aProp)) {
CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(p, aProp,
CSSEnabledState::ForAllContent) {
CollectImageURLsForProperty(*p, aStyle, aURLs);
}
return;
}
switch (aProp) {
case eCSSProperty_cursor:
for (auto& image : aStyle.StyleUI()->Cursor().images.AsSpan()) {
AddImageURL(image.image, aURLs);
}
break;
case eCSSProperty_background_image:
AddImageURLs(aStyle.StyleBackground()->mImage, aURLs);
break;
case eCSSProperty_mask_clip:
AddImageURLs(aStyle.StyleSVGReset()->mMask, aURLs);
break;
case eCSSProperty_list_style_image: {
const auto& image = aStyle.StyleList()->mListStyleImage;
if (image.IsUrl()) {
AddImageURL(image.AsUrl(), aURLs);
}
break;
}
case eCSSProperty_border_image_source:
AddImageURL(aStyle.StyleBorder()->mBorderImageSource, aURLs);
break;
case eCSSProperty_clip_path:
AddImageURL(aStyle.StyleSVGReset()->mClipPath, aURLs);
break;
case eCSSProperty_shape_outside:
AddImageURL(aStyle.StyleDisplay()->mShapeOutside, aURLs);
break;
default:
break;
}
}
float nsComputedDOMStyle::UsedFontSize() {
UpdateCurrentStyleSources(eCSSProperty_font_size);
if (!mComputedStyle) {
return -1.0;
}
return mComputedStyle->StyleFont()->mFont.size.ToCSSPixels();
}
void nsComputedDOMStyle::GetCSSImageURLs(const nsACString& aPropertyName,
nsTArray<nsCString>& aImageURLs,
mozilla::ErrorResult& aRv) {
nsCSSPropertyID prop = nsCSSProps::LookupProperty(aPropertyName);
if (prop == eCSSProperty_UNKNOWN) {
// Note: not using nsPrintfCString here in case aPropertyName contains
// nulls.
aRv.ThrowSyntaxError("Invalid property name '"_ns + aPropertyName + "'"_ns);
return;
}
UpdateCurrentStyleSources(prop);
if (!mComputedStyle) {
return;
}
CollectImageURLsForProperty(prop, *mComputedStyle, aImageURLs);
ClearCurrentStyleSources();
}
// nsDOMCSSDeclaration abstract methods which should never be called
// on a nsComputedDOMStyle object, but must be defined to avoid
// compile errors.
DeclarationBlock* nsComputedDOMStyle::GetOrCreateCSSDeclaration(
Operation aOperation, DeclarationBlock** aCreated) {
MOZ_CRASH("called nsComputedDOMStyle::GetCSSDeclaration");
}
nsresult nsComputedDOMStyle::SetCSSDeclaration(DeclarationBlock*,
MutationClosureData*) {
MOZ_CRASH("called nsComputedDOMStyle::SetCSSDeclaration");
}
Document* nsComputedDOMStyle::DocToUpdate() {
MOZ_CRASH("called nsComputedDOMStyle::DocToUpdate");
}
nsDOMCSSDeclaration::ParsingEnvironment
nsComputedDOMStyle::GetParsingEnvironment(
nsIPrincipal* aSubjectPrincipal) const {
MOZ_CRASH("called nsComputedDOMStyle::GetParsingEnvironment");
}
void nsComputedDOMStyle::ClearComputedStyle() {
if (mResolvedComputedStyle) {
mResolvedComputedStyle = false;
mElement->RemoveMutationObserver(this);
}
mComputedStyle = nullptr;
}
void nsComputedDOMStyle::SetResolvedComputedStyle(
RefPtr<const ComputedStyle>&& aContext, uint64_t aGeneration) {
if (!mResolvedComputedStyle) {
mResolvedComputedStyle = true;
mElement->AddMutationObserver(this);
}
mComputedStyle = aContext;
mComputedStyleGeneration = aGeneration;
mPresShellId = mPresShell->GetPresShellId();
}
void nsComputedDOMStyle::SetFrameComputedStyle(mozilla::ComputedStyle* aStyle,
uint64_t aGeneration) {
ClearComputedStyle();
mComputedStyle = aStyle;
mComputedStyleGeneration = aGeneration;
mPresShellId = mPresShell->GetPresShellId();
}
static bool MayNeedToFlushLayout(nsCSSPropertyID aPropID) {
switch (aPropID) {
case eCSSProperty_width:
case eCSSProperty_height:
case eCSSProperty_block_size:
case eCSSProperty_inline_size:
case eCSSProperty_line_height:
case eCSSProperty_grid_template_rows:
case eCSSProperty_grid_template_columns:
case eCSSProperty_perspective_origin:
case eCSSProperty_transform_origin:
case eCSSProperty_transform:
case eCSSProperty_border_top_width:
case eCSSProperty_border_bottom_width:
case eCSSProperty_border_left_width:
case eCSSProperty_border_right_width:
case eCSSProperty_border_block_start_width:
case eCSSProperty_border_block_end_width:
case eCSSProperty_border_inline_start_width:
case eCSSProperty_border_inline_end_width:
case eCSSProperty_top:
case eCSSProperty_right:
case eCSSProperty_bottom:
case eCSSProperty_left:
case eCSSProperty_inset_block_start:
case eCSSProperty_inset_block_end:
case eCSSProperty_inset_inline_start:
case eCSSProperty_inset_inline_end:
case eCSSProperty_padding_top:
case eCSSProperty_padding_right:
case eCSSProperty_padding_bottom:
case eCSSProperty_padding_left:
case eCSSProperty_padding_block_start:
case eCSSProperty_padding_block_end:
case eCSSProperty_padding_inline_start:
case eCSSProperty_padding_inline_end:
case eCSSProperty_margin_top:
case eCSSProperty_margin_right:
case eCSSProperty_margin_bottom:
case eCSSProperty_margin_left:
case eCSSProperty_margin_block_start:
case eCSSProperty_margin_block_end:
case eCSSProperty_margin_inline_start:
case eCSSProperty_margin_inline_end:
return true;
default:
return false;
}
}
bool nsComputedDOMStyle::NeedsToFlushStyle(nsCSSPropertyID aPropID) const {
bool mayNeedToFlushLayout = MayNeedToFlushLayout(aPropID);
// We always compute styles from the element's owner document.
if (ElementNeedsRestyle(mElement, mPseudo, mayNeedToFlushLayout)) {
return true;
}
Document* doc = mElement->OwnerDoc();
// If parent document is there, also needs to check if there is some change
// that needs to flush this document (e.g. size change for iframe).
while (doc->StyleOrLayoutObservablyDependsOnParentDocumentLayout()) {
if (Element* element = doc->GetEmbedderElement()) {
if (ElementNeedsRestyle(element, {}, mayNeedToFlushLayout)) {
return true;
}
}
doc = doc->GetInProcessParentDocument();
}
return false;
}
static bool IsNonReplacedInline(nsIFrame* aFrame) {
// FIXME: this should be IsInlineInsideStyle() since width/height
// doesn't apply to ruby boxes.
return aFrame->StyleDisplay()->IsInlineFlow() && !aFrame->IsReplaced() &&
!aFrame->IsFieldSetFrame() && !aFrame->IsBlockFrame() &&
!aFrame->IsScrollContainerFrame() &&
!aFrame->IsColumnSetWrapperFrame();
}
static Side SideForPaddingOrMarginOrInsetProperty(nsCSSPropertyID aPropID) {
switch (aPropID) {
case eCSSProperty_top:
case eCSSProperty_margin_top:
case eCSSProperty_padding_top:
return eSideTop;
case eCSSProperty_right:
case eCSSProperty_margin_right:
case eCSSProperty_padding_right:
return eSideRight;
case eCSSProperty_bottom:
case eCSSProperty_margin_bottom:
case eCSSProperty_padding_bottom:
return eSideBottom;
case eCSSProperty_left:
case eCSSProperty_margin_left:
case eCSSProperty_padding_left:
return eSideLeft;
default:
MOZ_ASSERT_UNREACHABLE("Unexpected property");
return eSideTop;
}
}
static bool PaddingNeedsUsedValue(const LengthPercentage& aValue,
const ComputedStyle& aStyle) {
return !aValue.ConvertsToLength() || aStyle.StyleDisplay()->HasAppearance();
}
bool nsComputedDOMStyle::NeedsToFlushLayout(nsCSSPropertyID aPropID) const {
MOZ_ASSERT(aPropID != eCSSProperty_UNKNOWN);
if (aPropID == eCSSPropertyExtra_variable) {
return false;
}
nsIFrame* outerFrame = GetOuterFrame();
if (!outerFrame) {
return false;
}
nsIFrame* frame = nsLayoutUtils::GetStyleFrame(outerFrame);
auto* style = frame->Style();
if (nsCSSProps::PropHasFlags(aPropID, CSSPropFlags::IsLogical)) {
aPropID = Servo_ResolveLogicalProperty(aPropID, style);
}
switch (aPropID) {
case eCSSProperty_width:
case eCSSProperty_height:
return !IsNonReplacedInline(frame);
case eCSSProperty_line_height:
return frame->StyleFont()->mLineHeight.IsMozBlockHeight();
case eCSSProperty_grid_template_rows:
case eCSSProperty_grid_template_columns:
return !!nsGridContainerFrame::GetGridContainerFrame(frame);
case eCSSProperty_perspective_origin:
return style->StyleDisplay()->mPerspectiveOrigin.HasPercent();
case eCSSProperty_transform_origin:
return style->StyleDisplay()->mTransformOrigin.HasPercent();
case eCSSProperty_transform:
return style->StyleDisplay()->mTransform.HasPercent();
case eCSSProperty_border_top_width:
case eCSSProperty_border_bottom_width:
case eCSSProperty_border_left_width:
case eCSSProperty_border_right_width:
// FIXME(emilio): This should return false per spec (bug 1551000), but
// themed borders don't make that easy, so for now flush for that case.
//
// TODO(emilio): If we make GetUsedBorder() stop returning 0 for an
// unreflowed frame, or something of that sort, then we can stop flushing
// layout for themed frames.
return style->StyleDisplay()->HasAppearance();
case eCSSProperty_top:
case eCSSProperty_right:
case eCSSProperty_bottom:
case eCSSProperty_left:
// Doing better than this is actually hard.
return style->StyleDisplay()->mPosition != StylePositionProperty::Static;
case eCSSProperty_padding_top:
case eCSSProperty_padding_right:
case eCSSProperty_padding_bottom:
case eCSSProperty_padding_left: {
Side side = SideForPaddingOrMarginOrInsetProperty(aPropID);
// Theming can override used padding, sigh.
//
// TODO(emilio): If we make GetUsedPadding() stop returning 0 for an
// unreflowed frame, or something of that sort, then we can stop flushing
// layout for themed frames.
return PaddingNeedsUsedValue(style->StylePadding()->mPadding.Get(side),
*style);
}
case eCSSProperty_margin_top:
case eCSSProperty_margin_right:
case eCSSProperty_margin_bottom:
case eCSSProperty_margin_left: {
// NOTE(emilio): This is dubious, but matches other browsers.
Side side = SideForPaddingOrMarginOrInsetProperty(aPropID);
return !style->StyleMargin()->GetMargin(side).ConvertsToLength();
}
default:
return false;
}
}
bool nsComputedDOMStyle::NeedsToFlushLayoutForContainerQuery() const {
const auto* outerFrame = GetOuterFrame();
if (!outerFrame) {
return false;
}
const auto* innerFrame = nsLayoutUtils::GetStyleFrame(outerFrame);
MOZ_ASSERT(innerFrame, "No valid inner frame?");
// It's possible that potential containers are styled but not yet reflowed,
// i.e. They don't have a correct size, which makes any container query
// evaluation against them invalid.
return innerFrame->HasUnreflowedContainerQueryAncestor();
}
void nsComputedDOMStyle::Flush(Document& aDocument, FlushType aFlushType) {
MOZ_ASSERT(mElement->IsInComposedDoc());
MOZ_ASSERT(mDocumentWeak == &aDocument);
if (MOZ_UNLIKELY(&aDocument != mElement->OwnerDoc())) {
aDocument.FlushPendingNotifications(aFlushType);
}
// This performs the flush, and also guarantees that content-visibility:
// hidden elements get laid out, if needed.
mElement->GetPrimaryFrame(aFlushType);
}
nsIFrame* nsComputedDOMStyle::GetOuterFrame() const {
if (mPseudo.mType == PseudoStyleType::NotPseudo) {
return mElement->GetPrimaryFrame();
}
nsAtom* property = nullptr;
if (mPseudo.mType == PseudoStyleType::before) {
property = nsGkAtoms::beforePseudoProperty;
} else if (mPseudo.mType == PseudoStyleType::after) {
property = nsGkAtoms::afterPseudoProperty;
} else if (mPseudo.mType == PseudoStyleType::marker) {
property = nsGkAtoms::markerPseudoProperty;
}
if (!property) {
return nullptr;
}
auto* pseudo = static_cast<Element*>(mElement->GetProperty(property));
return pseudo ? pseudo->GetPrimaryFrame() : nullptr;
}
void nsComputedDOMStyle::UpdateCurrentStyleSources(nsCSSPropertyID aPropID) {
nsCOMPtr<Document> document(mDocumentWeak);
if (!document) {
ClearComputedStyle();
return;
}
// We don't return styles for disconnected elements anymore, so don't go
// through the trouble of flushing or what not.
//
// TODO(emilio): We may want to return earlier for elements outside of the
if (!mElement->IsInComposedDoc()) {
ClearComputedStyle();
return;
}
if (mAlwaysReturnEmpty == AlwaysReturnEmptyStyle::Yes) {
ClearComputedStyle();
return;
}
DebugOnly<bool> didFlush = false;
if (NeedsToFlushStyle(aPropID)) {
didFlush = true;
// We look at the frame in NeedsToFlushLayout, so flush frames, not only
// styles.
Flush(*document, FlushType::Frames);
}
const bool needsToFlushLayoutForProp = NeedsToFlushLayout(aPropID);
if (needsToFlushLayoutForProp || NeedsToFlushLayoutForContainerQuery()) {
MOZ_ASSERT_IF(needsToFlushLayoutForProp, MayNeedToFlushLayout(aPropID));
didFlush = true;
Flush(*document, FlushType::Layout);
#ifdef DEBUG
mFlushedPendingReflows = true;
#endif
} else {
#ifdef DEBUG
mFlushedPendingReflows = false;
#endif
}
mPresShell = document->GetPresShell();
if (!mPresShell || !mPresShell->GetPresContext()) {
ClearComputedStyle();
return;
}
// We need to use GetUndisplayedRestyleGeneration instead of
// GetRestyleGeneration, because the caching of mComputedStyle is an
// optimization that is useful only for displayed elements.
// For undisplayed elements we need to take into account any DOM changes that
// might cause a restyle, because Servo will not increase the generation for
// undisplayed elements.
uint64_t currentGeneration =
mPresShell->GetPresContext()->GetUndisplayedRestyleGeneration();
if (mComputedStyle && mComputedStyleGeneration == currentGeneration &&
mPresShellId == mPresShell->GetPresShellId()) {
// Our cached style is still valid.
return;
}
mComputedStyle = nullptr;
// XXX the !mElement->IsHTMLElement(nsGkAtoms::area) check is needed due to
// bug 135040 (to avoid using mPrimaryFrame). Remove it once that's fixed.
if (mStyleType == StyleType::All &&
!mElement->IsHTMLElement(nsGkAtoms::area)) {
mOuterFrame = GetOuterFrame();
mInnerFrame = mOuterFrame;
if (mOuterFrame) {
mInnerFrame = nsLayoutUtils::GetStyleFrame(mOuterFrame);
SetFrameComputedStyle(mInnerFrame->Style(), currentGeneration);
NS_ASSERTION(mComputedStyle, "Frame without style?");
}
}
if (!mComputedStyle || MustReresolveStyle(mComputedStyle)) {
PresShell* presShellForContent = mElement->OwnerDoc()->GetPresShell();
// Need to resolve a style.
RefPtr<const ComputedStyle> resolvedComputedStyle =
DoGetComputedStyleNoFlush(
mElement, mPseudo,
presShellForContent ? presShellForContent : mPresShell, mStyleType);
if (!resolvedComputedStyle) {
ClearComputedStyle();
return;
}
// No need to re-get the generation, even though GetComputedStyle
// will flush, since we flushed style at the top of this function.
// We don't need to check this if we only flushed the parent.