diff options
Diffstat (limited to 'layout/style/nsStyleStruct.cpp')
-rw-r--r-- | layout/style/nsStyleStruct.cpp | 3865 |
1 files changed, 3865 insertions, 0 deletions
diff --git a/layout/style/nsStyleStruct.cpp b/layout/style/nsStyleStruct.cpp new file mode 100644 index 0000000000..cd1ea8acce --- /dev/null +++ b/layout/style/nsStyleStruct.cpp @@ -0,0 +1,3865 @@ +/* -*- 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/BaseKeyframeTypesBinding.h" // for CompositeOperation +#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) { + // We want to dispatch this async to prevent reentrancy issues, as + // imgRequestProxy going away can destroy documents, etc, see bug 1677555. + auto task = MakeRefPtr<StyleImageRequestCleanupTask>(*aData); + 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), + mFontPalette(aSrc.mFontPalette), + mMathDepth(aSrc.mMathDepth), + mMathVariant(aSrc.mMathVariant), + mMathStyle(aSrc.mMathStyle), + mMinFontSizeRatio(aSrc.mMinFontSizeRatio), + mExplicitLanguage(aSrc.mExplicitLanguage), + mXTextScale(aSrc.mXTextScale), + mScriptUnconstrainedSize(aSrc.mScriptUnconstrainedSize), + mScriptMinSize(aSrc.mScriptMinSize), + mScriptSizeMultiplier(aSrc.mScriptSizeMultiplier), + mLanguage(aSrc.mLanguage) { + MOZ_COUNT_CTOR(nsStyleFont); +} + +static StyleXTextScale InitialTextScale(const Document& aDoc) { + if (nsContentUtils::IsChromeDoc(&aDoc) || + nsContentUtils::IsPDFJS(aDoc.NodePrincipal())) { + return StyleXTextScale::ZoomOnly; + } + return StyleXTextScale::All; +} + +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), + mFontPalette(StyleFontPalette::Normal()), + mMathDepth(0), + mMathVariant(StyleMathVariant::None), + mMathStyle(StyleMathStyle::Normal), + mXTextScale(InitialTextScale(aDocument)), + mScriptUnconstrainedSize(mSize), + mScriptMinSize(Length::FromPixels( + CSSPixel::FromPoints(kMathMLDefaultScriptMinSizePt))), + mScriptSizeMultiplier(kMathMLDefaultScriptSizeMultiplier), + mLanguage(aDocument.GetLanguageForStyle()) { + MOZ_COUNT_CTOR(nsStyleFont); + MOZ_ASSERT(NS_IsMainThread()); + mFont.family.is_initial = true; + mFont.size = mSize; + if (MinFontSizeEnabled()) { + const 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(mXTextScale == aNewData.mXTextScale, + "expected -x-text-scale 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; + } + + if (mFontPalette != aNewData.mFontPalette) { + return NS_STYLE_HINT_VISUAL; + } + + // XXX Should any of these cause a non-nsChangeHint_NeutralChange change? + if (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->TextZoom()); + } + 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.})), + mOverflowClipMargin(StyleLength::Zero()) { + MOZ_COUNT_CTOR(nsStyleMargin); +} + +nsStyleMargin::nsStyleMargin(const nsStyleMargin& aSrc) + : mMargin(aSrc.mMargin), + mScrollMargin(aSrc.mScrollMargin), + mOverflowClipMargin(aSrc.mOverflowClipMargin) { + 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; + } + + if (mOverflowClipMargin != aNewData.mOverflowClipMargin) { + hint |= nsChangeHint_UpdateOverflow | nsChangeHint_RepaintFrame; + } + + 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 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) { + 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) { + 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 (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) + : mOutlineWidth(kMediumBorderWidth), + mOutlineOffset({0.0f}), + mOutlineColor(StyleColor::CurrentColor()), + mOutlineStyle(StyleOutlineStyle::BorderStyle(StyleBorderStyle::None)), + mActualOutlineWidth(0) { + MOZ_COUNT_CTOR(nsStyleOutline); +} + +nsStyleOutline::nsStyleOutline(const nsStyleOutline& aSrc) + : mOutlineWidth(aSrc.mOutlineWidth), + mOutlineOffset(aSrc.mOutlineOffset), + mOutlineColor(aSrc.mOutlineColor), + mOutlineStyle(aSrc.mOutlineStyle), + mActualOutlineWidth(aSrc.mActualOutlineWidth) { + MOZ_COUNT_CTOR(nsStyleOutline); +} + +nsChangeHint nsStyleOutline::CalcDifference( + const nsStyleOutline& aNewData) const { + const bool shouldPaintOutline = ShouldPaintOutline(); + // We need the explicit 'outline-style: auto' check because + // 'outline-style: auto' effectively also changes 'outline-width'. + if (shouldPaintOutline != aNewData.ShouldPaintOutline() || + mActualOutlineWidth != aNewData.mActualOutlineWidth || + mOutlineStyle.IsAuto() != aNewData.mOutlineStyle.IsAuto() || + (shouldPaintOutline && mOutlineOffset != aNewData.mOutlineOffset)) { + return nsChangeHint_UpdateOverflow | nsChangeHint_SchedulePaint | + nsChangeHint_RepaintFrame; + } + + if (mOutlineStyle != aNewData.mOutlineStyle || + mOutlineColor != aNewData.mOutlineColor) { + return shouldPaintOutline ? nsChangeHint_RepaintFrame + : nsChangeHint_NeutralChange; + } + + if (mOutlineWidth != aNewData.mOutlineWidth || + mOutlineOffset != aNewData.mOutlineOffset) { + return nsChangeHint_NeutralChange; + } + + return nsChangeHint(0); +} + +// -------------------- +// nsStyleList +// +nsStyleList::nsStyleList(const Document& aDocument) + : mListStylePosition(StyleListStylePosition::Outside), + mQuotes(StyleQuotes::Auto()), + mListStyleImage(StyleImage::None()) { + 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) { + 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 || + mCounterStyle != aNewData.mCounterStyle || + mListStyleImage != aNewData.mListStyleImage) { + return nsChangeHint_ReconstructFrame; + } + } else if (mListStylePosition != aNewData.mListStylePosition || + mCounterStyle != aNewData.mCounterStyle) { + hint = nsChangeHint_NeutralChange; + } + // 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; + } + 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), + mActualColumnRuleWidth(0) { + 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), + mActualColumnRuleWidth(aSource.mActualColumnRuleWidth) { + 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 (mActualColumnRuleWidth != aNewData.mActualColumnRuleWidth || + mColumnRuleStyle != aNewData.mColumnRuleStyle || + mColumnRuleColor != aNewData.mColumnRuleColor) { + return NS_STYLE_HINT_VISUAL; + } + + if (mColumnRuleWidth != aNewData.mColumnRuleWidth) { + 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_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; + } + 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_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), + mD(StyleDProperty::None()) { + 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), + mD(aSource.mD) { + 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 || mD != aNewData.mD) { + 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_RepaintFrame; + } else if (mStopColor != aNewData.mStopColor || + mFloodColor != aNewData.mFloodColor || + mLightingColor != aNewData.mLightingColor || + mStopOpacity != aNewData.mStopOpacity || + mFloodOpacity != aNewData.mFloodOpacity || + mMaskType != aNewData.mMaskType || mD != aNewData.mD) { + 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 || aNewData.mPage != mPage || + aNewData.mPageOrientation != mPageOrientation) { + 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( + {StyleMasonryPlacement::Pack, StyleMasonryItemOrder::DefiniteFirst}), + 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(0), + mFlexGrow(0.0f), + mFlexShrink(1.0f), + mZIndex(StyleZIndex::Auto()), + mGridTemplateColumns(StyleGridTemplateComponent::None()), + mGridTemplateRows(StyleGridTemplateComponent::None()), + mGridTemplateAreas(StyleGridTemplateAreas::None()), + mColumnGap(NonNegativeLengthPercentageOrNormal::Normal()), + mRowGap(NonNegativeLengthPercentageOrNormal::Normal()), + mContainIntrinsicWidth(StyleContainIntrinsicSize::None()), + mContainIntrinsicHeight(StyleContainIntrinsicSize::None()) { + 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), + mContainIntrinsicWidth(aSource.mContainIntrinsicWidth), + mContainIntrinsicHeight(aSource.mContainIntrinsicHeight) { + 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 (mContainIntrinsicWidth != aNewData.mContainIntrinsicWidth || + mContainIntrinsicHeight != aNewData.mContainIntrinsicHeight) { + hint |= NS_STYLE_HINT_REFLOW; + } + + 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; +} + +const StyleContainIntrinsicSize& nsStylePosition::ContainIntrinsicBSize( + const WritingMode& aWM) const { + return aWM.IsVertical() ? mContainIntrinsicWidth : mContainIntrinsicHeight; +} + +const StyleContainIntrinsicSize& nsStylePosition::ContainIntrinsicISize( + const WritingMode& aWM) const { + return aWM.IsVertical() ? mContainIntrinsicHeight : mContainIntrinsicWidth; +} + +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()); +} + +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); +} + +template <> +ImageResolution StyleImage::GetResolution() const { + ImageResolution resolution; + if (imgRequestProxy* request = GetImageRequest()) { + RefPtr<imgIContainer> image; + request->GetImage(getter_AddRefs(image)); + if (image) { + resolution = image->GetResolution(); + } + } + if (IsImageSet()) { + const auto& set = *AsImageSet(); + auto items = set.items.AsSpan(); + if (MOZ_LIKELY(set.selected_index < items.Length())) { + float r = items[set.selected_index].resolution._0; + resolution.ScaleBy(r); + } + } + return resolution; +} + +template <> +Maybe<CSSIntSize> StyleImage::GetIntrinsicSize() const { + imgRequestProxy* request = GetImageRequest(); + if (!request) { + return Nothing(); + } + RefPtr<imgIContainer> image; + request->GetImage(getter_AddRefs(image)); + if (!image) { + return Nothing(); + } + // FIXME(emilio): Seems like this should be smarter about unspecified width / + // height, aspect ratio, etc, but this preserves the current behavior of our + // only caller for now... + int32_t w = 0, h = 0; + image->GetWidth(&w); + image->GetHeight(&h); + GetResolution().ApplyTo(w, h); + return Some(CSSIntSize{w, h}); +} + +// -------------------- +// 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; + // We could bother getting the right resolution here but it doesn't matter + // since we ignore `imageSize`. + nsLayoutUtils::ComputeSizeForDrawing(imgContainer, ImageResolution(), + 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(const 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 = + StyleComputedTimingFunction::Keyword(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 = + StyleComputedTimingFunction::Keyword(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}; + mComposition = dom::CompositeOperation::Replace; + mTimeline = StyleAnimationTimeline::Auto(); +} + +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 && + mComposition == aOther.mComposition && mTimeline == aOther.mTimeline; +} + +// -------------------- +// nsStyleDisplay +// +nsStyleDisplay::nsStyleDisplay(const Document& aDocument) + : mDisplay(StyleDisplay::Inline), + mOriginalDisplay(StyleDisplay::Inline), + mContentVisibility(StyleContentVisibility::Visible), + mContainerType(StyleContainerType::Normal), + mAppearance(StyleAppearance::None), + mContain(StyleContain::NONE), + mEffectiveContainment(StyleContain::NONE), + mDefaultAppearance(StyleAppearance::None), + mPosition(StylePositionProperty::Static), + mFloat(StyleFloat::None), + mClear(StyleClear::None), + mBreakInside(StyleBreakWithin::Auto), + mBreakBefore(StyleBreakBetween::Auto), + mBreakAfter(StyleBreakBetween::Auto), + mOverflowX(StyleOverflow::Visible), + mOverflowY(StyleOverflow::Visible), + mOverflowClipBoxBlock(StyleOverflowClipBox::PaddingBox), + mOverflowClipBoxInline(StyleOverflowClipBox::PaddingBox), + mScrollbarGutter(StyleScrollbarGutter::AUTO), + 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}, + mBackfaceVisibility(StyleBackfaceVisibility::Visible), + mTransformStyle(StyleTransformStyle::Flat), + mTransformBox(StyleGeometryBox::BorderBox), + mRotate(StyleRotate::None()), + mTranslate(StyleTranslate::None()), + mScale(StyleScale::None()), + mWillChange{{}, {0}}, + mOffsetPath(StyleOffsetPath::None()), + mOffsetDistance(LengthPercentage::Zero()), + mOffsetRotate{true, StyleAngle{0.0}}, + mOffsetAnchor(StylePositionOrAuto::Auto()), + mOffsetPosition(StyleOffsetPosition::Auto()), + mTransformOrigin{LengthPercentage::FromPercentage(0.5), + LengthPercentage::FromPercentage(0.5), + {0.}}, + mChildPerspective(StylePerspective::None()), + mPerspectiveOrigin(Position::FromPercentage(0.5f)), + mVerticalAlign( + StyleVerticalAlign::Keyword(StyleVerticalAlignKeyword::Baseline)), + mBaselineSource(StyleBaselineSource::Auto), + mWebkitLineClamp(0), + mShapeMargin(LengthPercentage::Zero()), + mShapeOutside(StyleShapeOutside::None()) { + MOZ_COUNT_CTOR(nsStyleDisplay); +} + +nsStyleDisplay::nsStyleDisplay(const nsStyleDisplay& aSource) + : mDisplay(aSource.mDisplay), + mOriginalDisplay(aSource.mOriginalDisplay), + mContentVisibility(aSource.mContentVisibility), + mContainerType(aSource.mContainerType), + mAppearance(aSource.mAppearance), + mContain(aSource.mContain), + mEffectiveContainment(aSource.mEffectiveContainment), + mDefaultAppearance(aSource.mDefaultAppearance), + mPosition(aSource.mPosition), + mFloat(aSource.mFloat), + mClear(aSource.mClear), + mBreakInside(aSource.mBreakInside), + mBreakBefore(aSource.mBreakBefore), + mBreakAfter(aSource.mBreakAfter), + mOverflowX(aSource.mOverflowX), + mOverflowY(aSource.mOverflowY), + mOverflowClipBoxBlock(aSource.mOverflowClipBoxBlock), + mOverflowClipBoxInline(aSource.mOverflowClipBoxInline), + mScrollbarGutter(aSource.mScrollbarGutter), + 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), + mBackfaceVisibility(aSource.mBackfaceVisibility), + mTransformStyle(aSource.mTransformStyle), + mTransformBox(aSource.mTransformBox), + mTransform(aSource.mTransform), + mRotate(aSource.mRotate), + mTranslate(aSource.mTranslate), + mScale(aSource.mScale), + mContainerName(aSource.mContainerName), + mWillChange(aSource.mWillChange), + mOffsetPath(aSource.mOffsetPath), + mOffsetDistance(aSource.mOffsetDistance), + mOffsetRotate(aSource.mOffsetRotate), + mOffsetAnchor(aSource.mOffsetAnchor), + mOffsetPosition(aSource.mOffsetPosition), + mTransformOrigin(aSource.mTransformOrigin), + mChildPerspective(aSource.mChildPerspective), + mPerspectiveOrigin(aSource.mPerspectiveOrigin), + mVerticalAlign(aSource.mVerticalAlign), + mBaselineSource(aSource.mBaselineSource), + mWebkitLineClamp(aSource.mWebkitLineClamp), + 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 && + aDisplay.mOffsetPosition == aNewDisplay.mOffsetPosition) { + return nsChangeHint(0); + } + + // No motion path transform is applied. + 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); +} + +static bool AppearanceValueAffectsFrames(StyleAppearance aAppearance, + StyleAppearance aDefaultAppearance) { + switch (aAppearance) { + case StyleAppearance::Textfield: + // This is for <input type=number/search> where we allow authors to + // specify a |-moz-appearance:textfield| to get a control without buttons. + // We need to reframe since this affects the spinbox creation in + // nsNumber/SearchControlFrame::CreateAnonymousContent. + return aDefaultAppearance == StyleAppearance::NumberInput || + aDefaultAppearance == StyleAppearance::Searchfield; + case StyleAppearance::Menulist: + // This affects the menulist button creation. + return aDefaultAppearance == StyleAppearance::Menulist; + default: + return false; + } +} + +nsChangeHint nsStyleDisplay::CalcDifference( + const nsStyleDisplay& aNewData, const nsStylePosition& aOldPosition) const { + if (mDisplay != aNewData.mDisplay || mContain != aNewData.mContain || + (mFloat == StyleFloat::None) != (aNewData.mFloat == StyleFloat::None) || + mTopLayer != aNewData.mTopLayer || mResize != aNewData.mResize) { + return nsChangeHint_ReconstructFrame; + } + + // `content-visibility` can impact whether or not this frame has containment, + // so we reconstruct the frame like we do above. + // TODO: We should avoid reconstruction here, per bug 1765615. + if (mContentVisibility != aNewData.mContentVisibility) { + return nsChangeHint_ReconstructFrame; + } + + // Same issue as above for now. + if (mContainerType != aNewData.mContainerType) { + return nsChangeHint_ReconstructFrame; + } + + auto oldAppearance = EffectiveAppearance(); + auto newAppearance = aNewData.EffectiveAppearance(); + if (oldAppearance != newAppearance) { + // Changes to the relevant default appearance changes in + // AppearanceValueRequiresFrameReconstruction require reconstruction on + // their own, so we can just pick either the new or the old. + if (AppearanceValueAffectsFrames(oldAppearance, mDefaultAppearance) || + AppearanceValueAffectsFrames(newAppearance, mDefaultAppearance)) { + 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 (IsRelativelyOrStickyPositionedStyle() != + aNewData.IsRelativelyOrStickyPositionedStyle()) { + 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 || + mScrollSnapType != aNewData.mScrollSnapType || + mScrollSnapStop != aNewData.mScrollSnapStop) { + hint |= nsChangeHint_RepaintFrame; + } + if (mScrollBehavior != aNewData.mScrollBehavior) { + 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 might need to reframe in the case of hidden -> non-hidden case + // though, since nsHTMLScrollFrame::CreateAnonymousContent avoids + // creating scrollbars altogether for overflow: hidden. That seems it + // could create some interesting perf cliffs... + 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; + } + } + + if (mScrollbarGutter != aNewData.mScrollbarGutter) { + if (IsScrollableOverflow()) { + // Changing scrollbar-gutter affects available inline-size of a inner + // scrolled frame, so we need a reflow for scrollbar change. + hint |= nsChangeHint_ReflowHintsForScrollbarChange; + } else { + // scrollbar-gutter only applies to the scroll containers. + hint |= nsChangeHint_NeutralChange; + } + } + + 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 (mWebkitLineClamp != aNewData.mWebkitLineClamp || + mVerticalAlign != aNewData.mVerticalAlign || + mBaselineSource != aNewData.mBaselineSource) { + // 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 (mClear != aNewData.mClear || 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 = CalcTransformPropertyDifference(aNewData); + + 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_UNCONDITIONAL | + StyleWillChangeBits::SCROLL | StyleWillChangeBits::OPACITY | + StyleWillChangeBits::PERSPECTIVE | StyleWillChangeBits::TRANSFORM | + StyleWillChangeBits::Z_INDEX)) { + hint |= nsChangeHint_RepaintFrame; + } + + if (willChangeBitsChanged & + (StyleWillChangeBits::FIXPOS_CB_NON_SVG | StyleWillChangeBits::TRANSFORM | + StyleWillChangeBits::PERSPECTIVE | StyleWillChangeBits::POSITION | + StyleWillChangeBits::CONTAIN)) { + 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. + + // TODO(emilio): Figure out change hints for container-name, maybe it needs to + // be handled by the style system as a special-case (since it changes + // container-query selection on descendants). + if (!hint && (mWillChange != aNewData.mWillChange || + mOverflowAnchor != aNewData.mOverflowAnchor || + mContainerName != aNewData.mContainerName)) { + hint |= nsChangeHint_NeutralChange; + } + + return hint; +} + +nsChangeHint nsStyleDisplay::CalcTransformPropertyDifference( + const nsStyleDisplay& aNewData) const { + 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; + } + + return transformHint; +} + +// -------------------- +// nsStyleVisibility +// + +nsStyleVisibility::nsStyleVisibility(const Document& aDocument) + : mDirection(aDocument.GetBidiOptions() == IBMBIDI_TEXTDIRECTION_RTL + ? StyleDirection::Rtl + : StyleDirection::Ltr), + mVisible(StyleVisibility::Visible), + mImageRendering(StyleImageRendering::Auto), + mWritingMode(StyleWritingModeProperty::HorizontalTb), + mTextOrientation(StyleTextOrientation::Mixed), + mMozBoxCollapse(StyleMozBoxCollapse::Flex), + mPrintColorAdjust(StylePrintColorAdjust::Economy), + mImageOrientation(StyleImageOrientation::FromImage) { + MOZ_COUNT_CTOR(nsStyleVisibility); +} + +nsStyleVisibility::nsStyleVisibility(const nsStyleVisibility& aSource) + : mDirection(aSource.mDirection), + mVisible(aSource.mVisible), + mImageRendering(aSource.mImageRendering), + mWritingMode(aSource.mWritingMode), + mTextOrientation(aSource.mTextOrientation), + mMozBoxCollapse(aSource.mMozBoxCollapse), + mPrintColorAdjust(aSource.mPrintColorAdjust), + mImageOrientation(aSource.mImageOrientation) { + 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. + return nsChangeHint_ReconstructFrame; + } + 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 (mVisible == StyleVisibility::Collapse || + aNewData.mVisible == StyleVisibility::Collapse) { + hint |= NS_STYLE_HINT_REFLOW; + } else { + hint |= NS_STYLE_HINT_VISUAL; + } + } + if (mTextOrientation != aNewData.mTextOrientation || + mMozBoxCollapse != aNewData.mMozBoxCollapse) { + hint |= NS_STYLE_HINT_REFLOW; + } + if (mImageRendering != aNewData.mImageRendering) { + hint |= nsChangeHint_RepaintFrame; + } + if (mPrintColorAdjust != aNewData.mPrintColorAdjust) { + // color-adjust only affects media where dynamic changes can't happen. + hint |= nsChangeHint_NeutralChange; + } + return hint; +} + +StyleImageOrientation nsStyleVisibility::UsedImageOrientation( + imgIRequest* aRequest, StyleImageOrientation aOrientation) { + if (aOrientation == StyleImageOrientation::FromImage || !aRequest) { + return aOrientation; + } + + nsCOMPtr<nsIPrincipal> triggeringPrincipal = + aRequest->GetTriggeringPrincipal(); + + // If the request was for a blob, the request may not have a triggering + // principal and we should use the input orientation. + if (!triggeringPrincipal) { + return aOrientation; + } + + nsCOMPtr<nsIURI> uri = aRequest->GetURI(); + // If the image request is a data uri, then treat the request as a + // same origin request. + bool isSameOrigin = + uri->SchemeIs("data") || triggeringPrincipal->IsSameOrigin(uri); + + // If the image request is a cross-origin request that does not use CORS, + // do not enforce the image orientation found in the style. Use the image + // orientation found in the exif data. + if (!isSameOrigin && !nsLayoutUtils::ImageRequestUsesCORS(aRequest)) { + return StyleImageOrientation::FromImage; + } + + return aOrientation; +} + +//----------------------- +// 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(StyleTextDecorationStyle::Solid), + mUnicodeBidi(StyleUnicodeBidi::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 StyleAbsoluteColor DefaultColor(const Document& aDocument) { + return StyleAbsoluteColor::FromColor( + PreferenceSheet::PrefsFor(aDocument) + .ColorsFor(aDocument.DefaultColorScheme()) + .mDefault); +} + +nsStyleText::nsStyleText(const Document& aDocument) + : mColor(DefaultColor(aDocument)), + mForcedColorAdjust(StyleForcedColorAdjust::Auto), + mTextTransform(StyleTextTransform::None()), + mTextAlign(StyleTextAlign::Start), + mTextAlignLast(StyleTextAlignLast::Auto), + mTextJustify(StyleTextJustify::Auto), + mWhiteSpace(StyleWhiteSpace::Normal), + mHyphens(StyleHyphens::Manual), + mRubyAlign(StyleRubyAlign::SpaceAround), + mRubyPosition(StyleRubyPosition::AlternateOver), + mTextSizeAdjust(StyleTextSizeAdjust::Auto), + mTextCombineUpright(StyleTextCombineUpright::None), + mMozControlCharacterVisibility( + StaticPrefs::layout_css_control_characters_visible() + ? StyleMozControlCharacterVisibility::Visible + : StyleMozControlCharacterVisibility::Hidden), + mTextRendering(StyleTextRendering::Auto), + mTextEmphasisColor(StyleColor::CurrentColor()), + mWebkitTextFillColor(StyleColor::CurrentColor()), + mWebkitTextStrokeColor(StyleColor::CurrentColor()), + mTabSize(StyleNonNegativeLengthOrNumber::Number(8.f)), + 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") + ? StyleTextEmphasisPosition::UNDER + : StyleTextEmphasisPosition::OVER; +} + +nsStyleText::nsStyleText(const nsStyleText& aSource) + : mColor(aSource.mColor), + mForcedColorAdjust(aSource.mForcedColorAdjust), + 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), + mMozControlCharacterVisibility(aSource.mMozControlCharacterVisibility), + mTextEmphasisPosition(aSource.mTextEmphasisPosition), + mTextRendering(aSource.mTextRendering), + mTextEmphasisColor(aSource.mTextEmphasisColor), + mWebkitTextFillColor(aSource.mWebkitTextFillColor), + mWebkitTextStrokeColor(aSource.mWebkitTextStrokeColor), + mTabSize(aSource.mTabSize), + 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), + mHyphenateCharacter(aSource.mHyphenateCharacter), + mWebkitTextSecurity(aSource.mWebkitTextSecurity) { + 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 || + mMozControlCharacterVisibility != + aNewData.mMozControlCharacterVisibility) { + 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) || + (mTabSize != aNewData.mTabSize) || + (mHyphenateCharacter != aNewData.mHyphenateCharacter) || + (mWebkitTextSecurity != aNewData.mWebkitTextSecurity)) { + 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_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 || + mForcedColorAdjust != aNewData.mForcedColorAdjust) { + return nsChangeHint_NeutralChange; + } + + return nsChangeHint(0); +} + +LogicalSide nsStyleText::TextEmphasisSide(WritingMode aWM) const { + bool noLeftBit = !(mTextEmphasisPosition & StyleTextEmphasisPosition::LEFT); + DebugOnly<bool> noRightBit = + !(mTextEmphasisPosition & StyleTextEmphasisPosition::RIGHT); + bool noOverBit = !(mTextEmphasisPosition & StyleTextEmphasisPosition::OVER); + DebugOnly<bool> noUnderBit = + !(mTextEmphasisPosition & StyleTextEmphasisPosition::UNDER); + + MOZ_ASSERT((noOverBit != noUnderBit) && + ((noLeftBit != noRightBit) || noRightBit)); + mozilla::Side side = aWM.IsVertical() ? (noLeftBit ? eSideRight : eSideLeft) + : (noOverBit ? eSideBottom : eSideTop); + 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}, + mAccentColor(StyleColorOrAuto::Auto()), + mCaretColor(StyleColorOrAuto::Auto()), + mScrollbarColor(StyleScrollbarColor::Auto()), + mColorScheme(StyleColorScheme{{}, {}}) { + 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), + mAccentColor(aSource.mAccentColor), + mCaretColor(aSource.mCaretColor), + mScrollbarColor(aSource.mScrollbarColor), + mColorScheme(aSource.mColorScheme) { + 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 { + // 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.) + // + // pointer-events changes can change event regions overrides on layers and + // so needs a repaint. + const auto kPointerEventsHint = + nsChangeHint_NeedReflow | nsChangeHint_SchedulePaint; + + nsChangeHint hint = nsChangeHint(0); + if (mCursor != aNewData.mCursor) { + hint |= nsChangeHint_UpdateCursor; + } + + if (mPointerEvents != aNewData.mPointerEvents) { + hint |= kPointerEventsHint; + } + + if (mUserModify != aNewData.mUserModify) { + hint |= NS_STYLE_HINT_VISUAL; + } + + if (mInert != aNewData.mInert) { + // inert affects pointer-events, user-modify, user-select, user-focus and + // -moz-user-input, do the union of all them (minus + // nsChangeHint_NeutralChange which isn't needed if there's any other hint). + hint |= NS_STYLE_HINT_VISUAL | kPointerEventsHint; + } + + if (mUserFocus != aNewData.mUserFocus || mUserInput != aNewData.mUserInput) { + hint |= nsChangeHint_NeutralChange; + } + + if (mCaretColor != aNewData.mCaretColor || + mAccentColor != aNewData.mAccentColor || + mScrollbarColor != aNewData.mScrollbarColor || + mColorScheme != aNewData.mColorScheme) { + hint |= nsChangeHint_RepaintFrame; + } + + return hint; +} + +//----------------------- +// nsStyleUIReset +// + +nsStyleUIReset::nsStyleUIReset(const Document& aDocument) + : mUserSelect(StyleUserSelect::Auto), + mScrollbarWidth(StyleScrollbarWidth::Auto), + mMozForceBrokenImageIcon(false), + mMozSubtreeHiddenOnlyVisually(false), + mIMEMode(StyleImeMode::Auto), + mWindowDragging(StyleWindowDragging::Default), + mWindowShadow(StyleWindowShadow::Default), + mWindowOpacity(1.0), + mMozWindowInputRegionMargin(StyleLength::Zero()), + mWindowTransformOrigin{LengthPercentage::FromPercentage(0.5), + LengthPercentage::FromPercentage(0.5), + {0.}}, + 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), + mAnimationCompositionCount(1), + mAnimationTimelineCount(1), + mScrollTimelines( + nsStyleAutoArray<StyleScrollTimeline>::WITH_SINGLE_INITIAL_ELEMENT), + mScrollTimelineNameCount(1), + mScrollTimelineAxisCount(1), + mViewTimelines( + nsStyleAutoArray<StyleViewTimeline>::WITH_SINGLE_INITIAL_ELEMENT), + mViewTimelineNameCount(1), + mViewTimelineAxisCount(1), + mViewTimelineInsetCount(1) { + MOZ_COUNT_CTOR(nsStyleUIReset); + mTransitions[0].SetInitialValues(); + mAnimations[0].SetInitialValues(); +} + +nsStyleUIReset::nsStyleUIReset(const nsStyleUIReset& aSource) + : mUserSelect(aSource.mUserSelect), + mScrollbarWidth(aSource.mScrollbarWidth), + mMozForceBrokenImageIcon(aSource.mMozForceBrokenImageIcon), + mMozSubtreeHiddenOnlyVisually(aSource.mMozSubtreeHiddenOnlyVisually), + mIMEMode(aSource.mIMEMode), + mWindowDragging(aSource.mWindowDragging), + mWindowShadow(aSource.mWindowShadow), + mWindowOpacity(aSource.mWindowOpacity), + mMozWindowInputRegionMargin(aSource.mMozWindowInputRegionMargin), + mMozWindowTransform(aSource.mMozWindowTransform), + mWindowTransformOrigin(aSource.mWindowTransformOrigin), + 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), + mAnimationCompositionCount(aSource.mAnimationCompositionCount), + mAnimationTimelineCount(aSource.mAnimationTimelineCount), + mScrollTimelines(aSource.mScrollTimelines.Clone()), + mScrollTimelineNameCount(aSource.mScrollTimelineNameCount), + mScrollTimelineAxisCount(aSource.mScrollTimelineAxisCount), + mViewTimelines(aSource.mViewTimelines.Clone()), + mViewTimelineNameCount(aSource.mViewTimelineNameCount), + mViewTimelineAxisCount(aSource.mViewTimelineAxisCount), + mViewTimelineInsetCount(aSource.mViewTimelineInsetCount) { + 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 (mMozSubtreeHiddenOnlyVisually != aNewData.mMozSubtreeHiddenOnlyVisually) { + hint |= nsChangeHint_RepaintFrame; + } + 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 && + (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 || + mAnimationCompositionCount != aNewData.mAnimationCompositionCount || + mAnimationTimelineCount != aNewData.mAnimationTimelineCount || + mIMEMode != aNewData.mIMEMode || + mWindowOpacity != aNewData.mWindowOpacity || + mMozWindowInputRegionMargin != aNewData.mMozWindowInputRegionMargin || + mMozWindowTransform != aNewData.mMozWindowTransform || + mScrollTimelines != aNewData.mScrollTimelines || + mScrollTimelineNameCount != aNewData.mScrollTimelineNameCount || + mScrollTimelineAxisCount != aNewData.mScrollTimelineAxisCount || + mViewTimelines != aNewData.mViewTimelines || + mViewTimelineNameCount != aNewData.mViewTimelineNameCount || + mViewTimelineAxisCount != aNewData.mViewTimelineAxisCount || + mViewTimelineInsetCount != aNewData.mViewTimelineInsetCount)) { + hint |= nsChangeHint_NeutralChange; + } + + return hint; +} + +StyleScrollbarWidth nsStyleUIReset::ScrollbarWidth() const { + if (MOZ_UNLIKELY(StaticPrefs::layout_css_scrollbar_width_thin_disabled())) { + if (mScrollbarWidth == StyleScrollbarWidth::Thin) { + return StyleScrollbarWidth::Auto; + } + } + return mScrollbarWidth; +} + +//----------------------- +// 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::Round: { + const auto& round = AsRound(); + ScaleNode(*round.value); + ScaleNode(*round.step); + break; + } + case Tag::ModRem: { + const auto& mod_rem = AsModRem(); + ScaleNode(*mod_rem.dividend); + ScaleNode(*mod_rem.divisor); + break; + } + case Tag::MinMax: { + for (auto& child : AsMinMax()._0.AsSpan()) { + ScaleNode(child); + } + break; + } + case Tag::Sum: { + for (auto& child : AsSum().AsSpan()) { + ScaleNode(child); + } + break; + } + case Tag::Negate: { + const auto& negate = AsNegate(); + ScaleNode(*negate); + break; + } + case Tag::Hypot: { + for (const auto& child : AsHypot().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::Negate: { + const auto& negate = AsNegate(); + auto value = negate->ResolveInternal(aPercentageBasis, aConverter); + return -value; + } + 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::Round: { + const auto& round = AsRound(); + + // Make sure to do the math in CSS pixels, so that floor() and ceil() + // below round to an integer number of CSS pixels, not app units. + CSSCoord step, value; + if constexpr (std::is_same_v<ResultT, CSSCoord>) { + step = round.step->ResolveInternal(aPercentageBasis, aConverter); + value = round.value->ResolveInternal(aPercentageBasis, aConverter); + } else { + step = CSSPixel::FromAppUnits( + round.step->ResolveInternal(aPercentageBasis, aConverter)); + value = CSSPixel::FromAppUnits( + round.value->ResolveInternal(aPercentageBasis, aConverter)); + } + + const float div = value / step; + const CSSCoord lowerBound = std::floor(div) * step; + const CSSCoord upperBound = std::ceil(div) * step; + const CSSCoord result = [&] { + switch (round.strategy) { + case StyleRoundingStrategy::Nearest: + // In case of a tie, use the upper bound + if (value - lowerBound < upperBound - value) { + return lowerBound; + } + return upperBound; + case StyleRoundingStrategy::Up: + return upperBound; + case StyleRoundingStrategy::Down: + return lowerBound; + case StyleRoundingStrategy::ToZero: + // In case of a tie, use the upper bound + return std::abs(lowerBound) < std::abs(upperBound) ? lowerBound + : upperBound; + } + MOZ_ASSERT_UNREACHABLE("Unknown rounding strategy"); + return CSSCoord(0); + }(); + + if constexpr (std::is_same_v<ResultT, CSSCoord>) { + return result; + } else { + return CSSPixel::ToAppUnits(result); + } + } + case Tag::ModRem: { + const auto& mod_rem = AsModRem(); + + // Make sure to do the math in CSS pixels, so that floor() and trunc() + // below round to an integer number of CSS pixels, not app units. + CSSCoord dividend, divisor; + if constexpr (std::is_same_v<ResultT, CSSCoord>) { + dividend = + mod_rem.dividend->ResolveInternal(aPercentageBasis, aConverter); + divisor = + mod_rem.divisor->ResolveInternal(aPercentageBasis, aConverter); + } else { + dividend = CSSPixel::FromAppUnits( + mod_rem.dividend->ResolveInternal(aPercentageBasis, aConverter)); + divisor = CSSPixel::FromAppUnits( + mod_rem.divisor->ResolveInternal(aPercentageBasis, aConverter)); + } + + const CSSCoord result = + mod_rem.op == StyleModRemOp::Mod + ? dividend - divisor * std::floor(dividend / divisor) + : dividend - divisor * std::trunc(dividend / divisor); + + if constexpr (std::is_same_v<ResultT, CSSCoord>) { + return result; + } else { + return CSSPixel::ToAppUnits(result); + } + } + 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; + } + case Tag::Hypot: { + // Doing math in CSS pixels to avoid exceeding integer range of app units + CSSCoord result = 0; + for (const auto& child : AsHypot().AsSpan()) { + CSSCoord value; + if constexpr (std::is_same_v<ResultT, CSSCoord>) { + value = child.ResolveInternal(aPercentageBasis, aConverter); + } else { + value = CSSPixel::FromAppUnits( + child.ResolveInternal(aPercentageBasis, aConverter)); + } + result += std::pow(value, 2); + } + result = std::sqrt(result); + + if constexpr (std::is_same_v<ResultT, CSSCoord>) { + return result; + } else { + return CSSPixel::ToAppUnits(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 (std::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); +} + +bool nsStyleDisplay::PrecludesSizeContainmentOrContentVisibilityWithFrame( + const nsIFrame& aFrame) const { + // Note: The spec for size containment says it should have no effect on + // non-atomic, inline-level boxes. + bool isNonReplacedInline = aFrame.IsFrameOfType(nsIFrame::eLineParticipant) && + !aFrame.IsFrameOfType(nsIFrame::eReplaced); + return isNonReplacedInline || IsInternalRubyDisplayType() || + DisplayInside() == mozilla::StyleDisplayInside::Table || + IsInnerTableStyle(); +} + +ContainSizeAxes nsStyleDisplay::GetContainSizeAxes( + const nsIFrame& aFrame) const { + // Short circuit for no containment whatsoever + if (MOZ_LIKELY(!mEffectiveContainment)) { + return ContainSizeAxes(false, false); + } + + if (PrecludesSizeContainmentOrContentVisibilityWithFrame(aFrame)) { + return ContainSizeAxes(false, false); + } + + // Internal SVG elements do not use the standard CSS box model, and wouldn't + // be affected by size containment. By disabling it we prevent them from + // becoming query containers for size features. + if (aFrame.HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) { + return ContainSizeAxes(false, false); + } + + // https://drafts.csswg.org/css-contain-2/#content-visibility + // If this content skips its content via content-visibility, it always has + // size containment. + if (MOZ_LIKELY(!(mEffectiveContainment & StyleContain::SIZE)) && + MOZ_UNLIKELY(aFrame.HidesContent())) { + return ContainSizeAxes(true, true); + } + + return ContainSizeAxes( + static_cast<bool>(mEffectiveContainment & StyleContain::INLINE_SIZE), + static_cast<bool>(mEffectiveContainment & StyleContain::BLOCK_SIZE)); +} + +StyleContentVisibility nsStyleDisplay::ContentVisibility( + const nsIFrame& aFrame) const { + if (MOZ_LIKELY(mContentVisibility == StyleContentVisibility::Visible)) { + return StyleContentVisibility::Visible; + } + if (PrecludesSizeContainmentOrContentVisibilityWithFrame(aFrame)) { + return StyleContentVisibility::Visible; + } + return mContentVisibility; +} + +static nscoord Resolve(const StyleContainIntrinsicSize& aSize, + nscoord aNoneValue, const nsIFrame& aFrame, + LogicalAxis aAxis) { + if (aSize.IsNone()) { + return aNoneValue; + } + if (aSize.IsLength()) { + return aSize.AsLength().ToAppUnits(); + } + MOZ_ASSERT(aSize.IsAutoLength()); + if (const auto* element = Element::FromNodeOrNull(aFrame.GetContent())) { + Maybe<float> lastSize = aAxis == eLogicalAxisBlock + ? element->GetLastRememberedBSize() + : element->GetLastRememberedISize(); + if (lastSize && aFrame.HidesContent()) { + return CSSPixel::ToAppUnits(*lastSize); + } + } + return aSize.AsAutoLength().ToAppUnits(); +} + +Maybe<nscoord> ContainSizeAxes::ContainIntrinsicBSize( + const nsIFrame& aFrame, nscoord aNoneValue) const { + if (!mBContained) { + return Nothing(); + } + const StyleContainIntrinsicSize& bSize = + aFrame.StylePosition()->ContainIntrinsicBSize(aFrame.GetWritingMode()); + return Some(Resolve(bSize, aNoneValue, aFrame, eLogicalAxisBlock)); +} + +Maybe<nscoord> ContainSizeAxes::ContainIntrinsicISize( + const nsIFrame& aFrame, nscoord aNoneValue) const { + if (!mIContained) { + return Nothing(); + } + const StyleContainIntrinsicSize& iSize = + aFrame.StylePosition()->ContainIntrinsicISize(aFrame.GetWritingMode()); + return Some(Resolve(iSize, aNoneValue, aFrame, eLogicalAxisInline)); +} + +nsSize ContainSizeAxes::ContainSize(const nsSize& aUncontainedSize, + const nsIFrame& aFrame) const { + if (!IsAny()) { + return aUncontainedSize; + } + if (aFrame.GetWritingMode().IsVertical()) { + return nsSize( + ContainIntrinsicBSize(aFrame).valueOr(aUncontainedSize.Width()), + ContainIntrinsicISize(aFrame).valueOr(aUncontainedSize.Height())); + } + return nsSize( + ContainIntrinsicISize(aFrame).valueOr(aUncontainedSize.Width()), + ContainIntrinsicBSize(aFrame).valueOr(aUncontainedSize.Height())); +} + +IntrinsicSize ContainSizeAxes::ContainIntrinsicSize( + const IntrinsicSize& aUncontainedSize, const nsIFrame& aFrame) const { + if (!IsAny()) { + return aUncontainedSize; + } + IntrinsicSize result(aUncontainedSize); + const bool isVerticalWM = aFrame.GetWritingMode().IsVertical(); + if (Maybe<nscoord> containBSize = ContainIntrinsicBSize(aFrame)) { + (isVerticalWM ? result.width : result.height) = containBSize; + } + if (Maybe<nscoord> containISize = ContainIntrinsicISize(aFrame)) { + (isVerticalWM ? result.height : result.width) = containISize; + } + return result; +} |