зеркало из https://github.com/mozilla/gecko-dev.git
3452 строки
126 KiB
C++
3452 строки
126 KiB
C++
/* -*- 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/. */
|
|
|
|
/*
|
|
* structs that contain the data provided by ComputedStyle, the
|
|
* internal API for computed style data for an element
|
|
*/
|
|
|
|
#include "nsStyleStruct.h"
|
|
#include "nsStyleStructInlines.h"
|
|
#include "nsStyleConsts.h"
|
|
#include "nsString.h"
|
|
#include "nsPresContext.h"
|
|
#include "nsIWidget.h"
|
|
#include "nsCRTGlue.h"
|
|
#include "nsCSSProps.h"
|
|
#include "nsDeviceContext.h"
|
|
#include "nsStyleUtil.h"
|
|
#include "nsIURIMutator.h"
|
|
|
|
#include "nsCOMPtr.h"
|
|
|
|
#include "nsBidiUtils.h"
|
|
#include "nsLayoutUtils.h"
|
|
|
|
#include "imgIRequest.h"
|
|
#include "imgIContainer.h"
|
|
#include "CounterStyleManager.h"
|
|
|
|
#include "mozilla/dom/AnimationEffectBinding.h" // for PlaybackDirection
|
|
#include "mozilla/dom/DocGroup.h"
|
|
#include "mozilla/dom/ImageTracker.h"
|
|
#include "mozilla/CORSMode.h"
|
|
#include "mozilla/ClearOnShutdown.h"
|
|
#include "mozilla/GeckoBindings.h"
|
|
#include "mozilla/PreferenceSheet.h"
|
|
#include "mozilla/SchedulerGroup.h"
|
|
#include "mozilla/StaticPresData.h"
|
|
#include "mozilla/Likely.h"
|
|
#include "nsIURI.h"
|
|
#include "mozilla/dom/Document.h"
|
|
#include "mozilla/dom/DocumentInlines.h"
|
|
#include <algorithm>
|
|
#include "ImageLoader.h"
|
|
#include "mozilla/StaticPrefs_layout.h"
|
|
|
|
using namespace mozilla;
|
|
using namespace mozilla::dom;
|
|
|
|
static const nscoord kMediumBorderWidth = nsPresContext::CSSPixelsToAppUnits(3);
|
|
|
|
// We set the size limit of style structs to 504 bytes so that when they
|
|
// are allocated by Servo side with Arc, the total size doesn't exceed
|
|
// 512 bytes, which minimizes allocator slop.
|
|
static constexpr size_t kStyleStructSizeLimit = 504;
|
|
|
|
template <typename Struct, size_t Actual, size_t Limit>
|
|
struct AssertSizeIsLessThan {
|
|
static_assert(Actual == sizeof(Struct), "Bogus invocation");
|
|
static_assert(Actual <= Limit,
|
|
"Style struct became larger than the size limit");
|
|
static constexpr bool instantiate = true;
|
|
};
|
|
|
|
#define STYLE_STRUCT(name_) \
|
|
static_assert(AssertSizeIsLessThan<nsStyle##name_, sizeof(nsStyle##name_), \
|
|
kStyleStructSizeLimit>::instantiate, \
|
|
"");
|
|
#include "nsStyleStructList.h"
|
|
#undef STYLE_STRUCT
|
|
|
|
bool StyleCssUrlData::operator==(const StyleCssUrlData& aOther) const {
|
|
// This very intentionally avoids comparing LoadData and such.
|
|
const auto& extra = extra_data.get();
|
|
const auto& otherExtra = aOther.extra_data.get();
|
|
if (extra.BaseURI() != otherExtra.BaseURI() ||
|
|
extra.Principal() != otherExtra.Principal() ||
|
|
cors_mode != aOther.cors_mode) {
|
|
// NOTE(emilio): This does pointer comparison, but it's what URLValue used
|
|
// to do. That's ok though since this is only used for style struct diffing.
|
|
return false;
|
|
}
|
|
return serialization == aOther.serialization;
|
|
}
|
|
|
|
StyleLoadData::~StyleLoadData() { Gecko_LoadData_Drop(this); }
|
|
|
|
already_AddRefed<nsIURI> StyleComputedUrl::ResolveLocalRef(nsIURI* aURI) const {
|
|
nsCOMPtr<nsIURI> result = GetURI();
|
|
if (result && IsLocalRef()) {
|
|
nsCString ref;
|
|
result->GetRef(ref);
|
|
|
|
nsresult rv = NS_MutateURI(aURI).SetRef(ref).Finalize(result);
|
|
|
|
if (NS_FAILED(rv)) {
|
|
// If setting the ref failed, just return the original URI.
|
|
result = aURI;
|
|
}
|
|
}
|
|
return result.forget();
|
|
}
|
|
|
|
already_AddRefed<nsIURI> StyleComputedUrl::ResolveLocalRef(
|
|
const nsIContent* aContent) const {
|
|
return ResolveLocalRef(aContent->GetBaseURI());
|
|
}
|
|
|
|
void StyleComputedUrl::ResolveImage(Document& aDocument,
|
|
const StyleComputedUrl* aOldImage) {
|
|
MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
|
|
|
|
StyleLoadData& data = LoadData();
|
|
|
|
MOZ_ASSERT(!(data.flags & StyleLoadDataFlags::TRIED_TO_RESOLVE_IMAGE));
|
|
|
|
data.flags |= StyleLoadDataFlags::TRIED_TO_RESOLVE_IMAGE;
|
|
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
// TODO(emilio, bug 1440442): This is a hackaround to avoid flickering due the
|
|
// lack of non-http image caching in imagelib (bug 1406134), which causes
|
|
// stuff like bug 1439285. Cleanest fix if that doesn't get fixed is bug
|
|
// 1440305, but that seems too risky, and a lot of work to do before 60.
|
|
//
|
|
// Once that's fixed, the "old style" argument to TriggerImageLoads can go
|
|
// away, and same for mSharedCount in the image loader and so on.
|
|
const bool reuseProxy = nsContentUtils::IsChromeDoc(&aDocument) &&
|
|
aOldImage && aOldImage->IsImageResolved() &&
|
|
*this == *aOldImage;
|
|
|
|
RefPtr<imgRequestProxy> request;
|
|
if (reuseProxy) {
|
|
request = aOldImage->LoadData().resolved_image;
|
|
if (request) {
|
|
css::ImageLoader::NoteSharedLoad(request);
|
|
}
|
|
} else {
|
|
request = css::ImageLoader::LoadImage(*this, aDocument);
|
|
}
|
|
|
|
if (!request) {
|
|
return;
|
|
}
|
|
|
|
data.resolved_image = request.forget().take();
|
|
|
|
// Boost priority now that we know the image is present in the ComputedStyle
|
|
// of some frame.
|
|
data.resolved_image->BoostPriority(imgIRequest::CATEGORY_FRAME_STYLE);
|
|
}
|
|
|
|
/**
|
|
* Runnable to release the image request's mRequestProxy
|
|
* and mImageTracker on the main thread, and to perform
|
|
* any necessary unlocking and untracking of the image.
|
|
*/
|
|
class StyleImageRequestCleanupTask final : public mozilla::Runnable {
|
|
public:
|
|
explicit StyleImageRequestCleanupTask(StyleLoadData& aData)
|
|
: mozilla::Runnable("StyleImageRequestCleanupTask"),
|
|
mRequestProxy(dont_AddRef(aData.resolved_image)) {
|
|
MOZ_ASSERT(mRequestProxy);
|
|
aData.resolved_image = nullptr;
|
|
}
|
|
|
|
NS_IMETHOD Run() final {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
css::ImageLoader::UnloadImage(mRequestProxy);
|
|
return NS_OK;
|
|
}
|
|
|
|
protected:
|
|
virtual ~StyleImageRequestCleanupTask() {
|
|
MOZ_ASSERT(!mRequestProxy || NS_IsMainThread(),
|
|
"mRequestProxy destructor need to run on the main thread!");
|
|
}
|
|
|
|
private:
|
|
// Since we always dispatch this runnable to the main thread, these will be
|
|
// released on the main thread when the runnable itself is released.
|
|
RefPtr<imgRequestProxy> mRequestProxy;
|
|
};
|
|
|
|
// This is defined here for parallelism with LoadURI.
|
|
void Gecko_LoadData_Drop(StyleLoadData* aData) {
|
|
if (aData->resolved_image) {
|
|
auto task = MakeRefPtr<StyleImageRequestCleanupTask>(*aData);
|
|
if (NS_IsMainThread()) {
|
|
task->Run();
|
|
} else {
|
|
// if Resolve was not called at some point, mDocGroup is not set.
|
|
SchedulerGroup::Dispatch(TaskCategory::Other, task.forget());
|
|
}
|
|
}
|
|
|
|
// URIs are safe to refcount from any thread.
|
|
NS_IF_RELEASE(aData->resolved_uri);
|
|
}
|
|
|
|
// --------------------
|
|
// nsStyleFont
|
|
//
|
|
nsStyleFont::nsStyleFont(const nsStyleFont& aSrc)
|
|
: mFont(aSrc.mFont),
|
|
mSize(aSrc.mSize),
|
|
mFontSizeFactor(aSrc.mFontSizeFactor),
|
|
mFontSizeOffset(aSrc.mFontSizeOffset),
|
|
mFontSizeKeyword(aSrc.mFontSizeKeyword),
|
|
mGenericID(aSrc.mGenericID),
|
|
mMathDepth(aSrc.mMathDepth),
|
|
mMathVariant(aSrc.mMathVariant),
|
|
mMathStyle(aSrc.mMathStyle),
|
|
mMinFontSizeRatio(aSrc.mMinFontSizeRatio),
|
|
mExplicitLanguage(aSrc.mExplicitLanguage),
|
|
mAllowZoomAndMinSize(aSrc.mAllowZoomAndMinSize),
|
|
mScriptUnconstrainedSize(aSrc.mScriptUnconstrainedSize),
|
|
mScriptMinSize(aSrc.mScriptMinSize),
|
|
mScriptSizeMultiplier(aSrc.mScriptSizeMultiplier),
|
|
mLanguage(aSrc.mLanguage) {
|
|
MOZ_COUNT_CTOR(nsStyleFont);
|
|
}
|
|
|
|
nsStyleFont::nsStyleFont(const Document& aDocument)
|
|
: mFont(*aDocument.GetFontPrefsForLang(nullptr)->GetDefaultFont(
|
|
StyleGenericFontFamily::None)),
|
|
mSize(ZoomText(aDocument, mFont.size)),
|
|
mFontSizeFactor(1.0),
|
|
mFontSizeOffset{0},
|
|
mFontSizeKeyword(StyleFontSizeKeyword::Medium),
|
|
mGenericID(StyleGenericFontFamily::None),
|
|
mMathDepth(0),
|
|
mMathVariant(NS_MATHML_MATHVARIANT_NONE),
|
|
mMathStyle(NS_STYLE_MATH_STYLE_NORMAL),
|
|
mMinFontSizeRatio(100), // 100%
|
|
mExplicitLanguage(false),
|
|
mAllowZoomAndMinSize(true),
|
|
mScriptUnconstrainedSize(mSize),
|
|
mScriptMinSize(Length::FromPixels(
|
|
CSSPixel::FromPoints(NS_MATHML_DEFAULT_SCRIPT_MIN_SIZE_PT))),
|
|
mScriptSizeMultiplier(NS_MATHML_DEFAULT_SCRIPT_SIZE_MULTIPLIER),
|
|
mLanguage(aDocument.GetLanguageForStyle()) {
|
|
MOZ_COUNT_CTOR(nsStyleFont);
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
mFont.size = mSize;
|
|
if (!nsContentUtils::IsChromeDoc(&aDocument)) {
|
|
Length minimumFontSize =
|
|
aDocument.GetFontPrefsForLang(mLanguage)->mMinimumFontSize;
|
|
mFont.size = Length::FromPixels(
|
|
std::max(mSize.ToCSSPixels(), minimumFontSize.ToCSSPixels()));
|
|
}
|
|
}
|
|
|
|
nsChangeHint nsStyleFont::CalcDifference(const nsStyleFont& aNewData) const {
|
|
MOZ_ASSERT(
|
|
mAllowZoomAndMinSize == aNewData.mAllowZoomAndMinSize,
|
|
"expected mAllowZoomAndMinSize to be the same on both nsStyleFonts");
|
|
if (mSize != aNewData.mSize || mLanguage != aNewData.mLanguage ||
|
|
mExplicitLanguage != aNewData.mExplicitLanguage ||
|
|
mMathVariant != aNewData.mMathVariant ||
|
|
mMathStyle != aNewData.mMathStyle ||
|
|
mMinFontSizeRatio != aNewData.mMinFontSizeRatio) {
|
|
return NS_STYLE_HINT_REFLOW;
|
|
}
|
|
|
|
switch (mFont.CalcDifference(aNewData.mFont)) {
|
|
case nsFont::MaxDifference::eLayoutAffecting:
|
|
return NS_STYLE_HINT_REFLOW;
|
|
|
|
case nsFont::MaxDifference::eVisual:
|
|
return NS_STYLE_HINT_VISUAL;
|
|
|
|
case nsFont::MaxDifference::eNone:
|
|
break;
|
|
}
|
|
|
|
// XXX Should any of these cause a non-nsChangeHint_NeutralChange change?
|
|
if (mGenericID != aNewData.mGenericID || mMathDepth != aNewData.mMathDepth ||
|
|
mScriptUnconstrainedSize != aNewData.mScriptUnconstrainedSize ||
|
|
mScriptMinSize != aNewData.mScriptMinSize ||
|
|
mScriptSizeMultiplier != aNewData.mScriptSizeMultiplier) {
|
|
return nsChangeHint_NeutralChange;
|
|
}
|
|
|
|
return nsChangeHint(0);
|
|
}
|
|
|
|
Length nsStyleFont::ZoomText(const Document& aDocument, Length aSize) {
|
|
if (auto* pc = aDocument.GetPresContext()) {
|
|
aSize.ScaleBy(pc->EffectiveTextZoom());
|
|
}
|
|
return aSize;
|
|
}
|
|
|
|
template <typename T>
|
|
static StyleRect<T> StyleRectWithAllSides(const T& aSide) {
|
|
return {aSide, aSide, aSide, aSide};
|
|
}
|
|
|
|
nsStyleMargin::nsStyleMargin(const Document& aDocument)
|
|
: mMargin(StyleRectWithAllSides(
|
|
LengthPercentageOrAuto::LengthPercentage(LengthPercentage::Zero()))),
|
|
mScrollMargin(StyleRectWithAllSides(StyleLength{0.})) {
|
|
MOZ_COUNT_CTOR(nsStyleMargin);
|
|
}
|
|
|
|
nsStyleMargin::nsStyleMargin(const nsStyleMargin& aSrc)
|
|
: mMargin(aSrc.mMargin), mScrollMargin(aSrc.mScrollMargin) {
|
|
MOZ_COUNT_CTOR(nsStyleMargin);
|
|
}
|
|
|
|
nsChangeHint nsStyleMargin::CalcDifference(
|
|
const nsStyleMargin& aNewData) const {
|
|
nsChangeHint hint = nsChangeHint(0);
|
|
|
|
if (mMargin != aNewData.mMargin) {
|
|
// Margin differences can't affect descendant intrinsic sizes and
|
|
// don't need to force children to reflow.
|
|
hint |= nsChangeHint_NeedReflow | nsChangeHint_ReflowChangesSizeOrPosition |
|
|
nsChangeHint_ClearAncestorIntrinsics;
|
|
}
|
|
|
|
if (mScrollMargin != aNewData.mScrollMargin) {
|
|
// FIXME: Bug 1530253 Support re-snapping when scroll-margin changes.
|
|
hint |= nsChangeHint_NeutralChange;
|
|
}
|
|
|
|
return hint;
|
|
}
|
|
|
|
nsStylePadding::nsStylePadding(const Document& aDocument)
|
|
: mPadding(StyleRectWithAllSides(LengthPercentage::Zero())),
|
|
mScrollPadding(StyleRectWithAllSides(LengthPercentageOrAuto::Auto())) {
|
|
MOZ_COUNT_CTOR(nsStylePadding);
|
|
}
|
|
|
|
nsStylePadding::nsStylePadding(const nsStylePadding& aSrc)
|
|
: mPadding(aSrc.mPadding), mScrollPadding(aSrc.mScrollPadding) {
|
|
MOZ_COUNT_CTOR(nsStylePadding);
|
|
}
|
|
|
|
nsChangeHint nsStylePadding::CalcDifference(
|
|
const nsStylePadding& aNewData) const {
|
|
nsChangeHint hint = nsChangeHint(0);
|
|
|
|
if (mPadding != aNewData.mPadding) {
|
|
// Padding differences can't affect descendant intrinsic sizes, but do need
|
|
// to force children to reflow so that we can reposition them, since their
|
|
// offsets are from our frame bounds but our content rect's position within
|
|
// those bounds is moving.
|
|
// FIXME: It would be good to return a weaker hint here that doesn't
|
|
// force reflow of all descendants, but the hint would need to force
|
|
// reflow of the frame's children (see how
|
|
// ReflowInput::InitResizeFlags initializes the inline-resize flag).
|
|
hint |= NS_STYLE_HINT_REFLOW & ~nsChangeHint_ClearDescendantIntrinsics;
|
|
}
|
|
|
|
if (mScrollPadding != aNewData.mScrollPadding) {
|
|
// FIXME: Bug 1530253 Support re-snapping when scroll-padding changes.
|
|
hint |= nsChangeHint_NeutralChange;
|
|
}
|
|
|
|
return hint;
|
|
}
|
|
|
|
static nscoord TwipsPerPixel(const Document& aDocument) {
|
|
auto* pc = aDocument.GetPresContext();
|
|
return pc ? pc->AppUnitsPerDevPixel() : mozilla::AppUnitsPerCSSPixel();
|
|
}
|
|
|
|
static inline BorderRadius ZeroBorderRadius() {
|
|
auto zero = LengthPercentage::Zero();
|
|
return {{{zero, zero}}, {{zero, zero}}, {{zero, zero}}, {{zero, zero}}};
|
|
}
|
|
|
|
nsStyleBorder::nsStyleBorder(const Document& aDocument)
|
|
: mBorderRadius(ZeroBorderRadius()),
|
|
mBorderImageSource(StyleImage::None()),
|
|
mBorderImageWidth(
|
|
StyleRectWithAllSides(StyleBorderImageSideWidth::Number(1.))),
|
|
mBorderImageOutset(
|
|
StyleRectWithAllSides(StyleNonNegativeLengthOrNumber::Number(0.))),
|
|
mBorderImageSlice(
|
|
{StyleRectWithAllSides(StyleNumberOrPercentage::Percentage({1.})),
|
|
false}),
|
|
mBorderImageRepeatH(StyleBorderImageRepeat::Stretch),
|
|
mBorderImageRepeatV(StyleBorderImageRepeat::Stretch),
|
|
mFloatEdge(StyleFloatEdge::ContentBox),
|
|
mBoxDecorationBreak(StyleBoxDecorationBreak::Slice),
|
|
mBorderTopColor(StyleColor::CurrentColor()),
|
|
mBorderRightColor(StyleColor::CurrentColor()),
|
|
mBorderBottomColor(StyleColor::CurrentColor()),
|
|
mBorderLeftColor(StyleColor::CurrentColor()),
|
|
mComputedBorder(0, 0, 0, 0),
|
|
mTwipsPerPixel(TwipsPerPixel(aDocument)) {
|
|
MOZ_COUNT_CTOR(nsStyleBorder);
|
|
|
|
nscoord medium = kMediumBorderWidth;
|
|
for (const auto side : mozilla::AllPhysicalSides()) {
|
|
mBorder.Side(side) = medium;
|
|
mBorderStyle[side] = StyleBorderStyle::None;
|
|
}
|
|
}
|
|
|
|
nsStyleBorder::nsStyleBorder(const nsStyleBorder& aSrc)
|
|
: mBorderRadius(aSrc.mBorderRadius),
|
|
mBorderImageSource(aSrc.mBorderImageSource),
|
|
mBorderImageWidth(aSrc.mBorderImageWidth),
|
|
mBorderImageOutset(aSrc.mBorderImageOutset),
|
|
mBorderImageSlice(aSrc.mBorderImageSlice),
|
|
mBorderImageRepeatH(aSrc.mBorderImageRepeatH),
|
|
mBorderImageRepeatV(aSrc.mBorderImageRepeatV),
|
|
mFloatEdge(aSrc.mFloatEdge),
|
|
mBoxDecorationBreak(aSrc.mBoxDecorationBreak),
|
|
mBorderTopColor(aSrc.mBorderTopColor),
|
|
mBorderRightColor(aSrc.mBorderRightColor),
|
|
mBorderBottomColor(aSrc.mBorderBottomColor),
|
|
mBorderLeftColor(aSrc.mBorderLeftColor),
|
|
mComputedBorder(aSrc.mComputedBorder),
|
|
mBorder(aSrc.mBorder),
|
|
mTwipsPerPixel(aSrc.mTwipsPerPixel) {
|
|
MOZ_COUNT_CTOR(nsStyleBorder);
|
|
for (const auto side : mozilla::AllPhysicalSides()) {
|
|
mBorderStyle[side] = aSrc.mBorderStyle[side];
|
|
}
|
|
}
|
|
|
|
nsStyleBorder::~nsStyleBorder() { MOZ_COUNT_DTOR(nsStyleBorder); }
|
|
|
|
void nsStyleBorder::TriggerImageLoads(Document& aDocument,
|
|
const nsStyleBorder* aOldStyle) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
mBorderImageSource.ResolveImage(
|
|
aDocument, aOldStyle ? &aOldStyle->mBorderImageSource : nullptr);
|
|
}
|
|
|
|
nsMargin nsStyleBorder::GetImageOutset() const {
|
|
// We don't check whether there is a border-image (which is OK since
|
|
// the initial values yields 0 outset) so that we don't have to
|
|
// reflow to update overflow areas when an image loads.
|
|
nsMargin outset;
|
|
for (const auto s : mozilla::AllPhysicalSides()) {
|
|
const auto& coord = mBorderImageOutset.Get(s);
|
|
nscoord value;
|
|
if (coord.IsLength()) {
|
|
value = coord.AsLength().ToAppUnits();
|
|
} else {
|
|
MOZ_ASSERT(coord.IsNumber());
|
|
value = coord.AsNumber() * mComputedBorder.Side(s);
|
|
}
|
|
outset.Side(s) = value;
|
|
}
|
|
return outset;
|
|
}
|
|
|
|
nsChangeHint nsStyleBorder::CalcDifference(
|
|
const nsStyleBorder& aNewData) const {
|
|
// FIXME: XXXbz: As in nsStylePadding::CalcDifference, many of these
|
|
// differences should not need to clear descendant intrinsics.
|
|
// FIXME: It would be good to return a weaker hint for the
|
|
// GetComputedBorder() differences (and perhaps others) that doesn't
|
|
// force reflow of all descendants, but the hint would need to force
|
|
// reflow of the frame's children (see how
|
|
// ReflowInput::InitResizeFlags initializes the inline-resize flag).
|
|
if (mTwipsPerPixel != aNewData.mTwipsPerPixel ||
|
|
GetComputedBorder() != aNewData.GetComputedBorder() ||
|
|
mFloatEdge != aNewData.mFloatEdge ||
|
|
mBorderImageOutset != aNewData.mBorderImageOutset ||
|
|
mBoxDecorationBreak != aNewData.mBoxDecorationBreak) {
|
|
return NS_STYLE_HINT_REFLOW;
|
|
}
|
|
|
|
for (const auto ix : mozilla::AllPhysicalSides()) {
|
|
// See the explanation in nsChangeHint.h of
|
|
// nsChangeHint_BorderStyleNoneChange .
|
|
// Furthermore, even though we know *this* side is 0 width, just
|
|
// assume a repaint hint for some other change rather than bother
|
|
// tracking this result through the rest of the function.
|
|
if (HasVisibleStyle(ix) != aNewData.HasVisibleStyle(ix)) {
|
|
return nsChangeHint_RepaintFrame | nsChangeHint_BorderStyleNoneChange;
|
|
}
|
|
}
|
|
|
|
// Note that mBorderStyle stores not only the border style but also
|
|
// color-related flags. Given that we've already done an mComputedBorder
|
|
// comparison, border-style differences can only lead to a repaint hint. So
|
|
// it's OK to just compare the values directly -- if either the actual
|
|
// style or the color flags differ we want to repaint.
|
|
for (const auto ix : mozilla::AllPhysicalSides()) {
|
|
if (mBorderStyle[ix] != aNewData.mBorderStyle[ix] ||
|
|
BorderColorFor(ix) != aNewData.BorderColorFor(ix)) {
|
|
return nsChangeHint_RepaintFrame;
|
|
}
|
|
}
|
|
|
|
// Note that border radius also controls the outline radius if the
|
|
// layout.css.outline-follows-border-radius.enabled pref is set. Any
|
|
// optimizations here should apply to both.
|
|
if (mBorderRadius != aNewData.mBorderRadius) {
|
|
return nsChangeHint_RepaintFrame;
|
|
}
|
|
|
|
// Loading status of the border image can be accessed in main thread only
|
|
// while CalcDifference might be executed on a background thread. As a
|
|
// result, we have to check mBorderImage* fields even before border image was
|
|
// actually loaded.
|
|
if (!mBorderImageSource.IsNone() || !aNewData.mBorderImageSource.IsNone()) {
|
|
if (mBorderImageSource != aNewData.mBorderImageSource ||
|
|
mBorderImageRepeatH != aNewData.mBorderImageRepeatH ||
|
|
mBorderImageRepeatV != aNewData.mBorderImageRepeatV ||
|
|
mBorderImageSlice != aNewData.mBorderImageSlice ||
|
|
mBorderImageWidth != aNewData.mBorderImageWidth) {
|
|
return nsChangeHint_RepaintFrame;
|
|
}
|
|
}
|
|
|
|
// mBorder is the specified border value. Changes to this don't
|
|
// need any change processing, since we operate on the computed
|
|
// border values instead.
|
|
if (mBorder != aNewData.mBorder) {
|
|
return nsChangeHint_NeutralChange;
|
|
}
|
|
|
|
// mBorderImage* fields are checked only when border-image is not 'none'.
|
|
if (mBorderImageSource != aNewData.mBorderImageSource ||
|
|
mBorderImageRepeatH != aNewData.mBorderImageRepeatH ||
|
|
mBorderImageRepeatV != aNewData.mBorderImageRepeatV ||
|
|
mBorderImageSlice != aNewData.mBorderImageSlice ||
|
|
mBorderImageWidth != aNewData.mBorderImageWidth) {
|
|
return nsChangeHint_NeutralChange;
|
|
}
|
|
|
|
return nsChangeHint(0);
|
|
}
|
|
|
|
nsStyleOutline::nsStyleOutline(const Document& aDocument)
|
|
: mOutlineRadius(ZeroBorderRadius()),
|
|
mOutlineWidth(kMediumBorderWidth),
|
|
mOutlineOffset({0.0f}),
|
|
mOutlineColor(StyleColor::CurrentColor()),
|
|
mOutlineStyle(StyleOutlineStyle::BorderStyle(StyleBorderStyle::None)),
|
|
mActualOutlineWidth(0),
|
|
mTwipsPerPixel(TwipsPerPixel(aDocument)) {
|
|
MOZ_COUNT_CTOR(nsStyleOutline);
|
|
}
|
|
|
|
nsStyleOutline::nsStyleOutline(const nsStyleOutline& aSrc)
|
|
: mOutlineRadius(aSrc.mOutlineRadius),
|
|
mOutlineWidth(aSrc.mOutlineWidth),
|
|
mOutlineOffset(aSrc.mOutlineOffset),
|
|
mOutlineColor(aSrc.mOutlineColor),
|
|
mOutlineStyle(aSrc.mOutlineStyle),
|
|
mActualOutlineWidth(aSrc.mActualOutlineWidth),
|
|
mTwipsPerPixel(aSrc.mTwipsPerPixel) {
|
|
MOZ_COUNT_CTOR(nsStyleOutline);
|
|
}
|
|
|
|
nsChangeHint nsStyleOutline::CalcDifference(
|
|
const nsStyleOutline& aNewData) const {
|
|
if (mActualOutlineWidth != aNewData.mActualOutlineWidth ||
|
|
(mActualOutlineWidth > 0 && mOutlineOffset != aNewData.mOutlineOffset)) {
|
|
return nsChangeHint_UpdateOverflow | nsChangeHint_SchedulePaint |
|
|
nsChangeHint_RepaintFrame;
|
|
}
|
|
|
|
if (mOutlineStyle != aNewData.mOutlineStyle ||
|
|
mOutlineColor != aNewData.mOutlineColor ||
|
|
mOutlineRadius != aNewData.mOutlineRadius) {
|
|
if (mActualOutlineWidth > 0) {
|
|
return nsChangeHint_RepaintFrame;
|
|
}
|
|
return nsChangeHint_NeutralChange;
|
|
}
|
|
|
|
if (mOutlineWidth != aNewData.mOutlineWidth ||
|
|
mOutlineOffset != aNewData.mOutlineOffset ||
|
|
mTwipsPerPixel != aNewData.mTwipsPerPixel) {
|
|
return nsChangeHint_NeutralChange;
|
|
}
|
|
|
|
return nsChangeHint(0);
|
|
}
|
|
|
|
// --------------------
|
|
// nsStyleList
|
|
//
|
|
nsStyleList::nsStyleList(const Document& aDocument)
|
|
: mListStylePosition(NS_STYLE_LIST_STYLE_POSITION_OUTSIDE),
|
|
mQuotes(StyleQuotes::Auto()),
|
|
mListStyleImage(StyleImage::None()),
|
|
mImageRegion(StyleClipRectOrAuto::Auto()),
|
|
mMozListReversed(StyleMozListReversed::False) {
|
|
MOZ_COUNT_CTOR(nsStyleList);
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
mCounterStyle = nsGkAtoms::disc;
|
|
}
|
|
|
|
nsStyleList::~nsStyleList() { MOZ_COUNT_DTOR(nsStyleList); }
|
|
|
|
nsStyleList::nsStyleList(const nsStyleList& aSource)
|
|
: mListStylePosition(aSource.mListStylePosition),
|
|
mCounterStyle(aSource.mCounterStyle),
|
|
mQuotes(aSource.mQuotes),
|
|
mListStyleImage(aSource.mListStyleImage),
|
|
mImageRegion(aSource.mImageRegion),
|
|
mMozListReversed(aSource.mMozListReversed) {
|
|
MOZ_COUNT_CTOR(nsStyleList);
|
|
}
|
|
|
|
void nsStyleList::TriggerImageLoads(Document& aDocument,
|
|
const nsStyleList* aOldStyle) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
mListStyleImage.ResolveImage(
|
|
aDocument, aOldStyle ? &aOldStyle->mListStyleImage : nullptr);
|
|
}
|
|
|
|
nsChangeHint nsStyleList::CalcDifference(
|
|
const nsStyleList& aNewData, const nsStyleDisplay& aOldDisplay) const {
|
|
// If the quotes implementation is ever going to change we might not need
|
|
// a framechange here and a reflow should be sufficient. See bug 35768.
|
|
if (mQuotes != aNewData.mQuotes) {
|
|
return nsChangeHint_ReconstructFrame;
|
|
}
|
|
nsChangeHint hint = nsChangeHint(0);
|
|
// Only elements whose display value is list-item can be affected by
|
|
// list-style-position and list-style-type. If the old display struct
|
|
// doesn't exist, assume it isn't affected by display value at all,
|
|
// and thus these properties should not affect it either. This also
|
|
// relies on that when the display value changes from something else
|
|
// to list-item, that change itself would cause ReconstructFrame.
|
|
if (aOldDisplay.IsListItem()) {
|
|
if (mListStylePosition != aNewData.mListStylePosition) {
|
|
return nsChangeHint_ReconstructFrame;
|
|
}
|
|
if (mCounterStyle != aNewData.mCounterStyle) {
|
|
return NS_STYLE_HINT_REFLOW;
|
|
}
|
|
} else if (mListStylePosition != aNewData.mListStylePosition ||
|
|
mCounterStyle != aNewData.mCounterStyle) {
|
|
hint = nsChangeHint_NeutralChange;
|
|
}
|
|
// This is an internal UA-sheet property that is true only for <ol reversed>
|
|
// so hopefully it changes rarely.
|
|
if (mMozListReversed != aNewData.mMozListReversed) {
|
|
return NS_STYLE_HINT_REFLOW;
|
|
}
|
|
// list-style-image and -moz-image-region may affect some XUL elements
|
|
// regardless of display value, so we still need to check them.
|
|
if (mListStyleImage != aNewData.mListStyleImage) {
|
|
return NS_STYLE_HINT_REFLOW;
|
|
}
|
|
if (mImageRegion != aNewData.mImageRegion) {
|
|
nsRect region = GetImageRegion();
|
|
nsRect newRegion = aNewData.GetImageRegion();
|
|
if (region.width != newRegion.width || region.height != newRegion.height) {
|
|
return NS_STYLE_HINT_REFLOW;
|
|
}
|
|
return NS_STYLE_HINT_VISUAL;
|
|
}
|
|
return hint;
|
|
}
|
|
|
|
already_AddRefed<nsIURI> nsStyleList::GetListStyleImageURI() const {
|
|
if (!mListStyleImage.IsUrl()) {
|
|
return nullptr;
|
|
}
|
|
|
|
return do_AddRef(mListStyleImage.AsUrl().GetURI());
|
|
}
|
|
|
|
// --------------------
|
|
// nsStyleXUL
|
|
//
|
|
nsStyleXUL::nsStyleXUL(const Document& aDocument)
|
|
: mBoxFlex(0.0f),
|
|
mBoxOrdinal(1),
|
|
mBoxAlign(StyleBoxAlign::Stretch),
|
|
mBoxDirection(StyleBoxDirection::Normal),
|
|
mBoxOrient(StyleBoxOrient::Horizontal),
|
|
mBoxPack(StyleBoxPack::Start) {
|
|
MOZ_COUNT_CTOR(nsStyleXUL);
|
|
}
|
|
|
|
nsStyleXUL::~nsStyleXUL() { MOZ_COUNT_DTOR(nsStyleXUL); }
|
|
|
|
nsStyleXUL::nsStyleXUL(const nsStyleXUL& aSource)
|
|
: mBoxFlex(aSource.mBoxFlex),
|
|
mBoxOrdinal(aSource.mBoxOrdinal),
|
|
mBoxAlign(aSource.mBoxAlign),
|
|
mBoxDirection(aSource.mBoxDirection),
|
|
mBoxOrient(aSource.mBoxOrient),
|
|
mBoxPack(aSource.mBoxPack) {
|
|
MOZ_COUNT_CTOR(nsStyleXUL);
|
|
}
|
|
|
|
nsChangeHint nsStyleXUL::CalcDifference(const nsStyleXUL& aNewData) const {
|
|
if (mBoxAlign == aNewData.mBoxAlign &&
|
|
mBoxDirection == aNewData.mBoxDirection &&
|
|
mBoxFlex == aNewData.mBoxFlex && mBoxOrient == aNewData.mBoxOrient &&
|
|
mBoxPack == aNewData.mBoxPack && mBoxOrdinal == aNewData.mBoxOrdinal) {
|
|
return nsChangeHint(0);
|
|
}
|
|
if (mBoxOrdinal != aNewData.mBoxOrdinal) {
|
|
return nsChangeHint_ReconstructFrame;
|
|
}
|
|
return NS_STYLE_HINT_REFLOW;
|
|
}
|
|
|
|
// --------------------
|
|
// nsStyleColumn
|
|
//
|
|
/* static */ const uint32_t nsStyleColumn::kMaxColumnCount;
|
|
/* static */ const uint32_t nsStyleColumn::kColumnCountAuto;
|
|
|
|
nsStyleColumn::nsStyleColumn(const Document& aDocument)
|
|
: mColumnWidth(LengthOrAuto::Auto()),
|
|
mColumnRuleColor(StyleColor::CurrentColor()),
|
|
mColumnRuleStyle(StyleBorderStyle::None),
|
|
mColumnRuleWidth(kMediumBorderWidth),
|
|
mTwipsPerPixel(TwipsPerPixel(aDocument)) {
|
|
MOZ_COUNT_CTOR(nsStyleColumn);
|
|
}
|
|
|
|
nsStyleColumn::~nsStyleColumn() { MOZ_COUNT_DTOR(nsStyleColumn); }
|
|
|
|
nsStyleColumn::nsStyleColumn(const nsStyleColumn& aSource)
|
|
: mColumnCount(aSource.mColumnCount),
|
|
mColumnWidth(aSource.mColumnWidth),
|
|
mColumnRuleColor(aSource.mColumnRuleColor),
|
|
mColumnRuleStyle(aSource.mColumnRuleStyle),
|
|
mColumnFill(aSource.mColumnFill),
|
|
mColumnSpan(aSource.mColumnSpan),
|
|
mColumnRuleWidth(aSource.mColumnRuleWidth),
|
|
mTwipsPerPixel(aSource.mTwipsPerPixel) {
|
|
MOZ_COUNT_CTOR(nsStyleColumn);
|
|
}
|
|
|
|
nsChangeHint nsStyleColumn::CalcDifference(
|
|
const nsStyleColumn& aNewData) const {
|
|
if (mColumnWidth.IsAuto() != aNewData.mColumnWidth.IsAuto() ||
|
|
mColumnCount != aNewData.mColumnCount ||
|
|
mColumnSpan != aNewData.mColumnSpan) {
|
|
// We force column count changes to do a reframe, because it's tricky to
|
|
// handle some edge cases where the column count gets smaller and content
|
|
// overflows.
|
|
// XXX not ideal
|
|
return nsChangeHint_ReconstructFrame;
|
|
}
|
|
|
|
if (mColumnWidth != aNewData.mColumnWidth ||
|
|
mColumnFill != aNewData.mColumnFill) {
|
|
return NS_STYLE_HINT_REFLOW;
|
|
}
|
|
|
|
if (GetComputedColumnRuleWidth() != aNewData.GetComputedColumnRuleWidth() ||
|
|
mColumnRuleStyle != aNewData.mColumnRuleStyle ||
|
|
mColumnRuleColor != aNewData.mColumnRuleColor) {
|
|
return NS_STYLE_HINT_VISUAL;
|
|
}
|
|
|
|
// XXX Is it right that we never check mTwipsPerPixel to return a
|
|
// non-nsChangeHint_NeutralChange hint?
|
|
if (mColumnRuleWidth != aNewData.mColumnRuleWidth ||
|
|
mTwipsPerPixel != aNewData.mTwipsPerPixel) {
|
|
return nsChangeHint_NeutralChange;
|
|
}
|
|
|
|
return nsChangeHint(0);
|
|
}
|
|
|
|
using SVGPaintFallback = StyleGenericSVGPaintFallback<StyleColor>;
|
|
|
|
// --------------------
|
|
// nsStyleSVG
|
|
//
|
|
nsStyleSVG::nsStyleSVG(const Document& aDocument)
|
|
: mFill{StyleSVGPaintKind::Color(StyleColor::Black()),
|
|
SVGPaintFallback::Unset()},
|
|
mStroke{StyleSVGPaintKind::None(), SVGPaintFallback::Unset()},
|
|
mMarkerEnd(StyleUrlOrNone::None()),
|
|
mMarkerMid(StyleUrlOrNone::None()),
|
|
mMarkerStart(StyleUrlOrNone::None()),
|
|
mMozContextProperties{{}, {0}},
|
|
mStrokeDasharray(StyleSVGStrokeDashArray::Values({})),
|
|
mStrokeDashoffset(
|
|
StyleSVGLength::LengthPercentage(LengthPercentage::Zero())),
|
|
mStrokeWidth(
|
|
StyleSVGWidth::LengthPercentage(LengthPercentage::FromPixels(1.0f))),
|
|
mFillOpacity(StyleSVGOpacity::Opacity(1.0f)),
|
|
mStrokeMiterlimit(4.0f),
|
|
mStrokeOpacity(StyleSVGOpacity::Opacity(1.0f)),
|
|
mClipRule(StyleFillRule::Nonzero),
|
|
mColorInterpolation(StyleColorInterpolation::Srgb),
|
|
mColorInterpolationFilters(StyleColorInterpolation::Linearrgb),
|
|
mFillRule(StyleFillRule::Nonzero),
|
|
mPaintOrder(0),
|
|
mShapeRendering(StyleShapeRendering::Auto),
|
|
mStrokeLinecap(StyleStrokeLinecap::Butt),
|
|
mStrokeLinejoin(StyleStrokeLinejoin::Miter),
|
|
mDominantBaseline(StyleDominantBaseline::Auto),
|
|
mTextAnchor(StyleTextAnchor::Start) {
|
|
MOZ_COUNT_CTOR(nsStyleSVG);
|
|
}
|
|
|
|
nsStyleSVG::~nsStyleSVG() { MOZ_COUNT_DTOR(nsStyleSVG); }
|
|
|
|
nsStyleSVG::nsStyleSVG(const nsStyleSVG& aSource)
|
|
: mFill(aSource.mFill),
|
|
mStroke(aSource.mStroke),
|
|
mMarkerEnd(aSource.mMarkerEnd),
|
|
mMarkerMid(aSource.mMarkerMid),
|
|
mMarkerStart(aSource.mMarkerStart),
|
|
mMozContextProperties(aSource.mMozContextProperties),
|
|
mStrokeDasharray(aSource.mStrokeDasharray),
|
|
mStrokeDashoffset(aSource.mStrokeDashoffset),
|
|
mStrokeWidth(aSource.mStrokeWidth),
|
|
mFillOpacity(aSource.mFillOpacity),
|
|
mStrokeMiterlimit(aSource.mStrokeMiterlimit),
|
|
mStrokeOpacity(aSource.mStrokeOpacity),
|
|
mClipRule(aSource.mClipRule),
|
|
mColorInterpolation(aSource.mColorInterpolation),
|
|
mColorInterpolationFilters(aSource.mColorInterpolationFilters),
|
|
mFillRule(aSource.mFillRule),
|
|
mPaintOrder(aSource.mPaintOrder),
|
|
mShapeRendering(aSource.mShapeRendering),
|
|
mStrokeLinecap(aSource.mStrokeLinecap),
|
|
mStrokeLinejoin(aSource.mStrokeLinejoin),
|
|
mDominantBaseline(aSource.mDominantBaseline),
|
|
mTextAnchor(aSource.mTextAnchor) {
|
|
MOZ_COUNT_CTOR(nsStyleSVG);
|
|
}
|
|
|
|
static bool PaintURIChanged(const StyleSVGPaint& aPaint1,
|
|
const StyleSVGPaint& aPaint2) {
|
|
if (aPaint1.kind.IsPaintServer() != aPaint2.kind.IsPaintServer()) {
|
|
return true;
|
|
}
|
|
return aPaint1.kind.IsPaintServer() &&
|
|
aPaint1.kind.AsPaintServer() != aPaint2.kind.AsPaintServer();
|
|
}
|
|
|
|
nsChangeHint nsStyleSVG::CalcDifference(const nsStyleSVG& aNewData) const {
|
|
nsChangeHint hint = nsChangeHint(0);
|
|
|
|
if (mMarkerEnd != aNewData.mMarkerEnd || mMarkerMid != aNewData.mMarkerMid ||
|
|
mMarkerStart != aNewData.mMarkerStart) {
|
|
// Markers currently contribute to SVGGeometryFrame::mRect,
|
|
// so we need a reflow as well as a repaint. No intrinsic sizes need
|
|
// to change, so nsChangeHint_NeedReflow is sufficient.
|
|
return nsChangeHint_UpdateEffects | nsChangeHint_NeedReflow |
|
|
nsChangeHint_NeedDirtyReflow | // XXX remove me: bug 876085
|
|
nsChangeHint_RepaintFrame;
|
|
}
|
|
|
|
if (mFill != aNewData.mFill || mStroke != aNewData.mStroke ||
|
|
mFillOpacity != aNewData.mFillOpacity ||
|
|
mStrokeOpacity != aNewData.mStrokeOpacity) {
|
|
hint |= nsChangeHint_RepaintFrame;
|
|
if (HasStroke() != aNewData.HasStroke() ||
|
|
(!HasStroke() && HasFill() != aNewData.HasFill())) {
|
|
// Frame bounds and overflow rects depend on whether we "have" fill or
|
|
// stroke. Whether we have stroke or not just changed, or else we have no
|
|
// stroke (in which case whether we have fill or not is significant to
|
|
// frame bounds) and whether we have fill or not just changed. In either
|
|
// case we need to reflow so the frame rect is updated.
|
|
// XXXperf this is a waste on non SVGGeometryFrames.
|
|
hint |= nsChangeHint_NeedReflow |
|
|
nsChangeHint_NeedDirtyReflow; // XXX remove me: bug 876085
|
|
}
|
|
if (PaintURIChanged(mFill, aNewData.mFill) ||
|
|
PaintURIChanged(mStroke, aNewData.mStroke)) {
|
|
hint |= nsChangeHint_UpdateEffects;
|
|
}
|
|
}
|
|
|
|
// Stroke currently contributes to SVGGeometryFrame::mRect, so
|
|
// we need a reflow here. No intrinsic sizes need to change, so
|
|
// nsChangeHint_NeedReflow is sufficient.
|
|
// Note that stroke-dashoffset does not affect SVGGeometryFrame::mRect.
|
|
// text-anchor and dominant-baseline changes also require a reflow since
|
|
// they change frames' rects.
|
|
if (mStrokeWidth != aNewData.mStrokeWidth ||
|
|
mStrokeMiterlimit != aNewData.mStrokeMiterlimit ||
|
|
mStrokeLinecap != aNewData.mStrokeLinecap ||
|
|
mStrokeLinejoin != aNewData.mStrokeLinejoin ||
|
|
mDominantBaseline != aNewData.mDominantBaseline ||
|
|
mTextAnchor != aNewData.mTextAnchor) {
|
|
return hint | nsChangeHint_NeedReflow |
|
|
nsChangeHint_NeedDirtyReflow | // XXX remove me: bug 876085
|
|
nsChangeHint_RepaintFrame;
|
|
}
|
|
|
|
if (hint & nsChangeHint_RepaintFrame) {
|
|
return hint; // we don't add anything else below
|
|
}
|
|
|
|
if (mStrokeDashoffset != aNewData.mStrokeDashoffset ||
|
|
mClipRule != aNewData.mClipRule ||
|
|
mColorInterpolation != aNewData.mColorInterpolation ||
|
|
mColorInterpolationFilters != aNewData.mColorInterpolationFilters ||
|
|
mFillRule != aNewData.mFillRule || mPaintOrder != aNewData.mPaintOrder ||
|
|
mShapeRendering != aNewData.mShapeRendering ||
|
|
mStrokeDasharray != aNewData.mStrokeDasharray ||
|
|
mMozContextProperties.bits != aNewData.mMozContextProperties.bits) {
|
|
return hint | nsChangeHint_RepaintFrame;
|
|
}
|
|
|
|
if (!hint) {
|
|
if (mMozContextProperties.idents != aNewData.mMozContextProperties.idents) {
|
|
hint = nsChangeHint_NeutralChange;
|
|
}
|
|
}
|
|
|
|
return hint;
|
|
}
|
|
|
|
// --------------------
|
|
// nsStyleSVGReset
|
|
//
|
|
nsStyleSVGReset::nsStyleSVGReset(const Document& aDocument)
|
|
: mX(LengthPercentage::Zero()),
|
|
mY(LengthPercentage::Zero()),
|
|
mCx(LengthPercentage::Zero()),
|
|
mCy(LengthPercentage::Zero()),
|
|
mRx(NonNegativeLengthPercentageOrAuto::Auto()),
|
|
mRy(NonNegativeLengthPercentageOrAuto::Auto()),
|
|
mR(NonNegativeLengthPercentage::Zero()),
|
|
mMask(nsStyleImageLayers::LayerType::Mask),
|
|
mClipPath(StyleClipPath::None()),
|
|
mStopColor(StyleColor::Black()),
|
|
mFloodColor(StyleColor::Black()),
|
|
mLightingColor(StyleColor::White()),
|
|
mStopOpacity(1.0f),
|
|
mFloodOpacity(1.0f),
|
|
mVectorEffect(StyleVectorEffect::None),
|
|
mMaskType(StyleMaskType::Luminance) {
|
|
MOZ_COUNT_CTOR(nsStyleSVGReset);
|
|
}
|
|
|
|
nsStyleSVGReset::~nsStyleSVGReset() { MOZ_COUNT_DTOR(nsStyleSVGReset); }
|
|
|
|
nsStyleSVGReset::nsStyleSVGReset(const nsStyleSVGReset& aSource)
|
|
: mX(aSource.mX),
|
|
mY(aSource.mY),
|
|
mCx(aSource.mCx),
|
|
mCy(aSource.mCy),
|
|
mRx(aSource.mRx),
|
|
mRy(aSource.mRy),
|
|
mR(aSource.mR),
|
|
mMask(aSource.mMask),
|
|
mClipPath(aSource.mClipPath),
|
|
mStopColor(aSource.mStopColor),
|
|
mFloodColor(aSource.mFloodColor),
|
|
mLightingColor(aSource.mLightingColor),
|
|
mStopOpacity(aSource.mStopOpacity),
|
|
mFloodOpacity(aSource.mFloodOpacity),
|
|
mVectorEffect(aSource.mVectorEffect),
|
|
mMaskType(aSource.mMaskType) {
|
|
MOZ_COUNT_CTOR(nsStyleSVGReset);
|
|
}
|
|
|
|
void nsStyleSVGReset::TriggerImageLoads(Document& aDocument,
|
|
const nsStyleSVGReset* aOldStyle) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
// NOTE(emilio): we intentionally don't call TriggerImageLoads for clip-path.
|
|
|
|
NS_FOR_VISIBLE_IMAGE_LAYERS_BACK_TO_FRONT(i, mMask) {
|
|
auto& image = mMask.mLayers[i].mImage;
|
|
if (!image.IsImageRequestType()) {
|
|
continue;
|
|
}
|
|
const auto* url = image.GetImageRequestURLValue();
|
|
// If the url is a local ref, it must be a <mask-resource>, so we don't
|
|
// need to resolve the style image.
|
|
if (url->IsLocalRef()) {
|
|
continue;
|
|
}
|
|
#if 0
|
|
// XXX The old style system also checks whether this is a reference to
|
|
// the current document with reference, but it doesn't seem to be a
|
|
// behavior mentioned anywhere, so we comment out the code for now.
|
|
nsIURI* docURI = aPresContext->Document()->GetDocumentURI();
|
|
if (url->EqualsExceptRef(docURI)) {
|
|
continue;
|
|
}
|
|
#endif
|
|
// Otherwise, we may need the image even if it has a reference, in case
|
|
// the referenced element isn't a valid SVG <mask> element.
|
|
const auto* oldImage = (aOldStyle && aOldStyle->mMask.mLayers.Length() > i)
|
|
? &aOldStyle->mMask.mLayers[i].mImage
|
|
: nullptr;
|
|
|
|
image.ResolveImage(aDocument, oldImage);
|
|
}
|
|
}
|
|
|
|
nsChangeHint nsStyleSVGReset::CalcDifference(
|
|
const nsStyleSVGReset& aNewData) const {
|
|
nsChangeHint hint = nsChangeHint(0);
|
|
|
|
if (mX != aNewData.mX || mY != aNewData.mY || mCx != aNewData.mCx ||
|
|
mCy != aNewData.mCy || mR != aNewData.mR || mRx != aNewData.mRx ||
|
|
mRy != aNewData.mRy) {
|
|
hint |= nsChangeHint_InvalidateRenderingObservers | nsChangeHint_NeedReflow;
|
|
}
|
|
|
|
if (mClipPath != aNewData.mClipPath) {
|
|
hint |= nsChangeHint_UpdateEffects | nsChangeHint_RepaintFrame;
|
|
}
|
|
|
|
if (mVectorEffect != aNewData.mVectorEffect) {
|
|
// Stroke currently affects SVGGeometryFrame::mRect, and
|
|
// vector-effect affect stroke. As a result we need to reflow if
|
|
// vector-effect changes in order to have SVGGeometryFrame::
|
|
// ReflowSVG called to update its mRect. No intrinsic sizes need
|
|
// to change so nsChangeHint_NeedReflow is sufficient.
|
|
hint |= nsChangeHint_NeedReflow |
|
|
nsChangeHint_NeedDirtyReflow | // XXX remove me: bug 876085
|
|
nsChangeHint_RepaintFrame;
|
|
} else if (mStopColor != aNewData.mStopColor ||
|
|
mFloodColor != aNewData.mFloodColor ||
|
|
mLightingColor != aNewData.mLightingColor ||
|
|
mStopOpacity != aNewData.mStopOpacity ||
|
|
mFloodOpacity != aNewData.mFloodOpacity ||
|
|
mMaskType != aNewData.mMaskType) {
|
|
hint |= nsChangeHint_RepaintFrame;
|
|
}
|
|
|
|
hint |=
|
|
mMask.CalcDifference(aNewData.mMask, nsStyleImageLayers::LayerType::Mask);
|
|
|
|
return hint;
|
|
}
|
|
|
|
bool nsStyleSVGReset::HasMask() const {
|
|
for (uint32_t i = 0; i < mMask.mImageCount; i++) {
|
|
if (!mMask.mLayers[i].mImage.IsNone()) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// --------------------
|
|
// nsStylePage
|
|
//
|
|
|
|
nsChangeHint nsStylePage::CalcDifference(const nsStylePage& aNewData) const {
|
|
// Page rule styling only matters when printing or using print preview.
|
|
if (aNewData.mSize != mSize) {
|
|
return nsChangeHint_NeutralChange;
|
|
}
|
|
return nsChangeHint_Empty;
|
|
}
|
|
|
|
// --------------------
|
|
// nsStylePosition
|
|
//
|
|
nsStylePosition::nsStylePosition(const Document& aDocument)
|
|
: mObjectPosition(Position::FromPercentage(0.5f)),
|
|
mOffset(StyleRectWithAllSides(LengthPercentageOrAuto::Auto())),
|
|
mWidth(StyleSize::Auto()),
|
|
mMinWidth(StyleSize::Auto()),
|
|
mMaxWidth(StyleMaxSize::None()),
|
|
mHeight(StyleSize::Auto()),
|
|
mMinHeight(StyleSize::Auto()),
|
|
mMaxHeight(StyleMaxSize::None()),
|
|
mFlexBasis(StyleFlexBasis::Size(StyleSize::Auto())),
|
|
mAspectRatio(StyleAspectRatio::Auto()),
|
|
mGridAutoFlow(StyleGridAutoFlow::ROW),
|
|
mMasonryAutoFlow(NS_STYLE_MASONRY_AUTO_FLOW_INITIAL_VALUE),
|
|
mAlignContent({StyleAlignFlags::NORMAL}),
|
|
mAlignItems({StyleAlignFlags::NORMAL}),
|
|
mAlignSelf({StyleAlignFlags::AUTO}),
|
|
mJustifyContent({StyleAlignFlags::NORMAL}),
|
|
mJustifyItems({{StyleAlignFlags::LEGACY}, {StyleAlignFlags::NORMAL}}),
|
|
mJustifySelf({StyleAlignFlags::AUTO}),
|
|
mFlexDirection(StyleFlexDirection::Row),
|
|
mFlexWrap(StyleFlexWrap::Nowrap),
|
|
mObjectFit(StyleObjectFit::Fill),
|
|
mBoxSizing(StyleBoxSizing::Content),
|
|
mOrder(NS_STYLE_ORDER_INITIAL),
|
|
mFlexGrow(0.0f),
|
|
mFlexShrink(1.0f),
|
|
mZIndex(StyleZIndex::Auto()),
|
|
mGridTemplateColumns(StyleGridTemplateComponent::None()),
|
|
mGridTemplateRows(StyleGridTemplateComponent::None()),
|
|
mGridTemplateAreas(StyleGridTemplateAreas::None()),
|
|
mColumnGap(NonNegativeLengthPercentageOrNormal::Normal()),
|
|
mRowGap(NonNegativeLengthPercentageOrNormal::Normal()) {
|
|
MOZ_COUNT_CTOR(nsStylePosition);
|
|
|
|
// The initial value of grid-auto-columns and grid-auto-rows is 'auto',
|
|
// which computes to 'minmax(auto, auto)'.
|
|
|
|
// Other members get their default constructors
|
|
// which initialize them to representations of their respective initial value.
|
|
// mGridTemplate{Rows,Columns}: false and empty arrays for 'none'
|
|
// mGrid{Column,Row}{Start,End}: false/0/empty values for 'auto'
|
|
}
|
|
|
|
nsStylePosition::~nsStylePosition() { MOZ_COUNT_DTOR(nsStylePosition); }
|
|
|
|
nsStylePosition::nsStylePosition(const nsStylePosition& aSource)
|
|
: mAlignTracks(aSource.mAlignTracks),
|
|
mJustifyTracks(aSource.mJustifyTracks),
|
|
mObjectPosition(aSource.mObjectPosition),
|
|
mOffset(aSource.mOffset),
|
|
mWidth(aSource.mWidth),
|
|
mMinWidth(aSource.mMinWidth),
|
|
mMaxWidth(aSource.mMaxWidth),
|
|
mHeight(aSource.mHeight),
|
|
mMinHeight(aSource.mMinHeight),
|
|
mMaxHeight(aSource.mMaxHeight),
|
|
mFlexBasis(aSource.mFlexBasis),
|
|
mGridAutoColumns(aSource.mGridAutoColumns),
|
|
mGridAutoRows(aSource.mGridAutoRows),
|
|
mAspectRatio(aSource.mAspectRatio),
|
|
mGridAutoFlow(aSource.mGridAutoFlow),
|
|
mMasonryAutoFlow(aSource.mMasonryAutoFlow),
|
|
mAlignContent(aSource.mAlignContent),
|
|
mAlignItems(aSource.mAlignItems),
|
|
mAlignSelf(aSource.mAlignSelf),
|
|
mJustifyContent(aSource.mJustifyContent),
|
|
mJustifyItems(aSource.mJustifyItems),
|
|
mJustifySelf(aSource.mJustifySelf),
|
|
mFlexDirection(aSource.mFlexDirection),
|
|
mFlexWrap(aSource.mFlexWrap),
|
|
mObjectFit(aSource.mObjectFit),
|
|
mBoxSizing(aSource.mBoxSizing),
|
|
mOrder(aSource.mOrder),
|
|
mFlexGrow(aSource.mFlexGrow),
|
|
mFlexShrink(aSource.mFlexShrink),
|
|
mZIndex(aSource.mZIndex),
|
|
mGridTemplateColumns(aSource.mGridTemplateColumns),
|
|
mGridTemplateRows(aSource.mGridTemplateRows),
|
|
mGridTemplateAreas(aSource.mGridTemplateAreas),
|
|
mGridColumnStart(aSource.mGridColumnStart),
|
|
mGridColumnEnd(aSource.mGridColumnEnd),
|
|
mGridRowStart(aSource.mGridRowStart),
|
|
mGridRowEnd(aSource.mGridRowEnd),
|
|
mColumnGap(aSource.mColumnGap),
|
|
mRowGap(aSource.mRowGap) {
|
|
MOZ_COUNT_CTOR(nsStylePosition);
|
|
}
|
|
|
|
static bool IsAutonessEqual(const StyleRect<LengthPercentageOrAuto>& aSides1,
|
|
const StyleRect<LengthPercentageOrAuto>& aSides2) {
|
|
for (const auto side : mozilla::AllPhysicalSides()) {
|
|
if (aSides1.Get(side).IsAuto() != aSides2.Get(side).IsAuto()) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
nsChangeHint nsStylePosition::CalcDifference(
|
|
const nsStylePosition& aNewData,
|
|
const nsStyleVisibility& aOldStyleVisibility) const {
|
|
if (mGridTemplateColumns.IsMasonry() !=
|
|
aNewData.mGridTemplateColumns.IsMasonry() ||
|
|
mGridTemplateRows.IsMasonry() != aNewData.mGridTemplateRows.IsMasonry()) {
|
|
// XXXmats this could be optimized to AllReflowHints with a bit of work,
|
|
// but I'll assume this is a very rare use case in practice. (bug 1623886)
|
|
return nsChangeHint_ReconstructFrame;
|
|
}
|
|
|
|
nsChangeHint hint = nsChangeHint(0);
|
|
|
|
// Changes to "z-index" require a repaint.
|
|
if (mZIndex != aNewData.mZIndex) {
|
|
hint |= nsChangeHint_RepaintFrame;
|
|
}
|
|
|
|
// Changes to "object-fit" & "object-position" require a repaint. They
|
|
// may also require a reflow, if we have a nsSubDocumentFrame, so that we
|
|
// can adjust the size & position of the subdocument.
|
|
if (mObjectFit != aNewData.mObjectFit ||
|
|
mObjectPosition != aNewData.mObjectPosition) {
|
|
hint |= nsChangeHint_RepaintFrame | nsChangeHint_NeedReflow;
|
|
}
|
|
|
|
if (mOrder != aNewData.mOrder) {
|
|
// "order" impacts both layout order and stacking order, so we need both a
|
|
// reflow and a repaint when it changes. (Technically, we only need a
|
|
// reflow if we're in a multi-line flexbox (which we can't be sure about,
|
|
// since that's determined by styling on our parent) -- there, "order" can
|
|
// affect which flex line we end up on, & hence can affect our sizing by
|
|
// changing the group of flex items we're competing with for space.)
|
|
return hint | nsChangeHint_RepaintFrame | nsChangeHint_AllReflowHints;
|
|
}
|
|
|
|
if (mBoxSizing != aNewData.mBoxSizing) {
|
|
// Can affect both widths and heights; just a bad scene.
|
|
return hint | nsChangeHint_AllReflowHints;
|
|
}
|
|
|
|
if (mAlignItems != aNewData.mAlignItems ||
|
|
mAlignSelf != aNewData.mAlignSelf ||
|
|
mJustifyTracks != aNewData.mJustifyTracks ||
|
|
mAlignTracks != aNewData.mAlignTracks) {
|
|
return hint | nsChangeHint_AllReflowHints;
|
|
}
|
|
|
|
// Properties that apply to flex items:
|
|
// XXXdholbert These should probably be more targeted (bug 819536)
|
|
if (mFlexBasis != aNewData.mFlexBasis || mFlexGrow != aNewData.mFlexGrow ||
|
|
mFlexShrink != aNewData.mFlexShrink) {
|
|
return hint | nsChangeHint_AllReflowHints;
|
|
}
|
|
|
|
// Properties that apply to flex containers:
|
|
// - flex-direction can swap a flex container between vertical & horizontal.
|
|
// - flex-wrap changes whether a flex container's children are wrapped, which
|
|
// impacts their sizing/positioning and hence impacts the container's size.
|
|
if (mFlexDirection != aNewData.mFlexDirection ||
|
|
mFlexWrap != aNewData.mFlexWrap) {
|
|
return hint | nsChangeHint_AllReflowHints;
|
|
}
|
|
|
|
// Properties that apply to grid containers:
|
|
// FIXME: only for grid containers
|
|
// (ie. 'display: grid' or 'display: inline-grid')
|
|
if (mGridTemplateColumns != aNewData.mGridTemplateColumns ||
|
|
mGridTemplateRows != aNewData.mGridTemplateRows ||
|
|
mGridTemplateAreas != aNewData.mGridTemplateAreas ||
|
|
mGridAutoColumns != aNewData.mGridAutoColumns ||
|
|
mGridAutoRows != aNewData.mGridAutoRows ||
|
|
mGridAutoFlow != aNewData.mGridAutoFlow ||
|
|
mMasonryAutoFlow != aNewData.mMasonryAutoFlow) {
|
|
return hint | nsChangeHint_AllReflowHints;
|
|
}
|
|
|
|
// Properties that apply to grid items:
|
|
// FIXME: only for grid items
|
|
// (ie. parent frame is 'display: grid' or 'display: inline-grid')
|
|
if (mGridColumnStart != aNewData.mGridColumnStart ||
|
|
mGridColumnEnd != aNewData.mGridColumnEnd ||
|
|
mGridRowStart != aNewData.mGridRowStart ||
|
|
mGridRowEnd != aNewData.mGridRowEnd ||
|
|
mColumnGap != aNewData.mColumnGap || mRowGap != aNewData.mRowGap) {
|
|
return hint | nsChangeHint_AllReflowHints;
|
|
}
|
|
|
|
// Changing 'justify-content/items/self' might affect the positioning,
|
|
// but it won't affect any sizing.
|
|
if (mJustifyContent != aNewData.mJustifyContent ||
|
|
mJustifyItems.computed != aNewData.mJustifyItems.computed ||
|
|
mJustifySelf != aNewData.mJustifySelf) {
|
|
hint |= nsChangeHint_NeedReflow;
|
|
}
|
|
|
|
// No need to do anything if specified justify-items changes, as long as the
|
|
// computed one (tested above) is unchanged.
|
|
if (mJustifyItems.specified != aNewData.mJustifyItems.specified) {
|
|
hint |= nsChangeHint_NeutralChange;
|
|
}
|
|
|
|
// 'align-content' doesn't apply to a single-line flexbox but we don't know
|
|
// if we're a flex container at this point so we can't optimize for that.
|
|
if (mAlignContent != aNewData.mAlignContent) {
|
|
hint |= nsChangeHint_NeedReflow;
|
|
}
|
|
|
|
bool widthChanged = mWidth != aNewData.mWidth ||
|
|
mMinWidth != aNewData.mMinWidth ||
|
|
mMaxWidth != aNewData.mMaxWidth;
|
|
bool heightChanged = mHeight != aNewData.mHeight ||
|
|
mMinHeight != aNewData.mMinHeight ||
|
|
mMaxHeight != aNewData.mMaxHeight;
|
|
|
|
// It doesn't matter whether we're looking at the old or new visibility
|
|
// struct, since a change between vertical and horizontal writing-mode will
|
|
// cause a reframe.
|
|
bool isVertical = aOldStyleVisibility.mWritingMode !=
|
|
StyleWritingModeProperty::HorizontalTb;
|
|
if (isVertical ? widthChanged : heightChanged) {
|
|
hint |= nsChangeHint_ReflowHintsForBSizeChange;
|
|
}
|
|
|
|
if (isVertical ? heightChanged : widthChanged) {
|
|
hint |= nsChangeHint_ReflowHintsForISizeChange;
|
|
}
|
|
|
|
if (mAspectRatio != aNewData.mAspectRatio) {
|
|
hint |= nsChangeHint_ReflowHintsForISizeChange |
|
|
nsChangeHint_ReflowHintsForBSizeChange;
|
|
}
|
|
|
|
// If any of the offsets have changed, then return the respective hints
|
|
// so that we would hopefully be able to avoid reflowing.
|
|
// Note that it is possible that we'll need to reflow when processing
|
|
// restyles, but we don't have enough information to make a good decision
|
|
// right now.
|
|
// Don't try to handle changes between "auto" and non-auto efficiently;
|
|
// that's tricky to do and will hardly ever be able to avoid a reflow.
|
|
if (mOffset != aNewData.mOffset) {
|
|
if (IsAutonessEqual(mOffset, aNewData.mOffset)) {
|
|
hint |=
|
|
nsChangeHint_RecomputePosition | nsChangeHint_UpdateParentOverflow;
|
|
} else {
|
|
hint |=
|
|
nsChangeHint_NeedReflow | nsChangeHint_ReflowChangesSizeOrPosition;
|
|
}
|
|
}
|
|
return hint;
|
|
}
|
|
|
|
StyleAlignSelf nsStylePosition::UsedAlignSelf(
|
|
const ComputedStyle* aParent) const {
|
|
if (mAlignSelf._0 != StyleAlignFlags::AUTO) {
|
|
return mAlignSelf;
|
|
}
|
|
if (MOZ_LIKELY(aParent)) {
|
|
auto parentAlignItems = aParent->StylePosition()->mAlignItems;
|
|
MOZ_ASSERT(!(parentAlignItems._0 & StyleAlignFlags::LEGACY),
|
|
"align-items can't have 'legacy'");
|
|
return {parentAlignItems._0};
|
|
}
|
|
return {StyleAlignFlags::NORMAL};
|
|
}
|
|
|
|
StyleJustifySelf nsStylePosition::UsedJustifySelf(
|
|
const ComputedStyle* aParent) const {
|
|
if (mJustifySelf._0 != StyleAlignFlags::AUTO) {
|
|
return mJustifySelf;
|
|
}
|
|
if (MOZ_LIKELY(aParent)) {
|
|
const auto& inheritedJustifyItems =
|
|
aParent->StylePosition()->mJustifyItems.computed;
|
|
return {inheritedJustifyItems._0 & ~StyleAlignFlags::LEGACY};
|
|
}
|
|
return {StyleAlignFlags::NORMAL};
|
|
}
|
|
|
|
// --------------------
|
|
// nsStyleTable
|
|
//
|
|
|
|
nsStyleTable::nsStyleTable(const Document& aDocument)
|
|
: mLayoutStrategy(StyleTableLayout::Auto), mXSpan(1) {
|
|
MOZ_COUNT_CTOR(nsStyleTable);
|
|
}
|
|
|
|
nsStyleTable::~nsStyleTable() { MOZ_COUNT_DTOR(nsStyleTable); }
|
|
|
|
nsStyleTable::nsStyleTable(const nsStyleTable& aSource)
|
|
: mLayoutStrategy(aSource.mLayoutStrategy), mXSpan(aSource.mXSpan) {
|
|
MOZ_COUNT_CTOR(nsStyleTable);
|
|
}
|
|
|
|
nsChangeHint nsStyleTable::CalcDifference(const nsStyleTable& aNewData) const {
|
|
if (mXSpan != aNewData.mXSpan ||
|
|
mLayoutStrategy != aNewData.mLayoutStrategy) {
|
|
return nsChangeHint_ReconstructFrame;
|
|
}
|
|
return nsChangeHint(0);
|
|
}
|
|
|
|
// -----------------------
|
|
// nsStyleTableBorder
|
|
|
|
nsStyleTableBorder::nsStyleTableBorder(const Document& aDocument)
|
|
: mBorderSpacingCol(0),
|
|
mBorderSpacingRow(0),
|
|
mBorderCollapse(StyleBorderCollapse::Separate),
|
|
mCaptionSide(StyleCaptionSide::Top),
|
|
mEmptyCells(StyleEmptyCells::Show) {
|
|
MOZ_COUNT_CTOR(nsStyleTableBorder);
|
|
}
|
|
|
|
nsStyleTableBorder::~nsStyleTableBorder() {
|
|
MOZ_COUNT_DTOR(nsStyleTableBorder);
|
|
}
|
|
|
|
nsStyleTableBorder::nsStyleTableBorder(const nsStyleTableBorder& aSource)
|
|
: mBorderSpacingCol(aSource.mBorderSpacingCol),
|
|
mBorderSpacingRow(aSource.mBorderSpacingRow),
|
|
mBorderCollapse(aSource.mBorderCollapse),
|
|
mCaptionSide(aSource.mCaptionSide),
|
|
mEmptyCells(aSource.mEmptyCells) {
|
|
MOZ_COUNT_CTOR(nsStyleTableBorder);
|
|
}
|
|
|
|
nsChangeHint nsStyleTableBorder::CalcDifference(
|
|
const nsStyleTableBorder& aNewData) const {
|
|
// Border-collapse changes need a reframe, because we use a different frame
|
|
// class for table cells in the collapsed border model. This is used to
|
|
// conserve memory when using the separated border model (collapsed borders
|
|
// require extra state to be stored).
|
|
if (mBorderCollapse != aNewData.mBorderCollapse) {
|
|
return nsChangeHint_ReconstructFrame;
|
|
}
|
|
|
|
if ((mCaptionSide == aNewData.mCaptionSide) &&
|
|
(mBorderSpacingCol == aNewData.mBorderSpacingCol) &&
|
|
(mBorderSpacingRow == aNewData.mBorderSpacingRow)) {
|
|
if (mEmptyCells == aNewData.mEmptyCells) {
|
|
return nsChangeHint(0);
|
|
}
|
|
return NS_STYLE_HINT_VISUAL;
|
|
} else {
|
|
return NS_STYLE_HINT_REFLOW;
|
|
}
|
|
}
|
|
|
|
template <typename T>
|
|
static bool GradientItemsAreOpaque(
|
|
Span<const StyleGenericGradientItem<StyleColor, T>> aItems) {
|
|
for (auto& stop : aItems) {
|
|
if (stop.IsInterpolationHint()) {
|
|
continue;
|
|
}
|
|
|
|
auto& color = stop.IsSimpleColorStop() ? stop.AsSimpleColorStop()
|
|
: stop.AsComplexColorStop().color;
|
|
if (color.MaybeTransparent()) {
|
|
// We don't know the foreground color here, so if it's being used
|
|
// we must assume it might be transparent.
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
template <>
|
|
bool StyleGradient::IsOpaque() const {
|
|
if (IsLinear()) {
|
|
return GradientItemsAreOpaque(AsLinear().items.AsSpan());
|
|
}
|
|
if (IsRadial()) {
|
|
return GradientItemsAreOpaque(AsRadial().items.AsSpan());
|
|
}
|
|
return GradientItemsAreOpaque(AsConic().items.AsSpan());
|
|
}
|
|
|
|
// --------------------
|
|
// CachedBorderImageData
|
|
|
|
void CachedBorderImageData::PurgeCachedImages() {
|
|
MOZ_ASSERT(!ServoStyleSet::IsInServoTraversal());
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
mSubImages.Clear();
|
|
}
|
|
|
|
void CachedBorderImageData::PurgeCacheForViewportChange(
|
|
const Maybe<nsSize>& aSize, const bool aHasIntrinsicRatio) {
|
|
// If we're redrawing with a different viewport-size than we used for our
|
|
// cached subimages, then we can't trust that our subimages are valid;
|
|
// any percent sizes/positions in our SVG doc may be different now. Purge!
|
|
// (We don't have to purge if the SVG document has an intrinsic ratio,
|
|
// though, because the actual size of elements in SVG documant's coordinate
|
|
// axis are fixed in this case.)
|
|
if (aSize != mCachedSVGViewportSize && !aHasIntrinsicRatio) {
|
|
PurgeCachedImages();
|
|
SetCachedSVGViewportSize(aSize);
|
|
}
|
|
}
|
|
|
|
static int32_t ConvertToPixelCoord(const StyleNumberOrPercentage& aCoord,
|
|
int32_t aPercentScale) {
|
|
double pixelValue;
|
|
if (aCoord.IsNumber()) {
|
|
pixelValue = aCoord.AsNumber();
|
|
} else {
|
|
MOZ_ASSERT(aCoord.IsPercentage());
|
|
pixelValue = aCoord.AsPercentage()._0 * aPercentScale;
|
|
}
|
|
MOZ_ASSERT(pixelValue >= 0, "we ensured non-negative while parsing");
|
|
pixelValue = std::min(pixelValue, double(INT32_MAX)); // avoid overflow
|
|
return NS_lround(pixelValue);
|
|
}
|
|
|
|
template <>
|
|
Maybe<StyleImage::ActualCropRect> StyleImage::ComputeActualCropRect() const {
|
|
MOZ_ASSERT(IsRect(),
|
|
"This function is designed to be used only image-rect images");
|
|
|
|
imgRequestProxy* req = GetImageRequest();
|
|
if (!req) {
|
|
return Nothing();
|
|
}
|
|
|
|
nsCOMPtr<imgIContainer> imageContainer;
|
|
req->GetImage(getter_AddRefs(imageContainer));
|
|
if (!imageContainer) {
|
|
return Nothing();
|
|
}
|
|
|
|
nsIntSize imageSize;
|
|
imageContainer->GetWidth(&imageSize.width);
|
|
imageContainer->GetHeight(&imageSize.height);
|
|
if (imageSize.width <= 0 || imageSize.height <= 0) {
|
|
return Nothing();
|
|
}
|
|
|
|
auto& rect = AsRect();
|
|
|
|
int32_t left = ConvertToPixelCoord(rect->left, imageSize.width);
|
|
int32_t top = ConvertToPixelCoord(rect->top, imageSize.height);
|
|
int32_t right = ConvertToPixelCoord(rect->right, imageSize.width);
|
|
int32_t bottom = ConvertToPixelCoord(rect->bottom, imageSize.height);
|
|
|
|
// IntersectRect() returns an empty rect if we get negative width or height
|
|
nsIntRect cropRect(left, top, right - left, bottom - top);
|
|
nsIntRect imageRect(nsIntPoint(0, 0), imageSize);
|
|
auto finalRect = imageRect.Intersect(cropRect);
|
|
bool isEntireImage = finalRect.IsEqualInterior(imageRect);
|
|
return Some(ActualCropRect{finalRect, isEntireImage});
|
|
}
|
|
|
|
template <>
|
|
bool StyleImage::IsOpaque() const {
|
|
if (IsImageSet()) {
|
|
return FinalImage().IsOpaque();
|
|
}
|
|
|
|
if (!IsComplete()) {
|
|
return false;
|
|
}
|
|
|
|
if (IsGradient()) {
|
|
return AsGradient()->IsOpaque();
|
|
}
|
|
|
|
if (IsElement()) {
|
|
return false;
|
|
}
|
|
|
|
MOZ_ASSERT(IsImageRequestType(), "unexpected image type");
|
|
MOZ_ASSERT(GetImageRequest(), "should've returned earlier above");
|
|
|
|
nsCOMPtr<imgIContainer> imageContainer;
|
|
GetImageRequest()->GetImage(getter_AddRefs(imageContainer));
|
|
MOZ_ASSERT(imageContainer, "IsComplete() said image container is ready");
|
|
|
|
// Check if the crop region of the image is opaque.
|
|
if (imageContainer->WillDrawOpaqueNow()) {
|
|
if (!IsRect()) {
|
|
return true;
|
|
}
|
|
|
|
// Must make sure if the crop rect contains at least a pixel.
|
|
// XXX Is this optimization worth it? Maybe I should just return false.
|
|
auto croprect = ComputeActualCropRect();
|
|
return croprect && !croprect->mRect.IsEmpty();
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
template <>
|
|
bool StyleImage::IsComplete() const {
|
|
switch (tag) {
|
|
case Tag::None:
|
|
return false;
|
|
case Tag::Gradient:
|
|
case Tag::Element:
|
|
return true;
|
|
case Tag::Url:
|
|
case Tag::Rect: {
|
|
if (!IsResolved()) {
|
|
return false;
|
|
}
|
|
imgRequestProxy* req = GetImageRequest();
|
|
if (!req) {
|
|
return false;
|
|
}
|
|
uint32_t status = imgIRequest::STATUS_ERROR;
|
|
return NS_SUCCEEDED(req->GetImageStatus(&status)) &&
|
|
(status & imgIRequest::STATUS_SIZE_AVAILABLE) &&
|
|
(status & imgIRequest::STATUS_FRAME_COMPLETE);
|
|
}
|
|
case Tag::ImageSet:
|
|
return FinalImage().IsComplete();
|
|
// Bug 546052 cross-fade not yet implemented.
|
|
case Tag::CrossFade:
|
|
return true;
|
|
}
|
|
MOZ_ASSERT_UNREACHABLE("unexpected image type");
|
|
return false;
|
|
}
|
|
|
|
template <>
|
|
bool StyleImage::IsSizeAvailable() const {
|
|
switch (tag) {
|
|
case Tag::None:
|
|
return false;
|
|
case Tag::Gradient:
|
|
case Tag::Element:
|
|
return true;
|
|
case Tag::Url:
|
|
case Tag::Rect: {
|
|
imgRequestProxy* req = GetImageRequest();
|
|
if (!req) {
|
|
return false;
|
|
}
|
|
uint32_t status = imgIRequest::STATUS_ERROR;
|
|
return NS_SUCCEEDED(req->GetImageStatus(&status)) &&
|
|
!(status & imgIRequest::STATUS_ERROR) &&
|
|
(status & imgIRequest::STATUS_SIZE_AVAILABLE);
|
|
}
|
|
case Tag::ImageSet:
|
|
return FinalImage().IsSizeAvailable();
|
|
case Tag::CrossFade:
|
|
// TODO: Bug 546052 cross-fade not yet implemented.
|
|
return true;
|
|
}
|
|
MOZ_ASSERT_UNREACHABLE("unexpected image type");
|
|
return false;
|
|
}
|
|
|
|
template <>
|
|
void StyleImage::ResolveImage(Document& aDoc, const StyleImage* aOld) {
|
|
if (IsResolved()) {
|
|
return;
|
|
}
|
|
auto* old = aOld ? aOld->GetImageRequestURLValue() : nullptr;
|
|
auto* url = GetImageRequestURLValue();
|
|
// We could avoid this const_cast generating more code but it's not really
|
|
// worth it.
|
|
const_cast<StyleComputedImageUrl*>(url)->ResolveImage(aDoc, old);
|
|
}
|
|
|
|
// --------------------
|
|
// nsStyleImageLayers
|
|
//
|
|
|
|
const nsCSSPropertyID nsStyleImageLayers::kBackgroundLayerTable[] = {
|
|
eCSSProperty_background, // shorthand
|
|
eCSSProperty_background_color, // color
|
|
eCSSProperty_background_image, // image
|
|
eCSSProperty_background_repeat, // repeat
|
|
eCSSProperty_background_position_x, // positionX
|
|
eCSSProperty_background_position_y, // positionY
|
|
eCSSProperty_background_clip, // clip
|
|
eCSSProperty_background_origin, // origin
|
|
eCSSProperty_background_size, // size
|
|
eCSSProperty_background_attachment, // attachment
|
|
eCSSProperty_UNKNOWN, // maskMode
|
|
eCSSProperty_UNKNOWN // composite
|
|
};
|
|
|
|
const nsCSSPropertyID nsStyleImageLayers::kMaskLayerTable[] = {
|
|
eCSSProperty_mask, // shorthand
|
|
eCSSProperty_UNKNOWN, // color
|
|
eCSSProperty_mask_image, // image
|
|
eCSSProperty_mask_repeat, // repeat
|
|
eCSSProperty_mask_position_x, // positionX
|
|
eCSSProperty_mask_position_y, // positionY
|
|
eCSSProperty_mask_clip, // clip
|
|
eCSSProperty_mask_origin, // origin
|
|
eCSSProperty_mask_size, // size
|
|
eCSSProperty_UNKNOWN, // attachment
|
|
eCSSProperty_mask_mode, // maskMode
|
|
eCSSProperty_mask_composite // composite
|
|
};
|
|
|
|
nsStyleImageLayers::nsStyleImageLayers(nsStyleImageLayers::LayerType aType)
|
|
: mAttachmentCount(1),
|
|
mClipCount(1),
|
|
mOriginCount(1),
|
|
mRepeatCount(1),
|
|
mPositionXCount(1),
|
|
mPositionYCount(1),
|
|
mImageCount(1),
|
|
mSizeCount(1),
|
|
mMaskModeCount(1),
|
|
mBlendModeCount(1),
|
|
mCompositeCount(1),
|
|
mLayers(nsStyleAutoArray<Layer>::WITH_SINGLE_INITIAL_ELEMENT) {
|
|
MOZ_COUNT_CTOR(nsStyleImageLayers);
|
|
|
|
// Ensure first layer is initialized as specified layer type
|
|
mLayers[0].Initialize(aType);
|
|
}
|
|
|
|
nsStyleImageLayers::nsStyleImageLayers(const nsStyleImageLayers& aSource)
|
|
: mAttachmentCount(aSource.mAttachmentCount),
|
|
mClipCount(aSource.mClipCount),
|
|
mOriginCount(aSource.mOriginCount),
|
|
mRepeatCount(aSource.mRepeatCount),
|
|
mPositionXCount(aSource.mPositionXCount),
|
|
mPositionYCount(aSource.mPositionYCount),
|
|
mImageCount(aSource.mImageCount),
|
|
mSizeCount(aSource.mSizeCount),
|
|
mMaskModeCount(aSource.mMaskModeCount),
|
|
mBlendModeCount(aSource.mBlendModeCount),
|
|
mCompositeCount(aSource.mCompositeCount),
|
|
mLayers(aSource.mLayers.Clone()) {
|
|
MOZ_COUNT_CTOR(nsStyleImageLayers);
|
|
}
|
|
|
|
static bool AnyLayerIsElementImage(const nsStyleImageLayers& aLayers) {
|
|
NS_FOR_VISIBLE_IMAGE_LAYERS_BACK_TO_FRONT(i, aLayers) {
|
|
if (aLayers.mLayers[i].mImage.FinalImage().IsElement()) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
nsChangeHint nsStyleImageLayers::CalcDifference(
|
|
const nsStyleImageLayers& aNewLayers, LayerType aType) const {
|
|
nsChangeHint hint = nsChangeHint(0);
|
|
|
|
// If the number of visible images changes, then it's easy-peasy.
|
|
if (mImageCount != aNewLayers.mImageCount) {
|
|
hint |= nsChangeHint_RepaintFrame;
|
|
if (aType == nsStyleImageLayers::LayerType::Mask ||
|
|
AnyLayerIsElementImage(*this) || AnyLayerIsElementImage(aNewLayers)) {
|
|
hint |= nsChangeHint_UpdateEffects;
|
|
}
|
|
return hint;
|
|
}
|
|
|
|
const nsStyleImageLayers& moreLayers =
|
|
mLayers.Length() > aNewLayers.mLayers.Length() ? *this : aNewLayers;
|
|
const nsStyleImageLayers& lessLayers =
|
|
mLayers.Length() > aNewLayers.mLayers.Length() ? aNewLayers : *this;
|
|
|
|
for (size_t i = 0; i < moreLayers.mLayers.Length(); ++i) {
|
|
const Layer& moreLayersLayer = moreLayers.mLayers[i];
|
|
if (i < moreLayers.mImageCount) {
|
|
// This is a visible image we're diffing, we may need to repaint.
|
|
const Layer& lessLayersLayer = lessLayers.mLayers[i];
|
|
nsChangeHint layerDifference =
|
|
moreLayersLayer.CalcDifference(lessLayersLayer);
|
|
if (layerDifference &&
|
|
(moreLayersLayer.mImage.FinalImage().IsElement() ||
|
|
lessLayersLayer.mImage.FinalImage().IsElement())) {
|
|
layerDifference |=
|
|
nsChangeHint_UpdateEffects | nsChangeHint_RepaintFrame;
|
|
}
|
|
hint |= layerDifference;
|
|
continue;
|
|
}
|
|
if (hint) {
|
|
// If they're different by now, we're done.
|
|
return hint;
|
|
}
|
|
if (i >= lessLayers.mLayers.Length()) {
|
|
// The layer count differs, we know some property has changed, but if we
|
|
// got here we know it won't affect rendering.
|
|
return nsChangeHint_NeutralChange;
|
|
}
|
|
|
|
const Layer& lessLayersLayer = lessLayers.mLayers[i];
|
|
MOZ_ASSERT(moreLayersLayer.mImage.IsNone());
|
|
MOZ_ASSERT(lessLayersLayer.mImage.IsNone());
|
|
if (moreLayersLayer.CalcDifference(lessLayersLayer)) {
|
|
// We don't care about the difference returned, we know it's not visible,
|
|
// but if something changed, then we need to return the neutral change.
|
|
return nsChangeHint_NeutralChange;
|
|
}
|
|
}
|
|
|
|
if (hint) {
|
|
// If they're different by now, we're done.
|
|
return hint;
|
|
}
|
|
|
|
// We could have same layers and values, but different count still.
|
|
if (mAttachmentCount != aNewLayers.mAttachmentCount ||
|
|
mBlendModeCount != aNewLayers.mBlendModeCount ||
|
|
mClipCount != aNewLayers.mClipCount ||
|
|
mCompositeCount != aNewLayers.mCompositeCount ||
|
|
mMaskModeCount != aNewLayers.mMaskModeCount ||
|
|
mOriginCount != aNewLayers.mOriginCount ||
|
|
mRepeatCount != aNewLayers.mRepeatCount ||
|
|
mPositionXCount != aNewLayers.mPositionXCount ||
|
|
mPositionYCount != aNewLayers.mPositionYCount ||
|
|
mSizeCount != aNewLayers.mSizeCount) {
|
|
hint |= nsChangeHint_NeutralChange;
|
|
}
|
|
|
|
return hint;
|
|
}
|
|
|
|
nsStyleImageLayers& nsStyleImageLayers::operator=(
|
|
const nsStyleImageLayers& aOther) {
|
|
mAttachmentCount = aOther.mAttachmentCount;
|
|
mClipCount = aOther.mClipCount;
|
|
mOriginCount = aOther.mOriginCount;
|
|
mRepeatCount = aOther.mRepeatCount;
|
|
mPositionXCount = aOther.mPositionXCount;
|
|
mPositionYCount = aOther.mPositionYCount;
|
|
mImageCount = aOther.mImageCount;
|
|
mSizeCount = aOther.mSizeCount;
|
|
mMaskModeCount = aOther.mMaskModeCount;
|
|
mBlendModeCount = aOther.mBlendModeCount;
|
|
mCompositeCount = aOther.mCompositeCount;
|
|
mLayers = aOther.mLayers.Clone();
|
|
|
|
return *this;
|
|
}
|
|
|
|
bool nsStyleImageLayers::operator==(const nsStyleImageLayers& aOther) const {
|
|
if (mAttachmentCount != aOther.mAttachmentCount ||
|
|
mClipCount != aOther.mClipCount || mOriginCount != aOther.mOriginCount ||
|
|
mRepeatCount != aOther.mRepeatCount ||
|
|
mPositionXCount != aOther.mPositionXCount ||
|
|
mPositionYCount != aOther.mPositionYCount ||
|
|
mImageCount != aOther.mImageCount || mSizeCount != aOther.mSizeCount ||
|
|
mMaskModeCount != aOther.mMaskModeCount ||
|
|
mBlendModeCount != aOther.mBlendModeCount) {
|
|
return false;
|
|
}
|
|
|
|
if (mLayers.Length() != aOther.mLayers.Length()) {
|
|
return false;
|
|
}
|
|
|
|
for (uint32_t i = 0; i < mLayers.Length(); i++) {
|
|
if (mLayers[i].mPosition != aOther.mLayers[i].mPosition ||
|
|
mLayers[i].mImage != aOther.mLayers[i].mImage ||
|
|
mLayers[i].mSize != aOther.mLayers[i].mSize ||
|
|
mLayers[i].mClip != aOther.mLayers[i].mClip ||
|
|
mLayers[i].mOrigin != aOther.mLayers[i].mOrigin ||
|
|
mLayers[i].mAttachment != aOther.mLayers[i].mAttachment ||
|
|
mLayers[i].mBlendMode != aOther.mLayers[i].mBlendMode ||
|
|
mLayers[i].mComposite != aOther.mLayers[i].mComposite ||
|
|
mLayers[i].mMaskMode != aOther.mLayers[i].mMaskMode ||
|
|
mLayers[i].mRepeat != aOther.mLayers[i].mRepeat) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool SizeDependsOnPositioningAreaSize(const StyleBackgroundSize& aSize,
|
|
const StyleImage& aImage) {
|
|
MOZ_ASSERT(!aImage.IsNone(), "caller should have handled this");
|
|
MOZ_ASSERT(!aImage.IsImageSet(), "caller should have handled this");
|
|
|
|
// Contain and cover straightforwardly depend on frame size.
|
|
if (aSize.IsCover() || aSize.IsContain()) {
|
|
return true;
|
|
}
|
|
|
|
MOZ_ASSERT(aSize.IsExplicitSize());
|
|
auto& size = aSize.explicit_size;
|
|
|
|
// If either dimension contains a non-zero percentage, rendering for that
|
|
// dimension straightforwardly depends on frame size.
|
|
if (size.width.HasPercent() || size.height.HasPercent()) {
|
|
return true;
|
|
}
|
|
|
|
// If both dimensions are fixed lengths, there's no dependency.
|
|
if (!size.width.IsAuto() && !size.height.IsAuto()) {
|
|
return false;
|
|
}
|
|
|
|
// Gradient rendering depends on frame size when auto is involved because
|
|
// gradients have no intrinsic ratio or dimensions, and therefore the relevant
|
|
// dimension is "treat[ed] as 100%".
|
|
if (aImage.IsGradient()) {
|
|
return true;
|
|
}
|
|
|
|
// XXX Element rendering for auto or fixed length doesn't depend on frame size
|
|
// according to the spec. However, we don't implement the spec yet, so
|
|
// for now we bail and say element() plus auto affects ultimate size.
|
|
if (aImage.IsElement()) {
|
|
return true;
|
|
}
|
|
|
|
MOZ_ASSERT(aImage.IsImageRequestType(), "Missed some image");
|
|
if (auto* request = aImage.GetImageRequest()) {
|
|
nsCOMPtr<imgIContainer> imgContainer;
|
|
request->GetImage(getter_AddRefs(imgContainer));
|
|
if (imgContainer) {
|
|
CSSIntSize imageSize;
|
|
AspectRatio imageRatio;
|
|
bool hasWidth, hasHeight;
|
|
nsLayoutUtils::ComputeSizeForDrawing(imgContainer, imageSize, imageRatio,
|
|
hasWidth, hasHeight);
|
|
|
|
// If the image has a fixed width and height, rendering never depends on
|
|
// the frame size.
|
|
if (hasWidth && hasHeight) {
|
|
return false;
|
|
}
|
|
|
|
// If the image has an intrinsic ratio, rendering will depend on frame
|
|
// size when background-size is all auto.
|
|
if (imageRatio) {
|
|
return size.width.IsAuto() == size.height.IsAuto();
|
|
}
|
|
|
|
// Otherwise, rendering depends on frame size when the image dimensions
|
|
// and background-size don't complement each other.
|
|
return !(hasWidth && size.width.IsLengthPercentage()) &&
|
|
!(hasHeight && size.height.IsLengthPercentage());
|
|
}
|
|
}
|
|
|
|
// Passed the gauntlet: no dependency.
|
|
return false;
|
|
}
|
|
|
|
nsStyleImageLayers::Layer::Layer()
|
|
: mImage(StyleImage::None()),
|
|
mSize(StyleBackgroundSize::ExplicitSize(LengthPercentageOrAuto::Auto(),
|
|
LengthPercentageOrAuto::Auto())),
|
|
|
|
mClip(StyleGeometryBox::BorderBox),
|
|
mAttachment(StyleImageLayerAttachment::Scroll),
|
|
mBlendMode(StyleBlend::Normal),
|
|
mComposite(StyleMaskComposite::Add),
|
|
mMaskMode(StyleMaskMode::MatchSource) {}
|
|
|
|
nsStyleImageLayers::Layer::~Layer() = default;
|
|
|
|
void nsStyleImageLayers::Layer::Initialize(
|
|
nsStyleImageLayers::LayerType aType) {
|
|
mRepeat.SetInitialValues();
|
|
|
|
mPosition = Position::FromPercentage(0.);
|
|
|
|
if (aType == LayerType::Background) {
|
|
mOrigin = StyleGeometryBox::PaddingBox;
|
|
} else {
|
|
MOZ_ASSERT(aType == LayerType::Mask, "unsupported layer type.");
|
|
mOrigin = StyleGeometryBox::BorderBox;
|
|
}
|
|
}
|
|
|
|
bool nsStyleImageLayers::Layer::
|
|
RenderingMightDependOnPositioningAreaSizeChange() const {
|
|
// Do we even have an image?
|
|
if (mImage.IsNone()) {
|
|
return false;
|
|
}
|
|
|
|
return mPosition.DependsOnPositioningAreaSize() ||
|
|
SizeDependsOnPositioningAreaSize(mSize, mImage.FinalImage()) ||
|
|
mRepeat.DependsOnPositioningAreaSize();
|
|
}
|
|
|
|
bool nsStyleImageLayers::Layer::operator==(const Layer& aOther) const {
|
|
return mAttachment == aOther.mAttachment && mClip == aOther.mClip &&
|
|
mOrigin == aOther.mOrigin && mRepeat == aOther.mRepeat &&
|
|
mBlendMode == aOther.mBlendMode && mPosition == aOther.mPosition &&
|
|
mSize == aOther.mSize && mImage == aOther.mImage &&
|
|
mMaskMode == aOther.mMaskMode && mComposite == aOther.mComposite;
|
|
}
|
|
|
|
template <class ComputedValueItem>
|
|
static void FillImageLayerList(
|
|
nsStyleAutoArray<nsStyleImageLayers::Layer>& aLayers,
|
|
ComputedValueItem nsStyleImageLayers::Layer::*aResultLocation,
|
|
uint32_t aItemCount, uint32_t aFillCount) {
|
|
MOZ_ASSERT(aFillCount <= aLayers.Length(), "unexpected array length");
|
|
for (uint32_t sourceLayer = 0, destLayer = aItemCount; destLayer < aFillCount;
|
|
++sourceLayer, ++destLayer) {
|
|
aLayers[destLayer].*aResultLocation = aLayers[sourceLayer].*aResultLocation;
|
|
}
|
|
}
|
|
|
|
// The same as FillImageLayerList, but for values stored in
|
|
// layer.mPosition.*aResultLocation instead of layer.*aResultLocation.
|
|
static void FillImageLayerPositionCoordList(
|
|
nsStyleAutoArray<nsStyleImageLayers::Layer>& aLayers,
|
|
LengthPercentage Position::*aResultLocation, uint32_t aItemCount,
|
|
uint32_t aFillCount) {
|
|
MOZ_ASSERT(aFillCount <= aLayers.Length(), "unexpected array length");
|
|
for (uint32_t sourceLayer = 0, destLayer = aItemCount; destLayer < aFillCount;
|
|
++sourceLayer, ++destLayer) {
|
|
aLayers[destLayer].mPosition.*aResultLocation =
|
|
aLayers[sourceLayer].mPosition.*aResultLocation;
|
|
}
|
|
}
|
|
|
|
void nsStyleImageLayers::FillAllLayers(uint32_t aMaxItemCount) {
|
|
// Delete any extra items. We need to keep layers in which any
|
|
// property was specified.
|
|
mLayers.TruncateLengthNonZero(aMaxItemCount);
|
|
|
|
uint32_t fillCount = mImageCount;
|
|
FillImageLayerList(mLayers, &Layer::mImage, mImageCount, fillCount);
|
|
FillImageLayerList(mLayers, &Layer::mRepeat, mRepeatCount, fillCount);
|
|
FillImageLayerList(mLayers, &Layer::mAttachment, mAttachmentCount, fillCount);
|
|
FillImageLayerList(mLayers, &Layer::mClip, mClipCount, fillCount);
|
|
FillImageLayerList(mLayers, &Layer::mBlendMode, mBlendModeCount, fillCount);
|
|
FillImageLayerList(mLayers, &Layer::mOrigin, mOriginCount, fillCount);
|
|
FillImageLayerPositionCoordList(mLayers, &Position::horizontal,
|
|
mPositionXCount, fillCount);
|
|
FillImageLayerPositionCoordList(mLayers, &Position::vertical, mPositionYCount,
|
|
fillCount);
|
|
FillImageLayerList(mLayers, &Layer::mSize, mSizeCount, fillCount);
|
|
FillImageLayerList(mLayers, &Layer::mMaskMode, mMaskModeCount, fillCount);
|
|
FillImageLayerList(mLayers, &Layer::mComposite, mCompositeCount, fillCount);
|
|
}
|
|
|
|
static bool UrlValuesEqual(const StyleImage& aImage,
|
|
const StyleImage& aOtherImage) {
|
|
auto* url = aImage.GetImageRequestURLValue();
|
|
auto* other = aOtherImage.GetImageRequestURLValue();
|
|
return url == other || (url && other && *url == *other);
|
|
}
|
|
|
|
nsChangeHint nsStyleImageLayers::Layer::CalcDifference(
|
|
const nsStyleImageLayers::Layer& aNewLayer) const {
|
|
nsChangeHint hint = nsChangeHint(0);
|
|
if (!UrlValuesEqual(mImage, aNewLayer.mImage)) {
|
|
hint |= nsChangeHint_RepaintFrame | nsChangeHint_UpdateEffects;
|
|
} else if (mAttachment != aNewLayer.mAttachment || mClip != aNewLayer.mClip ||
|
|
mOrigin != aNewLayer.mOrigin || mRepeat != aNewLayer.mRepeat ||
|
|
mBlendMode != aNewLayer.mBlendMode || mSize != aNewLayer.mSize ||
|
|
mImage != aNewLayer.mImage || mMaskMode != aNewLayer.mMaskMode ||
|
|
mComposite != aNewLayer.mComposite) {
|
|
hint |= nsChangeHint_RepaintFrame;
|
|
}
|
|
|
|
if (mPosition != aNewLayer.mPosition) {
|
|
hint |= nsChangeHint_UpdateBackgroundPosition;
|
|
}
|
|
|
|
return hint;
|
|
}
|
|
|
|
// --------------------
|
|
// nsStyleBackground
|
|
//
|
|
|
|
nsStyleBackground::nsStyleBackground(const Document& aDocument)
|
|
: mImage(nsStyleImageLayers::LayerType::Background),
|
|
mBackgroundColor(StyleColor::Transparent()) {
|
|
MOZ_COUNT_CTOR(nsStyleBackground);
|
|
}
|
|
|
|
nsStyleBackground::nsStyleBackground(const nsStyleBackground& aSource)
|
|
: mImage(aSource.mImage), mBackgroundColor(aSource.mBackgroundColor) {
|
|
MOZ_COUNT_CTOR(nsStyleBackground);
|
|
}
|
|
|
|
nsStyleBackground::~nsStyleBackground() { MOZ_COUNT_DTOR(nsStyleBackground); }
|
|
|
|
void nsStyleBackground::TriggerImageLoads(Document& aDocument,
|
|
const nsStyleBackground* aOldStyle) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
mImage.ResolveImages(aDocument, aOldStyle ? &aOldStyle->mImage : nullptr);
|
|
}
|
|
|
|
nsChangeHint nsStyleBackground::CalcDifference(
|
|
const nsStyleBackground& aNewData) const {
|
|
nsChangeHint hint = nsChangeHint(0);
|
|
if (mBackgroundColor != aNewData.mBackgroundColor) {
|
|
hint |= nsChangeHint_RepaintFrame;
|
|
}
|
|
|
|
hint |= mImage.CalcDifference(aNewData.mImage,
|
|
nsStyleImageLayers::LayerType::Background);
|
|
|
|
return hint;
|
|
}
|
|
|
|
bool nsStyleBackground::HasFixedBackground(nsIFrame* aFrame) const {
|
|
NS_FOR_VISIBLE_IMAGE_LAYERS_BACK_TO_FRONT(i, mImage) {
|
|
const nsStyleImageLayers::Layer& layer = mImage.mLayers[i];
|
|
if (layer.mAttachment == StyleImageLayerAttachment::Fixed &&
|
|
!layer.mImage.IsNone() && !nsLayoutUtils::IsTransformed(aFrame)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
nscolor nsStyleBackground::BackgroundColor(const nsIFrame* aFrame) const {
|
|
return mBackgroundColor.CalcColor(aFrame);
|
|
}
|
|
|
|
nscolor nsStyleBackground::BackgroundColor(const ComputedStyle* aStyle) const {
|
|
return mBackgroundColor.CalcColor(*aStyle);
|
|
}
|
|
|
|
bool nsStyleBackground::IsTransparent(const nsIFrame* aFrame) const {
|
|
return IsTransparent(aFrame->Style());
|
|
}
|
|
|
|
bool nsStyleBackground::IsTransparent(mozilla::ComputedStyle* aStyle) const {
|
|
return BottomLayer().mImage.IsNone() && mImage.mImageCount == 1 &&
|
|
NS_GET_A(BackgroundColor(aStyle)) == 0;
|
|
}
|
|
|
|
StyleTransition::StyleTransition(const StyleTransition& aCopy) = default;
|
|
|
|
void StyleTransition::SetInitialValues() {
|
|
mTimingFunction = nsTimingFunction(StyleTimingKeyword::Ease);
|
|
mDuration = 0.0;
|
|
mDelay = 0.0;
|
|
mProperty = eCSSPropertyExtra_all_properties;
|
|
}
|
|
|
|
bool StyleTransition::operator==(const StyleTransition& aOther) const {
|
|
return mTimingFunction == aOther.mTimingFunction &&
|
|
mDuration == aOther.mDuration && mDelay == aOther.mDelay &&
|
|
mProperty == aOther.mProperty &&
|
|
(mProperty != eCSSProperty_UNKNOWN ||
|
|
mUnknownProperty == aOther.mUnknownProperty);
|
|
}
|
|
|
|
StyleAnimation::StyleAnimation(const StyleAnimation& aCopy) = default;
|
|
|
|
void StyleAnimation::SetInitialValues() {
|
|
mTimingFunction = nsTimingFunction(StyleTimingKeyword::Ease);
|
|
mDuration = 0.0;
|
|
mDelay = 0.0;
|
|
mName = nsGkAtoms::_empty;
|
|
mDirection = dom::PlaybackDirection::Normal;
|
|
mFillMode = dom::FillMode::None;
|
|
mPlayState = StyleAnimationPlayState::Running;
|
|
mIterationCount = 1.0f;
|
|
}
|
|
|
|
bool StyleAnimation::operator==(const StyleAnimation& aOther) const {
|
|
return mTimingFunction == aOther.mTimingFunction &&
|
|
mDuration == aOther.mDuration && mDelay == aOther.mDelay &&
|
|
mName == aOther.mName && mDirection == aOther.mDirection &&
|
|
mFillMode == aOther.mFillMode && mPlayState == aOther.mPlayState &&
|
|
mIterationCount == aOther.mIterationCount;
|
|
}
|
|
|
|
// --------------------
|
|
// nsStyleDisplay
|
|
//
|
|
nsStyleDisplay::nsStyleDisplay(const Document& aDocument)
|
|
: mTransitions(
|
|
nsStyleAutoArray<StyleTransition>::WITH_SINGLE_INITIAL_ELEMENT),
|
|
mTransitionTimingFunctionCount(1),
|
|
mTransitionDurationCount(1),
|
|
mTransitionDelayCount(1),
|
|
mTransitionPropertyCount(1),
|
|
mAnimations(
|
|
nsStyleAutoArray<StyleAnimation>::WITH_SINGLE_INITIAL_ELEMENT),
|
|
mAnimationTimingFunctionCount(1),
|
|
mAnimationDurationCount(1),
|
|
mAnimationDelayCount(1),
|
|
mAnimationNameCount(1),
|
|
mAnimationDirectionCount(1),
|
|
mAnimationFillModeCount(1),
|
|
mAnimationPlayStateCount(1),
|
|
mAnimationIterationCountCount(1),
|
|
mWillChange{{}, {0}},
|
|
mDisplay(StyleDisplay::Inline),
|
|
mOriginalDisplay(StyleDisplay::Inline),
|
|
mContain(StyleContain::NONE),
|
|
mAppearance(StyleAppearance::None),
|
|
mDefaultAppearance(StyleAppearance::None),
|
|
mPosition(StylePositionProperty::Static),
|
|
mFloat(StyleFloat::None),
|
|
mBreakType(StyleClear::None),
|
|
mBreakInside(StyleBreakWithin::Auto),
|
|
mBreakBefore(StyleBreakBetween::Auto),
|
|
mBreakAfter(StyleBreakBetween::Auto),
|
|
mOverflowX(StyleOverflow::Visible),
|
|
mOverflowY(StyleOverflow::Visible),
|
|
mOverflowClipBoxBlock(StyleOverflowClipBox::PaddingBox),
|
|
mOverflowClipBoxInline(StyleOverflowClipBox::PaddingBox),
|
|
mResize(StyleResize::None),
|
|
mOrient(StyleOrient::Inline),
|
|
mIsolation(StyleIsolation::Auto),
|
|
mTopLayer(StyleTopLayer::None),
|
|
mTouchAction(StyleTouchAction::AUTO),
|
|
mScrollBehavior(StyleScrollBehavior::Auto),
|
|
mOverscrollBehaviorX(StyleOverscrollBehavior::Auto),
|
|
mOverscrollBehaviorY(StyleOverscrollBehavior::Auto),
|
|
mOverflowAnchor(StyleOverflowAnchor::Auto),
|
|
mScrollSnapAlign{StyleScrollSnapAlignKeyword::None,
|
|
StyleScrollSnapAlignKeyword::None},
|
|
mScrollSnapType{StyleScrollSnapAxis::Both,
|
|
StyleScrollSnapStrictness::None},
|
|
mLineClamp(0),
|
|
mRotate(StyleRotate::None()),
|
|
mTranslate(StyleTranslate::None()),
|
|
mScale(StyleScale::None()),
|
|
mBackfaceVisibility(StyleBackfaceVisibility::Visible),
|
|
mTransformStyle(StyleTransformStyle::Flat),
|
|
mTransformBox(StyleGeometryBox::BorderBox),
|
|
mOffsetPath(StyleOffsetPath::None()),
|
|
mOffsetDistance(LengthPercentage::Zero()),
|
|
mOffsetRotate{true, StyleAngle{0.0}},
|
|
mOffsetAnchor(StylePositionOrAuto::Auto()),
|
|
mTransformOrigin{LengthPercentage::FromPercentage(0.5),
|
|
LengthPercentage::FromPercentage(0.5),
|
|
{0.}},
|
|
mChildPerspective(StylePerspective::None()),
|
|
mPerspectiveOrigin(Position::FromPercentage(0.5f)),
|
|
mVerticalAlign(
|
|
StyleVerticalAlign::Keyword(StyleVerticalAlignKeyword::Baseline)),
|
|
mShapeMargin(LengthPercentage::Zero()),
|
|
mShapeOutside(StyleShapeOutside::None()) {
|
|
MOZ_COUNT_CTOR(nsStyleDisplay);
|
|
|
|
mTransitions[0].SetInitialValues();
|
|
mAnimations[0].SetInitialValues();
|
|
}
|
|
|
|
nsStyleDisplay::nsStyleDisplay(const nsStyleDisplay& aSource)
|
|
: mTransitions(aSource.mTransitions.Clone()),
|
|
mTransitionTimingFunctionCount(aSource.mTransitionTimingFunctionCount),
|
|
mTransitionDurationCount(aSource.mTransitionDurationCount),
|
|
mTransitionDelayCount(aSource.mTransitionDelayCount),
|
|
mTransitionPropertyCount(aSource.mTransitionPropertyCount),
|
|
mAnimations(aSource.mAnimations.Clone()),
|
|
mAnimationTimingFunctionCount(aSource.mAnimationTimingFunctionCount),
|
|
mAnimationDurationCount(aSource.mAnimationDurationCount),
|
|
mAnimationDelayCount(aSource.mAnimationDelayCount),
|
|
mAnimationNameCount(aSource.mAnimationNameCount),
|
|
mAnimationDirectionCount(aSource.mAnimationDirectionCount),
|
|
mAnimationFillModeCount(aSource.mAnimationFillModeCount),
|
|
mAnimationPlayStateCount(aSource.mAnimationPlayStateCount),
|
|
mAnimationIterationCountCount(aSource.mAnimationIterationCountCount),
|
|
mWillChange(aSource.mWillChange),
|
|
mDisplay(aSource.mDisplay),
|
|
mOriginalDisplay(aSource.mOriginalDisplay),
|
|
mContain(aSource.mContain),
|
|
mAppearance(aSource.mAppearance),
|
|
mDefaultAppearance(aSource.mDefaultAppearance),
|
|
mPosition(aSource.mPosition),
|
|
mFloat(aSource.mFloat),
|
|
mBreakType(aSource.mBreakType),
|
|
mBreakInside(aSource.mBreakInside),
|
|
mBreakBefore(aSource.mBreakBefore),
|
|
mBreakAfter(aSource.mBreakAfter),
|
|
mOverflowX(aSource.mOverflowX),
|
|
mOverflowY(aSource.mOverflowY),
|
|
mOverflowClipBoxBlock(aSource.mOverflowClipBoxBlock),
|
|
mOverflowClipBoxInline(aSource.mOverflowClipBoxInline),
|
|
mResize(aSource.mResize),
|
|
mOrient(aSource.mOrient),
|
|
mIsolation(aSource.mIsolation),
|
|
mTopLayer(aSource.mTopLayer),
|
|
mTouchAction(aSource.mTouchAction),
|
|
mScrollBehavior(aSource.mScrollBehavior),
|
|
mOverscrollBehaviorX(aSource.mOverscrollBehaviorX),
|
|
mOverscrollBehaviorY(aSource.mOverscrollBehaviorY),
|
|
mOverflowAnchor(aSource.mOverflowAnchor),
|
|
mScrollSnapAlign(aSource.mScrollSnapAlign),
|
|
mScrollSnapType(aSource.mScrollSnapType),
|
|
mLineClamp(aSource.mLineClamp),
|
|
mTransform(aSource.mTransform),
|
|
mRotate(aSource.mRotate),
|
|
mTranslate(aSource.mTranslate),
|
|
mScale(aSource.mScale),
|
|
mBackfaceVisibility(aSource.mBackfaceVisibility),
|
|
mTransformStyle(aSource.mTransformStyle),
|
|
mTransformBox(aSource.mTransformBox),
|
|
mOffsetPath(aSource.mOffsetPath),
|
|
mOffsetDistance(aSource.mOffsetDistance),
|
|
mOffsetRotate(aSource.mOffsetRotate),
|
|
mOffsetAnchor(aSource.mOffsetAnchor),
|
|
mTransformOrigin(aSource.mTransformOrigin),
|
|
mChildPerspective(aSource.mChildPerspective),
|
|
mPerspectiveOrigin(aSource.mPerspectiveOrigin),
|
|
mVerticalAlign(aSource.mVerticalAlign),
|
|
mShapeImageThreshold(aSource.mShapeImageThreshold),
|
|
mShapeMargin(aSource.mShapeMargin),
|
|
mShapeOutside(aSource.mShapeOutside) {
|
|
MOZ_COUNT_CTOR(nsStyleDisplay);
|
|
}
|
|
|
|
nsStyleDisplay::~nsStyleDisplay() { MOZ_COUNT_DTOR(nsStyleDisplay); }
|
|
|
|
void nsStyleDisplay::TriggerImageLoads(Document& aDocument,
|
|
const nsStyleDisplay* aOldStyle) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
if (mShapeOutside.IsImage()) {
|
|
auto* old = aOldStyle && aOldStyle->mShapeOutside.IsImage()
|
|
? &aOldStyle->mShapeOutside.AsImage()
|
|
: nullptr;
|
|
// Const-cast is ugly but legit, we could avoid it by generating mut-casts
|
|
// with cbindgen.
|
|
const_cast<StyleImage&>(mShapeOutside.AsImage())
|
|
.ResolveImage(aDocument, old);
|
|
}
|
|
}
|
|
|
|
template <typename TransformLike>
|
|
static inline nsChangeHint CompareTransformValues(
|
|
const TransformLike& aOldTransform, const TransformLike& aNewTransform) {
|
|
nsChangeHint result = nsChangeHint(0);
|
|
|
|
// Note: If we add a new change hint for transform changes here, we have to
|
|
// modify KeyframeEffect::CalculateCumulativeChangeHint too!
|
|
if (aOldTransform != aNewTransform) {
|
|
result |= nsChangeHint_UpdateTransformLayer;
|
|
if (!aOldTransform.IsNone() && !aNewTransform.IsNone()) {
|
|
result |= nsChangeHint_UpdatePostTransformOverflow;
|
|
} else {
|
|
result |= nsChangeHint_UpdateOverflow;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static inline nsChangeHint CompareMotionValues(
|
|
const nsStyleDisplay& aDisplay, const nsStyleDisplay& aNewDisplay) {
|
|
if (aDisplay.mOffsetPath == aNewDisplay.mOffsetPath) {
|
|
if (aDisplay.mOffsetDistance == aNewDisplay.mOffsetDistance &&
|
|
aDisplay.mOffsetRotate == aNewDisplay.mOffsetRotate &&
|
|
aDisplay.mOffsetAnchor == aNewDisplay.mOffsetAnchor) {
|
|
return nsChangeHint(0);
|
|
}
|
|
|
|
if (aDisplay.mOffsetPath.IsNone()) {
|
|
return nsChangeHint_NeutralChange;
|
|
}
|
|
}
|
|
|
|
// TODO: Bug 1482737: This probably doesn't need to UpdateOverflow
|
|
// (or UpdateTransformLayer) if there's already a transform.
|
|
// Set the same hints as what we use for transform because motion path is
|
|
// a kind of transform and will be combined with other transforms.
|
|
nsChangeHint result = nsChangeHint_UpdateTransformLayer;
|
|
if (!aDisplay.mOffsetPath.IsNone() && !aNewDisplay.mOffsetPath.IsNone()) {
|
|
result |= nsChangeHint_UpdatePostTransformOverflow;
|
|
} else {
|
|
result |= nsChangeHint_UpdateOverflow;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static bool ScrollbarGenerationChanged(const nsStyleDisplay& aOld,
|
|
const nsStyleDisplay& aNew) {
|
|
auto changed = [](StyleOverflow aOld, StyleOverflow aNew) {
|
|
return aOld != aNew &&
|
|
(aOld == StyleOverflow::Hidden || aNew == StyleOverflow::Hidden);
|
|
};
|
|
return changed(aOld.mOverflowX, aNew.mOverflowX) ||
|
|
changed(aOld.mOverflowY, aNew.mOverflowY);
|
|
}
|
|
|
|
nsChangeHint nsStyleDisplay::CalcDifference(
|
|
const nsStyleDisplay& aNewData, const nsStylePosition& aOldPosition) const {
|
|
if (mDisplay != aNewData.mDisplay || mContain != aNewData.mContain ||
|
|
(mFloat == StyleFloat::None) != (aNewData.mFloat == StyleFloat::None) ||
|
|
mScrollBehavior != aNewData.mScrollBehavior ||
|
|
mScrollSnapType != aNewData.mScrollSnapType ||
|
|
mTopLayer != aNewData.mTopLayer || mResize != aNewData.mResize) {
|
|
return nsChangeHint_ReconstructFrame;
|
|
}
|
|
|
|
auto oldAppearance = EffectiveAppearance();
|
|
auto newAppearance = aNewData.EffectiveAppearance();
|
|
|
|
if ((oldAppearance == StyleAppearance::Textfield &&
|
|
newAppearance != StyleAppearance::Textfield) ||
|
|
(oldAppearance != StyleAppearance::Textfield &&
|
|
newAppearance == StyleAppearance::Textfield)) {
|
|
// This is for <input type=number> where we allow authors to specify a
|
|
// |-moz-appearance:textfield| to get a control without a spinner. (The
|
|
// spinner is present for |-moz-appearance:number-input| but also other
|
|
// values such as 'none'.) We need to reframe since we want to use
|
|
// nsTextControlFrame instead of nsNumberControlFrame if the author
|
|
// specifies 'textfield'.
|
|
return nsChangeHint_ReconstructFrame;
|
|
}
|
|
|
|
auto hint = nsChangeHint(0);
|
|
if (mPosition != aNewData.mPosition) {
|
|
if (IsAbsolutelyPositionedStyle() ||
|
|
aNewData.IsAbsolutelyPositionedStyle()) {
|
|
// This changes our parent relationship on the frame tree and / or needs
|
|
// to create a placeholder, so gotta reframe. There are some cases (when
|
|
// switching from fixed to absolute or viceversa, if our containing block
|
|
// happens to remain the same, i.e., if it has a transform or such) where
|
|
// this wouldn't really be needed (though we'd still need to move the
|
|
// frame from one child list to another). In any case we don't have a hand
|
|
// to that information from here, and it doesn't seem like a case worth
|
|
// optimizing for.
|
|
return nsChangeHint_ReconstructFrame;
|
|
}
|
|
// We start or stop being a containing block for abspos descendants. This
|
|
// also causes painting to change, as we'd become a pseudo-stacking context.
|
|
if (IsRelativelyPositionedStyle() !=
|
|
aNewData.IsRelativelyPositionedStyle()) {
|
|
hint |= nsChangeHint_UpdateContainingBlock | nsChangeHint_RepaintFrame;
|
|
}
|
|
if (IsPositionForcingStackingContext() !=
|
|
aNewData.IsPositionForcingStackingContext()) {
|
|
hint |= nsChangeHint_RepaintFrame;
|
|
}
|
|
// On top of that: if the above ends up not reframing, we need a reflow to
|
|
// compute our relative, static or sticky position.
|
|
hint |= nsChangeHint_NeedReflow | nsChangeHint_ReflowChangesSizeOrPosition;
|
|
}
|
|
|
|
if (mScrollSnapAlign != aNewData.mScrollSnapAlign) {
|
|
// FIXME: Bug 1530253 Support re-snapping when scroll-snap-align changes.
|
|
hint |= nsChangeHint_NeutralChange;
|
|
}
|
|
|
|
if (mOverflowX != aNewData.mOverflowX || mOverflowY != aNewData.mOverflowY) {
|
|
const bool isScrollable = IsScrollableOverflow();
|
|
if (isScrollable != aNewData.IsScrollableOverflow()) {
|
|
// We may need to construct or destroy a scroll frame as a result of this
|
|
// change.
|
|
hint |= nsChangeHint_ScrollbarChange;
|
|
} else if (isScrollable) {
|
|
if (ScrollbarGenerationChanged(*this, aNewData)) {
|
|
// We need to reframe in the case of hidden -> non-hidden case though,
|
|
// since ScrollFrameHelper::CreateAnonymousContent avoids creating
|
|
// scrollbars altogether for overflow: hidden. That seems it could
|
|
// create some interesting perf cliffs...
|
|
//
|
|
// We reframe when non-hidden -> hidden too, for now.
|
|
//
|
|
// FIXME(bug 1590247): Seems we could avoid reframing once we've created
|
|
// scrollbars, which should get us the optimization for elements that
|
|
// have toggled scrollbars, but would prevent the cliff of toggling
|
|
// overflow causing jank.
|
|
hint |= nsChangeHint_ScrollbarChange;
|
|
} else {
|
|
// Otherwise, for changes where both overflow values are scrollable,
|
|
// means that scrollbars may appear or disappear. We need to reflow,
|
|
// since reflow is what determines which scrollbars if any are visible.
|
|
hint |= nsChangeHint_ReflowHintsForScrollbarChange;
|
|
}
|
|
} else {
|
|
// Otherwise this is a change between 'visible' and 'clip'.
|
|
// Here only whether we have a 'clip' changes, so just repaint and
|
|
// update our overflow areas in that case.
|
|
hint |= nsChangeHint_UpdateOverflow | nsChangeHint_RepaintFrame;
|
|
}
|
|
}
|
|
|
|
/* Note: When mScrollBehavior or mScrollSnapType are changed,
|
|
* nsChangeHint_NeutralChange is not sufficient to enter
|
|
* nsCSSFrameConstructor::PropagateScrollToViewport. By using the same hint as
|
|
* used when the overflow css property changes, nsChangeHint_ReconstructFrame,
|
|
* PropagateScrollToViewport will be called.
|
|
*
|
|
* The scroll-behavior css property is not expected to change often (the
|
|
* CSSOM-View DOM methods are likely to be used in those cases); however,
|
|
* if this does become common perhaps a faster-path might be worth while.
|
|
*
|
|
* FIXME(emilio): Can we do what we do for overflow changes?
|
|
*
|
|
* FIXME(emilio): These properties no longer propagate from the body to the
|
|
* viewport.
|
|
*/
|
|
|
|
if (mFloat != aNewData.mFloat) {
|
|
// Changing which side we're floating on (float:none was handled above).
|
|
hint |= nsChangeHint_ReflowHintsForFloatAreaChange;
|
|
}
|
|
|
|
if (mShapeOutside != aNewData.mShapeOutside ||
|
|
mShapeMargin != aNewData.mShapeMargin ||
|
|
mShapeImageThreshold != aNewData.mShapeImageThreshold) {
|
|
if (aNewData.mFloat != StyleFloat::None) {
|
|
// If we are floating, and our shape-outside, shape-margin, or
|
|
// shape-image-threshold are changed, our descendants are not impacted,
|
|
// but our ancestor and siblings are.
|
|
hint |= nsChangeHint_ReflowHintsForFloatAreaChange;
|
|
} else {
|
|
// shape-outside or shape-margin or shape-image-threshold changed,
|
|
// but we don't need to reflow because we're not floating.
|
|
hint |= nsChangeHint_NeutralChange;
|
|
}
|
|
}
|
|
|
|
if (mLineClamp != aNewData.mLineClamp) {
|
|
hint |= NS_STYLE_HINT_REFLOW;
|
|
}
|
|
|
|
if (mVerticalAlign != aNewData.mVerticalAlign) {
|
|
// XXX Can this just be AllReflowHints + RepaintFrame, and be included in
|
|
// the block below?
|
|
hint |= NS_STYLE_HINT_REFLOW;
|
|
}
|
|
|
|
// XXX the following is conservative, for now: changing float breaking
|
|
// shouldn't necessarily require a repaint, reflow should suffice.
|
|
//
|
|
// FIXME(emilio): We definitely change the frame tree in nsCSSFrameConstructor
|
|
// based on break-before / break-after... Shouldn't that reframe?
|
|
if (mBreakType != aNewData.mBreakType ||
|
|
mBreakInside != aNewData.mBreakInside ||
|
|
mBreakBefore != aNewData.mBreakBefore ||
|
|
mBreakAfter != aNewData.mBreakAfter ||
|
|
mAppearance != aNewData.mAppearance ||
|
|
mDefaultAppearance != aNewData.mDefaultAppearance ||
|
|
mOrient != aNewData.mOrient ||
|
|
mOverflowClipBoxBlock != aNewData.mOverflowClipBoxBlock ||
|
|
mOverflowClipBoxInline != aNewData.mOverflowClipBoxInline) {
|
|
hint |= nsChangeHint_AllReflowHints | nsChangeHint_RepaintFrame;
|
|
}
|
|
|
|
if (mIsolation != aNewData.mIsolation) {
|
|
hint |= nsChangeHint_RepaintFrame;
|
|
}
|
|
|
|
/* If we've added or removed the transform property, we need to reconstruct
|
|
* the frame to add or remove the view object, and also to handle abs-pos and
|
|
* fixed-pos containers.
|
|
*/
|
|
if (HasTransformStyle() != aNewData.HasTransformStyle()) {
|
|
hint |= nsChangeHint_ComprehensiveAddOrRemoveTransform;
|
|
} else {
|
|
/* Otherwise, if we've kept the property lying around and we already had a
|
|
* transform, we need to see whether or not we've changed the transform.
|
|
* If so, we need to recompute its overflow rect (which probably changed
|
|
* if the transform changed) and to redraw within the bounds of that new
|
|
* overflow rect.
|
|
*
|
|
* If the property isn't present in either style struct, we still do the
|
|
* comparisons but turn all the resulting change hints into
|
|
* nsChangeHint_NeutralChange.
|
|
*/
|
|
nsChangeHint transformHint = nsChangeHint(0);
|
|
|
|
transformHint |= CompareTransformValues(mTransform, aNewData.mTransform);
|
|
transformHint |= CompareTransformValues(mRotate, aNewData.mRotate);
|
|
transformHint |= CompareTransformValues(mTranslate, aNewData.mTranslate);
|
|
transformHint |= CompareTransformValues(mScale, aNewData.mScale);
|
|
transformHint |= CompareMotionValues(*this, aNewData);
|
|
|
|
if (mTransformOrigin != aNewData.mTransformOrigin) {
|
|
transformHint |= nsChangeHint_UpdateTransformLayer |
|
|
nsChangeHint_UpdatePostTransformOverflow;
|
|
}
|
|
|
|
if (mPerspectiveOrigin != aNewData.mPerspectiveOrigin ||
|
|
mTransformStyle != aNewData.mTransformStyle ||
|
|
mTransformBox != aNewData.mTransformBox) {
|
|
transformHint |= nsChangeHint_UpdateOverflow | nsChangeHint_RepaintFrame;
|
|
}
|
|
|
|
if (mBackfaceVisibility != aNewData.mBackfaceVisibility) {
|
|
transformHint |= nsChangeHint_RepaintFrame;
|
|
}
|
|
|
|
if (transformHint) {
|
|
if (HasTransformStyle()) {
|
|
hint |= transformHint;
|
|
} else {
|
|
hint |= nsChangeHint_NeutralChange;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (HasPerspectiveStyle() != aNewData.HasPerspectiveStyle()) {
|
|
// A change from/to being a containing block for position:fixed.
|
|
hint |= nsChangeHint_UpdateContainingBlock | nsChangeHint_UpdateOverflow |
|
|
nsChangeHint_RepaintFrame;
|
|
} else if (mChildPerspective != aNewData.mChildPerspective) {
|
|
hint |= nsChangeHint_UpdateOverflow | nsChangeHint_RepaintFrame;
|
|
}
|
|
|
|
// Note that the HasTransformStyle() != aNewData.HasTransformStyle()
|
|
// test above handles relevant changes in the StyleWillChangeBit_TRANSFORM
|
|
// bit, which in turn handles frame reconstruction for changes in the
|
|
// containing block of fixed-positioned elements.
|
|
auto willChangeBitsChanged = mWillChange.bits ^ aNewData.mWillChange.bits;
|
|
|
|
if (willChangeBitsChanged &
|
|
(StyleWillChangeBits::STACKING_CONTEXT | StyleWillChangeBits::SCROLL |
|
|
StyleWillChangeBits::OPACITY)) {
|
|
hint |= nsChangeHint_RepaintFrame;
|
|
}
|
|
|
|
if (willChangeBitsChanged &
|
|
(StyleWillChangeBits::FIXPOS_CB | StyleWillChangeBits::ABSPOS_CB)) {
|
|
hint |= nsChangeHint_UpdateContainingBlock;
|
|
}
|
|
|
|
// If touch-action is changed, we need to regenerate the event regions on
|
|
// the layers and send it over to the compositor for APZ to handle.
|
|
if (mTouchAction != aNewData.mTouchAction) {
|
|
hint |= nsChangeHint_RepaintFrame;
|
|
}
|
|
|
|
// If overscroll-behavior has changed, the changes are picked up
|
|
// during a repaint.
|
|
if (mOverscrollBehaviorX != aNewData.mOverscrollBehaviorX ||
|
|
mOverscrollBehaviorY != aNewData.mOverscrollBehaviorY) {
|
|
hint |= nsChangeHint_SchedulePaint;
|
|
}
|
|
|
|
if (mOriginalDisplay != aNewData.mOriginalDisplay) {
|
|
// Our hypothetical box position may have changed.
|
|
//
|
|
// Note that it doesn't matter if we look at the old or the new struct,
|
|
// since a change on whether we need a hypothetical position would trigger
|
|
// reflow anyway.
|
|
if (IsAbsolutelyPositionedStyle() &&
|
|
aOldPosition.NeedsHypotheticalPositionIfAbsPos()) {
|
|
hint |=
|
|
nsChangeHint_NeedReflow | nsChangeHint_ReflowChangesSizeOrPosition;
|
|
} else {
|
|
hint |= nsChangeHint_NeutralChange;
|
|
}
|
|
}
|
|
|
|
// Note: Our current behavior for handling changes to the
|
|
// transition-duration, transition-delay, and transition-timing-function
|
|
// properties is to do nothing. In other words, the transition
|
|
// property that matters is what it is when the transition begins, and
|
|
// we don't stop a transition later because the transition property
|
|
// changed.
|
|
// We do handle changes to transition-property, but we don't need to
|
|
// bother with anything here, since the transition manager is notified
|
|
// of any ComputedStyle change anyway.
|
|
|
|
// Note: Likewise, for animation-*, the animation manager gets
|
|
// notified about every new ComputedStyle constructed, and it uses
|
|
// that opportunity to handle dynamic changes appropriately.
|
|
|
|
// But we still need to return nsChangeHint_NeutralChange for these
|
|
// properties, since some data did change in the style struct.
|
|
|
|
if (!hint && (mTransitions != aNewData.mTransitions ||
|
|
mTransitionTimingFunctionCount !=
|
|
aNewData.mTransitionTimingFunctionCount ||
|
|
mTransitionDurationCount != aNewData.mTransitionDurationCount ||
|
|
mTransitionDelayCount != aNewData.mTransitionDelayCount ||
|
|
mTransitionPropertyCount != aNewData.mTransitionPropertyCount ||
|
|
mAnimations != aNewData.mAnimations ||
|
|
mAnimationTimingFunctionCount !=
|
|
aNewData.mAnimationTimingFunctionCount ||
|
|
mAnimationDurationCount != aNewData.mAnimationDurationCount ||
|
|
mAnimationDelayCount != aNewData.mAnimationDelayCount ||
|
|
mAnimationNameCount != aNewData.mAnimationNameCount ||
|
|
mAnimationDirectionCount != aNewData.mAnimationDirectionCount ||
|
|
mAnimationFillModeCount != aNewData.mAnimationFillModeCount ||
|
|
mAnimationPlayStateCount != aNewData.mAnimationPlayStateCount ||
|
|
mAnimationIterationCountCount !=
|
|
aNewData.mAnimationIterationCountCount ||
|
|
mWillChange != aNewData.mWillChange ||
|
|
mOverflowAnchor != aNewData.mOverflowAnchor)) {
|
|
hint |= nsChangeHint_NeutralChange;
|
|
}
|
|
|
|
return hint;
|
|
}
|
|
|
|
// --------------------
|
|
// nsStyleVisibility
|
|
//
|
|
|
|
nsStyleVisibility::nsStyleVisibility(const Document& aDocument)
|
|
: mImageOrientation(
|
|
StaticPrefs::layout_css_image_orientation_initial_from_image()
|
|
? StyleImageOrientation::FromImage
|
|
: StyleImageOrientation::None),
|
|
mDirection(aDocument.GetBidiOptions() == IBMBIDI_TEXTDIRECTION_RTL
|
|
? StyleDirection::Rtl
|
|
: StyleDirection::Ltr),
|
|
mVisible(StyleVisibility::Visible),
|
|
mImageRendering(StyleImageRendering::Auto),
|
|
mWritingMode(StyleWritingModeProperty::HorizontalTb),
|
|
mTextOrientation(StyleTextOrientation::Mixed),
|
|
mColorAdjust(StyleColorAdjust::Economy) {
|
|
MOZ_COUNT_CTOR(nsStyleVisibility);
|
|
}
|
|
|
|
nsStyleVisibility::nsStyleVisibility(const nsStyleVisibility& aSource)
|
|
: mImageOrientation(aSource.mImageOrientation),
|
|
mDirection(aSource.mDirection),
|
|
mVisible(aSource.mVisible),
|
|
mImageRendering(aSource.mImageRendering),
|
|
mWritingMode(aSource.mWritingMode),
|
|
mTextOrientation(aSource.mTextOrientation),
|
|
mColorAdjust(aSource.mColorAdjust) {
|
|
MOZ_COUNT_CTOR(nsStyleVisibility);
|
|
}
|
|
|
|
nsChangeHint nsStyleVisibility::CalcDifference(
|
|
const nsStyleVisibility& aNewData) const {
|
|
nsChangeHint hint = nsChangeHint(0);
|
|
|
|
if (mDirection != aNewData.mDirection ||
|
|
mWritingMode != aNewData.mWritingMode) {
|
|
// It's important that a change in mWritingMode results in frame
|
|
// reconstruction, because it may affect intrinsic size (see
|
|
// nsSubDocumentFrame::GetIntrinsicISize/BSize).
|
|
// Also, the used writing-mode value is now a field on nsIFrame and some
|
|
// classes (e.g. table rows/cells) copy their value from an ancestor.
|
|
hint |= nsChangeHint_ReconstructFrame;
|
|
} else {
|
|
if ((mImageOrientation != aNewData.mImageOrientation)) {
|
|
hint |= nsChangeHint_AllReflowHints | nsChangeHint_RepaintFrame;
|
|
}
|
|
if (mVisible != aNewData.mVisible) {
|
|
if (mVisible == StyleVisibility::Visible ||
|
|
aNewData.mVisible == StyleVisibility::Visible) {
|
|
hint |= nsChangeHint_VisibilityChange;
|
|
}
|
|
if (StyleVisibility::Collapse == mVisible ||
|
|
StyleVisibility::Collapse == aNewData.mVisible) {
|
|
hint |= NS_STYLE_HINT_REFLOW;
|
|
} else {
|
|
hint |= NS_STYLE_HINT_VISUAL;
|
|
}
|
|
}
|
|
if (mTextOrientation != aNewData.mTextOrientation) {
|
|
hint |= NS_STYLE_HINT_REFLOW;
|
|
}
|
|
if (mImageRendering != aNewData.mImageRendering) {
|
|
hint |= nsChangeHint_RepaintFrame;
|
|
}
|
|
if (mColorAdjust != aNewData.mColorAdjust) {
|
|
// color-adjust only affects media where dynamic changes can't happen.
|
|
hint |= nsChangeHint_NeutralChange;
|
|
}
|
|
}
|
|
return hint;
|
|
}
|
|
|
|
//-----------------------
|
|
// nsStyleContent
|
|
//
|
|
|
|
nsStyleContent::nsStyleContent(const Document& aDocument)
|
|
: mContent(StyleContent::Normal()) {
|
|
MOZ_COUNT_CTOR(nsStyleContent);
|
|
}
|
|
|
|
nsStyleContent::~nsStyleContent() { MOZ_COUNT_DTOR(nsStyleContent); }
|
|
|
|
nsStyleContent::nsStyleContent(const nsStyleContent& aSource)
|
|
: mContent(aSource.mContent),
|
|
mCounterIncrement(aSource.mCounterIncrement),
|
|
mCounterReset(aSource.mCounterReset),
|
|
mCounterSet(aSource.mCounterSet) {
|
|
MOZ_COUNT_CTOR(nsStyleContent);
|
|
}
|
|
|
|
nsChangeHint nsStyleContent::CalcDifference(
|
|
const nsStyleContent& aNewData) const {
|
|
// Unfortunately we need to reframe even if the content lengths are the same;
|
|
// a simple reflow will not pick up different text or different image URLs,
|
|
// since we set all that up in the CSSFrameConstructor
|
|
if (mContent != aNewData.mContent ||
|
|
mCounterIncrement != aNewData.mCounterIncrement ||
|
|
mCounterReset != aNewData.mCounterReset ||
|
|
mCounterSet != aNewData.mCounterSet) {
|
|
return nsChangeHint_ReconstructFrame;
|
|
}
|
|
|
|
return nsChangeHint(0);
|
|
}
|
|
|
|
void nsStyleContent::TriggerImageLoads(Document& aDoc,
|
|
const nsStyleContent* aOld) {
|
|
if (!mContent.IsItems()) {
|
|
return;
|
|
}
|
|
|
|
Span<const StyleContentItem> oldItems;
|
|
if (aOld && aOld->mContent.IsItems()) {
|
|
oldItems = aOld->mContent.AsItems().AsSpan();
|
|
}
|
|
|
|
auto items = mContent.AsItems().AsSpan();
|
|
|
|
for (size_t i = 0; i < items.Length(); ++i) {
|
|
auto& item = items[i];
|
|
if (!item.IsImage()) {
|
|
continue;
|
|
}
|
|
auto& image = item.AsImage();
|
|
auto* oldImage = i < oldItems.Length() && oldItems[i].IsImage()
|
|
? &oldItems[i].AsImage()
|
|
: nullptr;
|
|
const_cast<StyleImage&>(image).ResolveImage(aDoc, oldImage);
|
|
}
|
|
}
|
|
|
|
// --------------------
|
|
// nsStyleTextReset
|
|
//
|
|
|
|
nsStyleTextReset::nsStyleTextReset(const Document& aDocument)
|
|
: mTextOverflow(),
|
|
mTextDecorationLine(StyleTextDecorationLine::NONE),
|
|
mTextDecorationStyle(NS_STYLE_TEXT_DECORATION_STYLE_SOLID),
|
|
mUnicodeBidi(NS_STYLE_UNICODE_BIDI_NORMAL),
|
|
mInitialLetterSink(0),
|
|
mInitialLetterSize(0.0f),
|
|
mTextDecorationColor(StyleColor::CurrentColor()),
|
|
mTextDecorationThickness(StyleTextDecorationLength::Auto()) {
|
|
MOZ_COUNT_CTOR(nsStyleTextReset);
|
|
}
|
|
|
|
nsStyleTextReset::nsStyleTextReset(const nsStyleTextReset& aSource)
|
|
: mTextOverflow(aSource.mTextOverflow),
|
|
mTextDecorationLine(aSource.mTextDecorationLine),
|
|
mTextDecorationStyle(aSource.mTextDecorationStyle),
|
|
mUnicodeBidi(aSource.mUnicodeBidi),
|
|
mInitialLetterSink(aSource.mInitialLetterSink),
|
|
mInitialLetterSize(aSource.mInitialLetterSize),
|
|
mTextDecorationColor(aSource.mTextDecorationColor),
|
|
mTextDecorationThickness(aSource.mTextDecorationThickness) {
|
|
MOZ_COUNT_CTOR(nsStyleTextReset);
|
|
}
|
|
|
|
nsStyleTextReset::~nsStyleTextReset() { MOZ_COUNT_DTOR(nsStyleTextReset); }
|
|
|
|
nsChangeHint nsStyleTextReset::CalcDifference(
|
|
const nsStyleTextReset& aNewData) const {
|
|
if (mUnicodeBidi != aNewData.mUnicodeBidi ||
|
|
mInitialLetterSink != aNewData.mInitialLetterSink ||
|
|
mInitialLetterSize != aNewData.mInitialLetterSize) {
|
|
return NS_STYLE_HINT_REFLOW;
|
|
}
|
|
|
|
if (mTextDecorationLine != aNewData.mTextDecorationLine ||
|
|
mTextDecorationStyle != aNewData.mTextDecorationStyle ||
|
|
mTextDecorationThickness != aNewData.mTextDecorationThickness) {
|
|
// Changes to our text-decoration line can impact our overflow area &
|
|
// also our descendants' overflow areas (particularly for text-frame
|
|
// descendants). So, we update those areas & trigger a repaint.
|
|
return nsChangeHint_RepaintFrame | nsChangeHint_UpdateSubtreeOverflow |
|
|
nsChangeHint_SchedulePaint;
|
|
}
|
|
|
|
// Repaint for decoration color changes
|
|
if (mTextDecorationColor != aNewData.mTextDecorationColor) {
|
|
return nsChangeHint_RepaintFrame;
|
|
}
|
|
|
|
if (mTextOverflow != aNewData.mTextOverflow) {
|
|
return nsChangeHint_RepaintFrame;
|
|
}
|
|
|
|
return nsChangeHint(0);
|
|
}
|
|
|
|
// --------------------
|
|
// nsStyleText
|
|
//
|
|
|
|
static StyleRGBA DefaultColor(const Document& aDocument) {
|
|
return StyleRGBA::FromColor(
|
|
PreferenceSheet::PrefsFor(aDocument).mDefaultColor);
|
|
}
|
|
|
|
nsStyleText::nsStyleText(const Document& aDocument)
|
|
: mColor(DefaultColor(aDocument)),
|
|
mTextTransform(StyleTextTransform::None()),
|
|
mTextAlign(StyleTextAlign::Start),
|
|
mTextAlignLast(StyleTextAlignLast::Auto),
|
|
mTextJustify(StyleTextJustify::Auto),
|
|
mWhiteSpace(StyleWhiteSpace::Normal),
|
|
mHyphens(StyleHyphens::Manual),
|
|
mRubyAlign(StyleRubyAlign::SpaceAround),
|
|
mRubyPosition(StaticPrefs::layout_css_ruby_position_alternate_enabled()
|
|
? StyleRubyPosition::AlternateOver
|
|
: StyleRubyPosition::Over),
|
|
mTextSizeAdjust(StyleTextSizeAdjust::Auto),
|
|
mTextCombineUpright(NS_STYLE_TEXT_COMBINE_UPRIGHT_NONE),
|
|
mControlCharacterVisibility(
|
|
nsLayoutUtils::ControlCharVisibilityDefault()),
|
|
mTextRendering(StyleTextRendering::Auto),
|
|
mTextEmphasisColor(StyleColor::CurrentColor()),
|
|
mWebkitTextFillColor(StyleColor::CurrentColor()),
|
|
mWebkitTextStrokeColor(StyleColor::CurrentColor()),
|
|
mMozTabSize(
|
|
StyleNonNegativeLengthOrNumber::Number(NS_STYLE_TABSIZE_INITIAL)),
|
|
mWordSpacing(LengthPercentage::Zero()),
|
|
mLetterSpacing({0.}),
|
|
mLineHeight(StyleLineHeight::Normal()),
|
|
mTextIndent(LengthPercentage::Zero()),
|
|
mTextUnderlineOffset(LengthPercentageOrAuto::Auto()),
|
|
mTextDecorationSkipInk(StyleTextDecorationSkipInk::Auto),
|
|
mTextUnderlinePosition(StyleTextUnderlinePosition::AUTO),
|
|
mWebkitTextStrokeWidth(0),
|
|
mTextEmphasisStyle(StyleTextEmphasisStyle::None()) {
|
|
MOZ_COUNT_CTOR(nsStyleText);
|
|
RefPtr<nsAtom> language = aDocument.GetContentLanguageAsAtomForStyle();
|
|
mTextEmphasisPosition =
|
|
language && nsStyleUtil::MatchesLanguagePrefix(language, u"zh")
|
|
? NS_STYLE_TEXT_EMPHASIS_POSITION_DEFAULT_ZH
|
|
: NS_STYLE_TEXT_EMPHASIS_POSITION_DEFAULT;
|
|
}
|
|
|
|
nsStyleText::nsStyleText(const nsStyleText& aSource)
|
|
: mColor(aSource.mColor),
|
|
mTextTransform(aSource.mTextTransform),
|
|
mTextAlign(aSource.mTextAlign),
|
|
mTextAlignLast(aSource.mTextAlignLast),
|
|
mTextJustify(aSource.mTextJustify),
|
|
mWhiteSpace(aSource.mWhiteSpace),
|
|
mLineBreak(aSource.mLineBreak),
|
|
mWordBreak(aSource.mWordBreak),
|
|
mOverflowWrap(aSource.mOverflowWrap),
|
|
mHyphens(aSource.mHyphens),
|
|
mRubyAlign(aSource.mRubyAlign),
|
|
mRubyPosition(aSource.mRubyPosition),
|
|
mTextSizeAdjust(aSource.mTextSizeAdjust),
|
|
mTextCombineUpright(aSource.mTextCombineUpright),
|
|
mControlCharacterVisibility(aSource.mControlCharacterVisibility),
|
|
mTextEmphasisPosition(aSource.mTextEmphasisPosition),
|
|
mTextRendering(aSource.mTextRendering),
|
|
mTextEmphasisColor(aSource.mTextEmphasisColor),
|
|
mWebkitTextFillColor(aSource.mWebkitTextFillColor),
|
|
mWebkitTextStrokeColor(aSource.mWebkitTextStrokeColor),
|
|
mMozTabSize(aSource.mMozTabSize),
|
|
mWordSpacing(aSource.mWordSpacing),
|
|
mLetterSpacing(aSource.mLetterSpacing),
|
|
mLineHeight(aSource.mLineHeight),
|
|
mTextIndent(aSource.mTextIndent),
|
|
mTextUnderlineOffset(aSource.mTextUnderlineOffset),
|
|
mTextDecorationSkipInk(aSource.mTextDecorationSkipInk),
|
|
mTextUnderlinePosition(aSource.mTextUnderlinePosition),
|
|
mWebkitTextStrokeWidth(aSource.mWebkitTextStrokeWidth),
|
|
mTextShadow(aSource.mTextShadow),
|
|
mTextEmphasisStyle(aSource.mTextEmphasisStyle) {
|
|
MOZ_COUNT_CTOR(nsStyleText);
|
|
}
|
|
|
|
nsStyleText::~nsStyleText() { MOZ_COUNT_DTOR(nsStyleText); }
|
|
|
|
nsChangeHint nsStyleText::CalcDifference(const nsStyleText& aNewData) const {
|
|
if (WhiteSpaceOrNewlineIsSignificant() !=
|
|
aNewData.WhiteSpaceOrNewlineIsSignificant()) {
|
|
// This may require construction of suppressed text frames
|
|
return nsChangeHint_ReconstructFrame;
|
|
}
|
|
|
|
if (mTextCombineUpright != aNewData.mTextCombineUpright ||
|
|
mControlCharacterVisibility != aNewData.mControlCharacterVisibility) {
|
|
return nsChangeHint_ReconstructFrame;
|
|
}
|
|
|
|
if ((mTextAlign != aNewData.mTextAlign) ||
|
|
(mTextAlignLast != aNewData.mTextAlignLast) ||
|
|
(mTextTransform != aNewData.mTextTransform) ||
|
|
(mWhiteSpace != aNewData.mWhiteSpace) ||
|
|
(mLineBreak != aNewData.mLineBreak) ||
|
|
(mWordBreak != aNewData.mWordBreak) ||
|
|
(mOverflowWrap != aNewData.mOverflowWrap) ||
|
|
(mHyphens != aNewData.mHyphens) || (mRubyAlign != aNewData.mRubyAlign) ||
|
|
(mRubyPosition != aNewData.mRubyPosition) ||
|
|
(mTextSizeAdjust != aNewData.mTextSizeAdjust) ||
|
|
(mLetterSpacing != aNewData.mLetterSpacing) ||
|
|
(mLineHeight != aNewData.mLineHeight) ||
|
|
(mTextIndent != aNewData.mTextIndent) ||
|
|
(mTextJustify != aNewData.mTextJustify) ||
|
|
(mWordSpacing != aNewData.mWordSpacing) ||
|
|
(mMozTabSize != aNewData.mMozTabSize)) {
|
|
return NS_STYLE_HINT_REFLOW;
|
|
}
|
|
|
|
if (HasEffectiveTextEmphasis() != aNewData.HasEffectiveTextEmphasis() ||
|
|
(HasEffectiveTextEmphasis() &&
|
|
mTextEmphasisPosition != aNewData.mTextEmphasisPosition)) {
|
|
// Text emphasis position change could affect line height calculation.
|
|
return nsChangeHint_AllReflowHints | nsChangeHint_RepaintFrame;
|
|
}
|
|
|
|
nsChangeHint hint = nsChangeHint(0);
|
|
|
|
// text-rendering changes require a reflow since they change SVG
|
|
// frames' rects.
|
|
if (mTextRendering != aNewData.mTextRendering) {
|
|
hint |= nsChangeHint_NeedReflow |
|
|
nsChangeHint_NeedDirtyReflow | // XXX remove me: bug 876085
|
|
nsChangeHint_RepaintFrame;
|
|
}
|
|
|
|
if (mTextShadow != aNewData.mTextShadow ||
|
|
mTextEmphasisStyle != aNewData.mTextEmphasisStyle ||
|
|
mWebkitTextStrokeWidth != aNewData.mWebkitTextStrokeWidth ||
|
|
mTextUnderlineOffset != aNewData.mTextUnderlineOffset ||
|
|
mTextDecorationSkipInk != aNewData.mTextDecorationSkipInk ||
|
|
mTextUnderlinePosition != aNewData.mTextUnderlinePosition) {
|
|
hint |= nsChangeHint_UpdateSubtreeOverflow | nsChangeHint_SchedulePaint |
|
|
nsChangeHint_RepaintFrame;
|
|
|
|
// We don't add any other hints below.
|
|
return hint;
|
|
}
|
|
|
|
if (mColor != aNewData.mColor) {
|
|
hint |= nsChangeHint_RepaintFrame;
|
|
}
|
|
|
|
if (mTextEmphasisColor != aNewData.mTextEmphasisColor ||
|
|
mWebkitTextFillColor != aNewData.mWebkitTextFillColor ||
|
|
mWebkitTextStrokeColor != aNewData.mWebkitTextStrokeColor) {
|
|
hint |= nsChangeHint_SchedulePaint | nsChangeHint_RepaintFrame;
|
|
}
|
|
|
|
if (hint) {
|
|
return hint;
|
|
}
|
|
|
|
if (mTextEmphasisPosition != aNewData.mTextEmphasisPosition) {
|
|
return nsChangeHint_NeutralChange;
|
|
}
|
|
|
|
return nsChangeHint(0);
|
|
}
|
|
|
|
LogicalSide nsStyleText::TextEmphasisSide(WritingMode aWM) const {
|
|
MOZ_ASSERT(
|
|
(!(mTextEmphasisPosition & NS_STYLE_TEXT_EMPHASIS_POSITION_LEFT) !=
|
|
!(mTextEmphasisPosition & NS_STYLE_TEXT_EMPHASIS_POSITION_RIGHT)) &&
|
|
(!(mTextEmphasisPosition & NS_STYLE_TEXT_EMPHASIS_POSITION_OVER) !=
|
|
!(mTextEmphasisPosition & NS_STYLE_TEXT_EMPHASIS_POSITION_UNDER)));
|
|
mozilla::Side side =
|
|
aWM.IsVertical()
|
|
? (mTextEmphasisPosition & NS_STYLE_TEXT_EMPHASIS_POSITION_LEFT
|
|
? eSideLeft
|
|
: eSideRight)
|
|
: (mTextEmphasisPosition & NS_STYLE_TEXT_EMPHASIS_POSITION_OVER
|
|
? eSideTop
|
|
: eSideBottom);
|
|
LogicalSide result = aWM.LogicalSideForPhysicalSide(side);
|
|
MOZ_ASSERT(IsBlock(result));
|
|
return result;
|
|
}
|
|
|
|
//-----------------------
|
|
// nsStyleUI
|
|
//
|
|
|
|
nsStyleUI::nsStyleUI(const Document& aDocument)
|
|
: mInert(StyleInert::None),
|
|
mUserInput(StyleUserInput::Auto),
|
|
mUserModify(StyleUserModify::ReadOnly),
|
|
mUserFocus(StyleUserFocus::None),
|
|
mPointerEvents(StylePointerEvents::Auto),
|
|
mCursor{{}, StyleCursorKind::Auto},
|
|
mCaretColor(StyleColorOrAuto::Auto()),
|
|
mScrollbarColor(StyleScrollbarColor::Auto()) {
|
|
MOZ_COUNT_CTOR(nsStyleUI);
|
|
}
|
|
|
|
nsStyleUI::nsStyleUI(const nsStyleUI& aSource)
|
|
: mInert(aSource.mInert),
|
|
mUserInput(aSource.mUserInput),
|
|
mUserModify(aSource.mUserModify),
|
|
mUserFocus(aSource.mUserFocus),
|
|
mPointerEvents(aSource.mPointerEvents),
|
|
mCursor(aSource.mCursor),
|
|
mCaretColor(aSource.mCaretColor),
|
|
mScrollbarColor(aSource.mScrollbarColor) {
|
|
MOZ_COUNT_CTOR(nsStyleUI);
|
|
}
|
|
|
|
nsStyleUI::~nsStyleUI() { MOZ_COUNT_DTOR(nsStyleUI); }
|
|
|
|
void nsStyleUI::TriggerImageLoads(Document& aDocument,
|
|
const nsStyleUI* aOldStyle) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
auto cursorImages = mCursor.images.AsSpan();
|
|
auto oldCursorImages = aOldStyle ? aOldStyle->mCursor.images.AsSpan()
|
|
: Span<const StyleCursorImage>();
|
|
for (size_t i = 0; i < cursorImages.Length(); ++i) {
|
|
auto& cursor = cursorImages[i];
|
|
const auto* oldCursorImage =
|
|
oldCursorImages.Length() > i ? &oldCursorImages[i].image : nullptr;
|
|
const_cast<StyleCursorImage&>(cursor).image.ResolveImage(aDocument,
|
|
oldCursorImage);
|
|
}
|
|
}
|
|
|
|
nsChangeHint nsStyleUI::CalcDifference(const nsStyleUI& aNewData) const {
|
|
nsChangeHint hint = nsChangeHint(0);
|
|
if (mCursor != aNewData.mCursor) {
|
|
hint |= nsChangeHint_UpdateCursor;
|
|
}
|
|
|
|
if (mPointerEvents != aNewData.mPointerEvents) {
|
|
// SVGGeometryFrame's mRect depends on stroke _and_ on the value
|
|
// of pointer-events. See SVGGeometryFrame::ReflowSVG's use of
|
|
// GetHitTestFlags. (Only a reflow, no visual change.)
|
|
hint |= nsChangeHint_NeedReflow |
|
|
nsChangeHint_NeedDirtyReflow | // XXX remove me: bug 876085
|
|
nsChangeHint_SchedulePaint; // pointer-events changes can change
|
|
// event regions overrides on layers
|
|
// and so needs a repaint.
|
|
}
|
|
|
|
if (mUserModify != aNewData.mUserModify) {
|
|
hint |= NS_STYLE_HINT_VISUAL;
|
|
}
|
|
|
|
if (mUserFocus != aNewData.mUserFocus || mInert != aNewData.mInert ||
|
|
mUserInput != aNewData.mUserInput) {
|
|
hint |= nsChangeHint_NeutralChange;
|
|
}
|
|
|
|
if (mCaretColor != aNewData.mCaretColor ||
|
|
mScrollbarColor != aNewData.mScrollbarColor) {
|
|
hint |= nsChangeHint_RepaintFrame;
|
|
}
|
|
|
|
return hint;
|
|
}
|
|
|
|
//-----------------------
|
|
// nsStyleUIReset
|
|
//
|
|
|
|
nsStyleUIReset::nsStyleUIReset(const Document& aDocument)
|
|
: mUserSelect(StyleUserSelect::Auto),
|
|
mScrollbarWidth(StyleScrollbarWidth::Auto),
|
|
mMozForceBrokenImageIcon(0),
|
|
mIMEMode(StyleImeMode::Auto),
|
|
mWindowDragging(StyleWindowDragging::Default),
|
|
mWindowShadow(StyleWindowShadow::Default),
|
|
mWindowOpacity(1.0),
|
|
mWindowTransformOrigin{LengthPercentage::FromPercentage(0.5),
|
|
LengthPercentage::FromPercentage(0.5),
|
|
{0.}} {
|
|
MOZ_COUNT_CTOR(nsStyleUIReset);
|
|
}
|
|
|
|
nsStyleUIReset::nsStyleUIReset(const nsStyleUIReset& aSource)
|
|
: mUserSelect(aSource.mUserSelect),
|
|
mScrollbarWidth(aSource.mScrollbarWidth),
|
|
mMozForceBrokenImageIcon(aSource.mMozForceBrokenImageIcon),
|
|
mIMEMode(aSource.mIMEMode),
|
|
mWindowDragging(aSource.mWindowDragging),
|
|
mWindowShadow(aSource.mWindowShadow),
|
|
mWindowOpacity(aSource.mWindowOpacity),
|
|
mMozWindowTransform(aSource.mMozWindowTransform),
|
|
mWindowTransformOrigin(aSource.mWindowTransformOrigin) {
|
|
MOZ_COUNT_CTOR(nsStyleUIReset);
|
|
}
|
|
|
|
nsStyleUIReset::~nsStyleUIReset() { MOZ_COUNT_DTOR(nsStyleUIReset); }
|
|
|
|
nsChangeHint nsStyleUIReset::CalcDifference(
|
|
const nsStyleUIReset& aNewData) const {
|
|
nsChangeHint hint = nsChangeHint(0);
|
|
|
|
if (mMozForceBrokenImageIcon != aNewData.mMozForceBrokenImageIcon) {
|
|
hint |= nsChangeHint_ReconstructFrame;
|
|
}
|
|
if (mScrollbarWidth != aNewData.mScrollbarWidth) {
|
|
// For scrollbar-width change, we need some special handling similar
|
|
// to overflow properties. Specifically, we may need to reconstruct
|
|
// the scrollbar or force reflow of the viewport scrollbar.
|
|
hint |= nsChangeHint_ScrollbarChange;
|
|
}
|
|
if (mWindowShadow != aNewData.mWindowShadow) {
|
|
// We really need just an nsChangeHint_SyncFrameView, except
|
|
// on an ancestor of the frame, so we get that by doing a
|
|
// reflow.
|
|
hint |= NS_STYLE_HINT_REFLOW;
|
|
}
|
|
if (mUserSelect != aNewData.mUserSelect) {
|
|
hint |= NS_STYLE_HINT_VISUAL;
|
|
}
|
|
|
|
if (mWindowDragging != aNewData.mWindowDragging) {
|
|
hint |= nsChangeHint_SchedulePaint;
|
|
}
|
|
|
|
if (!hint && (mIMEMode != aNewData.mIMEMode ||
|
|
mWindowOpacity != aNewData.mWindowOpacity ||
|
|
mMozWindowTransform != aNewData.mMozWindowTransform)) {
|
|
hint |= nsChangeHint_NeutralChange;
|
|
}
|
|
|
|
return hint;
|
|
}
|
|
|
|
//-----------------------
|
|
// nsStyleEffects
|
|
//
|
|
|
|
nsStyleEffects::nsStyleEffects(const Document&)
|
|
: mClip(StyleClipRectOrAuto::Auto()),
|
|
mOpacity(1.0f),
|
|
mMixBlendMode(StyleBlend::Normal) {
|
|
MOZ_COUNT_CTOR(nsStyleEffects);
|
|
}
|
|
|
|
nsStyleEffects::nsStyleEffects(const nsStyleEffects& aSource)
|
|
: mFilters(aSource.mFilters),
|
|
mBoxShadow(aSource.mBoxShadow),
|
|
mBackdropFilters(aSource.mBackdropFilters),
|
|
mClip(aSource.mClip),
|
|
mOpacity(aSource.mOpacity),
|
|
mMixBlendMode(aSource.mMixBlendMode) {
|
|
MOZ_COUNT_CTOR(nsStyleEffects);
|
|
}
|
|
|
|
nsStyleEffects::~nsStyleEffects() { MOZ_COUNT_DTOR(nsStyleEffects); }
|
|
|
|
static bool AnyAutonessChanged(const StyleClipRectOrAuto& aOld,
|
|
const StyleClipRectOrAuto& aNew) {
|
|
if (aOld.IsAuto() != aNew.IsAuto()) {
|
|
return true;
|
|
}
|
|
if (aOld.IsAuto()) {
|
|
return false;
|
|
}
|
|
auto& oldRect = aOld.AsRect();
|
|
auto& newRect = aNew.AsRect();
|
|
return oldRect.top.IsAuto() != newRect.top.IsAuto() ||
|
|
oldRect.right.IsAuto() != newRect.right.IsAuto() ||
|
|
oldRect.bottom.IsAuto() != newRect.bottom.IsAuto() ||
|
|
oldRect.left.IsAuto() != newRect.left.IsAuto();
|
|
}
|
|
|
|
nsChangeHint nsStyleEffects::CalcDifference(
|
|
const nsStyleEffects& aNewData) const {
|
|
nsChangeHint hint = nsChangeHint(0);
|
|
|
|
if (mBoxShadow != aNewData.mBoxShadow) {
|
|
// Update overflow regions & trigger DLBI to be sure it's noticed.
|
|
// Also request a repaint, since it's possible that only the color
|
|
// of the shadow is changing (and UpdateOverflow/SchedulePaint won't
|
|
// repaint for that, since they won't know what needs invalidating.)
|
|
hint |= nsChangeHint_UpdateOverflow | nsChangeHint_SchedulePaint |
|
|
nsChangeHint_RepaintFrame;
|
|
}
|
|
|
|
if (AnyAutonessChanged(mClip, aNewData.mClip)) {
|
|
hint |= nsChangeHint_AllReflowHints | nsChangeHint_RepaintFrame;
|
|
} else if (mClip != aNewData.mClip) {
|
|
// If the clip has changed, we just need to update overflow areas. DLBI
|
|
// will handle the invalidation.
|
|
hint |= nsChangeHint_UpdateOverflow | nsChangeHint_SchedulePaint;
|
|
}
|
|
|
|
if (mOpacity != aNewData.mOpacity) {
|
|
hint |= nsChangeHint_UpdateOpacityLayer;
|
|
|
|
// If we're going from the optimized >=0.99 opacity value to 1.0 or back,
|
|
// then repaint the frame because DLBI will not catch the invalidation.
|
|
// Otherwise, just update the opacity layer.
|
|
if ((mOpacity >= 0.99f && mOpacity < 1.0f && aNewData.mOpacity == 1.0f) ||
|
|
(aNewData.mOpacity >= 0.99f && aNewData.mOpacity < 1.0f &&
|
|
mOpacity == 1.0f)) {
|
|
hint |= nsChangeHint_RepaintFrame;
|
|
} else {
|
|
if ((mOpacity == 1.0f) != (aNewData.mOpacity == 1.0f)) {
|
|
hint |= nsChangeHint_UpdateUsesOpacity;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (HasFilters() != aNewData.HasFilters()) {
|
|
// A change from/to being a containing block for position:fixed.
|
|
hint |= nsChangeHint_UpdateContainingBlock;
|
|
}
|
|
|
|
if (mFilters != aNewData.mFilters) {
|
|
hint |= nsChangeHint_UpdateEffects | nsChangeHint_RepaintFrame |
|
|
nsChangeHint_UpdateOverflow;
|
|
}
|
|
|
|
if (mMixBlendMode != aNewData.mMixBlendMode) {
|
|
hint |= nsChangeHint_RepaintFrame;
|
|
}
|
|
|
|
if (HasBackdropFilters() != aNewData.HasBackdropFilters()) {
|
|
// A change from/to being a containing block for position:fixed.
|
|
hint |= nsChangeHint_UpdateContainingBlock;
|
|
}
|
|
|
|
if (mBackdropFilters != aNewData.mBackdropFilters) {
|
|
hint |= nsChangeHint_UpdateEffects | nsChangeHint_RepaintFrame;
|
|
}
|
|
|
|
return hint;
|
|
}
|
|
|
|
static bool TransformOperationHasPercent(const StyleTransformOperation& aOp) {
|
|
switch (aOp.tag) {
|
|
case StyleTransformOperation::Tag::TranslateX:
|
|
return aOp.AsTranslateX().HasPercent();
|
|
case StyleTransformOperation::Tag::TranslateY:
|
|
return aOp.AsTranslateY().HasPercent();
|
|
case StyleTransformOperation::Tag::TranslateZ:
|
|
return false;
|
|
case StyleTransformOperation::Tag::Translate3D: {
|
|
auto& translate = aOp.AsTranslate3D();
|
|
// NOTE(emilio): z translation is a `<length>`, so can't have percentages.
|
|
return translate._0.HasPercent() || translate._1.HasPercent();
|
|
}
|
|
case StyleTransformOperation::Tag::Translate: {
|
|
auto& translate = aOp.AsTranslate();
|
|
return translate._0.HasPercent() || translate._1.HasPercent();
|
|
}
|
|
case StyleTransformOperation::Tag::AccumulateMatrix: {
|
|
auto& accum = aOp.AsAccumulateMatrix();
|
|
return accum.from_list.HasPercent() || accum.to_list.HasPercent();
|
|
}
|
|
case StyleTransformOperation::Tag::InterpolateMatrix: {
|
|
auto& interpolate = aOp.AsInterpolateMatrix();
|
|
return interpolate.from_list.HasPercent() ||
|
|
interpolate.to_list.HasPercent();
|
|
}
|
|
case StyleTransformOperation::Tag::Perspective:
|
|
case StyleTransformOperation::Tag::RotateX:
|
|
case StyleTransformOperation::Tag::RotateY:
|
|
case StyleTransformOperation::Tag::RotateZ:
|
|
case StyleTransformOperation::Tag::Rotate:
|
|
case StyleTransformOperation::Tag::Rotate3D:
|
|
case StyleTransformOperation::Tag::SkewX:
|
|
case StyleTransformOperation::Tag::SkewY:
|
|
case StyleTransformOperation::Tag::Skew:
|
|
case StyleTransformOperation::Tag::ScaleX:
|
|
case StyleTransformOperation::Tag::ScaleY:
|
|
case StyleTransformOperation::Tag::ScaleZ:
|
|
case StyleTransformOperation::Tag::Scale:
|
|
case StyleTransformOperation::Tag::Scale3D:
|
|
case StyleTransformOperation::Tag::Matrix:
|
|
case StyleTransformOperation::Tag::Matrix3D:
|
|
return false;
|
|
default:
|
|
MOZ_ASSERT_UNREACHABLE("Unknown transform operation");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
template <>
|
|
bool StyleTransform::HasPercent() const {
|
|
for (const auto& op : Operations()) {
|
|
if (TransformOperationHasPercent(op)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
template <>
|
|
void StyleCalcNode::ScaleLengthsBy(float aScale) {
|
|
auto ScaleNode = [aScale](const StyleCalcNode& aNode) {
|
|
// This const_cast could be removed by generating more mut-casts, if
|
|
// needed.
|
|
const_cast<StyleCalcNode&>(aNode).ScaleLengthsBy(aScale);
|
|
};
|
|
|
|
switch (tag) {
|
|
case Tag::Leaf: {
|
|
auto& leaf = AsLeaf();
|
|
if (leaf.IsLength()) {
|
|
// This const_cast could be removed by generating more mut-casts, if
|
|
// needed.
|
|
const_cast<Length&>(leaf.AsLength()).ScaleBy(aScale);
|
|
}
|
|
break;
|
|
}
|
|
case Tag::Clamp: {
|
|
auto& clamp = AsClamp();
|
|
ScaleNode(*clamp.min);
|
|
ScaleNode(*clamp.center);
|
|
ScaleNode(*clamp.max);
|
|
break;
|
|
}
|
|
case Tag::MinMax: {
|
|
for (auto& child : AsMinMax()._0.AsSpan()) {
|
|
ScaleNode(child);
|
|
}
|
|
break;
|
|
}
|
|
case Tag::Sum: {
|
|
for (auto& child : AsSum().AsSpan()) {
|
|
ScaleNode(child);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
template <>
|
|
template <typename ResultT, typename PercentageConverter>
|
|
ResultT StyleCalcNode::ResolveInternal(ResultT aPercentageBasis,
|
|
PercentageConverter aConverter) const {
|
|
static_assert(std::is_same_v<decltype(aConverter(1.0f)), ResultT>);
|
|
static_assert(std::is_same_v<ResultT, nscoord> ||
|
|
std::is_same_v<ResultT, CSSCoord>);
|
|
|
|
switch (tag) {
|
|
case Tag::Leaf: {
|
|
auto& leaf = AsLeaf();
|
|
if (leaf.IsPercentage()) {
|
|
return aConverter(leaf.AsPercentage()._0 * aPercentageBasis);
|
|
}
|
|
if constexpr (std::is_same_v<ResultT, nscoord>) {
|
|
return leaf.AsLength().ToAppUnits();
|
|
} else {
|
|
return leaf.AsLength().ToCSSPixels();
|
|
}
|
|
}
|
|
case Tag::Clamp: {
|
|
auto& clamp = AsClamp();
|
|
auto min = clamp.min->ResolveInternal(aPercentageBasis, aConverter);
|
|
auto center = clamp.center->ResolveInternal(aPercentageBasis, aConverter);
|
|
auto max = clamp.max->ResolveInternal(aPercentageBasis, aConverter);
|
|
return std::max(min, std::min(center, max));
|
|
}
|
|
case Tag::MinMax: {
|
|
auto children = AsMinMax()._0.AsSpan();
|
|
StyleMinMaxOp op = AsMinMax()._1;
|
|
|
|
ResultT result =
|
|
children[0].ResolveInternal(aPercentageBasis, aConverter);
|
|
for (auto& child : children.From(1)) {
|
|
ResultT candidate = child.ResolveInternal(aPercentageBasis, aConverter);
|
|
if (op == StyleMinMaxOp::Max) {
|
|
result = std::max(result, candidate);
|
|
} else {
|
|
result = std::min(result, candidate);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
case Tag::Sum: {
|
|
ResultT result = 0;
|
|
for (auto& child : AsSum().AsSpan()) {
|
|
result += child.ResolveInternal(aPercentageBasis, aConverter);
|
|
}
|
|
return result;
|
|
}
|
|
}
|
|
|
|
MOZ_ASSERT_UNREACHABLE("Unknown calc node");
|
|
return 0;
|
|
}
|
|
|
|
template <>
|
|
CSSCoord StyleCalcNode::ResolveToCSSPixels(CSSCoord aBasis) const {
|
|
CSSCoord result =
|
|
ResolveInternal(aBasis, [](CSSCoord aPercent) { return aPercent; });
|
|
if (IsNaN(float(result))) {
|
|
return 0.0f; // This matches style::values::normalize
|
|
}
|
|
return result;
|
|
}
|
|
|
|
template <>
|
|
nscoord StyleCalcNode::Resolve(nscoord aBasis,
|
|
CoordPercentageRounder aRounder) const {
|
|
return ResolveInternal(aBasis, aRounder);
|
|
}
|