diff options
Diffstat (limited to 'layout/painting')
76 files changed, 36763 insertions, 0 deletions
diff --git a/layout/painting/ActiveLayerTracker.cpp b/layout/painting/ActiveLayerTracker.cpp new file mode 100644 index 0000000000..fd36734c3c --- /dev/null +++ b/layout/painting/ActiveLayerTracker.cpp @@ -0,0 +1,414 @@ +/* -*- 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/. */ + +#include "ActiveLayerTracker.h" + +#include "mozilla/AnimationUtils.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/gfx/gfxVars.h" +#include "mozilla/gfx/Matrix.h" +#include "mozilla/EffectSet.h" +#include "mozilla/MotionPathUtils.h" +#include "mozilla/PodOperations.h" +#include "mozilla/StaticPtr.h" +#include "gfx2DGlue.h" +#include "nsExpirationTracker.h" +#include "nsContainerFrame.h" +#include "nsIContent.h" +#include "nsRefreshDriver.h" +#include "nsPIDOMWindow.h" +#include "mozilla/dom/Document.h" +#include "nsAnimationManager.h" +#include "nsStyleTransformMatrix.h" +#include "nsTransitionManager.h" +#include "nsDisplayList.h" +#include "nsDOMCSSDeclaration.h" +#include "nsLayoutUtils.h" + +namespace mozilla { + +using namespace gfx; + +/** + * This tracks the state of a frame that may need active layers due to + * ongoing content changes or style changes that indicate animation. + * + * When no changes of *any* kind are detected after 75-100ms we remove this + * object. Because we only track all kinds of activity with a single + * nsExpirationTracker, it's possible a frame might remain active somewhat + * spuriously if different kinds of changes kept happening, but that almost + * certainly doesn't matter. + */ +class LayerActivity { + public: + enum ActivityIndex { + ACTIVITY_OPACITY, + ACTIVITY_TRANSFORM, + + ACTIVITY_SCALE, + ACTIVITY_TRIGGERED_REPAINT, + + // keep as last item + ACTIVITY_COUNT + }; + + explicit LayerActivity(nsIFrame* aFrame) : mFrame(aFrame), mContent(nullptr) { + PodArrayZero(mRestyleCounts); + } + ~LayerActivity(); + nsExpirationState* GetExpirationState() { return &mState; } + uint8_t& RestyleCountForProperty(nsCSSPropertyID aProperty) { + return mRestyleCounts[GetActivityIndexForProperty(aProperty)]; + } + + static ActivityIndex GetActivityIndexForProperty(nsCSSPropertyID aProperty) { + switch (aProperty) { + case eCSSProperty_opacity: + return ACTIVITY_OPACITY; + case eCSSProperty_transform: + case eCSSProperty_translate: + case eCSSProperty_rotate: + case eCSSProperty_scale: + case eCSSProperty_offset_path: + case eCSSProperty_offset_distance: + case eCSSProperty_offset_rotate: + case eCSSProperty_offset_anchor: + // TODO: Bug 1559232: Add offset-position. + return ACTIVITY_TRANSFORM; + default: + MOZ_ASSERT(false); + return ACTIVITY_OPACITY; + } + } + + static ActivityIndex GetActivityIndexForPropertySet( + const nsCSSPropertyIDSet& aPropertySet) { + if (aPropertySet.IsSubsetOf( + nsCSSPropertyIDSet::TransformLikeProperties())) { + return ACTIVITY_TRANSFORM; + } + MOZ_ASSERT( + aPropertySet.IsSubsetOf(nsCSSPropertyIDSet::OpacityProperties())); + return ACTIVITY_OPACITY; + } + + // While tracked, exactly one of mFrame or mContent is non-null, depending + // on whether this property is stored on a frame or on a content node. + // When this property is expired by the layer activity tracker, both mFrame + // and mContent are nulled-out and the property is deleted. + nsIFrame* mFrame; + nsIContent* mContent; + + nsExpirationState mState; + + // Previous scale due to the CSS transform property. + Maybe<MatrixScales> mPreviousTransformScale; + + // Number of restyle operations detected + uint8_t mRestyleCounts[ACTIVITY_COUNT]; +}; + +class LayerActivityTracker final + : public nsExpirationTracker<LayerActivity, 4> { + public: + // 75-100ms is a good timeout period. We use 4 generations of 25ms each. + enum { GENERATION_MS = 100 }; + + explicit LayerActivityTracker(nsIEventTarget* aEventTarget) + : nsExpirationTracker<LayerActivity, 4>( + GENERATION_MS, "LayerActivityTracker", aEventTarget) {} + ~LayerActivityTracker() override { AgeAllGenerations(); } + + void NotifyExpired(LayerActivity* aObject) override; +}; + +static StaticAutoPtr<LayerActivityTracker> gLayerActivityTracker; + +LayerActivity::~LayerActivity() { + if (mFrame || mContent) { + NS_ASSERTION(gLayerActivityTracker, "Should still have a tracker"); + gLayerActivityTracker->RemoveObject(this); + } +} + +// Frames with this property have NS_FRAME_HAS_LAYER_ACTIVITY_PROPERTY set +NS_DECLARE_FRAME_PROPERTY_DELETABLE(LayerActivityProperty, LayerActivity) + +void LayerActivityTracker::NotifyExpired(LayerActivity* aObject) { + RemoveObject(aObject); + + nsIFrame* f = aObject->mFrame; + nsIContent* c = aObject->mContent; + aObject->mFrame = nullptr; + aObject->mContent = nullptr; + + MOZ_ASSERT((f == nullptr) != (c == nullptr), + "A LayerActivity object should always have a reference to either " + "its frame or its content"); + + if (f) { + f->RemoveStateBits(NS_FRAME_HAS_LAYER_ACTIVITY_PROPERTY); + f->RemoveProperty(LayerActivityProperty()); + } else { + c->RemoveProperty(nsGkAtoms::LayerActivity); + } +} + +static LayerActivity* GetLayerActivity(nsIFrame* aFrame) { + if (!aFrame->HasAnyStateBits(NS_FRAME_HAS_LAYER_ACTIVITY_PROPERTY)) { + return nullptr; + } + return aFrame->GetProperty(LayerActivityProperty()); +} + +static LayerActivity* GetLayerActivityForUpdate(nsIFrame* aFrame) { + LayerActivity* layerActivity = GetLayerActivity(aFrame); + if (layerActivity) { + gLayerActivityTracker->MarkUsed(layerActivity); + } else { + if (!gLayerActivityTracker) { + gLayerActivityTracker = + new LayerActivityTracker(GetMainThreadSerialEventTarget()); + } + layerActivity = new LayerActivity(aFrame); + gLayerActivityTracker->AddObject(layerActivity); + aFrame->AddStateBits(NS_FRAME_HAS_LAYER_ACTIVITY_PROPERTY); + aFrame->SetProperty(LayerActivityProperty(), layerActivity); + } + return layerActivity; +} + +static void IncrementMutationCount(uint8_t* aCount) { + *aCount = uint8_t(std::min(0xFF, *aCount + 1)); +} + +/* static */ +void ActiveLayerTracker::TransferActivityToContent(nsIFrame* aFrame, + nsIContent* aContent) { + if (!aFrame->HasAnyStateBits(NS_FRAME_HAS_LAYER_ACTIVITY_PROPERTY)) { + return; + } + LayerActivity* layerActivity = aFrame->TakeProperty(LayerActivityProperty()); + aFrame->RemoveStateBits(NS_FRAME_HAS_LAYER_ACTIVITY_PROPERTY); + if (!layerActivity) { + return; + } + layerActivity->mFrame = nullptr; + layerActivity->mContent = aContent; + aContent->SetProperty(nsGkAtoms::LayerActivity, layerActivity, + nsINode::DeleteProperty<LayerActivity>, true); +} + +/* static */ +void ActiveLayerTracker::TransferActivityToFrame(nsIContent* aContent, + nsIFrame* aFrame) { + auto* layerActivity = static_cast<LayerActivity*>( + aContent->TakeProperty(nsGkAtoms::LayerActivity)); + if (!layerActivity) { + return; + } + layerActivity->mContent = nullptr; + layerActivity->mFrame = aFrame; + aFrame->AddStateBits(NS_FRAME_HAS_LAYER_ACTIVITY_PROPERTY); + aFrame->SetProperty(LayerActivityProperty(), layerActivity); +} + +static void IncrementScaleRestyleCountIfNeeded(nsIFrame* aFrame, + LayerActivity* aActivity) { + const nsStyleDisplay* display = aFrame->StyleDisplay(); + if (!display->HasTransformProperty() && !display->HasIndividualTransform() && + display->mOffsetPath.IsNone()) { + // The transform was removed. + aActivity->mPreviousTransformScale = Nothing(); + IncrementMutationCount( + &aActivity->mRestyleCounts[LayerActivity::ACTIVITY_SCALE]); + return; + } + + // Compute the new scale due to the CSS transform property. + // Note: Motion path doesn't contribute to scale factor. (It only has 2d + // translate and 2d rotate, so we use Nothing() for it.) + nsStyleTransformMatrix::TransformReferenceBox refBox(aFrame); + Matrix4x4 transform = nsStyleTransformMatrix::ReadTransforms( + display->mTranslate, display->mRotate, display->mScale, Nothing(), + display->mTransform, refBox, AppUnitsPerCSSPixel()); + Matrix transform2D; + if (!transform.Is2D(&transform2D)) { + // We don't attempt to handle 3D transforms; just assume the scale changed. + aActivity->mPreviousTransformScale = Nothing(); + IncrementMutationCount( + &aActivity->mRestyleCounts[LayerActivity::ACTIVITY_SCALE]); + return; + } + + MatrixScales scale = transform2D.ScaleFactors(); + if (aActivity->mPreviousTransformScale == Some(scale)) { + return; // Nothing changed. + } + + aActivity->mPreviousTransformScale = Some(scale); + IncrementMutationCount( + &aActivity->mRestyleCounts[LayerActivity::ACTIVITY_SCALE]); +} + +/* static */ +void ActiveLayerTracker::NotifyRestyle(nsIFrame* aFrame, + nsCSSPropertyID aProperty) { + LayerActivity* layerActivity = GetLayerActivityForUpdate(aFrame); + uint8_t& mutationCount = layerActivity->RestyleCountForProperty(aProperty); + IncrementMutationCount(&mutationCount); + + if (nsCSSPropertyIDSet::TransformLikeProperties().HasProperty(aProperty)) { + IncrementScaleRestyleCountIfNeeded(aFrame, layerActivity); + } +} + +static bool IsPresContextInScriptAnimationCallback( + nsPresContext* aPresContext) { + if (aPresContext->RefreshDriver()->IsInRefresh()) { + return true; + } + // Treat timeouts/setintervals as scripted animation callbacks for our + // purposes. + nsPIDOMWindowInner* win = aPresContext->Document()->GetInnerWindow(); + return win && win->IsRunningTimeout(); +} + +/* static */ +void ActiveLayerTracker::NotifyInlineStyleRuleModified( + nsIFrame* aFrame, nsCSSPropertyID aProperty) { + if (IsPresContextInScriptAnimationCallback(aFrame->PresContext())) { + LayerActivity* layerActivity = GetLayerActivityForUpdate(aFrame); + // We know this is animated, so just hack the mutation count. + layerActivity->RestyleCountForProperty(aProperty) = 0xff; + } +} + +/* static */ +void ActiveLayerTracker::NotifyNeedsRepaint(nsIFrame* aFrame) { + LayerActivity* layerActivity = GetLayerActivityForUpdate(aFrame); + if (IsPresContextInScriptAnimationCallback(aFrame->PresContext())) { + // This is mirroring NotifyInlineStyleRuleModified's NotifyAnimated logic. + // Just max out the restyle count if we're in an animation callback. + layerActivity->mRestyleCounts[LayerActivity::ACTIVITY_TRIGGERED_REPAINT] = + 0xFF; + } else { + IncrementMutationCount( + &layerActivity + ->mRestyleCounts[LayerActivity::ACTIVITY_TRIGGERED_REPAINT]); + } +} + +static bool IsMotionPathAnimated(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame) { + return ActiveLayerTracker::IsStyleAnimated( + aBuilder, aFrame, nsCSSPropertyIDSet{eCSSProperty_offset_path}) || + (!aFrame->StyleDisplay()->mOffsetPath.IsNone() && + ActiveLayerTracker::IsStyleAnimated( + aBuilder, aFrame, + nsCSSPropertyIDSet{eCSSProperty_offset_distance, + eCSSProperty_offset_rotate, + eCSSProperty_offset_anchor})); +} + +/* static */ +bool ActiveLayerTracker::IsTransformAnimated(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame) { + return IsStyleAnimated(aBuilder, aFrame, + nsCSSPropertyIDSet::CSSTransformProperties()) || + IsMotionPathAnimated(aBuilder, aFrame); +} + +/* static */ +bool ActiveLayerTracker::IsTransformMaybeAnimated(nsIFrame* aFrame) { + return IsStyleAnimated(nullptr, aFrame, + nsCSSPropertyIDSet::CSSTransformProperties()) || + IsMotionPathAnimated(nullptr, aFrame); +} + +/* static */ +bool ActiveLayerTracker::IsStyleAnimated( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + const nsCSSPropertyIDSet& aPropertySet) { + MOZ_ASSERT( + aPropertySet.IsSubsetOf(nsCSSPropertyIDSet::TransformLikeProperties()) || + aPropertySet.IsSubsetOf(nsCSSPropertyIDSet::OpacityProperties()), + "Only subset of opacity or transform-like properties set calls this"); + + // For display:table content, transforms are applied to the table wrapper + // (primary frame) but their will-change style will be specified on the style + // frame and, unlike other transform properties, not inherited. + // As a result, for transform properties only we need to be careful to look up + // the will-change style on the _style_ frame. + const nsIFrame* styleFrame = nsLayoutUtils::GetStyleFrame(aFrame); + const nsCSSPropertyIDSet transformSet = + nsCSSPropertyIDSet::TransformLikeProperties(); + if ((styleFrame && (styleFrame->StyleDisplay()->mWillChange.bits & + StyleWillChangeBits::TRANSFORM)) && + aPropertySet.Intersects(transformSet) && + (!aBuilder || + aBuilder->IsInWillChangeBudget(aFrame, aFrame->GetSize()))) { + return true; + } + if ((aFrame->StyleDisplay()->mWillChange.bits & + StyleWillChangeBits::OPACITY) && + aPropertySet.Intersects(nsCSSPropertyIDSet::OpacityProperties()) && + (!aBuilder || + aBuilder->IsInWillChangeBudget(aFrame, aFrame->GetSize()))) { + return !StaticPrefs::gfx_will_change_ignore_opacity(); + } + + LayerActivity* layerActivity = GetLayerActivity(aFrame); + if (layerActivity) { + LayerActivity::ActivityIndex activityIndex = + LayerActivity::GetActivityIndexForPropertySet(aPropertySet); + if (layerActivity->mRestyleCounts[activityIndex] >= 2) { + // If the frame needs to be repainted frequently, we probably don't get + // much from treating the property as animated, *unless* this frame's + // 'scale' (which includes the bounds changes of a rotation) is changing. + // Marking a scaling transform as animating allows us to avoid resizing + // the texture, even if we have to repaint the contents of that texture. + if (layerActivity + ->mRestyleCounts[LayerActivity::ACTIVITY_TRIGGERED_REPAINT] < + 2 || + (aPropertySet.Intersects(transformSet) && + IsScaleSubjectToAnimation(aFrame))) { + return true; + } + } + } + + if (nsLayoutUtils::HasEffectiveAnimation(aFrame, aPropertySet)) { + return true; + } + + if (!aPropertySet.Intersects(transformSet) || + !aFrame->Combines3DTransformWithAncestors()) { + return false; + } + + // For preserve-3d, we check if there is any transform animation on its parent + // frames in the 3d rendering context. If there is one, this function will + // return true. + return IsStyleAnimated(aBuilder, aFrame->GetParent(), aPropertySet); +} + +/* static */ +bool ActiveLayerTracker::IsScaleSubjectToAnimation(nsIFrame* aFrame) { + // Check whether JavaScript is animating this frame's scale. + LayerActivity* layerActivity = GetLayerActivity(aFrame); + if (layerActivity && + layerActivity->mRestyleCounts[LayerActivity::ACTIVITY_SCALE] >= 2) { + return true; + } + + return AnimationUtils::FrameHasAnimatedScale(aFrame); +} + +/* static */ +void ActiveLayerTracker::Shutdown() { gLayerActivityTracker = nullptr; } + +} // namespace mozilla diff --git a/layout/painting/ActiveLayerTracker.h b/layout/painting/ActiveLayerTracker.h new file mode 100644 index 0000000000..7a6005f284 --- /dev/null +++ b/layout/painting/ActiveLayerTracker.h @@ -0,0 +1,102 @@ +/* -*- 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/. */ + +#ifndef ACTIVELAYERTRACKER_H_ +#define ACTIVELAYERTRACKER_H_ + +#include "nsCSSPropertyID.h" + +class nsIFrame; +class nsIContent; +class nsCSSPropertyIDSet; +class nsDOMCSSDeclaration; + +namespace mozilla { + +class nsDisplayListBuilder; + +/** + * This class receives various notifications about style changes and content + * changes that affect layerization decisions, and implements the heuristics + * that drive those decisions. It manages per-frame state to support those + * heuristics. + */ +class ActiveLayerTracker { + public: + static void Shutdown(); + + /* + * We track style changes to selected styles: + * eCSSProperty_transform, eCSSProperty_translate, + * eCSSProperty_rotate, eCSSProperty_scale + * eCSSProperty_offset_path, eCSSProperty_offset_distance, + * eCSSProperty_offset_rotate, eCSSProperty_offset_anchor, + * eCSSProperty_opacity + * and use that information to guess whether style changes are animated. + */ + + /** + * Notify aFrame's style property as having changed due to a restyle, + * and therefore possibly wanting an active layer to render that style. + * Any such marking will time out after a short period. + * @param aProperty the property that has changed + */ + static void NotifyRestyle(nsIFrame* aFrame, nsCSSPropertyID aProperty); + + /** + * Notify that a property in the inline style rule of aFrame's element + * has been modified. + */ + static void NotifyInlineStyleRuleModified(nsIFrame* aFrame, + nsCSSPropertyID aProperty); + /** + * Notify that a frame needs to be repainted. This is important for layering + * decisions where, say, aFrame's transform is updated from JS, but we need + * to repaint aFrame anyway, so we get no benefit from giving it its own + * layer. + */ + static void NotifyNeedsRepaint(nsIFrame* aFrame); + /** + * Return true if aFrame's property style in |aPropertySet| should be + * considered as being animated for constructing active layers. + */ + static bool IsStyleAnimated(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + const nsCSSPropertyIDSet& aPropertySet); + /** + * Return true if aFrame's transform-like property, + * i.e. transform/translate/rotate/scale, is animated. + */ + static bool IsTransformAnimated(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame); + /** + * Return true if aFrame's transform style should be considered as being + * animated for pre-rendering. + */ + static bool IsTransformMaybeAnimated(nsIFrame* aFrame); + /** + * Return true if aFrame either has an animated scale now, or is likely to + * have one in the future because it has a CSS animation or transition + * (which may not be playing right now) that affects its scale. + */ + static bool IsScaleSubjectToAnimation(nsIFrame* aFrame); + + /** + * Transfer the LayerActivity property to the frame's content node when the + * frame is about to be destroyed so that layer activity can be tracked + * throughout reframes of an element. Only call this when aFrame is the + * primary frame of aContent. + */ + static void TransferActivityToContent(nsIFrame* aFrame, nsIContent* aContent); + /** + * Transfer the LayerActivity property back to the content node's primary + * frame after the frame has been created. + */ + static void TransferActivityToFrame(nsIContent* aContent, nsIFrame* aFrame); +}; + +} // namespace mozilla + +#endif /* ACTIVELAYERTRACKER_H_ */ diff --git a/layout/painting/BorderCache.h b/layout/painting/BorderCache.h new file mode 100644 index 0000000000..bb6bf7e4ed --- /dev/null +++ b/layout/painting/BorderCache.h @@ -0,0 +1,67 @@ +/* -*- 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/. */ + +#ifndef mozilla_BorderCache_h_ +#define mozilla_BorderCache_h_ + +#include "mozilla/gfx/2D.h" +#include "mozilla/HashFunctions.h" +#include "PLDHashTable.h" + +namespace mozilla { +// Cache for best overlap and best dashLength. + +struct FourFloats { + typedef mozilla::gfx::Float Float; + + Float n[4]; + + FourFloats() { + n[0] = 0.0f; + n[1] = 0.0f; + n[2] = 0.0f; + n[3] = 0.0f; + } + + FourFloats(Float a, Float b, Float c, Float d) { + n[0] = a; + n[1] = b; + n[2] = c; + n[3] = d; + } + + bool operator==(const FourFloats& aOther) const { + return n[0] == aOther.n[0] && n[1] == aOther.n[1] && n[2] == aOther.n[2] && + n[3] == aOther.n[3]; + } +}; + +class FourFloatsHashKey : public PLDHashEntryHdr { + public: + typedef const FourFloats& KeyType; + typedef const FourFloats* KeyTypePointer; + + explicit FourFloatsHashKey(KeyTypePointer aKey) : mValue(*aKey) {} + FourFloatsHashKey(const FourFloatsHashKey& aToCopy) + : mValue(aToCopy.mValue) {} + ~FourFloatsHashKey() = default; + + KeyType GetKey() const { return mValue; } + bool KeyEquals(KeyTypePointer aKey) const { return *aKey == mValue; } + + static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; } + static PLDHashNumber HashKey(KeyTypePointer aKey) { + return HashBytes(aKey->n, sizeof(mozilla::gfx::Float) * 4); + } + enum { ALLOW_MEMMOVE = true }; + + private: + const FourFloats mValue; +}; + +} // namespace mozilla + +#endif /* mozilla_BorderCache_h_ */ diff --git a/layout/painting/BorderConsts.h b/layout/painting/BorderConsts.h new file mode 100644 index 0000000000..e25c697101 --- /dev/null +++ b/layout/painting/BorderConsts.h @@ -0,0 +1,22 @@ +/* -*- 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/. */ + +#ifndef mozilla_BorderConsts_h_ +#define mozilla_BorderConsts_h_ + +// thickness of dashed line relative to dotted line +#define DOT_LENGTH 1 // square +#define DASH_LENGTH 3 // 3 times longer than dot + +#define C_TL mozilla::eCornerTopLeft +#define C_TR mozilla::eCornerTopRight +#define C_BR mozilla::eCornerBottomRight +#define C_BL mozilla::eCornerBottomLeft + +#define BORDER_SEGMENT_COUNT_MAX 100 +#define BORDER_DOTTED_CORNER_MAX_RADIUS 100000 + +#endif /* mozilla_BorderConsts_h_ */ diff --git a/layout/painting/DashedCornerFinder.cpp b/layout/painting/DashedCornerFinder.cpp new file mode 100644 index 0000000000..c892179df9 --- /dev/null +++ b/layout/painting/DashedCornerFinder.cpp @@ -0,0 +1,416 @@ +/* -*- 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/. */ + +#include "DashedCornerFinder.h" + +#include <utility> + +#include "BorderCache.h" +#include "BorderConsts.h" +#include "nsTHashMap.h" + +namespace mozilla { + +using namespace gfx; + +struct BestDashLength { + typedef mozilla::gfx::Float Float; + + Float dashLength; + size_t count; + + BestDashLength() : dashLength(0.0f), count(0) {} + + BestDashLength(Float aDashLength, size_t aCount) + : dashLength(aDashLength), count(aCount) {} +}; + +static const size_t DashedCornerCacheSize = 256; +nsTHashMap<FourFloatsHashKey, BestDashLength> DashedCornerCache; + +DashedCornerFinder::DashedCornerFinder(const Bezier& aOuterBezier, + const Bezier& aInnerBezier, + Float aBorderWidthH, Float aBorderWidthV, + const Size& aCornerDim) + : mOuterBezier(aOuterBezier), + mInnerBezier(aInnerBezier), + mLastOuterP(aOuterBezier.mPoints[0]), + mLastInnerP(aInnerBezier.mPoints[0]), + mLastOuterT(0.0f), + mLastInnerT(0.0f), + mBestDashLength(DOT_LENGTH * DASH_LENGTH), + mHasZeroBorderWidth(false), + mHasMore(true), + mMaxCount(aCornerDim.width + aCornerDim.height), + mType(OTHER), + mI(0), + mCount(0) { + NS_ASSERTION(aBorderWidthH > 0.0f || aBorderWidthV > 0.0f, + "At least one side should have non-zero width."); + + DetermineType(aBorderWidthH, aBorderWidthV); + + Reset(); +} + +void DashedCornerFinder::DetermineType(Float aBorderWidthH, + Float aBorderWidthV) { + if (aBorderWidthH < aBorderWidthV) { + // Always draw from wider side to thinner side. + std::swap(mInnerBezier.mPoints[0], mInnerBezier.mPoints[3]); + std::swap(mInnerBezier.mPoints[1], mInnerBezier.mPoints[2]); + std::swap(mOuterBezier.mPoints[0], mOuterBezier.mPoints[3]); + std::swap(mOuterBezier.mPoints[1], mOuterBezier.mPoints[2]); + mLastOuterP = mOuterBezier.mPoints[0]; + mLastInnerP = mInnerBezier.mPoints[0]; + } + + // See the comment at mType declaration for each condition. + + Float borderRadiusA = + fabs(mOuterBezier.mPoints[0].x - mOuterBezier.mPoints[3].x); + Float borderRadiusB = + fabs(mOuterBezier.mPoints[0].y - mOuterBezier.mPoints[3].y); + if (aBorderWidthH == aBorderWidthV && borderRadiusA == borderRadiusB && + borderRadiusA > aBorderWidthH * 2.0f) { + Float curveHeight = borderRadiusA - aBorderWidthH / 2.0; + + mType = PERFECT; + Float borderLength = M_PI * curveHeight / 2.0f; + + Float dashWidth = aBorderWidthH * DOT_LENGTH * DASH_LENGTH; + size_t count = ceil(borderLength / dashWidth); + if (count % 2) { + count++; + } + mCount = count / 2 + 1; + mBestDashLength = borderLength / (aBorderWidthH * count); + } + + Float minBorderWidth = std::min(aBorderWidthH, aBorderWidthV); + if (minBorderWidth == 0.0f) { + mHasZeroBorderWidth = true; + } + + if (mType == OTHER && !mHasZeroBorderWidth) { + Float minBorderRadius = std::min(borderRadiusA, borderRadiusB); + Float maxBorderRadius = std::max(borderRadiusA, borderRadiusB); + Float maxBorderWidth = std::max(aBorderWidthH, aBorderWidthV); + + FindBestDashLength(minBorderWidth, maxBorderWidth, minBorderRadius, + maxBorderRadius); + } +} + +bool DashedCornerFinder::HasMore(void) const { + if (mHasZeroBorderWidth) { + return mI < mMaxCount && mHasMore; + } + + return mI < mCount; +} + +DashedCornerFinder::Result DashedCornerFinder::Next(void) { + Float lastOuterT, lastInnerT, outerT, innerT; + + if (mI == 0) { + lastOuterT = 0.0f; + lastInnerT = 0.0f; + } else { + if (mType == PERFECT) { + lastOuterT = lastInnerT = (mI * 2.0f - 0.5f) / ((mCount - 1) * 2.0f); + } else { + Float last2OuterT = mLastOuterT; + Float last2InnerT = mLastInnerT; + + (void)FindNext(mBestDashLength); + + // + // mLastOuterT lastOuterT + // | | + // v v + // +---+---+---+---+ <- last2OuterT + // | |###|###| | + // | |###|###| | + // | |###|###| | + // +---+---+---+---+ <- last2InnerT + // ^ ^ + // | | + // mLastInnerT lastInnerT + lastOuterT = (mLastOuterT + last2OuterT) / 2.0f; + lastInnerT = (mLastInnerT + last2InnerT) / 2.0f; + } + } + + if ((!mHasZeroBorderWidth && mI == mCount - 1) || + (mHasZeroBorderWidth && !mHasMore)) { + outerT = 1.0f; + innerT = 1.0f; + } else { + if (mType == PERFECT) { + outerT = innerT = (mI * 2.0f + 0.5f) / ((mCount - 1) * 2.0f); + } else { + Float last2OuterT = mLastOuterT; + Float last2InnerT = mLastInnerT; + + (void)FindNext(mBestDashLength); + + // + // outerT last2OuterT + // | | + // v v + // mLastOuterT -> +---+---+---+---+ + // | |###|###| | + // | |###|###| | + // | |###|###| | + // mLastInnerT -> +---+---+---+---+ + // ^ ^ + // | | + // innerT last2InnerT + outerT = (mLastOuterT + last2OuterT) / 2.0f; + innerT = (mLastInnerT + last2InnerT) / 2.0f; + } + } + + mI++; + + Bezier outerSectionBezier; + Bezier innerSectionBezier; + GetSubBezier(&outerSectionBezier, mOuterBezier, lastOuterT, outerT); + GetSubBezier(&innerSectionBezier, mInnerBezier, lastInnerT, innerT); + return DashedCornerFinder::Result(outerSectionBezier, innerSectionBezier); +} + +void DashedCornerFinder::Reset(void) { + mLastOuterP = mOuterBezier.mPoints[0]; + mLastInnerP = mInnerBezier.mPoints[0]; + mLastOuterT = 0.0f; + mLastInnerT = 0.0f; + mHasMore = true; +} + +Float DashedCornerFinder::FindNext(Float dashLength) { + Float upper = 1.0f; + Float lower = mLastOuterT; + + Point OuterP, InnerP; + // Start from upper bound to check if this is the last segment. + Float outerT = upper; + Float innerT; + Float W = 0.0f; + Float L = 0.0f; + + const Float LENGTH_MARGIN = 0.1f; + for (size_t i = 0; i < MAX_LOOP; i++) { + OuterP = GetBezierPoint(mOuterBezier, outerT); + InnerP = FindBezierNearestPoint(mInnerBezier, OuterP, outerT, &innerT); + + // Calculate approximate dash length. + // + // W = (W1 + W2) / 2 + // L = (OuterL + InnerL) / 2 + // dashLength = L / W + // + // ____----+----____ + // OuterP ___--- | ---___ mLastOuterP + // +--- | ---+ + // | | | + // | | | + // | W | W1 | + // | | | + // W2 | | | + // | | ______------+ + // | ____+---- mLastInnerP + // | ___--- + // | __--- + // +-- + // InnerP + // OuterL + // ____---------____ + // OuterP ___--- ---___ mLastOuterP + // +--- ---+ + // | L | + // | ___----------______ | + // | __--- -----+ + // | __-- | + // +-- | + // | InnerL ______------+ + // | ____----- mLastInnerP + // | ___--- + // | __--- + // +-- + // InnerP + Float W1 = (mLastOuterP - mLastInnerP).Length(); + Float W2 = (OuterP - InnerP).Length(); + Float OuterL = GetBezierLength(mOuterBezier, mLastOuterT, outerT); + Float InnerL = GetBezierLength(mInnerBezier, mLastInnerT, innerT); + W = (W1 + W2) / 2.0f; + L = (OuterL + InnerL) / 2.0f; + if (L > W * dashLength + LENGTH_MARGIN) { + if (i > 0) { + upper = outerT; + } + } else if (L < W * dashLength - LENGTH_MARGIN) { + if (i == 0) { + // This is the last segment with shorter dashLength. + mHasMore = false; + break; + } + lower = outerT; + } else { + break; + } + + outerT = (upper + lower) / 2.0f; + } + + mLastOuterP = OuterP; + mLastInnerP = InnerP; + mLastOuterT = outerT; + mLastInnerT = innerT; + + if (W == 0.0f) { + return 1.0f; + } + + return L / W; +} + +void DashedCornerFinder::FindBestDashLength(Float aMinBorderWidth, + Float aMaxBorderWidth, + Float aMinBorderRadius, + Float aMaxBorderRadius) { + // If dashLength is not calculateable, find it with binary search, + // such that there exists i that OuterP_i == OuterP_n and + // InnerP_i == InnerP_n with given dashLength. + + FourFloats key(aMinBorderWidth, aMaxBorderWidth, aMinBorderRadius, + aMaxBorderRadius); + BestDashLength best; + if (DashedCornerCache.Get(key, &best)) { + mCount = best.count; + mBestDashLength = best.dashLength; + return; + } + + Float lower = 1.0f; + Float upper = DOT_LENGTH * DASH_LENGTH; + Float dashLength = upper; + size_t targetCount = 0; + + const Float LENGTH_MARGIN = 0.1f; + for (size_t j = 0; j < MAX_LOOP; j++) { + size_t count; + Float actualDashLength; + if (!GetCountAndLastDashLength(dashLength, &count, &actualDashLength)) { + if (j == 0) { + mCount = mMaxCount; + break; + } + } + + if (j == 0) { + if (count == 1) { + // If only 1 segment fits, fill entire region + // + // count = 1 + // mCount = 1 + // | 1 | + // +---+---+ + // |###|###| + // |###|###| + // |###|###| + // +---+---+ + // 1 + mCount = 1; + break; + } + + // targetCount should be 2n. + // + // targetCount = 2 + // mCount = 2 + // | 1 | 2 | + // +---+---+---+---+ + // |###| | |###| + // |###| | |###| + // |###| | |###| + // +---+---+---+---+ + // 1 2 + // + // targetCount = 6 + // mCount = 4 + // | 1 | 2 | 3 | 4 | 5 | 6 | + // +---+---+---+---+---+---+---+---+---+---+---+---+ + // |###| | |###|###| | |###|###| | |###| + // |###| | |###|###| | |###|###| | |###| + // |###| | |###|###| | |###|###| | |###| + // +---+---+---+---+---+---+---+---+---+---+---+---+ + // 1 2 3 4 + if (count % 2) { + targetCount = count + 1; + } else { + targetCount = count; + } + + mCount = targetCount / 2 + 1; + } + + if (count == targetCount) { + mBestDashLength = dashLength; + + // actualDashLength won't be greater than dashLength. + if (actualDashLength > dashLength - LENGTH_MARGIN) { + break; + } + + // We started from upper bound, no need to update range when j == 0. + if (j > 0) { + upper = dashLength; + } + } else { + // |j == 0 && count != targetCount| means that |targetCount = count + 1|, + // and we started from upper bound, no need to update range when j == 0. + if (j > 0) { + if (count > targetCount) { + lower = dashLength; + } else { + upper = dashLength; + } + } + } + + dashLength = (upper + lower) / 2.0f; + } + + if (DashedCornerCache.Count() > DashedCornerCacheSize) { + DashedCornerCache.Clear(); + } + DashedCornerCache.InsertOrUpdate(key, + BestDashLength(mBestDashLength, mCount)); +} + +bool DashedCornerFinder::GetCountAndLastDashLength(Float aDashLength, + size_t* aCount, + Float* aActualDashLength) { + // Return the number of segments and the last segment's dashLength for + // the given dashLength. + + Reset(); + + for (size_t i = 0; i < mMaxCount; i++) { + Float actualDashLength = FindNext(aDashLength); + if (mLastOuterT >= 1.0f) { + *aCount = i + 1; + *aActualDashLength = actualDashLength; + return true; + } + } + + return false; +} + +} // namespace mozilla diff --git a/layout/painting/DashedCornerFinder.h b/layout/painting/DashedCornerFinder.h new file mode 100644 index 0000000000..3a409d19f7 --- /dev/null +++ b/layout/painting/DashedCornerFinder.h @@ -0,0 +1,274 @@ +/* -*- 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/. */ + +#ifndef mozilla_DashedCornerFinder_h_ +#define mozilla_DashedCornerFinder_h_ + +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/BezierUtils.h" + +namespace mozilla { + +// Calculate {OuterT_i, InnerT_i} for each 1 < i < n, that +// (OuterL_i + InnerL_i) / 2 == dashLength * (W_i + W_{i-1}) / 2 +// where +// OuterP_i: OuterCurve(OuterT_i) +// InnerP_i: InnerCurve(OuterT_i) +// OuterL_i: Elliptic arc length between OuterP_i - OuterP_{i-1} +// InnerL_i: Elliptic arc length between InnerP_i - InnerP_{i-1} +// W_i = |OuterP_i - InnerP_i| +// 1.0 < dashLength < 3.0 +// +// OuterP_1 OuterP_0 +// _+__-----------+ OuterCurve +// OuterP_2 __---- | OuterL_1 | +// __+--- | | +// __--- | OuterL_2 | | +// OuterP_3 _-- | | W_1 | W_0 +// _+ | | | +// / \ W_2 | | | +// / \ | | InnerL_1 | +// | \ | InnerL_2|____-------+ InnerCurve +// | \ |____----+ InnerP_0 +// | . \ __---+ InnerP_1 +// | \ / InnerP_2 +// | . /+ InnerP_3 +// | | +// | . | +// | | +// | | +// | | +// OuterP_{n-1} +--------+ InnerP_{n-1} +// | | +// | | +// | | +// | | +// | | +// OuterP_n +--------+ InnerP_n +// +// Returns region with [OuterCurve((OuterT_{2j} + OuterT_{2j-1}) / 2), +// OuterCurve((OuterT_{2j} + OuterT_{2j-1}) / 2), +// InnerCurve((OuterT_{2j} + OuterT_{2j+1}) / 2), +// InnerCurve((OuterT_{2j} + OuterT_{2j+1}) / 2)], +// to start and end with half segment. +// +// _+__----+------+ OuterCurve +// _+---- | |######| +// __+---#| | |######| +// _+---##|####| | |######| +// _-- |#####|#####| | |#####| +// _+ |#####|#####| | |#####| +// / \ |#####|####| | |#####| +// / \ |####|#####| | |#####| +// | \ |####|####| |____-+-----+ InnerCurve +// | \ |####|____+---+ +// | . \ __+---+ +// | \ / +// | . /+ +// | | +// | . | +// | | +// | | +// | | +// +--------+ +// | | +// | | +// +--------+ +// |########| +// |########| +// +--------+ + +class DashedCornerFinder { + typedef mozilla::gfx::Bezier Bezier; + typedef mozilla::gfx::Float Float; + typedef mozilla::gfx::Point Point; + typedef mozilla::gfx::Size Size; + + public: + struct Result { + // Control points for the outer curve and the inner curve. + // + // outerSectionBezier + // | + // v _+ 3 + // ___---#| + // 0 +---#######| + // |###########| + // |###########| + // |##########| + // |##########| + // |#########| + // |#####____+ 3 + // 0 +---- + // ^ + // | + // innerSectionBezier + Bezier outerSectionBezier; + Bezier innerSectionBezier; + + Result(const Bezier& aOuterSectionBezier, const Bezier& aInnerSectionBezier) + : outerSectionBezier(aOuterSectionBezier), + innerSectionBezier(aInnerSectionBezier) {} + }; + + // aCornerDim.width + // |<----------------->| + // | | + // --+-------------___---+-- + // ^ | __-- | ^ + // | | _- | | + // | | / | | aBorderWidthH + // | | / | | + // | | | | v + // | | | __--+-- + // aCornerDim.height | || _- + // | || / + // | | / + // | | | + // | | | + // | | | + // | | | + // v | | + // --+---------+ + // | | + // |<------->| + // aBorderWidthV + DashedCornerFinder(const Bezier& aOuterBezier, const Bezier& aInnerBezier, + Float aBorderWidthH, Float aBorderWidthV, + const Size& aCornerDim); + + bool HasMore(void) const; + Result Next(void); + + private: + static const size_t MAX_LOOP = 32; + + // Bezier control points for the outer curve and the inner curve. + // + // ___---+ outer curve + // __-- | + // _- | + // / | + // / | + // | | + // | __--+ inner curve + // | _- + // | / + // | / + // | | + // | | + // | | + // | | + // | | + // +---------+ + Bezier mOuterBezier; + Bezier mInnerBezier; + + Point mLastOuterP; + Point mLastInnerP; + Float mLastOuterT; + Float mLastInnerT; + + // Length for each segment, ratio of the border width at that point. + Float mBestDashLength; + + // If one of border-widths is 0, do not calculate mBestDashLength, and draw + // segments until it reaches the other side or exceeds mMaxCount. + bool mHasZeroBorderWidth; + bool mHasMore; + + // The maximum number of segments. + size_t mMaxCount; + + enum { + // radius.width + // |<----------------->| + // | | + // --+-------------___---+-- + // ^ | __-- | ^ + // | | _- | | + // | | / + | top-width + // | | / | | + // | | | | v + // | | | __--+-- + // radius.height | || _- + // | || / + // | | / + // | | | + // | | | + // | | | + // | | | + // v | | + // --+----+----+ + // | | + // |<------->| + // left-width + + // * top-width == left-width + // * radius.width == radius.height + // * top-width < radius.width * 2 + // + // Split the perfect circle's arc into 2n segments, each segment's length is + // top-width * dashLength. Then split the inner curve and the outer curve + // with same angles. + // + // radius.width + // |<---------------------->| + // | | v + // --+------------------------+-- + // ^ | | | top-width / 2 + // | | perfect | | + // | | circle's ___---+-- + // | | arc __-+ | ^ + // | | | _- | | + // radius.height | | | + | +-- + // | | | / \ | | + // | | | | \ | | + // | | | | \ | | + // | | +->| \ | | + // | | +---__ \ | | + // | | | --__ \ | | + // | | | ---__ \ | | + // v | | --_\|| + // --+----+----+--------------+ + // | | | + // |<-->| | + // left-width / 2 + PERFECT, + + // Other cases. + // + // Split the outer curve and the inner curve into 2n segments, each segment + // satisfies following: + // (OuterL_i + InnerL_i) / 2 == dashLength * (W_i + W_{i-1}) / 2 + OTHER + } mType; + + size_t mI; + size_t mCount; + + // Determine mType from parameters. + void DetermineType(Float aBorderWidthH, Float aBorderWidthV); + + // Reset calculation. + void Reset(void); + + // Find next segment. + Float FindNext(Float dashLength); + + // Find mBestDashLength for parameters. + void FindBestDashLength(Float aMinBorderWidth, Float aMaxBorderWidth, + Float aMinBorderRadius, Float aMaxBorderRadius); + + // Fill corner with dashes with given dash length, and return the number of + // segments and last segment's dash length. + bool GetCountAndLastDashLength(Float aDashLength, size_t* aCount, + Float* aActualDashLength); +}; + +} // namespace mozilla + +#endif /* mozilla_DashedCornerFinder_h_ */ diff --git a/layout/painting/DisplayItemClip.cpp b/layout/painting/DisplayItemClip.cpp new file mode 100644 index 0000000000..78aae2e1ec --- /dev/null +++ b/layout/painting/DisplayItemClip.cpp @@ -0,0 +1,486 @@ +/* -*- 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/. */ + +#include "DisplayItemClip.h" + +#include "gfxContext.h" +#include "gfxUtils.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/PathHelpers.h" +#include "mozilla/layers/StackingContextHelper.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/webrender/WebRenderTypes.h" +#include "nsPresContext.h" +#include "nsCSSRendering.h" +#include "nsLayoutUtils.h" +#include "nsRegion.h" + +using namespace mozilla::gfx; + +namespace mozilla { + +void DisplayItemClip::SetTo(const nsRect& aRect) { SetTo(aRect, nullptr); } + +void DisplayItemClip::SetTo(const nsRect& aRect, const nscoord* aRadii) { + mHaveClipRect = true; + mClipRect = aRect; + if (aRadii) { + mRoundedClipRects.SetLength(1); + mRoundedClipRects[0].mRect = aRect; + memcpy(mRoundedClipRects[0].mRadii, aRadii, sizeof(nscoord) * 8); + } else { + mRoundedClipRects.Clear(); + } +} + +void DisplayItemClip::SetTo(const nsRect& aRect, const nsRect& aRoundedRect, + const nscoord* aRadii) { + mHaveClipRect = true; + mClipRect = aRect; + mRoundedClipRects.SetLength(1); + mRoundedClipRects[0].mRect = aRoundedRect; + memcpy(mRoundedClipRects[0].mRadii, aRadii, sizeof(nscoord) * 8); +} + +bool DisplayItemClip::MayIntersect(const nsRect& aRect) const { + if (!mHaveClipRect) { + return !aRect.IsEmpty(); + } + nsRect r = aRect.Intersect(mClipRect); + if (r.IsEmpty()) { + return false; + } + for (uint32_t i = 0; i < mRoundedClipRects.Length(); ++i) { + const RoundedRect& rr = mRoundedClipRects[i]; + if (!nsLayoutUtils::RoundedRectIntersectsRect(rr.mRect, rr.mRadii, r)) { + return false; + } + } + return true; +} + +void DisplayItemClip::IntersectWith(const DisplayItemClip& aOther) { + if (!aOther.mHaveClipRect) { + return; + } + if (!mHaveClipRect) { + *this = aOther; + return; + } + if (!mClipRect.IntersectRect(mClipRect, aOther.mClipRect)) { + mRoundedClipRects.Clear(); + return; + } + mRoundedClipRects.AppendElements(aOther.mRoundedClipRects); +} + +void DisplayItemClip::ApplyTo(gfxContext* aContext, int32_t A2D) const { + ApplyRectTo(aContext, A2D); + ApplyRoundedRectClipsTo(aContext, A2D, 0, mRoundedClipRects.Length()); +} + +void DisplayItemClip::ApplyRectTo(gfxContext* aContext, int32_t A2D) const { + aContext->NewPath(); + gfxRect clip = nsLayoutUtils::RectToGfxRect(mClipRect, A2D); + aContext->SnappedRectangle(clip); + aContext->Clip(); +} + +void DisplayItemClip::ApplyRoundedRectClipsTo(gfxContext* aContext, int32_t A2D, + uint32_t aBegin, + uint32_t aEnd) const { + DrawTarget& aDrawTarget = *aContext->GetDrawTarget(); + + aEnd = std::min<uint32_t>(aEnd, mRoundedClipRects.Length()); + + for (uint32_t i = aBegin; i < aEnd; ++i) { + RefPtr<Path> roundedRect = + MakeRoundedRectPath(aDrawTarget, A2D, mRoundedClipRects[i]); + aContext->Clip(roundedRect); + } +} + +void DisplayItemClip::FillIntersectionOfRoundedRectClips( + gfxContext* aContext, const DeviceColor& aColor, + int32_t aAppUnitsPerDevPixel) const { + DrawTarget& aDrawTarget = *aContext->GetDrawTarget(); + + uint32_t end = mRoundedClipRects.Length(); + if (!end) { + return; + } + + // Push clips for any rects that come BEFORE the rect at |aEnd - 1|, if any: + ApplyRoundedRectClipsTo(aContext, aAppUnitsPerDevPixel, 0, end - 1); + + // Now fill the rect at |aEnd - 1|: + RefPtr<Path> roundedRect = MakeRoundedRectPath( + aDrawTarget, aAppUnitsPerDevPixel, mRoundedClipRects[end - 1]); + aDrawTarget.Fill(roundedRect, ColorPattern(aColor)); + + // Finally, pop any clips that we may have pushed: + for (uint32_t i = 0; i < end - 1; ++i) { + aContext->PopClip(); + } +} + +already_AddRefed<Path> DisplayItemClip::MakeRoundedRectPath( + DrawTarget& aDrawTarget, int32_t A2D, const RoundedRect& aRoundRect) const { + RectCornerRadii pixelRadii; + nsCSSRendering::ComputePixelRadii(aRoundRect.mRadii, A2D, &pixelRadii); + + Rect rect = NSRectToSnappedRect(aRoundRect.mRect, A2D, aDrawTarget); + + return MakePathForRoundedRect(aDrawTarget, rect, pixelRadii); +} + +nsRect DisplayItemClip::ApproximateIntersectInward(const nsRect& aRect) const { + nsRect r = aRect; + if (mHaveClipRect) { + r.IntersectRect(r, mClipRect); + } + for (uint32_t i = 0, iEnd = mRoundedClipRects.Length(); i < iEnd; ++i) { + const RoundedRect& rr = mRoundedClipRects[i]; + nsRegion rgn = + nsLayoutUtils::RoundedRectIntersectRect(rr.mRect, rr.mRadii, r); + r = rgn.GetLargestRectangle(); + } + return r; +} + +// Test if (aXPoint, aYPoint) is in the ellipse with center (aXCenter, aYCenter) +// and radii aXRadius, aYRadius. +static bool IsInsideEllipse(nscoord aXRadius, nscoord aXCenter, nscoord aXPoint, + nscoord aYRadius, nscoord aYCenter, + nscoord aYPoint) { + float scaledX = float(aXPoint - aXCenter) / float(aXRadius); + float scaledY = float(aYPoint - aYCenter) / float(aYRadius); + return scaledX * scaledX + scaledY * scaledY < 1.0f; +} + +bool DisplayItemClip::IsRectClippedByRoundedCorner(const nsRect& aRect) const { + if (mRoundedClipRects.IsEmpty()) return false; + + nsRect rect; + rect.IntersectRect(aRect, NonRoundedIntersection()); + for (uint32_t i = 0, iEnd = mRoundedClipRects.Length(); i < iEnd; ++i) { + const RoundedRect& rr = mRoundedClipRects[i]; + // top left + if (rect.x < rr.mRect.x + rr.mRadii[eCornerTopLeftX] && + rect.y < rr.mRect.y + rr.mRadii[eCornerTopLeftY]) { + if (!IsInsideEllipse(rr.mRadii[eCornerTopLeftX], + rr.mRect.x + rr.mRadii[eCornerTopLeftX], rect.x, + rr.mRadii[eCornerTopLeftY], + rr.mRect.y + rr.mRadii[eCornerTopLeftY], rect.y)) { + return true; + } + } + // top right + if (rect.XMost() > rr.mRect.XMost() - rr.mRadii[eCornerTopRightX] && + rect.y < rr.mRect.y + rr.mRadii[eCornerTopRightY]) { + if (!IsInsideEllipse(rr.mRadii[eCornerTopRightX], + rr.mRect.XMost() - rr.mRadii[eCornerTopRightX], + rect.XMost(), rr.mRadii[eCornerTopRightY], + rr.mRect.y + rr.mRadii[eCornerTopRightY], rect.y)) { + return true; + } + } + // bottom left + if (rect.x < rr.mRect.x + rr.mRadii[eCornerBottomLeftX] && + rect.YMost() > rr.mRect.YMost() - rr.mRadii[eCornerBottomLeftY]) { + if (!IsInsideEllipse(rr.mRadii[eCornerBottomLeftX], + rr.mRect.x + rr.mRadii[eCornerBottomLeftX], rect.x, + rr.mRadii[eCornerBottomLeftY], + rr.mRect.YMost() - rr.mRadii[eCornerBottomLeftY], + rect.YMost())) { + return true; + } + } + // bottom right + if (rect.XMost() > rr.mRect.XMost() - rr.mRadii[eCornerBottomRightX] && + rect.YMost() > rr.mRect.YMost() - rr.mRadii[eCornerBottomRightY]) { + if (!IsInsideEllipse(rr.mRadii[eCornerBottomRightX], + rr.mRect.XMost() - rr.mRadii[eCornerBottomRightX], + rect.XMost(), rr.mRadii[eCornerBottomRightY], + rr.mRect.YMost() - rr.mRadii[eCornerBottomRightY], + rect.YMost())) { + return true; + } + } + } + return false; +} + +nsRect DisplayItemClip::NonRoundedIntersection() const { + NS_ASSERTION(mHaveClipRect, "Must have a clip rect!"); + nsRect result = mClipRect; + for (uint32_t i = 0, iEnd = mRoundedClipRects.Length(); i < iEnd; ++i) { + result.IntersectRect(result, mRoundedClipRects[i].mRect); + } + return result; +} + +bool DisplayItemClip::IsRectAffectedByClip(const nsRect& aRect) const { + if (mHaveClipRect && !mClipRect.Contains(aRect)) { + return true; + } + for (uint32_t i = 0, iEnd = mRoundedClipRects.Length(); i < iEnd; ++i) { + const RoundedRect& rr = mRoundedClipRects[i]; + nsRegion rgn = + nsLayoutUtils::RoundedRectIntersectRect(rr.mRect, rr.mRadii, aRect); + if (!rgn.Contains(aRect)) { + return true; + } + } + return false; +} + +bool DisplayItemClip::IsRectAffectedByClip(const nsIntRect& aRect, + float aXScale, float aYScale, + int32_t A2D) const { + if (mHaveClipRect) { + nsIntRect pixelClipRect = + mClipRect.ScaleToNearestPixels(aXScale, aYScale, A2D); + if (!pixelClipRect.Contains(aRect)) { + return true; + } + } + + // Rounded rect clipping only snaps to user-space pixels, not device space. + nsIntRect unscaled = aRect; + unscaled.Scale(1 / aXScale, 1 / aYScale); + + for (uint32_t i = 0, iEnd = mRoundedClipRects.Length(); i < iEnd; ++i) { + const RoundedRect& rr = mRoundedClipRects[i]; + + nsIntRect pixelRect = rr.mRect.ToNearestPixels(A2D); + + RectCornerRadii pixelRadii; + nsCSSRendering::ComputePixelRadii(rr.mRadii, A2D, &pixelRadii); + + nsIntRegion rgn = nsLayoutUtils::RoundedRectIntersectIntRect( + pixelRect, pixelRadii, unscaled); + if (!rgn.Contains(unscaled)) { + return true; + } + } + return false; +} + +nsRect DisplayItemClip::ApplyNonRoundedIntersection(const nsRect& aRect) const { + if (!mHaveClipRect) { + return aRect; + } + + nsRect result = aRect.Intersect(mClipRect); + for (uint32_t i = 0, iEnd = mRoundedClipRects.Length(); i < iEnd; ++i) { + result = result.Intersect(mRoundedClipRects[i].mRect); + } + return result; +} + +void DisplayItemClip::RemoveRoundedCorners() { + if (mRoundedClipRects.IsEmpty()) return; + + mClipRect = NonRoundedIntersection(); + mRoundedClipRects.Clear(); +} + +// Computes the difference between aR1 and aR2, limited to aBounds. +static void AccumulateRectDifference(const nsRect& aR1, const nsRect& aR2, + const nsRect& aBounds, nsRegion* aOut) { + if (aR1.IsEqualInterior(aR2)) return; + nsRegion r; + r.Xor(aR1, aR2); + r.And(r, aBounds); + aOut->Or(*aOut, r); +} + +static void AccumulateRoundedRectDifference( + const DisplayItemClip::RoundedRect& aR1, + const DisplayItemClip::RoundedRect& aR2, const nsRect& aBounds, + const nsRect& aOtherBounds, nsRegion* aOut) { + const nsRect& rect1 = aR1.mRect; + const nsRect& rect2 = aR2.mRect; + + // If the two rectangles are totally disjoint, just add them both - otherwise + // we'd end up adding one big enclosing rect + if (!rect1.Intersects(rect2) || + memcmp(aR1.mRadii, aR2.mRadii, sizeof(aR1.mRadii))) { + aOut->Or(*aOut, rect1.Intersect(aBounds)); + aOut->Or(*aOut, rect2.Intersect(aOtherBounds)); + return; + } + + nscoord lowestBottom = std::max(rect1.YMost(), rect2.YMost()); + nscoord highestTop = std::min(rect1.Y(), rect2.Y()); + nscoord maxRight = std::max(rect1.XMost(), rect2.XMost()); + nscoord minLeft = std::min(rect1.X(), rect2.X()); + + // At this point, we know that the radii haven't changed, and that the bounds + // are different in some way. To explain how this works, consider the case + // where the rounded rect has just been translated along the X direction. + // | ______________________ _ _ _ _ _ _ | + // | / / \ \ | + // | | | | + // | | aR1 | | aR2 | | + // | | | | + // | \ __________\___________ / _ _ _ _ _ / | + // | | + // The invalidation region will be as if we lopped off the left rounded part + // of aR2, and the right rounded part of aR1, and XOR'd them: + // | ______________________ _ _ _ _ _ _ | + // | -/-----------/- -\-----------\- | + // | |-------------- --|------------ | + // | |-----aR1---|-- --|-----aR2---| | + // | |-------------- --|------------ | + // | -\ __________\-__________-/ _ _ _ _ _ /- | + // | | + // The logic below just implements this idea, but generalized to both the + // X and Y dimensions. The "(...)Adjusted(...)" values represent the lopped + // off sides. + nscoord highestAdjustedBottom = std::min( + rect1.YMost() - aR1.mRadii[eCornerBottomLeftY], + std::min(rect1.YMost() - aR1.mRadii[eCornerBottomRightY], + std::min(rect2.YMost() - aR2.mRadii[eCornerBottomLeftY], + rect2.YMost() - aR2.mRadii[eCornerBottomRightY]))); + nscoord lowestAdjustedTop = + std::max(rect1.Y() + aR1.mRadii[eCornerTopLeftY], + std::max(rect1.Y() + aR1.mRadii[eCornerTopRightY], + std::max(rect2.Y() + aR2.mRadii[eCornerTopLeftY], + rect2.Y() + aR2.mRadii[eCornerTopRightY]))); + + nscoord minAdjustedRight = std::min( + rect1.XMost() - aR1.mRadii[eCornerTopRightX], + std::min(rect1.XMost() - aR1.mRadii[eCornerBottomRightX], + std::min(rect2.XMost() - aR2.mRadii[eCornerTopRightX], + rect2.XMost() - aR2.mRadii[eCornerBottomRightX]))); + nscoord maxAdjustedLeft = + std::max(rect1.X() + aR1.mRadii[eCornerTopLeftX], + std::max(rect1.X() + aR1.mRadii[eCornerBottomLeftX], + std::max(rect2.X() + aR2.mRadii[eCornerTopLeftX], + rect2.X() + aR2.mRadii[eCornerBottomLeftX]))); + + // We only want to add an invalidation rect if the bounds have changed. If we + // always added all of the 4 rects below, we would always be invalidating a + // border around the rects, even in cases where we just translated along the X + // or Y axis. + nsRegion r; + // First, or with the Y delta rects, wide along the X axis + if (rect1.Y() != rect2.Y()) { + r.Or(r, nsRect(minLeft, highestTop, maxRight - minLeft, + lowestAdjustedTop - highestTop)); + } + if (rect1.YMost() != rect2.YMost()) { + r.Or(r, nsRect(minLeft, highestAdjustedBottom, maxRight - minLeft, + lowestBottom - highestAdjustedBottom)); + } + // Then, or with the X delta rects, wide along the Y axis + if (rect1.X() != rect2.X()) { + r.Or(r, nsRect(minLeft, highestTop, maxAdjustedLeft - minLeft, + lowestBottom - highestTop)); + } + if (rect1.XMost() != rect2.XMost()) { + r.Or(r, nsRect(minAdjustedRight, highestTop, maxRight - minAdjustedRight, + lowestBottom - highestTop)); + } + + r.And(r, aBounds.Union(aOtherBounds)); + aOut->Or(*aOut, r); +} + +void DisplayItemClip::AddOffsetAndComputeDifference( + const nsPoint& aOffset, const nsRect& aBounds, + const DisplayItemClip& aOther, const nsRect& aOtherBounds, + nsRegion* aDifference) { + if (mHaveClipRect != aOther.mHaveClipRect || + mRoundedClipRects.Length() != aOther.mRoundedClipRects.Length()) { + aDifference->Or(*aDifference, aBounds); + aDifference->Or(*aDifference, aOtherBounds); + return; + } + if (mHaveClipRect) { + AccumulateRectDifference(mClipRect + aOffset, aOther.mClipRect, + aBounds.Union(aOtherBounds), aDifference); + } + for (uint32_t i = 0; i < mRoundedClipRects.Length(); ++i) { + if (mRoundedClipRects[i] + aOffset != aOther.mRoundedClipRects[i]) { + AccumulateRoundedRectDifference(mRoundedClipRects[i] + aOffset, + aOther.mRoundedClipRects[i], aBounds, + aOtherBounds, aDifference); + } + } +} + +void DisplayItemClip::AppendRoundedRects(nsTArray<RoundedRect>* aArray) const { + aArray->AppendElements(mRoundedClipRects.Elements(), + mRoundedClipRects.Length()); +} + +bool DisplayItemClip::ComputeRegionInClips(const DisplayItemClip* aOldClip, + const nsPoint& aShift, + nsRegion* aCombined) const { + if (!mHaveClipRect || (aOldClip && !aOldClip->mHaveClipRect)) { + return false; + } + + if (aOldClip) { + *aCombined = aOldClip->NonRoundedIntersection(); + aCombined->MoveBy(aShift); + aCombined->Or(*aCombined, NonRoundedIntersection()); + } else { + *aCombined = NonRoundedIntersection(); + } + return true; +} + +void DisplayItemClip::MoveBy(const nsPoint& aPoint) { + if (!mHaveClipRect) { + return; + } + mClipRect += aPoint; + for (uint32_t i = 0; i < mRoundedClipRects.Length(); ++i) { + mRoundedClipRects[i].mRect += aPoint; + } +} + +static StaticAutoPtr<DisplayItemClip> gNoClip; + +const DisplayItemClip& DisplayItemClip::NoClip() { + if (!gNoClip) { + gNoClip = new DisplayItemClip(); + } + return *gNoClip; +} + +void DisplayItemClip::Shutdown() { gNoClip = nullptr; } + +nsCString DisplayItemClip::ToString() const { + nsAutoCString str; + if (mHaveClipRect) { + str.AppendPrintf("%d,%d,%d,%d", mClipRect.x, mClipRect.y, mClipRect.width, + mClipRect.height); + for (uint32_t i = 0; i < mRoundedClipRects.Length(); ++i) { + const RoundedRect& r = mRoundedClipRects[i]; + str.AppendPrintf(" [%d,%d,%d,%d corners %d,%d,%d,%d,%d,%d,%d,%d]", + r.mRect.x, r.mRect.y, r.mRect.width, r.mRect.height, + r.mRadii[0], r.mRadii[1], r.mRadii[2], r.mRadii[3], + r.mRadii[4], r.mRadii[5], r.mRadii[6], r.mRadii[7]); + } + } + return std::move(str); +} + +void DisplayItemClip::ToComplexClipRegions( + int32_t aAppUnitsPerDevPixel, + nsTArray<wr::ComplexClipRegion>& aOutArray) const { + for (const auto& clipRect : mRoundedClipRects) { + aOutArray.AppendElement(wr::ToComplexClipRegion( + clipRect.mRect, clipRect.mRadii, aAppUnitsPerDevPixel)); + } +} + +} // namespace mozilla diff --git a/layout/painting/DisplayItemClip.h b/layout/painting/DisplayItemClip.h new file mode 100644 index 0000000000..ff9df26603 --- /dev/null +++ b/layout/painting/DisplayItemClip.h @@ -0,0 +1,193 @@ +/* -*- 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/. */ + +#ifndef DISPLAYITEMCLIP_H_ +#define DISPLAYITEMCLIP_H_ + +#include "mozilla/RefPtr.h" +#include "nsRect.h" +#include "nsTArray.h" + +class gfxContext; +class nsPresContext; +class nsRegion; + +namespace mozilla { +namespace gfx { +class DrawTarget; +class Path; +} // namespace gfx +namespace layers { +class StackingContextHelper; +} // namespace layers +namespace wr { +struct ComplexClipRegion; +} // namespace wr +} // namespace mozilla + +namespace mozilla { + +/** + * An DisplayItemClip represents the intersection of an optional rectangle + * with a list of rounded rectangles (which is often empty), all in appunits. + * It can represent everything CSS clipping can do to an element (except for + * SVG clip-path), including no clipping at all. + */ +class DisplayItemClip { + typedef mozilla::gfx::DeviceColor DeviceColor; + typedef mozilla::gfx::DrawTarget DrawTarget; + typedef mozilla::gfx::Path Path; + + public: + struct RoundedRect { + nsRect mRect; + // Indices into mRadii are the HalfCorner values in gfx/2d/Types.h + nscoord mRadii[8]; + + RoundedRect operator+(const nsPoint& aOffset) const { + RoundedRect r = *this; + r.mRect += aOffset; + return r; + } + bool operator==(const RoundedRect& aOther) const { + if (!mRect.IsEqualInterior(aOther.mRect)) { + return false; + } + + for (const auto corner : mozilla::AllPhysicalHalfCorners()) { + if (mRadii[corner] != aOther.mRadii[corner]) { + return false; + } + } + return true; + } + bool operator!=(const RoundedRect& aOther) const { + return !(*this == aOther); + } + }; + + // Constructs a DisplayItemClip that does no clipping at all. + DisplayItemClip() : mHaveClipRect(false) {} + + void SetTo(const nsRect& aRect); + void SetTo(const nsRect& aRect, const nscoord* aRadii); + void SetTo(const nsRect& aRect, const nsRect& aRoundedRect, + const nscoord* aRadii); + void IntersectWith(const DisplayItemClip& aOther); + + // Apply this |DisplayItemClip| to the given gfxContext. Any saving of state + // or clearing of other clips must be done by the caller. + // See aBegin/aEnd note on ApplyRoundedRectsTo. + void ApplyTo(gfxContext* aContext, int32_t A2D) const; + + void ApplyRectTo(gfxContext* aContext, int32_t A2D) const; + // Applies the rounded rects in this Clip to aContext + // Will only apply rounded rects from aBegin (inclusive) to aEnd + // (exclusive) or the number of rounded rects, whichever is smaller. + void ApplyRoundedRectClipsTo(gfxContext* aContext, int32_t A2DPRInt32, + uint32_t aBegin, uint32_t aEnd) const; + + // Draw (fill) the rounded rects in this clip to aContext + void FillIntersectionOfRoundedRectClips(gfxContext* aContext, + const DeviceColor& aColor, + int32_t aAppUnitsPerDevPixel) const; + // 'Draw' (create as a path, does not stroke or fill) aRoundRect to aContext + already_AddRefed<Path> MakeRoundedRectPath( + DrawTarget& aDrawTarget, int32_t A2D, + const RoundedRect& aRoundRect) const; + + // Returns true if the intersection of aRect and this clip region is + // non-empty. This is precise for DisplayItemClips with at most one + // rounded rectangle. When multiple rounded rectangles are present, we just + // check that the rectangle intersects all of them (but possibly in different + // places). So it may return true when the correct answer is false. + bool MayIntersect(const nsRect& aRect) const; + + // Return a rectangle contained in the intersection of aRect with this + // clip region. Tries to return the largest possible rectangle, but may + // not succeed. + nsRect ApproximateIntersectInward(const nsRect& aRect) const; + + /* + * Computes a region which contains the clipped area of this DisplayItemClip, + * or if aOldClip is non-null, the union of the clipped area of this + * DisplayItemClip with the clipped area of aOldClip translated by aShift. + * The result is stored in aCombined. If the result would be infinite + * (because one or both of the clips does no clipping), returns false. + */ + bool ComputeRegionInClips(const DisplayItemClip* aOldClip, + const nsPoint& aShift, nsRegion* aCombined) const; + + // Returns false if aRect is definitely not clipped by a rounded corner in + // this clip. Returns true if aRect is clipped by a rounded corner in this + // clip or it can not be quickly determined that it is not clipped by a + // rounded corner in this clip. + bool IsRectClippedByRoundedCorner(const nsRect& aRect) const; + + // Returns false if aRect is definitely not clipped by anything in this clip. + // Fast but not necessarily accurate. + bool IsRectAffectedByClip(const nsRect& aRect) const; + bool IsRectAffectedByClip(const nsIntRect& aRect, float aXScale, + float aYScale, int32_t A2D) const; + + // Intersection of all rects in this clip ignoring any rounded corners. + nsRect NonRoundedIntersection() const; + + // Intersect the given rects with all rects in this clip, ignoring any + // rounded corners. + nsRect ApplyNonRoundedIntersection(const nsRect& aRect) const; + + // Gets rid of any rounded corners in this clip. + void RemoveRoundedCorners(); + + // Adds the difference between Intersect(*this + aPoint, aBounds) and + // Intersect(aOther, aOtherBounds) to aDifference (or a bounding-box thereof). + void AddOffsetAndComputeDifference(const nsPoint& aPoint, + const nsRect& aBounds, + const DisplayItemClip& aOther, + const nsRect& aOtherBounds, + nsRegion* aDifference); + + bool operator==(const DisplayItemClip& aOther) const { + return mHaveClipRect == aOther.mHaveClipRect && + (!mHaveClipRect || mClipRect.IsEqualInterior(aOther.mClipRect)) && + mRoundedClipRects == aOther.mRoundedClipRects; + } + bool operator!=(const DisplayItemClip& aOther) const { + return !(*this == aOther); + } + + bool HasClip() const { return mHaveClipRect; } + const nsRect& GetClipRect() const { + NS_ASSERTION(HasClip(), "No clip rect!"); + return mClipRect; + } + + void MoveBy(const nsPoint& aPoint); + + nsCString ToString() const; + + uint32_t GetRoundedRectCount() const { return mRoundedClipRects.Length(); } + void AppendRoundedRects(nsTArray<RoundedRect>* aArray) const; + + void ToComplexClipRegions(int32_t aAppUnitsPerDevPixel, + nsTArray<wr::ComplexClipRegion>& aOutArray) const; + + static const DisplayItemClip& NoClip(); + + static void Shutdown(); + + private: + nsRect mClipRect; + CopyableTArray<RoundedRect> mRoundedClipRects; + // If mHaveClipRect is false then this object represents no clipping at all + // and mRoundedClipRects must be empty. + bool mHaveClipRect; +}; + +} // namespace mozilla + +#endif /* DISPLAYITEMCLIP_H_ */ diff --git a/layout/painting/DisplayItemClipChain.cpp b/layout/painting/DisplayItemClipChain.cpp new file mode 100644 index 0000000000..537c3615a1 --- /dev/null +++ b/layout/painting/DisplayItemClipChain.cpp @@ -0,0 +1,88 @@ +/* -*- 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/. */ + +#include "DisplayItemClipChain.h" + +#include "nsDisplayList.h" + +namespace mozilla { + +/* static */ const DisplayItemClip* DisplayItemClipChain::ClipForASR( + const DisplayItemClipChain* aClipChain, const ActiveScrolledRoot* aASR) { + while (aClipChain && + !ActiveScrolledRoot::IsAncestor(aClipChain->mASR, aASR)) { + aClipChain = aClipChain->mParent; + } + return (aClipChain && aClipChain->mASR == aASR) ? &aClipChain->mClip + : nullptr; +} + +bool DisplayItemClipChain::Equal(const DisplayItemClipChain* aClip1, + const DisplayItemClipChain* aClip2) { + if (aClip1 == aClip2) { + return true; + } + + if (!aClip1 || !aClip2) { + return false; + } + + bool ret = aClip1->mASR == aClip2->mASR && aClip1->mClip == aClip2->mClip && + Equal(aClip1->mParent, aClip2->mParent); + // Sanity check: if two clip chains are equal they must hash to the same + // thing too, or Bad Things (TM) will happen. + MOZ_ASSERT(!ret || (Hash(aClip1) == Hash(aClip2))); + return ret; +} + +uint32_t DisplayItemClipChain::Hash(const DisplayItemClipChain* aClip) { + if (!aClip) { + return 0; + } + + // We include the number of rounded rects in the hash but not their contents. + // This is to keep the hash fast, because most clips will not have rounded + // rects and including them will slow down the hash in the common case. Note + // that the ::Equal check still checks the rounded rect contents, so in case + // of hash collisions the clip chains can still be distinguished using that. + uint32_t hash = HashGeneric(aClip->mASR, aClip->mClip.GetRoundedRectCount()); + if (aClip->mClip.HasClip()) { + const nsRect& rect = aClip->mClip.GetClipRect(); + // empty rects are considered equal in DisplayItemClipChain::Equal, even + // though they may have different x and y coordinates. So make sure they + // hash to the same thing in those cases too. + if (!rect.IsEmpty()) { + hash = AddToHash(hash, rect.x, rect.y, rect.width, rect.height); + } + } + + return hash; +} + +/* static */ +nsCString DisplayItemClipChain::ToString( + const DisplayItemClipChain* aClipChain) { + nsAutoCString str; + for (auto* sc = aClipChain; sc; sc = sc->mParent) { + if (sc->mASR) { + str.AppendPrintf("0x%p <%s> [0x%p]", sc, sc->mClip.ToString().get(), + sc->mASR->mScrollableFrame); + } else { + str.AppendPrintf("0x%p <%s> [root asr]", sc, sc->mClip.ToString().get()); + } + if (sc->mParent) { + str.AppendLiteral(", "); + } + } + return std::move(str); +} + +bool DisplayItemClipChain::HasRoundedCorners() const { + return mClip.GetRoundedRectCount() > 0 || + (mParent && mParent->HasRoundedCorners()); +} + +} // namespace mozilla diff --git a/layout/painting/DisplayItemClipChain.h b/layout/painting/DisplayItemClipChain.h new file mode 100644 index 0000000000..32779e09c0 --- /dev/null +++ b/layout/painting/DisplayItemClipChain.h @@ -0,0 +1,117 @@ +/* -*- 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/. */ + +#ifndef DISPLAYITEMCLIPCHAIN_H_ +#define DISPLAYITEMCLIPCHAIN_H_ + +#include "mozilla/Assertions.h" +#include "DisplayItemClip.h" +#include "nsString.h" + +class nsIScrollableFrame; + +namespace mozilla { + +struct ActiveScrolledRoot; + +/** + * A DisplayItemClipChain is a linked list of DisplayItemClips where each clip + * is associated with an active scrolled root that describes what the clip + * moves with. + * We use a chain instead of just one intersected clip due to async scrolling: + * A clip that moves along with a display item can be fused to the item's + * contents when drawing the layer contents, but all other clips in the chain + * need to be kept separate so that they can be applied at composition time, + * after any async scroll offsets have been applied. + * The clip chain is created during display list construction by the builder's + * DisplayListClipState. + * The clip chain order is determined by the active scrolled root order. + * For every DisplayItemClipChain object |clipChain|, the following holds: + * !clipChain->mParent || + * ActiveScrolledRoot::IsAncestor(clipChain->mParent->mASR, clipChain->mASR). + * The clip chain can skip over active scrolled roots. That just means that + * there is no clip that moves with the skipped ASR in this chain. + */ +struct DisplayItemClipChain { + /** + * Get the display item clip in this chain that moves with aASR, or nullptr + * if no such clip exists. aClipChain can be null. + */ + static const DisplayItemClip* ClipForASR( + const DisplayItemClipChain* aClipChain, const ActiveScrolledRoot* aASR); + + static bool Equal(const DisplayItemClipChain* aClip1, + const DisplayItemClipChain* aClip2); + /** + * Hash function that returns the same value for any two clips A and B + * where Equal(A, B) is true. + */ + static uint32_t Hash(const DisplayItemClipChain* aClip); + + static nsCString ToString(const DisplayItemClipChain* aClipChain); + + bool HasRoundedCorners() const; + + void AddRef() { mRefCount++; } + void Release() { + MOZ_ASSERT(mRefCount > 0); + mRefCount--; + } + + DisplayItemClipChain(const DisplayItemClip& aClip, + const ActiveScrolledRoot* aASR, + const DisplayItemClipChain* aParent, + DisplayItemClipChain* aNextClipChainToDestroy) + : mClip(aClip), + mASR(aASR), + mParent(aParent), + mNextClipChainToDestroy(aNextClipChainToDestroy) +#ifdef DEBUG + , + mOnStack(true) +#endif + { + } + + DisplayItemClipChain() + : mASR(nullptr), + mNextClipChainToDestroy(nullptr) +#ifdef DEBUG + , + mOnStack(true) +#endif + { + } + + DisplayItemClip mClip; + const ActiveScrolledRoot* mASR; + RefPtr<const DisplayItemClipChain> mParent; + uint32_t mRefCount = 0; + DisplayItemClipChain* mNextClipChainToDestroy; +#ifdef DEBUG + bool mOnStack; +#endif +}; + +struct DisplayItemClipChainHasher { + typedef const DisplayItemClipChain* Key; + + std::size_t operator()(const Key& aKey) const { + return DisplayItemClipChain::Hash(aKey); + } +}; + +struct DisplayItemClipChainEqualer { + typedef const DisplayItemClipChain* Key; + + bool operator()(const Key& lhs, const Key& rhs) const { + return DisplayItemClipChain::Equal(lhs, rhs); + } +}; + +} // namespace mozilla + +#endif /* DISPLAYITEMCLIPCHAIN_H_ */ diff --git a/layout/painting/DisplayListClipState.cpp b/layout/painting/DisplayListClipState.cpp new file mode 100644 index 0000000000..8a87e2906b --- /dev/null +++ b/layout/painting/DisplayListClipState.cpp @@ -0,0 +1,149 @@ +/* -*- 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/. */ + +#include "DisplayListClipState.h" + +#include "nsDisplayList.h" + +namespace mozilla { + +const DisplayItemClipChain* DisplayListClipState::GetCurrentCombinedClipChain( + nsDisplayListBuilder* aBuilder) { + if (mCurrentCombinedClipChainIsValid) { + return mCurrentCombinedClipChain; + } + if (!mClipChainContentDescendants && !mClipChainContainingBlockDescendants) { + mCurrentCombinedClipChain = nullptr; + mCurrentCombinedClipChainIsValid = true; + return nullptr; + } + + mCurrentCombinedClipChain = aBuilder->CreateClipChainIntersection( + mCurrentCombinedClipChain, mClipChainContentDescendants, + mClipChainContainingBlockDescendants); + mCurrentCombinedClipChainIsValid = true; + return mCurrentCombinedClipChain; +} + +static void ApplyClip(nsDisplayListBuilder* aBuilder, + const DisplayItemClipChain*& aClipToModify, + const ActiveScrolledRoot* aASR, + DisplayItemClipChain& aClipChainOnStack) { + aClipChainOnStack.mASR = aASR; + if (aClipToModify && aClipToModify->mASR == aASR) { + // Intersect with aClipToModify and replace the clip chain item. + aClipChainOnStack.mClip.IntersectWith(aClipToModify->mClip); + aClipChainOnStack.mParent = aClipToModify->mParent; + aClipToModify = &aClipChainOnStack; + } else if (!aClipToModify || + ActiveScrolledRoot::IsAncestor(aClipToModify->mASR, aASR)) { + // Add a new clip chain item at the bottom. + aClipChainOnStack.mParent = aClipToModify; + aClipToModify = &aClipChainOnStack; + } else { + // We need to insert / intersect a DisplayItemClipChain in the middle of the + // aClipToModify chain. This is a very rare case. + // Find the common ancestor and have the builder create the + // DisplayItemClipChain intersection. This will create new + // DisplayItemClipChain objects for all descendants of ancestorSC and we + // will not hold on to a pointer to aClipChainOnStack. + const DisplayItemClipChain* ancestorSC = aClipToModify; + while (ancestorSC && + ActiveScrolledRoot::IsAncestor(aASR, ancestorSC->mASR)) { + ancestorSC = ancestorSC->mParent; + } + ancestorSC = aBuilder->CopyWholeChain(ancestorSC); + aClipChainOnStack.mParent = nullptr; + aClipToModify = aBuilder->CreateClipChainIntersection( + ancestorSC, aClipToModify, &aClipChainOnStack); + } +} + +void DisplayListClipState::ClipContainingBlockDescendants( + nsDisplayListBuilder* aBuilder, const nsRect& aRect, const nscoord* aRadii, + DisplayItemClipChain& aClipChainOnStack) { + if (aRadii) { + aClipChainOnStack.mClip.SetTo(aRect, aRadii); + } else { + aClipChainOnStack.mClip.SetTo(aRect); + } + const ActiveScrolledRoot* asr = aBuilder->CurrentActiveScrolledRoot(); + ApplyClip(aBuilder, mClipChainContainingBlockDescendants, asr, + aClipChainOnStack); + InvalidateCurrentCombinedClipChain(asr); +} + +void DisplayListClipState::ClipContentDescendants( + nsDisplayListBuilder* aBuilder, const nsRect& aRect, const nscoord* aRadii, + DisplayItemClipChain& aClipChainOnStack) { + if (aRadii) { + aClipChainOnStack.mClip.SetTo(aRect, aRadii); + } else { + aClipChainOnStack.mClip.SetTo(aRect); + } + const ActiveScrolledRoot* asr = aBuilder->CurrentActiveScrolledRoot(); + ApplyClip(aBuilder, mClipChainContentDescendants, asr, aClipChainOnStack); + InvalidateCurrentCombinedClipChain(asr); +} + +void DisplayListClipState::ClipContentDescendants( + nsDisplayListBuilder* aBuilder, const nsRect& aRect, + const nsRect& aRoundedRect, const nscoord* aRadii, + DisplayItemClipChain& aClipChainOnStack) { + if (aRadii) { + aClipChainOnStack.mClip.SetTo(aRect, aRoundedRect, aRadii); + } else { + nsRect intersect = aRect.Intersect(aRoundedRect); + aClipChainOnStack.mClip.SetTo(intersect); + } + const ActiveScrolledRoot* asr = aBuilder->CurrentActiveScrolledRoot(); + ApplyClip(aBuilder, mClipChainContentDescendants, asr, aClipChainOnStack); + InvalidateCurrentCombinedClipChain(asr); +} + +void DisplayListClipState::InvalidateCurrentCombinedClipChain( + const ActiveScrolledRoot* aInvalidateUpTo) { + mClippedToDisplayPort = false; + mCurrentCombinedClipChainIsValid = false; + while (mCurrentCombinedClipChain && + ActiveScrolledRoot::IsAncestor(aInvalidateUpTo, + mCurrentCombinedClipChain->mASR)) { + mCurrentCombinedClipChain = mCurrentCombinedClipChain->mParent; + } +} + +void DisplayListClipState::ClipContainingBlockDescendantsToContentBox( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + DisplayItemClipChain& aClipChainOnStack, uint32_t aFlags) { + nscoord radii[8]; + bool hasBorderRadius = aFrame->GetContentBoxBorderRadii(radii); + if (!hasBorderRadius && + (aFlags & ASSUME_DRAWING_RESTRICTED_TO_CONTENT_RECT)) { + return; + } + + nsRect clipRect = aFrame->GetContentRectRelativeToSelf() + + aBuilder->ToReferenceFrame(aFrame); + // If we have a border-radius, we have to clip our content to that + // radius. + ClipContainingBlockDescendants( + aBuilder, clipRect, hasBorderRadius ? radii : nullptr, aClipChainOnStack); +} + +DisplayListClipState::AutoSaveRestore::AutoSaveRestore( + nsDisplayListBuilder* aBuilder) + : mBuilder(aBuilder), + mState(aBuilder->ClipState()), + mSavedState(aBuilder->ClipState()) +#ifdef DEBUG + , + mClipUsed(false), + mRestored(false) +#endif +{ +} + +} // namespace mozilla diff --git a/layout/painting/DisplayListClipState.h b/layout/painting/DisplayListClipState.h new file mode 100644 index 0000000000..0f1df2ca58 --- /dev/null +++ b/layout/painting/DisplayListClipState.h @@ -0,0 +1,301 @@ +/* -*- 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/. */ + +#ifndef DISPLAYLISTCLIPSTATE_H_ +#define DISPLAYLISTCLIPSTATE_H_ + +#include "DisplayItemClip.h" +#include "DisplayItemClipChain.h" + +#include "mozilla/DebugOnly.h" + +class nsIFrame; +class nsIScrollableFrame; + +namespace mozilla { + +class nsDisplayListBuilder; + +/** + * All clip coordinates are in appunits relative to the reference frame + * for the display item we're building. + */ +class DisplayListClipState { + public: + DisplayListClipState() + : mClipChainContentDescendants(nullptr), + mClipChainContainingBlockDescendants(nullptr), + mCurrentCombinedClipChain(nullptr), + mCurrentCombinedClipChainIsValid(false), + mClippedToDisplayPort(false) {} + + void SetClippedToDisplayPort() { mClippedToDisplayPort = true; } + bool IsClippedToDisplayPort() const { return mClippedToDisplayPort; } + + /** + * Returns intersection of mClipChainContainingBlockDescendants and + * mClipChainContentDescendants, allocated on aBuilder's arena. + */ + const DisplayItemClipChain* GetCurrentCombinedClipChain( + nsDisplayListBuilder* aBuilder); + + const DisplayItemClipChain* GetClipChainForContainingBlockDescendants() + const { + return mClipChainContainingBlockDescendants; + } + const DisplayItemClipChain* GetClipChainForContentDescendants() const { + return mClipChainContentDescendants; + } + + const ActiveScrolledRoot* GetContentClipASR() const { + return mClipChainContentDescendants ? mClipChainContentDescendants->mASR + : nullptr; + } + + class AutoSaveRestore; + + class AutoClipContainingBlockDescendantsToContentBox; + + class AutoClipMultiple; + + enum { ASSUME_DRAWING_RESTRICTED_TO_CONTENT_RECT = 0x01 }; + + private: + void Clear() { + mClipChainContentDescendants = nullptr; + mClipChainContainingBlockDescendants = nullptr; + mCurrentCombinedClipChain = nullptr; + mCurrentCombinedClipChainIsValid = false; + mClippedToDisplayPort = false; + } + + void SetClipChainForContainingBlockDescendants( + const DisplayItemClipChain* aClipChain) { + mClipChainContainingBlockDescendants = aClipChain; + InvalidateCurrentCombinedClipChain(aClipChain ? aClipChain->mASR : nullptr); + } + + /** + * Intersects the given clip rect (with optional aRadii) with the current + * mClipContainingBlockDescendants and sets mClipContainingBlockDescendants to + * the result, stored in aClipOnStack. + */ + void ClipContainingBlockDescendants(nsDisplayListBuilder* aBuilder, + const nsRect& aRect, + const nscoord* aRadii, + DisplayItemClipChain& aClipChainOnStack); + + void ClipContentDescendants(nsDisplayListBuilder* aBuilder, + const nsRect& aRect, const nscoord* aRadii, + DisplayItemClipChain& aClipChainOnStack); + void ClipContentDescendants(nsDisplayListBuilder* aBuilder, + const nsRect& aRect, const nsRect& aRoundedRect, + const nscoord* aRadii, + DisplayItemClipChain& aClipChainOnStack); + + void InvalidateCurrentCombinedClipChain( + const ActiveScrolledRoot* aInvalidateUpTo); + + /** + * Clips containing-block descendants to the frame's content-box, + * taking border-radius into account. + * If aFlags contains ASSUME_DRAWING_RESTRICTED_TO_CONTENT_RECT then + * we assume display items will not draw outside the content rect, so + * clipping is only required if there is a border-radius. This is an + * optimization to reduce the amount of clipping required. + */ + void ClipContainingBlockDescendantsToContentBox( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + DisplayItemClipChain& aClipChainOnStack, uint32_t aFlags); + + /** + * All content descendants (i.e. following placeholder frames to their + * out-of-flows if necessary) should be clipped by + * mClipChainContentDescendants. Null if no clipping applies. + */ + const DisplayItemClipChain* mClipChainContentDescendants; + /** + * All containing-block descendants (i.e. frame descendants), including + * display items for the current frame, should be clipped by + * mClipChainContainingBlockDescendants. + * Null if no clipping applies. + */ + const DisplayItemClipChain* mClipChainContainingBlockDescendants; + /** + * The intersection of mClipChainContentDescendants and + * mClipChainContainingBlockDescendants. + * Allocated in the nsDisplayListBuilder arena. Null if none has been + * allocated or both mClipChainContentDescendants and + * mClipChainContainingBlockDescendants are null. + */ + const DisplayItemClipChain* mCurrentCombinedClipChain; + bool mCurrentCombinedClipChainIsValid; + /** + * A flag that is used by sticky positioned items to know if the clip applied + * to them is just the displayport clip or if there is additional clipping. + */ + bool mClippedToDisplayPort; +}; + +/** + * A class to automatically save and restore the current clip state. Also + * offers methods for modifying the clip state. Only one modification is allowed + * to be in scope at a time using one of these objects; multiple modifications + * require nested objects. The interface is written this way to prevent + * dangling pointers to DisplayItemClips. + */ +class DisplayListClipState::AutoSaveRestore { + public: + explicit AutoSaveRestore(nsDisplayListBuilder* aBuilder); + void Restore() { + mState = mSavedState; +#ifdef DEBUG + mRestored = true; +#endif + } + ~AutoSaveRestore() { Restore(); } + + void Clear() { + NS_ASSERTION(!mRestored, "Already restored!"); + mState.Clear(); +#ifdef DEBUG + mClipUsed = false; +#endif + } + + void SetClipChainForContainingBlockDescendants( + const DisplayItemClipChain* aClipChain) { + mState.SetClipChainForContainingBlockDescendants(aClipChain); + } + + /** + * Intersects the given clip rect (with optional aRadii) with the current + * mClipContainingBlockDescendants and sets mClipContainingBlockDescendants to + * the result, stored in aClipOnStack. + */ + void ClipContainingBlockDescendants(const nsRect& aRect, + const nscoord* aRadii = nullptr) { + NS_ASSERTION(!mRestored, "Already restored!"); + NS_ASSERTION(!mClipUsed, "mClip already used"); +#ifdef DEBUG + mClipUsed = true; +#endif + mState.ClipContainingBlockDescendants(mBuilder, aRect, aRadii, mClipChain); + } + + void ClipContentDescendants(const nsRect& aRect, + const nscoord* aRadii = nullptr) { + NS_ASSERTION(!mRestored, "Already restored!"); + NS_ASSERTION(!mClipUsed, "mClip already used"); +#ifdef DEBUG + mClipUsed = true; +#endif + mState.ClipContentDescendants(mBuilder, aRect, aRadii, mClipChain); + } + + void ClipContentDescendants(const nsRect& aRect, const nsRect& aRoundedRect, + const nscoord* aRadii = nullptr) { + NS_ASSERTION(!mRestored, "Already restored!"); + NS_ASSERTION(!mClipUsed, "mClip already used"); +#ifdef DEBUG + mClipUsed = true; +#endif + mState.ClipContentDescendants(mBuilder, aRect, aRoundedRect, aRadii, + mClipChain); + } + + /** + * Clips containing-block descendants to the frame's content-box, + * taking border-radius into account. + * If aFlags contains ASSUME_DRAWING_RESTRICTED_TO_CONTENT_RECT then + * we assume display items will not draw outside the content rect, so + * clipping is only required if there is a border-radius. This is an + * optimization to reduce the amount of clipping required. + */ + void ClipContainingBlockDescendantsToContentBox( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, uint32_t aFlags = 0) { + NS_ASSERTION(!mRestored, "Already restored!"); + NS_ASSERTION(!mClipUsed, "mClip already used"); +#ifdef DEBUG + mClipUsed = true; +#endif + mState.ClipContainingBlockDescendantsToContentBox(aBuilder, aFrame, + mClipChain, aFlags); + } + + void SetClippedToDisplayPort() { mState.SetClippedToDisplayPort(); } + bool IsClippedToDisplayPort() const { + return mState.IsClippedToDisplayPort(); + } + + protected: + nsDisplayListBuilder* mBuilder; + DisplayListClipState& mState; + DisplayListClipState mSavedState; + DisplayItemClipChain mClipChain; +#ifdef DEBUG + bool mClipUsed; + bool mRestored; +#endif +}; + +class DisplayListClipState::AutoClipContainingBlockDescendantsToContentBox + : public AutoSaveRestore { + public: + AutoClipContainingBlockDescendantsToContentBox(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame, + uint32_t aFlags = 0) + : AutoSaveRestore(aBuilder) { +#ifdef DEBUG + mClipUsed = true; +#endif + mState.ClipContainingBlockDescendantsToContentBox(aBuilder, aFrame, + mClipChain, aFlags); + } +}; + +/** + * Do not use this outside of nsIFrame::BuildDisplayListForChild, use + * multiple AutoSaveRestores instead. We provide this class just to ensure + * BuildDisplayListForChild is as efficient as possible. + */ +class DisplayListClipState::AutoClipMultiple : public AutoSaveRestore { + public: + explicit AutoClipMultiple(nsDisplayListBuilder* aBuilder) + : AutoSaveRestore(aBuilder) +#ifdef DEBUG + , + mExtraClipUsed(false) +#endif + { + } + + /** + * Intersects the given clip rect (with optional aRadii) with the current + * mClipContainingBlockDescendants and sets mClipContainingBlockDescendants to + * the result, stored in aClipOnStack. + */ + void ClipContainingBlockDescendantsExtra(const nsRect& aRect, + const nscoord* aRadii) { + NS_ASSERTION(!mRestored, "Already restored!"); + NS_ASSERTION(!mExtraClipUsed, "mExtraClip already used"); +#ifdef DEBUG + mExtraClipUsed = true; +#endif + mState.ClipContainingBlockDescendants(mBuilder, aRect, aRadii, + mExtraClipChain); + } + + protected: + DisplayItemClipChain mExtraClipChain; +#ifdef DEBUG + bool mExtraClipUsed; +#endif +}; + +} // namespace mozilla + +#endif /* DISPLAYLISTCLIPSTATE_H_ */ diff --git a/layout/painting/DottedCornerFinder.cpp b/layout/painting/DottedCornerFinder.cpp new file mode 100644 index 0000000000..2e8408d80b --- /dev/null +++ b/layout/painting/DottedCornerFinder.cpp @@ -0,0 +1,539 @@ +/* -*- 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/. */ + +#include "DottedCornerFinder.h" + +#include <utility> + +#include "BorderCache.h" +#include "BorderConsts.h" +#include "nsTHashMap.h" + +namespace mozilla { + +using namespace gfx; + +static inline Float Square(Float x) { return x * x; } + +static Point PointRotateCCW90(const Point& aP) { return Point(aP.y, -aP.x); } + +struct BestOverlap { + Float overlap; + size_t count; + + BestOverlap() : overlap(0.0f), count(0) {} + + BestOverlap(Float aOverlap, size_t aCount) + : overlap(aOverlap), count(aCount) {} +}; + +static const size_t DottedCornerCacheSize = 256; +nsTHashMap<FourFloatsHashKey, BestOverlap> DottedCornerCache; + +DottedCornerFinder::DottedCornerFinder(const Bezier& aOuterBezier, + const Bezier& aInnerBezier, + Corner aCorner, Float aBorderRadiusX, + Float aBorderRadiusY, const Point& aC0, + Float aR0, const Point& aCn, Float aRn, + const Size& aCornerDim) + : mOuterBezier(aOuterBezier), + mInnerBezier(aInnerBezier), + mCorner(aCorner), + mNormalSign((aCorner == C_TL || aCorner == C_BR) ? -1.0f : 1.0f), + mC0(aC0), + mCn(aCn), + mR0(aR0), + mRn(aRn), + mMaxR(std::max(aR0, aRn)), + mCenterCurveOrigin(mC0.x, mCn.y), + mCenterCurveR(0.0), + mInnerCurveOrigin(mInnerBezier.mPoints[0].x, mInnerBezier.mPoints[3].y), + mBestOverlap(0.0f), + mHasZeroBorderWidth(false), + mHasMore(true), + mMaxCount(aCornerDim.width + aCornerDim.height), + mType(OTHER), + mI(0), + mCount(0) { + NS_ASSERTION(mR0 > 0.0f || mRn > 0.0f, + "At least one side should have non-zero radius."); + + mInnerWidth = fabs(mInnerBezier.mPoints[0].x - mInnerBezier.mPoints[3].x); + mInnerHeight = fabs(mInnerBezier.mPoints[0].y - mInnerBezier.mPoints[3].y); + + DetermineType(aBorderRadiusX, aBorderRadiusY); + + Reset(); +} + +static bool IsSingleCurve(Float aMinR, Float aMaxR, Float aMinBorderRadius, + Float aMaxBorderRadius) { + return aMinR > 0.0f && aMinBorderRadius > aMaxR * 4.0f && + aMinBorderRadius / aMaxBorderRadius > 0.5f; +} + +void DottedCornerFinder::DetermineType(Float aBorderRadiusX, + Float aBorderRadiusY) { + // Calculate parameters for the center curve before swap. + Float centerCurveWidth = fabs(mC0.x - mCn.x); + Float centerCurveHeight = fabs(mC0.y - mCn.y); + Point cornerPoint(mCn.x, mC0.y); + + bool swapped = false; + if (mR0 < mRn) { + // Always draw from wider side to thinner side. + std::swap(mC0, mCn); + std::swap(mR0, mRn); + std::swap(mInnerBezier.mPoints[0], mInnerBezier.mPoints[3]); + std::swap(mInnerBezier.mPoints[1], mInnerBezier.mPoints[2]); + std::swap(mOuterBezier.mPoints[0], mOuterBezier.mPoints[3]); + std::swap(mOuterBezier.mPoints[1], mOuterBezier.mPoints[2]); + mNormalSign = -mNormalSign; + swapped = true; + } + + // See the comment at mType declaration for each condition. + + Float minR = std::min(mR0, mRn); + Float minBorderRadius = std::min(aBorderRadiusX, aBorderRadiusY); + Float maxBorderRadius = std::max(aBorderRadiusX, aBorderRadiusY); + if (IsSingleCurve(minR, mMaxR, minBorderRadius, maxBorderRadius)) { + if (mR0 == mRn) { + Float borderLength; + if (minBorderRadius == maxBorderRadius) { + mType = PERFECT; + borderLength = M_PI * centerCurveHeight / 2.0f; + + mCenterCurveR = centerCurveWidth; + } else { + mType = SINGLE_CURVE_AND_RADIUS; + borderLength = + GetQuarterEllipticArcLength(centerCurveWidth, centerCurveHeight); + } + + Float diameter = mR0 * 2.0f; + size_t count = round(borderLength / diameter); + if (count % 2) { + count++; + } + mCount = count / 2 - 1; + if (mCount > 0) { + mBestOverlap = 1.0f - borderLength / (diameter * count); + } + } else { + mType = SINGLE_CURVE; + } + } + + if (mType == SINGLE_CURVE_AND_RADIUS || mType == SINGLE_CURVE) { + Size cornerSize(centerCurveWidth, centerCurveHeight); + GetBezierPointsForCorner(&mCenterBezier, mCorner, cornerPoint, cornerSize); + if (swapped) { + std::swap(mCenterBezier.mPoints[0], mCenterBezier.mPoints[3]); + std::swap(mCenterBezier.mPoints[1], mCenterBezier.mPoints[2]); + } + } + + if (minR == 0.0f) { + mHasZeroBorderWidth = true; + } + + if ((mType == SINGLE_CURVE || mType == OTHER) && !mHasZeroBorderWidth) { + FindBestOverlap(minR, minBorderRadius, maxBorderRadius); + } +} + +bool DottedCornerFinder::HasMore(void) const { + if (mHasZeroBorderWidth) { + return mI < mMaxCount && mHasMore; + } + + return mI < mCount; +} + +DottedCornerFinder::Result DottedCornerFinder::Next(void) { + mI++; + + if (mType == PERFECT) { + Float phi = mI * 4.0f * mR0 * (1 - mBestOverlap) / mCenterCurveR; + if (mCorner == C_TL) { + phi = -M_PI / 2.0f - phi; + } else if (mCorner == C_TR) { + phi = -M_PI / 2.0f + phi; + } else if (mCorner == C_BR) { + phi = M_PI / 2.0f - phi; + } else { + phi = M_PI / 2.0f + phi; + } + + Point C(mCenterCurveOrigin.x + mCenterCurveR * cos(phi), + mCenterCurveOrigin.y + mCenterCurveR * sin(phi)); + return DottedCornerFinder::Result(C, mR0); + } + + // Find unfilled and filled circles. + (void)FindNext(mBestOverlap); + if (mHasMore) { + (void)FindNext(mBestOverlap); + } + + return Result(mLastC, mLastR); +} + +void DottedCornerFinder::Reset(void) { + mLastC = mC0; + mLastR = mR0; + mLastT = 0.0f; + mHasMore = true; +} + +void DottedCornerFinder::FindPointAndRadius(Point& C, Float& r, + const Point& innerTangent, + const Point& normal, Float t) { + // Find radius for the given tangent point on the inner curve such that the + // circle is also tangent to the outer curve. + + NS_ASSERTION(mType == OTHER, "Wrong mType"); + + Float lower = 0.0f; + Float upper = mMaxR; + const Float DIST_MARGIN = 0.1f; + for (size_t i = 0; i < MAX_LOOP; i++) { + r = (upper + lower) / 2.0f; + C = innerTangent + normal * r; + + Point Near = FindBezierNearestPoint(mOuterBezier, C, t); + Float distSquare = (C - Near).LengthSquare(); + + if (distSquare > Square(r + DIST_MARGIN)) { + lower = r; + } else if (distSquare < Square(r - DIST_MARGIN)) { + upper = r; + } else { + break; + } + } +} + +Float DottedCornerFinder::FindNext(Float overlap) { + Float lower = mLastT; + Float upper = 1.0f; + Float t; + + Point C = mLastC; + Float r = 0.0f; + + Float factor = (1.0f - overlap); + + Float circlesDist = 0.0f; + Float expectedDist = 0.0f; + + const Float DIST_MARGIN = 0.1f; + if (mType == SINGLE_CURVE_AND_RADIUS) { + r = mR0; + + expectedDist = (r + mLastR) * factor; + + // Find C_i on the center curve. + for (size_t i = 0; i < MAX_LOOP; i++) { + t = (upper + lower) / 2.0f; + C = GetBezierPoint(mCenterBezier, t); + + // Check overlap along arc. + circlesDist = GetBezierLength(mCenterBezier, mLastT, t); + if (circlesDist < expectedDist - DIST_MARGIN) { + lower = t; + } else if (circlesDist > expectedDist + DIST_MARGIN) { + upper = t; + } else { + break; + } + } + } else if (mType == SINGLE_CURVE) { + // Find C_i on the center curve, and calculate r_i. + for (size_t i = 0; i < MAX_LOOP; i++) { + t = (upper + lower) / 2.0f; + C = GetBezierPoint(mCenterBezier, t); + + Point Diff = GetBezierDifferential(mCenterBezier, t); + Float DiffLength = Diff.Length(); + if (DiffLength == 0.0f) { + // Basically this shouldn't happen. + // If differential is 0, we cannot calculate tangent circle, + // skip this point. + t = (t + upper) / 2.0f; + continue; + } + + Point normal = PointRotateCCW90(Diff / DiffLength) * (-mNormalSign); + r = CalculateDistanceToEllipticArc(C, normal, mInnerCurveOrigin, + mInnerWidth, mInnerHeight); + + // Check overlap along arc. + circlesDist = GetBezierLength(mCenterBezier, mLastT, t); + expectedDist = (r + mLastR) * factor; + if (circlesDist < expectedDist - DIST_MARGIN) { + lower = t; + } else if (circlesDist > expectedDist + DIST_MARGIN) { + upper = t; + } else { + break; + } + } + } else { + Float distSquareMax = Square(mMaxR * 3.0f); + Float circlesDistSquare = 0.0f; + + // Find C_i and r_i. + for (size_t i = 0; i < MAX_LOOP; i++) { + t = (upper + lower) / 2.0f; + Point innerTangent = GetBezierPoint(mInnerBezier, t); + if ((innerTangent - mLastC).LengthSquare() > distSquareMax) { + // It's clear that this tangent point is too far, skip it. + upper = t; + continue; + } + + Point Diff = GetBezierDifferential(mInnerBezier, t); + Float DiffLength = Diff.Length(); + if (DiffLength == 0.0f) { + // Basically this shouldn't happen. + // If differential is 0, we cannot calculate tangent circle, + // skip this point. + t = (t + upper) / 2.0f; + continue; + } + + Point normal = PointRotateCCW90(Diff / DiffLength) * mNormalSign; + FindPointAndRadius(C, r, innerTangent, normal, t); + + // Check overlap with direct distance. + circlesDistSquare = (C - mLastC).LengthSquare(); + expectedDist = (r + mLastR) * factor; + if (circlesDistSquare < Square(expectedDist - DIST_MARGIN)) { + lower = t; + } else if (circlesDistSquare > Square(expectedDist + DIST_MARGIN)) { + upper = t; + } else { + break; + } + } + + circlesDist = sqrt(circlesDistSquare); + } + + if (mHasZeroBorderWidth) { + // When calculating circle around r=0, it may result in wrong radius that + // is bigger than previous circle. Detect it and stop calculating. + const Float R_MARGIN = 0.1f; + if (mLastR < R_MARGIN && r > mLastR) { + mHasMore = false; + mLastR = 0.0f; + return 0.0f; + } + } + + mLastT = t; + mLastC = C; + mLastR = r; + + if (mHasZeroBorderWidth) { + const Float T_MARGIN = 0.001f; + if (mLastT >= 1.0f - T_MARGIN || + (mLastC - mCn).LengthSquare() < Square(mLastR)) { + mHasMore = false; + } + } + + if (expectedDist == 0.0f) { + return 0.0f; + } + + return 1.0f - circlesDist * factor / expectedDist; +} + +void DottedCornerFinder::FindBestOverlap(Float aMinR, Float aMinBorderRadius, + Float aMaxBorderRadius) { + // If overlap is not calculateable, find it with binary search, + // such that there exists i that C_i == C_n with the given overlap. + + FourFloats key(aMinR, mMaxR, aMinBorderRadius, aMaxBorderRadius); + BestOverlap best; + if (DottedCornerCache.Get(key, &best)) { + mCount = best.count; + mBestOverlap = best.overlap; + return; + } + + Float lower = 0.0f; + Float upper = 0.5f; + // Start from lower bound to find the minimum number of circles. + Float overlap = 0.0f; + mBestOverlap = overlap; + size_t targetCount = 0; + + const Float OVERLAP_MARGIN = 0.1f; + for (size_t j = 0; j < MAX_LOOP; j++) { + Reset(); + + size_t count; + Float actualOverlap; + if (!GetCountAndLastOverlap(overlap, &count, &actualOverlap)) { + if (j == 0) { + mCount = mMaxCount; + break; + } + } + + if (j == 0) { + if (count < 3 || (count == 3 && actualOverlap > 0.5f)) { + // |count == 3 && actualOverlap > 0.5f| means there could be + // a circle but it is too near from both ends. + // + // if actualOverlap == 0.0 + // 1 2 3 + // +-------+-------+-------+-------+ + // | ##### | ***** | ##### | ##### | + // |#######|*******|#######|#######| + // |###+###|***+***|###+###|###+###| + // |# C_0 #|* C_1 *|# C_2 #|# C_n #| + // | ##### | ***** | ##### | ##### | + // +-------+-------+-------+-------+ + // | + // V + // +-------+---+-------+---+-------+ + // | ##### | | ##### | | ##### | + // |#######| |#######| |#######| + // |###+###| |###+###| |###+###| Find the best overlap to place + // |# C_0 #| |# C_1 #| |# C_n #| C_1 at the middle of them + // | ##### | | ##### | | ##### | + // +-------+---+-------+---|-------+ + // + // if actualOverlap == 0.5 + // 1 2 3 + // +-------+-------+-------+---+ + // | ##### | ***** | ##### |## | + // |#######|*******|##### C_n #| + // |###+###|***+***|###+###+###| + // |# C_0 #|* C_1 *|# C_2 #|###| + // | ##### | ***** | ##### |## | + // +-------+-------+-------+---+ + // | + // V + // +-------+-+-------+-+-------+ + // | ##### | | ##### | | ##### | + // |#######| |#######| |#######| + // |###+###| |###+###| |###+###| Even if we place C_1 at the middle + // |# C_0 #| |# C_1 #| |# C_n #| of them, it's too near from them + // | ##### | | ##### | | ##### | + // +-------+-+-------+-|-------+ + // | + // V + // +-------+-----------+-------+ + // | ##### | | ##### | + // |#######| |#######| + // |###+###| |###+###| Do not draw any circle + // |# C_0 #| |# C_n #| + // | ##### | | ##### | + // +-------+-----------+-------+ + mCount = 0; + break; + } + + // targetCount should be 2n, as we're searching C_1 to C_n. + // + // targetCount = 4 + // mCount = 1 + // 1 2 3 4 + // +-------+-------+-------+-------+-------+ + // | ##### | ***** | ##### | ***** | ##### | + // |#######|*******|#######|*******|#######| + // |###+###|***+***|###+###|***+***|###+###| + // |# C_0 #|* C_1 *|# C_2 #|* C_3 *|# C_n #| + // | ##### | ***** | ##### | ***** | ##### | + // +-------+-------+-------+-------+-------+ + // 1 + // + // targetCount = 6 + // mCount = 2 + // 1 2 3 4 5 6 + // +-------+-------+-------+-------+-------+-------+-------+ + // | ##### | ***** | ##### | ***** | ##### | ***** | ##### | + // |#######|*******|#######|*******|#######|*******|#######| + // |###+###|***+***|###+###|***+***|###+###|***+***|###+###| + // |# C_0 #|* C_1 *|# C_2 #|* C_3 *|# C_4 #|* C_5 *|# C_n #| + // | ##### | ***** | ##### | ***** | ##### | ***** | ##### | + // +-------+-------+-------+-------+-------+-------+-------+ + // 1 2 + if (count % 2) { + targetCount = count + 1; + } else { + targetCount = count; + } + + mCount = targetCount / 2 - 1; + } + + if (count == targetCount) { + mBestOverlap = overlap; + + if (fabs(actualOverlap - overlap) < OVERLAP_MARGIN) { + break; + } + + // We started from upper bound, no need to update range when j == 0. + if (j > 0) { + if (actualOverlap > overlap) { + lower = overlap; + } else { + upper = overlap; + } + } + } else { + // |j == 0 && count != targetCount| means that |targetCount = count + 1|, + // and we started from upper bound, no need to update range when j == 0. + if (j > 0) { + if (count > targetCount) { + upper = overlap; + } else { + lower = overlap; + } + } + } + + overlap = (upper + lower) / 2.0f; + } + + if (DottedCornerCache.Count() > DottedCornerCacheSize) { + DottedCornerCache.Clear(); + } + DottedCornerCache.InsertOrUpdate(key, BestOverlap(mBestOverlap, mCount)); +} + +bool DottedCornerFinder::GetCountAndLastOverlap(Float aOverlap, size_t* aCount, + Float* aActualOverlap) { + // Return the number of circles and the last circles' overlap for the + // given overlap. + + Reset(); + + const Float T_MARGIN = 0.001f; + const Float DIST_MARGIN = 0.1f; + const Float DIST_MARGIN_SQUARE = Square(DIST_MARGIN); + for (size_t i = 0; i < mMaxCount; i++) { + Float actualOverlap = FindNext(aOverlap); + if (mLastT >= 1.0f - T_MARGIN || + (mLastC - mCn).LengthSquare() < DIST_MARGIN_SQUARE) { + *aCount = i + 1; + *aActualOverlap = actualOverlap; + return true; + } + } + + return false; +} + +} // namespace mozilla diff --git a/layout/painting/DottedCornerFinder.h b/layout/painting/DottedCornerFinder.h new file mode 100644 index 0000000000..fd3a9e5d04 --- /dev/null +++ b/layout/painting/DottedCornerFinder.h @@ -0,0 +1,432 @@ +/* -*- 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/. */ + +#ifndef mozilla_DottedCornerFinder_h_ +#define mozilla_DottedCornerFinder_h_ + +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/BezierUtils.h" +#include "gfxRect.h" + +namespace mozilla { + +// Calculate C_i and r_i for each filled/unfilled circles in dotted corner. +// Returns circle with C_{2j} and r_{2j} where 0 < 2j < n. +// +// ____-----------+ +// __---- ***** ###| +// __---- ********* ####| +// __--- ##### ***********#####| +// _-- ######### *****+*****#####+ C_0 +// _- ########### *** C_1****#####| +// / #####+##### ********* ####| +// / . ### C_2 ### ***** ###| +// | ######### ____-------+ +// | . #####____----- +// | __---- +// | . / +// | / +// | ***** | +// | ******* | +// |*********| +// |****+****| +// | C_{n-1} | +// | ******* | +// | ***** | +// | ##### | +// | ####### | +// |#########| +// +----+----+ +// C_n + +class DottedCornerFinder { + typedef mozilla::gfx::Bezier Bezier; + typedef mozilla::gfx::Float Float; + typedef mozilla::gfx::Point Point; + typedef mozilla::gfx::Size Size; + + public: + struct Result { + // Center point of dot and its radius. + Point C; + Float r; + + Result(const Point& aC, Float aR) : C(aC), r(aR) { MOZ_ASSERT(aR >= 0); } + }; + + // aBorderRadiusX + // aCornerDim.width + // |<----------------->| + // | | v + // --+-------------___---+-- + // ^ | __-- | | + // | | _- | | aR0 + // | | / aC0 +-- + // | | / | ^ + // | | | | + // aBorderRadiusY | | | __--+ + // aCornerDim.height | || _- + // | || / + // | | / + // | | | + // | | | + // | | | + // | | | + // v | aCn | + // --+----+----+ + // | | + // |<-->| + // aRn + // + // aCornerDim and (aBorderRadiusX, aBorderRadiusY) can be different when + // aBorderRadiusX is smaller than aRn*2 or + // aBorderRadiusY is smaller than aR0*2. + // + // aCornerDim.width + // |<----------------->| + // | | + // | aBorderRadiusX | + // |<--------->| | + // | | | + // -------------------+-------__--+-------+-- + // ^ ^ | _- | ^ + // | | | / | | + // | | | / | | + // | aBorderRadiusY | | | | | aR0 + // | | || | | + // | | | | | + // aCornerDim.height | v | | v + // | --+ aC0 +-- + // | | | + // | | | + // | | | + // | | | + // | | | + // v | aCn | + // -------------------+---------+---------+ + // | | + // |<------->| + // aRn + DottedCornerFinder(const Bezier& aOuterBezier, const Bezier& aInnerBezier, + mozilla::Corner aCorner, Float aBorderRadiusX, + Float aBorderRadiusY, const Point& aC0, Float aR0, + const Point& aCn, Float aRn, const Size& aCornerDim); + + bool HasMore(void) const; + Result Next(void); + + private: + static const size_t MAX_LOOP = 32; + + // Bezier control points for the outer curve, the inner curve, and a curve + // that center points of circles are on (center curve). + // + // ___---+ outer curve + // __-- | + // _- | + // / __---+ center curve + // / __-- | + // | / | + // | / __--+ inner curve + // | | _- + // | | / + // | | / + // | | | + // | | | + // | | | + // | | | + // | | | + // +----+----+ + Bezier mOuterBezier; + Bezier mInnerBezier; + Bezier mCenterBezier; + + mozilla::Corner mCorner; + + // Sign of the normal vector used in radius calculation, flipped depends on + // corner and start and end radii. + Float mNormalSign; + + // Center points and raii for start and end circles, mR0 >= mRn. + // mMaxR = max(mR0, mRn) + // + // v + // ___---+------ + // __-- #|# | mRn + // _- ##|## | + // / ##+## --- + // / mCn ^ + // | #|# + // | __--+ + // | _- + // | / + // | / + // | | + // | | + // | ##### | + // | ####### | + // |#########| + // +----+----+ + // |## mC0 ##| + // | ####### | + // | ##### | + // | | + // |<-->| + // + // mR0 + // + Point mC0; + Point mCn; + Float mR0; + Float mRn; + Float mMaxR; + + // Parameters for the center curve with perfect circle and the inner curve. + // The center curve doesn't necessarily share the origin with others. + // + // ___---+ + // __-- | + // _- | + // / __-+ | + // / __-- | + // | / | + // | / __--+-- + // | | _- | ^ + // | | / | | + // | | / | | + // | | | | | + // | | | | | mInnerHeight + // | | | | | + // | + | | | + // | | | v + // +---------+---------+ + // | | mInnerCurveOrigin + // |<------->| + // mInnerWidth + // + // ___---+ + // __-- + // _- + // / __-+ + // / __-- | + // | / | + // | / __--+ + // | | _- | + // | | / | + // | | / | + // | | | | + // | | | | + // | | | | + // | +--- | ------+ + // | | | | mCenterCurveOrigin + // + | + | + // | | + // | | + // | | + // | | + // |<---------->| + // mCenterCurveR + // + Point mCenterCurveOrigin; + Float mCenterCurveR; + Point mInnerCurveOrigin; + Float mInnerWidth; + Float mInnerHeight; + + Point mLastC; + Float mLastR; + Float mLastT; + + // Overlap between two circles. + // It uses arc length on PERFECT, SINGLE_CURVE_AND_RADIUS, and SINGLE_CURVE, + // and direct distance on OTHER. + Float mBestOverlap; + + // If one of border-widths is 0, do not calculate overlap, and draw circles + // until it reaches the other side or exceeds mMaxCount. + bool mHasZeroBorderWidth; + bool mHasMore; + + // The maximum number of filled/unfilled circles. + size_t mMaxCount; + + enum { + // radius.width + // |<----------------->| + // | | + // --+-------------___---+---- + // ^ | __-- #|# ^ + // | | _- ##|## | + // | | / ##+## | top-width + // | | / ##|## | + // | | | #|# v + // | | | __--+---- + // radius.height | || _- + // | || / + // | | / + // | | | + // | | | + // | | ##### | + // | | ####### | + // v |#########| + // --+----+----+ + // |#########| + // | ####### | + // | ##### | + // | | + // |<------->| + // left-width + + // * top-width == left-width + // * radius.width == radius.height + // * top-width < radius.width * 2 + // + // All circles has same radii and are on single perfect circle's arc. + // Overlap is known. + // + // Split the perfect circle's arc into 2n segments, each segment's length is + // top-width * (1 - overlap). Place each circle's center point C_i on each + // end of the segment, each circle's radius r_i is top-width / 2 + // + // ##### + // ####### + // perfect ######### + // circle's ___---+#### + // arc ##### __-- ## C_0 ## + // | #####_- ###|### + // | ####+#### ##|## + // | ##/C_i ## | + // | |###### | + // | | ##### | + // +->| | + // | | + // ##|## | + // ###|### | + // ####|#### | + // ####+-------------------+ + // ## C_n ## + // ####### + // ##### + PERFECT, + + // * top-width == left-width + // * 0.5 < radius.width / radius.height < 2.0 + // * top-width < min(radius.width, radius.height) * 2 + // + // All circles has same radii and are on single elliptic arc. + // Overlap is known. + // + // Split the elliptic arc into 2n segments, each segment's length is + // top-width * (1 - overlap). Place each circle's center point C_i on each + // end of the segment, each circle's radius r_i is top-width / 2 + // + // ##### + // ####### + // ##### ######### + // ####### ____----+#### + // elliptic ######__--- ## C_0 ## + // arc ##__+-### ###|### + // | / # C_i # ##|## + // +--> / ##### | + // | | + // ###|# | + // ###|### | + // ####|#### | + // ####+------------------------+ + // ## C_n ## + // ####### + // ##### + SINGLE_CURVE_AND_RADIUS, + + // * top-width != left-width + // * 0 < min(top-width, left-width) + // * 0.5 < radius.width / radius.height < 2.0 + // * max(top-width, left-width) < min(radius.width, radius.height) * 2 + // + // All circles are on single elliptic arc. + // Overlap is unknown. + // + // Place each circle's center point C_i on elliptic arc, each circle's + // radius r_i is the distance between the center point and the inner curve. + // The arc segment's length between C_i and C_{i-1} is + // (r_i + r_{i-1}) * (1 - overlap). + // + // outer curve + // / + // / + // / / center curve + // / ####### / + // /## /# + // +# / # + // /# / # + // / # C_i / # + // / # + # / + // / # / \ # / inner curve + // # / \ #/ + // # / r_i \+ + // #/ ##/ + // / ####### / + // / + SINGLE_CURVE, + + // Other cases. + // Circles are not on single elliptic arc. + // Overlap are unknown. + // + // Place tangent point innerTangent on the inner curve and find circle's + // center point C_i and radius r_i where the circle is also tangent to the + // outer curve. + // Distance between C_i and C_{i-1} is (r_i + r_{i-1}) * (1 - overlap). + // + // outer curve + // / + // / + // / + // / ####### + // /## ## + // +# # + // /# \ # + // / # \ # + // / # + # / + // / # C_i \ # / inner curve + // # \ #/ + // # r_i \+ + // ## ##/ innerTangent + // ####### / + // / + OTHER + } mType; + + size_t mI; + size_t mCount; + + // Determine mType from parameters. + void DetermineType(Float aBorderRadiusX, Float aBorderRadiusY); + + // Reset calculation. + void Reset(void); + + // Find radius for the given tangent point on the inner curve such that the + // circle is also tangent to the outer curve. + void FindPointAndRadius(Point& C, Float& r, const Point& innerTangent, + const Point& normal, Float t); + + // Find next dot. + Float FindNext(Float overlap); + + // Find mBestOverlap for parameters. + void FindBestOverlap(Float aMinR, Float aMinBorderRadius, + Float aMaxBorderRadius); + + // Fill corner with dots with given overlap, and return the number of dots + // and last two dots's overlap. + bool GetCountAndLastOverlap(Float aOverlap, size_t* aCount, + Float* aActualOverlap); +}; + +} // namespace mozilla + +#endif /* mozilla_DottedCornerFinder_h_ */ diff --git a/layout/painting/HitTestInfo.cpp b/layout/painting/HitTestInfo.cpp new file mode 100644 index 0000000000..872773c32f --- /dev/null +++ b/layout/painting/HitTestInfo.cpp @@ -0,0 +1,74 @@ +/* -*- 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/. */ + +#include "HitTestInfo.h" + +#include "mozilla/StaticPtr.h" +#include "mozilla/webrender/WebRenderAPI.h" +#include "nsDisplayList.h" +#include "nsIFrame.h" +#include "mozilla/layers/ScrollableLayerGuid.h" + +using namespace mozilla::gfx; + +namespace mozilla { + +static StaticAutoPtr<const HitTestInfo> gEmptyHitTestInfo; + +const HitTestInfo& HitTestInfo::Empty() { + if (!gEmptyHitTestInfo) { + gEmptyHitTestInfo = new HitTestInfo(); + } + + return *gEmptyHitTestInfo; +} + +void HitTestInfo::Shutdown() { gEmptyHitTestInfo = nullptr; } + +using ViewID = layers::ScrollableLayerGuid::ViewID; + +ViewID HitTestInfo::GetViewId(wr::DisplayListBuilder& aBuilder, + const ActiveScrolledRoot* aASR) const { + if (mScrollTarget) { + return *mScrollTarget; + } + + Maybe<ViewID> fixedTarget = aBuilder.GetContainingFixedPosScrollTarget(aASR); + + if (fixedTarget) { + return *fixedTarget; + } + + if (aASR) { + return aASR->GetViewId(); + } + + return layers::ScrollableLayerGuid::NULL_SCROLL_ID; +} + +void HitTestInfo::Initialize(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame) { + if (!aBuilder->BuildCompositorHitTestInfo()) { + return; + } + + mInfo = aFrame->GetCompositorHitTestInfo(aBuilder); + if (mInfo != gfx::CompositorHitTestInvisibleToHit) { + mArea = aFrame->GetCompositorHitTestArea(aBuilder); + InitializeScrollTarget(aBuilder); + } +} + +void HitTestInfo::InitializeScrollTarget(nsDisplayListBuilder* aBuilder) { + if (aBuilder->GetCurrentScrollbarDirection().isSome()) { + // In the case of scrollbar frames, we use the scrollbar's target + // scrollframe instead of the scrollframe with which the scrollbar actually + // moves. + MOZ_ASSERT(Info().contains(CompositorHitTestFlags::eScrollbar)); + mScrollTarget = Some(aBuilder->GetCurrentScrollbarTarget()); + } +} + +} // namespace mozilla diff --git a/layout/painting/HitTestInfo.h b/layout/painting/HitTestInfo.h new file mode 100644 index 0000000000..95438b810c --- /dev/null +++ b/layout/painting/HitTestInfo.h @@ -0,0 +1,59 @@ +/* -*- 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/. */ + +#ifndef GFX_HITTESTINFO_H +#define GFX_HITTESTINFO_H + +#include "mozilla/gfx/CompositorHitTestInfo.h" +#include "mozilla/layers/ScrollableLayerGuid.h" +#include "nsRect.h" + +class nsIFrame; + +namespace mozilla { + +class nsDisplayListBuilder; +struct ActiveScrolledRoot; + +namespace wr { +class DisplayListBuilder; +} // namespace wr + +/** + * A helper class that manages compositor hit testing information. + */ +class HitTestInfo { + public: + using CompositorHitTestInfo = gfx::CompositorHitTestInfo; + using ViewID = layers::ScrollableLayerGuid::ViewID; + + ViewID GetViewId(wr::DisplayListBuilder& aBuilder, + const ActiveScrolledRoot* aASR) const; + + void Initialize(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame); + void InitializeScrollTarget(nsDisplayListBuilder* aBuilder); + + void SetAreaAndInfo(const nsRect& aArea, const CompositorHitTestInfo& aInfo) { + mArea = aArea; + mInfo = aInfo; + } + + static void Shutdown(); + + const nsRect& Area() const { return mArea; } + const CompositorHitTestInfo& Info() const { return mInfo; } + + static const HitTestInfo& Empty(); + + private: + nsRect mArea; + CompositorHitTestInfo mInfo; + mozilla::Maybe<ViewID> mScrollTarget; +}; + +} // namespace mozilla + +#endif diff --git a/layout/painting/MaskLayerImageCache.cpp b/layout/painting/MaskLayerImageCache.cpp new file mode 100644 index 0000000000..2b3b63dc57 --- /dev/null +++ b/layout/painting/MaskLayerImageCache.cpp @@ -0,0 +1,64 @@ +/* -*- 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/. */ + +#include "MaskLayerImageCache.h" + +#include "ImageContainer.h" +#include "mozilla/layers/KnowsCompositor.h" + +using namespace mozilla::layers; + +namespace mozilla { + +MaskLayerImageCache::MaskLayerImageCache() { + MOZ_COUNT_CTOR(MaskLayerImageCache); +} +MaskLayerImageCache::~MaskLayerImageCache() { + MOZ_COUNT_DTOR(MaskLayerImageCache); +} + +void MaskLayerImageCache::Sweep() { + for (auto iter = mMaskImageContainers.Iter(); !iter.Done(); iter.Next()) { + const auto& key = iter.Get()->mKey; + if (key->HasZeroLayerCount()) { + iter.Remove(); + } + } +} + +ImageContainer* MaskLayerImageCache::FindImageFor( + const MaskLayerImageKey** aKey) { + if (MaskLayerImageEntry* entry = mMaskImageContainers.GetEntry(**aKey)) { + *aKey = entry->mKey.get(); + return entry->mContainer; + } + + return nullptr; +} + +void MaskLayerImageCache::PutImage(const MaskLayerImageKey* aKey, + ImageContainer* aContainer) { + MaskLayerImageEntry* entry = mMaskImageContainers.PutEntry(*aKey); + entry->mContainer = aContainer; +} + +MaskLayerImageCache::MaskLayerImageKey::MaskLayerImageKey() + : mRoundedClipRects(), mLayerCount(0) { + MOZ_COUNT_CTOR(MaskLayerImageKey); +} + +MaskLayerImageCache::MaskLayerImageKey::MaskLayerImageKey( + const MaskLayerImageKey& aKey) + : mRoundedClipRects(aKey.mRoundedClipRects.Clone()), + mLayerCount(aKey.mLayerCount) { + MOZ_COUNT_CTOR(MaskLayerImageKey); +} + +MaskLayerImageCache::MaskLayerImageKey::~MaskLayerImageKey() { + MOZ_COUNT_DTOR(MaskLayerImageKey); +} + +} // namespace mozilla diff --git a/layout/painting/MaskLayerImageCache.h b/layout/painting/MaskLayerImageCache.h new file mode 100644 index 0000000000..7e20f72a20 --- /dev/null +++ b/layout/painting/MaskLayerImageCache.h @@ -0,0 +1,257 @@ +/* -*- 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/. */ + +#ifndef MASKLAYERIMAGECACHE_H_ +#define MASKLAYERIMAGECACHE_H_ + +#include "DisplayItemClip.h" +#include "nsPresContext.h" +#include "mozilla/gfx/Matrix.h" +#include "mozilla/UniquePtr.h" + +namespace mozilla { + +namespace layers { +class ImageContainer; +class KnowsCompositor; +} // namespace layers + +/** + * Keeps a record of image containers for mask layers, containers are mapped + * from the rounded rects used to create them. + * The cache stores MaskLayerImageEntries indexed by MaskLayerImageKeys. + * Each MaskLayerImageEntry owns a heap-allocated MaskLayerImageKey + * (heap-allocated so that a mask layer's userdata can keep a pointer to the + * key for its image, in spite of the hashtable moving its entries around). + * The key consists of the rounded rects used to create the mask, + * an nsRefPtr to the ImageContainer containing the image, and a count + * of the number of layers currently using this ImageContainer. + * When the key's layer count is zero, the cache + * may remove the entry, which deletes the key object. + */ +class MaskLayerImageCache { + typedef mozilla::layers::ImageContainer ImageContainer; + typedef mozilla::layers::KnowsCompositor KnowsCompositor; + + public: + MaskLayerImageCache(); + ~MaskLayerImageCache(); + + /** + * Representation of a rounded rectangle in device pixel coordinates, in + * contrast to DisplayItemClip::RoundedRect, which uses app units. + * In particular, our internal representation uses a gfxRect, rather than + * an nsRect, so this class is easier to use with transforms. + */ + struct PixelRoundedRect { + PixelRoundedRect() = delete; + + PixelRoundedRect(const DisplayItemClip::RoundedRect& aRRect, + nsPresContext* aPresContext) + : mRect(aPresContext->AppUnitsToGfxUnits(aRRect.mRect.x), + aPresContext->AppUnitsToGfxUnits(aRRect.mRect.y), + aPresContext->AppUnitsToGfxUnits(aRRect.mRect.width), + aPresContext->AppUnitsToGfxUnits(aRRect.mRect.height)) { + MOZ_COUNT_CTOR(PixelRoundedRect); + for (const auto corner : mozilla::AllPhysicalHalfCorners()) { + mRadii[corner] = + aPresContext->AppUnitsToGfxUnits(aRRect.mRadii[corner]); + } + } + + PixelRoundedRect(const PixelRoundedRect& aPRR) : mRect(aPRR.mRect) { + MOZ_COUNT_CTOR(PixelRoundedRect); + for (const auto corner : mozilla::AllPhysicalHalfCorners()) { + mRadii[corner] = aPRR.mRadii[corner]; + } + } + + MOZ_COUNTED_DTOR(PixelRoundedRect) + + // Applies the scale and translate components of aTransform. + // It is an error to pass a matrix which does more than just scale + // and translate. + void ScaleAndTranslate(const gfx::Matrix& aTransform) { + NS_ASSERTION(aTransform._12 == 0 && aTransform._21 == 0, + "Transform has a component other than scale and translate"); + + mRect = aTransform.TransformBounds(mRect); + + for (size_t i = 0; i < ArrayLength(mRadii); i += 2) { + mRadii[i] *= aTransform._11; + mRadii[i + 1] *= aTransform._22; + } + } + + bool operator==(const PixelRoundedRect& aOther) const { + if (!mRect.IsEqualInterior(aOther.mRect)) { + return false; + } + + for (const auto corner : mozilla::AllPhysicalHalfCorners()) { + if (mRadii[corner] != aOther.mRadii[corner]) { + return false; + } + } + return true; + } + bool operator!=(const PixelRoundedRect& aOther) const { + return !(*this == aOther); + } + + // Create a hash for this object. + PLDHashNumber Hash() const { + PLDHashNumber hash = HashBytes(&mRect.x, 4 * sizeof(gfxFloat)); + hash = AddToHash(hash, HashBytes(mRadii, 8 * sizeof(gfxFloat))); + + return hash; + } + + gfx::Rect mRect; + // Indices into mRadii are the enum HalfCorner constants in gfx/2d/Types.h + gfxFloat mRadii[8]; + }; + + struct MaskLayerImageKeyRef; + + /** + * A key to identify cached image containers. + * The const-ness of this class is with respect to its use as a key into a + * hashtable, so anything not used to create the hash is mutable. + * mLayerCount counts the number of mask layers which have a reference to + * MaskLayerImageEntry::mContainer; it is maintained by MaskLayerUserData, + * which keeps a reference to the key. There will usually be mLayerCount + 1 + * pointers to a key object (the +1 being from the hashtable entry), but this + * invariant may be temporarily broken. + */ + struct MaskLayerImageKey { + friend struct MaskLayerImageKeyRef; + + MaskLayerImageKey(); + MaskLayerImageKey(const MaskLayerImageKey& aKey); + + ~MaskLayerImageKey(); + + bool HasZeroLayerCount() const { return mLayerCount == 0; } + + PLDHashNumber Hash() const { + PLDHashNumber hash = 0; + + for (uint32_t i = 0; i < mRoundedClipRects.Length(); ++i) { + hash = AddToHash(hash, mRoundedClipRects[i].Hash()); + } + hash = AddToHash(hash, mKnowsCompositor.get()); + + return hash; + } + + bool operator==(const MaskLayerImageKey& aOther) const { + return mKnowsCompositor == aOther.mKnowsCompositor && + mRoundedClipRects == aOther.mRoundedClipRects; + } + + nsTArray<PixelRoundedRect> mRoundedClipRects; + RefPtr<KnowsCompositor> mKnowsCompositor; + + private: + void IncLayerCount() const { ++mLayerCount; } + void DecLayerCount() const { + NS_ASSERTION(mLayerCount > 0, "Inconsistent layer count"); + --mLayerCount; + } + mutable uint32_t mLayerCount; + }; + + /** + * This struct maintains a reference to a MaskLayerImageKey, via a variant on + * refcounting. When a key is passed in via Reset(), we increment the + * passed-in key's mLayerCount, and we decrement its mLayerCount when we're + * destructed (or when the key is replaced via a second Reset() call). + * + * However, unlike standard refcounting smart-pointers, this object does + * *not* delete the tracked MaskLayerImageKey -- instead, deletion happens + * in MaskLayerImageCache::Sweep(), for any keys whose mLayerCount is 0. + */ + struct MaskLayerImageKeyRef { + ~MaskLayerImageKeyRef() { + if (mRawPtr) { + mRawPtr->DecLayerCount(); + } + } + + MaskLayerImageKeyRef() : mRawPtr(nullptr) {} + MaskLayerImageKeyRef(const MaskLayerImageKeyRef&) = delete; + void operator=(const MaskLayerImageKeyRef&) = delete; + + void Reset(const MaskLayerImageKey* aPtr) { + MOZ_ASSERT( + aPtr, "Cannot initialize a MaskLayerImageKeyRef with a null pointer"); + aPtr->IncLayerCount(); + if (mRawPtr) { + mRawPtr->DecLayerCount(); + } + mRawPtr = aPtr; + } + + private: + const MaskLayerImageKey* mRawPtr; + }; + + // Find an image container for aKey, returns nullptr if there is no suitable + // cached image. If there is an image, then aKey is set to point at the stored + // key for the image. + ImageContainer* FindImageFor(const MaskLayerImageKey** aKey); + + // Add an image container with a key to the cache + // The image container used will be set as the container in aKey and aKey + // itself will be linked from this cache + void PutImage(const MaskLayerImageKey* aKey, ImageContainer* aContainer); + + // Sweep the cache for old image containers that can be deleted + void Sweep(); + + protected: + class MaskLayerImageEntry : public PLDHashEntryHdr { + public: + typedef const MaskLayerImageKey& KeyType; + typedef const MaskLayerImageKey* KeyTypePointer; + + explicit MaskLayerImageEntry(KeyTypePointer aKey) : mKey(aKey) { + MOZ_COUNT_CTOR(MaskLayerImageEntry); + } + MaskLayerImageEntry(const MaskLayerImageEntry& aOther) + : mKey(aOther.mKey.get()) { + NS_ERROR("ALLOW_MEMMOVE == true, should never be called"); + } + MOZ_COUNTED_DTOR(MaskLayerImageEntry) + + // KeyEquals(): does this entry match this key? + bool KeyEquals(KeyTypePointer aKey) const { return *mKey == *aKey; } + + // KeyToPointer(): Convert KeyType to KeyTypePointer + static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; } + + // HashKey(): calculate the hash number + static PLDHashNumber HashKey(KeyTypePointer aKey) { return aKey->Hash(); } + + // ALLOW_MEMMOVE can we move this class with memmove(), or do we have + // to use the copy constructor? + enum { ALLOW_MEMMOVE = true }; + + bool operator==(const MaskLayerImageEntry& aOther) const { + return KeyEquals(aOther.mKey.get()); + } + + mozilla::UniquePtr<const MaskLayerImageKey> mKey; + RefPtr<ImageContainer> mContainer; + }; + + nsTHashtable<MaskLayerImageEntry> mMaskImageContainers; +}; + +} // namespace mozilla + +#endif diff --git a/layout/painting/MatrixStack.h b/layout/painting/MatrixStack.h new file mode 100644 index 0000000000..320d08317b --- /dev/null +++ b/layout/painting/MatrixStack.h @@ -0,0 +1,61 @@ +/* -*- 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/. */ + +#ifndef MOZILLA_PAINTING_MATRIXSTACK_H +#define MOZILLA_PAINTING_MATRIXSTACK_H + +#include "nsTArray.h" +#include "mozilla/gfx/MatrixFwd.h" + +namespace mozilla { + +/** + * MatrixStack stores a stack of matrices and keeps track of the accumulated + * transform matrix. + */ +template <typename T> +class MatrixStack { + public: + MatrixStack() = default; + + ~MatrixStack() { MOZ_ASSERT(mMatrices.IsEmpty()); } + + /** + * Returns the current accumulated transform matrix. + */ + const T& CurrentMatrix() const { return mCurrentMatrix; } + + /** + * Returns true if any matrices are currently pushed to the stack. + */ + bool HasTransform() const { return mMatrices.Length() > 0; } + + /** + * Pushes the given |aMatrix| to the stack. + */ + void Push(const T& aMatrix) { + mMatrices.AppendElement(mCurrentMatrix); + mCurrentMatrix = aMatrix * mCurrentMatrix; + } + + /** + * Pops the most recently added matrix from the stack. + */ + void Pop() { + MOZ_ASSERT(mMatrices.Length() > 0); + mCurrentMatrix = mMatrices.PopLastElement(); + } + + private: + T mCurrentMatrix; + AutoTArray<T, 2> mMatrices; +}; + +typedef MatrixStack<gfx::Matrix4x4Flagged> MatrixStack4x4; + +} // namespace mozilla + +#endif /* MOZILLA_PAINTING_MATRIXSTACK_H */ diff --git a/layout/painting/PaintTracker.cpp b/layout/painting/PaintTracker.cpp new file mode 100644 index 0000000000..208f037e7a --- /dev/null +++ b/layout/painting/PaintTracker.cpp @@ -0,0 +1,13 @@ +/* -*- 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/. */ + +#include "mozilla/PaintTracker.h" + +namespace mozilla { + +int PaintTracker::gPaintTracker; + +} // namespace mozilla diff --git a/layout/painting/PaintTracker.h b/layout/painting/PaintTracker.h new file mode 100644 index 0000000000..1f5554f983 --- /dev/null +++ b/layout/painting/PaintTracker.h @@ -0,0 +1,31 @@ +/* -*- 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/. */ + +#ifndef mozilla_PaintTracker_h +#define mozilla_PaintTracker_h + +#include "mozilla/Attributes.h" +#include "nsDebug.h" + +namespace mozilla { + +class MOZ_STACK_CLASS PaintTracker { + public: + PaintTracker() { ++gPaintTracker; } + ~PaintTracker() { + NS_ASSERTION(gPaintTracker > 0, "Mismatched constructor/destructor"); + --gPaintTracker; + } + + static bool IsPainting() { return !!gPaintTracker; } + + private: + static int gPaintTracker; +}; + +} // namespace mozilla + +#endif // mozilla_PaintTracker_h diff --git a/layout/painting/RetainedDisplayListBuilder.cpp b/layout/painting/RetainedDisplayListBuilder.cpp new file mode 100644 index 0000000000..3e3b8e9a94 --- /dev/null +++ b/layout/painting/RetainedDisplayListBuilder.cpp @@ -0,0 +1,1732 @@ +/* -*- 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/. + */ + +#include "RetainedDisplayListBuilder.h" + +#include "mozilla/Attributes.h" +#include "mozilla/StaticPrefs_layout.h" +#include "nsIFrame.h" +#include "nsIFrameInlines.h" +#include "nsIScrollableFrame.h" +#include "nsPlaceholderFrame.h" +#include "nsSubDocumentFrame.h" +#include "nsViewManager.h" +#include "nsCanvasFrame.h" +#include "mozilla/AutoRestore.h" +#include "mozilla/DisplayPortUtils.h" +#include "mozilla/PresShell.h" +#include "mozilla/ProfilerLabels.h" + +/** + * Code for doing display list building for a modified subset of the window, + * and then merging it into the existing display list (for the full window). + * + * The approach primarily hinges on the observation that the 'true' ordering + * of display items is represented by a DAG (only items that intersect in 2d + * space have a defined ordering). Our display list is just one of a many + * possible linear representations of this ordering. + * + * Each time a frame changes (gets a new ComputedStyle, or has a size/position + * change), we schedule a paint (as we do currently), but also reord the frame + * that changed. + * + * When the next paint occurs we union the overflow areas (in screen space) of + * the changed frames, and compute a rect/region that contains all changed + * items. We then build a display list just for this subset of the screen and + * merge it into the display list from last paint. + * + * Any items that exist in one list and not the other must not have a defined + * ordering in the DAG, since they need to intersect to have an ordering and + * we would have built both in the new list if they intersected. Given that, we + * can align items that appear in both lists, and any items that appear between + * matched items can be inserted into the merged list in any order. + * + * Frames that are a stacking context, containing blocks for position:fixed + * descendants, and don't have any continuations (see + * CanStoreDisplayListBuildingRect) trigger recursion into the algorithm with + * separate retaining decisions made. + * + * RDL defines the concept of an AnimatedGeometryRoot (AGR), the nearest + * ancestor frame which can be moved asynchronously on the compositor thread. + * These are currently nsDisplayItems which return true from CanMoveAsync + * (animated nsDisplayTransform and nsDisplayStickyPosition) and + * ActiveScrolledRoots. + * + * For each context that we run the retaining algorithm, there can only be + * mutations to one AnimatedGeometryRoot. This is because we are unable to + * reason about intersections of items that might then move relative to each + * other without RDL running again. If there are mutations to multiple + * AnimatedGeometryRoots, then we bail out and rebuild all the items in the + * context. + * + * Otherwise, when mutations are restricted to a single AGR, we pre-process the + * old display list and mark the frames for all existing (unmodified!) items + * that belong to a different AGR and ensure that we rebuild those items for + * correct sorting with the modified ones. + */ + +namespace mozilla { + +RetainedDisplayListData::RetainedDisplayListData() + : mModifiedFrameLimit( + StaticPrefs::layout_display_list_rebuild_frame_limit()) {} + +void RetainedDisplayListData::AddModifiedFrame(nsIFrame* aFrame) { + MOZ_ASSERT(!aFrame->IsFrameModified()); + Flags(aFrame) += RetainedDisplayListData::FrameFlag::Modified; + aFrame->SetFrameIsModified(true); + mModifiedFrameCount++; +} + +static void MarkFramesWithItemsAndImagesModified(nsDisplayList* aList) { + for (nsDisplayItem* i : *aList) { + if (!i->HasDeletedFrame() && i->CanBeReused() && + !i->Frame()->IsFrameModified()) { + // If we have existing cached geometry for this item, then check that for + // whether we need to invalidate for a sync decode. If we don't, then + // use the item's flags. + // XXX: handle webrender case by looking up retained data for the item + // and checking InvalidateForSyncDecodeImages + bool invalidate = false; + if (!(i->GetFlags() & TYPE_RENDERS_NO_IMAGES)) { + invalidate = true; + } + + if (invalidate) { + DL_LOGV("RDL - Invalidating item %p (%s)", i, i->Name()); + i->FrameForInvalidation()->MarkNeedsDisplayItemRebuild(); + if (i->GetDependentFrame()) { + i->GetDependentFrame()->MarkNeedsDisplayItemRebuild(); + } + } + } + if (i->GetChildren()) { + MarkFramesWithItemsAndImagesModified(i->GetChildren()); + } + } +} + +static nsIFrame* SelectAGRForFrame(nsIFrame* aFrame, nsIFrame* aParentAGR) { + if (!aFrame->IsStackingContext() || !aFrame->IsFixedPosContainingBlock()) { + return aParentAGR; + } + + if (!aFrame->HasOverrideDirtyRegion()) { + return nullptr; + } + + nsDisplayListBuilder::DisplayListBuildingData* data = + aFrame->GetProperty(nsDisplayListBuilder::DisplayListBuildingRect()); + + return data && data->mModifiedAGR ? data->mModifiedAGR : nullptr; +} + +void RetainedDisplayListBuilder::AddSizeOfIncludingThis( + nsWindowSizes& aSizes) const { + aSizes.mLayoutRetainedDisplayListSize += aSizes.mState.mMallocSizeOf(this); + mBuilder.AddSizeOfExcludingThis(aSizes); + mList.AddSizeOfExcludingThis(aSizes); +} + +bool AnyContentAncestorModified(nsIFrame* aFrame, nsIFrame* aStopAtFrame) { + nsIFrame* f = aFrame; + while (f) { + if (f->IsFrameModified()) { + return true; + } + + if (aStopAtFrame && f == aStopAtFrame) { + break; + } + + f = nsLayoutUtils::GetDisplayListParent(f); + } + + return false; +} + +// Removes any display items that belonged to a frame that was deleted, +// and mark frames that belong to a different AGR so that get their +// items built again. +// TODO: We currently descend into all children even if we don't have an AGR +// to mark, as child stacking contexts might. It would be nice if we could +// jump into those immediately rather than walking the entire thing. +bool RetainedDisplayListBuilder::PreProcessDisplayList( + RetainedDisplayList* aList, nsIFrame* aAGR, PartialUpdateResult& aUpdated, + nsIFrame* aAsyncAncestor, const ActiveScrolledRoot* aAsyncAncestorASR, + nsIFrame* aOuterFrame, uint32_t aCallerKey, uint32_t aNestingDepth, + bool aKeepLinked) { + // The DAG merging algorithm does not have strong mechanisms in place to keep + // the complexity of the resulting DAG under control. In some cases we can + // build up edges very quickly. Detect those cases and force a full display + // list build if we hit them. + static const uint32_t kMaxEdgeRatio = 5; + const bool initializeDAG = !aList->mDAG.Length(); + if (!aKeepLinked && !initializeDAG && + aList->mDAG.mDirectPredecessorList.Length() > + (aList->mDAG.mNodesInfo.Length() * kMaxEdgeRatio)) { + return false; + } + + // If we had aKeepLinked=true for this list on the previous paint, then + // mOldItems will already be initialized as it won't have been consumed during + // a merge. + const bool initializeOldItems = aList->mOldItems.IsEmpty(); + if (initializeOldItems) { + aList->mOldItems.SetCapacity(aList->Length()); + } else { + MOZ_RELEASE_ASSERT(!initializeDAG); + } + + MOZ_RELEASE_ASSERT( + initializeDAG || + aList->mDAG.Length() == + (initializeOldItems ? aList->Length() : aList->mOldItems.Length())); + + nsDisplayList out(Builder()); + + size_t i = 0; + while (nsDisplayItem* item = aList->RemoveBottom()) { +#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + item->SetMergedPreProcessed(false, true); +#endif + + // If we have a previously initialized old items list, then it can differ + // from the current list due to items removed for having a deleted frame. + // We can't easily remove these, since the DAG has entries for those indices + // and it's hard to rewrite in-place. + // Skip over entries with no current item to keep the iterations in sync. + if (!initializeOldItems) { + while (!aList->mOldItems[i].mItem) { + i++; + } + } + + if (initializeDAG) { + if (i == 0) { + aList->mDAG.AddNode(Span<const MergedListIndex>()); + } else { + MergedListIndex previous(i - 1); + aList->mDAG.AddNode(Span<const MergedListIndex>(&previous, 1)); + } + } + + if (!item->CanBeReused() || item->HasDeletedFrame() || + AnyContentAncestorModified(item->FrameForInvalidation(), aOuterFrame)) { + if (initializeOldItems) { + aList->mOldItems.AppendElement(OldItemInfo(nullptr)); + } else { + MOZ_RELEASE_ASSERT(aList->mOldItems[i].mItem == item); + aList->mOldItems[i].mItem = nullptr; + } + + if (item->IsGlassItem() && item == mBuilder.GetGlassDisplayItem()) { + mBuilder.ClearGlassDisplayItem(); + } + + item->Destroy(&mBuilder); + Metrics()->mRemovedItems++; + + i++; + aUpdated = PartialUpdateResult::Updated; + continue; + } + + if (initializeOldItems) { + aList->mOldItems.AppendElement(OldItemInfo(item)); + } + + // If we're not going to keep the list linked, then this old item entry + // is the only pointer to the item. Let it know that it now strongly + // owns the item, so it can destroy it if it goes away. + aList->mOldItems[i].mOwnsItem = !aKeepLinked; + + item->SetOldListIndex(aList, OldListIndex(i), aCallerKey, aNestingDepth); + + nsIFrame* f = item->Frame(); + + if (item->GetChildren()) { + // If children inside this list were invalid, then we'd have walked the + // ancestors and set ForceDescendIntoVisible on the current frame. If an + // ancestor is modified, then we'll throw this away entirely. Either way, + // we won't need to run merging on this sublist, and we can keep the items + // linked into their display list. + // The caret can move without invalidating, but we always set the force + // descend into frame state bit on that frame, so check for that too. + // TODO: AGR marking below can call MarkFrameForDisplayIfVisible and make + // us think future siblings need to be merged, even though we don't really + // need to. + bool keepLinked = aKeepLinked; + nsIFrame* invalid = item->FrameForInvalidation(); + if (!invalid->ForceDescendIntoIfVisible() && + !invalid->HasAnyStateBits(NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO)) { + keepLinked = true; + } + + // If this item's frame is an AGR (can be moved asynchronously by the + // compositor), then use that frame for descendants. Also pass the ASR + // for that item, so that descendants can compare to see if any new + // ASRs have been pushed since. + nsIFrame* asyncAncestor = aAsyncAncestor; + const ActiveScrolledRoot* asyncAncestorASR = aAsyncAncestorASR; + if (item->CanMoveAsync()) { + asyncAncestor = item->Frame(); + asyncAncestorASR = item->GetActiveScrolledRoot(); + } + + if (!PreProcessDisplayList( + item->GetChildren(), SelectAGRForFrame(f, aAGR), aUpdated, + asyncAncestor, asyncAncestorASR, item->Frame(), + item->GetPerFrameKey(), aNestingDepth + 1, keepLinked)) { + MOZ_RELEASE_ASSERT( + !aKeepLinked, + "Can't early return since we need to move the out list back"); + return false; + } + } + + // TODO: We should be able to check the clipped bounds relative + // to the common AGR (of both the existing item and the invalidated + // frame) and determine if they can ever intersect. + // TODO: We only really need to build the ancestor container item that is a + // sibling of the changed thing to get correct ordering. The changed content + // is a frame though, and it's hard to map that to container items in this + // list. + // If an ancestor display item is an AGR, and our ASR matches the ASR + // of that item, then there can't have been any new ASRs pushed since that + // item, so that item is our AGR. Otherwise, our AGR is our ASR. + // TODO: If aAsyncAncestorASR is non-null, then item->GetActiveScrolledRoot + // should be the same or a descendant and also non-null. Unfortunately an + // RDL bug means this can be wrong for sticky items after a partial update, + // so we have to work around it. Bug 1730749 and bug 1730826 should resolve + // this. + nsIFrame* agrFrame = nullptr; + if (aAsyncAncestorASR == item->GetActiveScrolledRoot() || + !item->GetActiveScrolledRoot()) { + agrFrame = aAsyncAncestor; + } else { + agrFrame = + item->GetActiveScrolledRoot()->mScrollableFrame->GetScrolledFrame(); + } + + if (aAGR && agrFrame != aAGR) { + mBuilder.MarkFrameForDisplayIfVisible(f, RootReferenceFrame()); + } + + // If we're going to keep this linked list and not merge it, then mark the + // item as used and put it back into the list. + if (aKeepLinked) { + item->SetReused(true); + if (item->GetChildren()) { + item->UpdateBounds(Builder()); + } + if (item->GetType() == DisplayItemType::TYPE_SUBDOCUMENT) { + IncrementSubDocPresShellPaintCount(item); + } + out.AppendToTop(item); + } + i++; + } + + MOZ_RELEASE_ASSERT(aList->mOldItems.Length() == aList->mDAG.Length()); + + if (aKeepLinked) { + aList->AppendToTop(&out); + } + + return true; +} + +void IncrementPresShellPaintCount(nsDisplayListBuilder* aBuilder, + nsDisplayItem* aItem) { + MOZ_ASSERT(aItem->GetType() == DisplayItemType::TYPE_SUBDOCUMENT); + + nsSubDocumentFrame* subDocFrame = + static_cast<nsDisplaySubDocument*>(aItem)->SubDocumentFrame(); + MOZ_ASSERT(subDocFrame); + + PresShell* presShell = subDocFrame->GetSubdocumentPresShellForPainting(0); + MOZ_ASSERT(presShell); + + aBuilder->IncrementPresShellPaintCount(presShell); +} + +void RetainedDisplayListBuilder::IncrementSubDocPresShellPaintCount( + nsDisplayItem* aItem) { + IncrementPresShellPaintCount(&mBuilder, aItem); +} + +static Maybe<const ActiveScrolledRoot*> SelectContainerASR( + const DisplayItemClipChain* aClipChain, const ActiveScrolledRoot* aItemASR, + Maybe<const ActiveScrolledRoot*>& aContainerASR) { + const ActiveScrolledRoot* itemClipASR = + aClipChain ? aClipChain->mASR : nullptr; + + const ActiveScrolledRoot* finiteBoundsASR = + ActiveScrolledRoot::PickDescendant(itemClipASR, aItemASR); + + if (!aContainerASR) { + return Some(finiteBoundsASR); + } + + return Some( + ActiveScrolledRoot::PickAncestor(*aContainerASR, finiteBoundsASR)); +} + +static void UpdateASR(nsDisplayItem* aItem, + Maybe<const ActiveScrolledRoot*>& aContainerASR) { + if (!aContainerASR) { + return; + } + + nsDisplayWrapList* wrapList = aItem->AsDisplayWrapList(); + if (!wrapList) { + aItem->SetActiveScrolledRoot(*aContainerASR); + return; + } + + wrapList->SetActiveScrolledRoot(ActiveScrolledRoot::PickAncestor( + wrapList->GetFrameActiveScrolledRoot(), *aContainerASR)); +} + +static void CopyASR(nsDisplayItem* aOld, nsDisplayItem* aNew) { + aNew->SetActiveScrolledRoot(aOld->GetActiveScrolledRoot()); +} + +OldItemInfo::OldItemInfo(nsDisplayItem* aItem) + : mItem(aItem), mUsed(false), mDiscarded(false), mOwnsItem(false) { + if (mItem) { + // Clear cached modified frame state when adding an item to the old list. + mItem->SetModifiedFrame(false); + } +} + +void OldItemInfo::AddedMatchToMergedList(RetainedDisplayListBuilder* aBuilder, + MergedListIndex aIndex) { + AddedToMergedList(aIndex); +} + +void OldItemInfo::Discard(RetainedDisplayListBuilder* aBuilder, + nsTArray<MergedListIndex>&& aDirectPredecessors) { + MOZ_ASSERT(!IsUsed()); + mUsed = mDiscarded = true; + mDirectPredecessors = std::move(aDirectPredecessors); + if (mItem) { + MOZ_ASSERT(mOwnsItem); + mItem->Destroy(aBuilder->Builder()); + aBuilder->Metrics()->mRemovedItems++; + } + mItem = nullptr; +} + +bool OldItemInfo::IsChanged() { + return !mItem || !mItem->CanBeReused() || mItem->HasDeletedFrame(); +} + +/** + * A C++ implementation of Markus Stange's merge-dags algorithm. + * https://github.com/mstange/merge-dags + * + * MergeState handles combining a new list of display items into an existing + * DAG and computes the new DAG in a single pass. + * Each time we add a new item, we resolve all dependencies for it, so that the + * resulting list and DAG are built in topological ordering. + */ +class MergeState { + public: + MergeState(RetainedDisplayListBuilder* aBuilder, + RetainedDisplayList& aOldList, nsDisplayItem* aOuterItem) + : mBuilder(aBuilder), + mOldList(&aOldList), + mOldItems(std::move(aOldList.mOldItems)), + mOldDAG( + std::move(*reinterpret_cast<DirectedAcyclicGraph<OldListUnits>*>( + &aOldList.mDAG))), + mMergedItems(aBuilder->Builder()), + mOuterItem(aOuterItem), + mResultIsModified(false) { + mMergedDAG.EnsureCapacityFor(mOldDAG); + MOZ_RELEASE_ASSERT(mOldItems.Length() == mOldDAG.Length()); + } + + Maybe<MergedListIndex> ProcessItemFromNewList( + nsDisplayItem* aNewItem, const Maybe<MergedListIndex>& aPreviousItem) { + OldListIndex oldIndex; + MOZ_DIAGNOSTIC_ASSERT(aNewItem->HasModifiedFrame() == + HasModifiedFrame(aNewItem)); + if (!aNewItem->HasModifiedFrame() && + HasMatchingItemInOldList(aNewItem, &oldIndex)) { + mBuilder->Metrics()->mRebuiltItems++; + nsDisplayItem* oldItem = mOldItems[oldIndex.val].mItem; + MOZ_DIAGNOSTIC_ASSERT(oldItem->GetPerFrameKey() == + aNewItem->GetPerFrameKey() && + oldItem->Frame() == aNewItem->Frame()); + if (!mOldItems[oldIndex.val].IsChanged()) { + MOZ_DIAGNOSTIC_ASSERT(!mOldItems[oldIndex.val].IsUsed()); + nsDisplayItem* destItem; + if (ShouldUseNewItem(aNewItem)) { + destItem = aNewItem; + } else { + destItem = oldItem; + // The building rect can depend on the overflow rect (when the parent + // frame is position:fixed), which can change without invalidating + // the frame/items. If we're using the old item, copy the building + // rect across from the new item. + oldItem->SetBuildingRect(aNewItem->GetBuildingRect()); + } + + if (destItem == aNewItem) { + if (oldItem->IsGlassItem() && + oldItem == mBuilder->Builder()->GetGlassDisplayItem()) { + mBuilder->Builder()->ClearGlassDisplayItem(); + } + } // aNewItem can't be the glass item on the builder yet. + + if (destItem->IsGlassItem()) { + if (destItem != oldItem || + destItem != mBuilder->Builder()->GetGlassDisplayItem()) { + mBuilder->Builder()->SetGlassDisplayItem(destItem); + } + } + + MergeChildLists(aNewItem, oldItem, destItem); + + AutoTArray<MergedListIndex, 2> directPredecessors = + ProcessPredecessorsOfOldNode(oldIndex); + MergedListIndex newIndex = AddNewNode( + destItem, Some(oldIndex), directPredecessors, aPreviousItem); + mOldItems[oldIndex.val].AddedMatchToMergedList(mBuilder, newIndex); + if (destItem == aNewItem) { + oldItem->Destroy(mBuilder->Builder()); + } else { + aNewItem->Destroy(mBuilder->Builder()); + } + return Some(newIndex); + } + } + mResultIsModified = true; + if (aNewItem->IsGlassItem()) { + mBuilder->Builder()->SetGlassDisplayItem(aNewItem); + } + return Some(AddNewNode(aNewItem, Nothing(), Span<MergedListIndex>(), + aPreviousItem)); + } + + void MergeChildLists(nsDisplayItem* aNewItem, nsDisplayItem* aOldItem, + nsDisplayItem* aOutItem) { + if (!aOutItem->GetChildren()) { + return; + } + + Maybe<const ActiveScrolledRoot*> containerASRForChildren; + nsDisplayList empty(mBuilder->Builder()); + const bool modified = mBuilder->MergeDisplayLists( + aNewItem ? aNewItem->GetChildren() : &empty, aOldItem->GetChildren(), + aOutItem->GetChildren(), containerASRForChildren, aOutItem); + if (modified) { + aOutItem->InvalidateCachedChildInfo(mBuilder->Builder()); + UpdateASR(aOutItem, containerASRForChildren); + mResultIsModified = true; + } else if (aOutItem == aNewItem) { + // If nothing changed, but we copied the contents across to + // the new item, then also copy the ASR data. + CopyASR(aOldItem, aNewItem); + } + // Ideally we'd only UpdateBounds if something changed, but + // nsDisplayWrapList also uses this to update the clip chain for the + // current ASR, which gets reset during RestoreState(), so we always need + // to run it again. + aOutItem->UpdateBounds(mBuilder->Builder()); + } + + bool ShouldUseNewItem(nsDisplayItem* aNewItem) { + // Generally we want to use the old item when the frame isn't marked as + // modified so that any cached information on the item (or referencing the + // item) gets retained. Quite a few FrameLayerBuilder performance + // improvements benefit by this. Sometimes, however, we can end up where the + // new item paints something different from the old item, even though we + // haven't modified the frame, and it's hard to fix. In these cases we just + // always use the new item to be safe. + DisplayItemType type = aNewItem->GetType(); + if (type == DisplayItemType::TYPE_CANVAS_BACKGROUND_COLOR || + type == DisplayItemType::TYPE_SOLID_COLOR) { + // The canvas background color item can paint the color from another + // frame, and even though we schedule a paint, we don't mark the canvas + // frame as invalid. + return true; + } + + if (type == DisplayItemType::TYPE_TABLE_BORDER_COLLAPSE) { + // We intentionally don't mark the root table frame as modified when a + // subframe changes, even though the border collapse item for the root + // frame is what paints the changed border. Marking the root frame as + // modified would rebuild display items for the whole table area, and we + // don't want that. + return true; + } + + if (type == DisplayItemType::TYPE_TEXT_OVERFLOW) { + // Text overflow marker items are created with the wrapping block as their + // frame, and have an index value to note which line they are created for. + // Their rendering can change if the items on that line change, which may + // not mark the block as modified. We rebuild them if we build any item on + // the line, so we should always get new items if they might have changed + // rendering, and it's easier to just use the new items rather than + // computing if we actually need them. + return true; + } + + if (type == DisplayItemType::TYPE_SUBDOCUMENT || + type == DisplayItemType::TYPE_STICKY_POSITION) { + // nsDisplaySubDocument::mShouldFlatten can change without an invalidation + // (and is the reason we unconditionally build the subdocument item), so + // always use the new one to make sure we get the right value. + // Same for |nsDisplayStickyPosition::mShouldFlatten|. + return true; + } + + if (type == DisplayItemType::TYPE_CARET) { + // The caret can change position while still being owned by the same frame + // and we don't invalidate in that case. Use the new version since the + // changed bounds are needed for DLBI. + return true; + } + + if (type == DisplayItemType::TYPE_MASK || + type == DisplayItemType::TYPE_FILTER || + type == DisplayItemType::TYPE_SVG_WRAPPER) { + // SVG items have some invalidation issues, see bugs 1494110 and 1494663. + return true; + } + + if (type == DisplayItemType::TYPE_TRANSFORM) { + // Prerendering of transforms can change without frame invalidation. + return true; + } + + return false; + } + + RetainedDisplayList Finalize() { + for (size_t i = 0; i < mOldDAG.Length(); i++) { + if (mOldItems[i].IsUsed()) { + continue; + } + + AutoTArray<MergedListIndex, 2> directPredecessors = + ResolveNodeIndexesOldToMerged( + mOldDAG.GetDirectPredecessors(OldListIndex(i))); + ProcessOldNode(OldListIndex(i), std::move(directPredecessors)); + } + + RetainedDisplayList result(mBuilder->Builder()); + result.AppendToTop(&mMergedItems); + result.mDAG = std::move(mMergedDAG); + MOZ_RELEASE_ASSERT(result.mDAG.Length() == result.Length()); + return result; + } + + bool HasMatchingItemInOldList(nsDisplayItem* aItem, OldListIndex* aOutIndex) { + // Look for an item that matches aItem's frame and per-frame-key, but isn't + // the same item. + uint32_t outerKey = mOuterItem ? mOuterItem->GetPerFrameKey() : 0; + nsIFrame* frame = aItem->Frame(); + for (nsDisplayItem* i : frame->DisplayItems()) { + if (i != aItem && i->Frame() == frame && + i->GetPerFrameKey() == aItem->GetPerFrameKey()) { + if (i->GetOldListIndex(mOldList, outerKey, aOutIndex)) { + return true; + } + } + } + return false; + } + +#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + bool HasModifiedFrame(nsDisplayItem* aItem) { + nsIFrame* stopFrame = mOuterItem ? mOuterItem->Frame() : nullptr; + return AnyContentAncestorModified(aItem->FrameForInvalidation(), stopFrame); + } +#endif + + void UpdateContainerASR(nsDisplayItem* aItem) { + mContainerASR = SelectContainerASR( + aItem->GetClipChain(), aItem->GetActiveScrolledRoot(), mContainerASR); + } + + MergedListIndex AddNewNode( + nsDisplayItem* aItem, const Maybe<OldListIndex>& aOldIndex, + Span<const MergedListIndex> aDirectPredecessors, + const Maybe<MergedListIndex>& aExtraDirectPredecessor) { + UpdateContainerASR(aItem); + aItem->NotifyUsed(mBuilder->Builder()); + +#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + for (nsDisplayItem* i : aItem->Frame()->DisplayItems()) { + if (i->Frame() == aItem->Frame() && + i->GetPerFrameKey() == aItem->GetPerFrameKey()) { + MOZ_DIAGNOSTIC_ASSERT(!i->IsMergedItem()); + } + } + + aItem->SetMergedPreProcessed(true, false); +#endif + + mMergedItems.AppendToTop(aItem); + mBuilder->Metrics()->mTotalItems++; + + MergedListIndex newIndex = + mMergedDAG.AddNode(aDirectPredecessors, aExtraDirectPredecessor); + return newIndex; + } + + void ProcessOldNode(OldListIndex aNode, + nsTArray<MergedListIndex>&& aDirectPredecessors) { + nsDisplayItem* item = mOldItems[aNode.val].mItem; + if (mOldItems[aNode.val].IsChanged()) { + if (item && item->IsGlassItem() && + item == mBuilder->Builder()->GetGlassDisplayItem()) { + mBuilder->Builder()->ClearGlassDisplayItem(); + } + + mOldItems[aNode.val].Discard(mBuilder, std::move(aDirectPredecessors)); + mResultIsModified = true; + } else { + MergeChildLists(nullptr, item, item); + + if (item->GetType() == DisplayItemType::TYPE_SUBDOCUMENT) { + mBuilder->IncrementSubDocPresShellPaintCount(item); + } + item->SetReused(true); + mBuilder->Metrics()->mReusedItems++; + mOldItems[aNode.val].AddedToMergedList( + AddNewNode(item, Some(aNode), aDirectPredecessors, Nothing())); + } + } + + struct PredecessorStackItem { + PredecessorStackItem(OldListIndex aNode, Span<OldListIndex> aPredecessors) + : mNode(aNode), + mDirectPredecessors(aPredecessors), + mCurrentPredecessorIndex(0) {} + + bool IsFinished() { + return mCurrentPredecessorIndex == mDirectPredecessors.Length(); + } + + OldListIndex GetAndIncrementCurrentPredecessor() { + return mDirectPredecessors[mCurrentPredecessorIndex++]; + } + + OldListIndex mNode; + Span<OldListIndex> mDirectPredecessors; + size_t mCurrentPredecessorIndex; + }; + + AutoTArray<MergedListIndex, 2> ProcessPredecessorsOfOldNode( + OldListIndex aNode) { + AutoTArray<PredecessorStackItem, 256> mStack; + mStack.AppendElement( + PredecessorStackItem(aNode, mOldDAG.GetDirectPredecessors(aNode))); + + while (true) { + if (mStack.LastElement().IsFinished()) { + // If we've finished processing all the entries in the current set, then + // pop it off the processing stack and process it. + PredecessorStackItem item = mStack.PopLastElement(); + AutoTArray<MergedListIndex, 2> result = + ResolveNodeIndexesOldToMerged(item.mDirectPredecessors); + + if (mStack.IsEmpty()) { + return result; + } + + ProcessOldNode(item.mNode, std::move(result)); + } else { + // Grab the current predecessor, push predecessors of that onto the + // processing stack (if it hasn't already been processed), and then + // advance to the next entry. + OldListIndex currentIndex = + mStack.LastElement().GetAndIncrementCurrentPredecessor(); + if (!mOldItems[currentIndex.val].IsUsed()) { + mStack.AppendElement(PredecessorStackItem( + currentIndex, mOldDAG.GetDirectPredecessors(currentIndex))); + } + } + } + } + + AutoTArray<MergedListIndex, 2> ResolveNodeIndexesOldToMerged( + Span<OldListIndex> aDirectPredecessors) { + AutoTArray<MergedListIndex, 2> result; + result.SetCapacity(aDirectPredecessors.Length()); + for (OldListIndex index : aDirectPredecessors) { + OldItemInfo& oldItem = mOldItems[index.val]; + if (oldItem.IsDiscarded()) { + for (MergedListIndex inner : oldItem.mDirectPredecessors) { + if (!result.Contains(inner)) { + result.AppendElement(inner); + } + } + } else { + result.AppendElement(oldItem.mIndex); + } + } + return result; + } + + RetainedDisplayListBuilder* mBuilder; + RetainedDisplayList* mOldList; + Maybe<const ActiveScrolledRoot*> mContainerASR; + nsTArray<OldItemInfo> mOldItems; + DirectedAcyclicGraph<OldListUnits> mOldDAG; + // Unfortunately we can't use strong typing for the hashtables + // since they internally encode the type with the mOps pointer, + // and assert when we try swap the contents + nsDisplayList mMergedItems; + DirectedAcyclicGraph<MergedListUnits> mMergedDAG; + nsDisplayItem* mOuterItem; + bool mResultIsModified; +}; + +#ifdef DEBUG +void VerifyNotModified(nsDisplayList* aList) { + for (nsDisplayItem* item : *aList) { + MOZ_ASSERT(!AnyContentAncestorModified(item->FrameForInvalidation())); + + if (item->GetChildren()) { + VerifyNotModified(item->GetChildren()); + } + } +} +#endif + +/** + * Takes two display lists and merges them into an output list. + * + * Display lists wthout an explicit DAG are interpreted as linear DAGs (with a + * maximum of one direct predecessor and one direct successor per node). We add + * the two DAGs together, and then output the topological sorted ordering as the + * final display list. + * + * Once we've merged a list, we then retain the DAG (as part of the + * RetainedDisplayList object) to use for future merges. + */ +bool RetainedDisplayListBuilder::MergeDisplayLists( + nsDisplayList* aNewList, RetainedDisplayList* aOldList, + RetainedDisplayList* aOutList, + mozilla::Maybe<const mozilla::ActiveScrolledRoot*>& aOutContainerASR, + nsDisplayItem* aOuterItem) { + AUTO_PROFILER_LABEL_CATEGORY_PAIR(GRAPHICS_DisplayListMerging); + + if (!aOldList->IsEmpty()) { + // If we still have items in the actual list, then it is because + // PreProcessDisplayList decided that it was sure it can't be modified. We + // can just use it directly, and throw any new items away. + + aNewList->DeleteAll(&mBuilder); +#ifdef DEBUG + VerifyNotModified(aOldList); +#endif + + if (aOldList != aOutList) { + *aOutList = std::move(*aOldList); + } + + return false; + } + + MergeState merge(this, *aOldList, aOuterItem); + + Maybe<MergedListIndex> previousItemIndex; + for (nsDisplayItem* item : aNewList->TakeItems()) { + Metrics()->mNewItems++; + previousItemIndex = merge.ProcessItemFromNewList(item, previousItemIndex); + } + + *aOutList = merge.Finalize(); + aOutContainerASR = merge.mContainerASR; + return merge.mResultIsModified; +} + +void RetainedDisplayListBuilder::GetModifiedAndFramesWithProps( + nsTArray<nsIFrame*>* aOutModifiedFrames, + nsTArray<nsIFrame*>* aOutFramesWithProps) { + for (auto it = Data()->ConstIterator(); !it.Done(); it.Next()) { + nsIFrame* frame = it.Key(); + const RetainedDisplayListData::FrameFlags& flags = it.Data(); + + if (flags.contains(RetainedDisplayListData::FrameFlag::Modified)) { + aOutModifiedFrames->AppendElement(frame); + } + + if (flags.contains(RetainedDisplayListData::FrameFlag::HasProps)) { + aOutFramesWithProps->AppendElement(frame); + } + + if (flags.contains(RetainedDisplayListData::FrameFlag::HadWillChange)) { + Builder()->RemoveFromWillChangeBudgets(frame); + } + } + + Data()->Clear(); +} + +// ComputeRebuildRegion debugging +// #define CRR_DEBUG 1 +#if CRR_DEBUG +# define CRR_LOG(...) printf_stderr(__VA_ARGS__) +#else +# define CRR_LOG(...) +#endif + +static nsDisplayItem* GetFirstDisplayItemWithChildren(nsIFrame* aFrame) { + for (nsDisplayItem* i : aFrame->DisplayItems()) { + if (i->HasDeletedFrame() || i->Frame() != aFrame) { + // The main frame for the display item has been deleted or the display + // item belongs to another frame. + continue; + } + + if (i->HasChildren()) { + return static_cast<nsDisplayItem*>(i); + } + } + return nullptr; +} + +static bool IsInPreserve3DContext(const nsIFrame* aFrame) { + return aFrame->Extend3DContext() || + aFrame->Combines3DTransformWithAncestors(); +} + +// Returns true if |aFrame| can store a display list building rect. +// These limitations are necessary to guarantee that +// 1) Just enough items are rebuilt to properly update display list +// 2) Modified frames will be visited during a partial display list build. +static bool CanStoreDisplayListBuildingRect(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame) { + return aFrame != aBuilder->RootReferenceFrame() && + aFrame->IsStackingContext() && aFrame->IsFixedPosContainingBlock() && + // Split frames might have placeholders for modified frames in their + // unmodified continuation frame. + !aFrame->GetPrevContinuation() && !aFrame->GetNextContinuation(); +} + +static bool ProcessFrameInternal(nsIFrame* aFrame, + nsDisplayListBuilder* aBuilder, + nsIFrame** aAGR, nsRect& aOverflow, + const nsIFrame* aStopAtFrame, + nsTArray<nsIFrame*>& aOutFramesWithProps, + const bool aStopAtStackingContext) { + nsIFrame* currentFrame = aFrame; + + while (currentFrame != aStopAtFrame) { + CRR_LOG("currentFrame: %p (placeholder=%d), aOverflow: %d %d %d %d\n", + currentFrame, !aStopAtStackingContext, aOverflow.x, aOverflow.y, + aOverflow.width, aOverflow.height); + + // If the current frame is an OOF frame, DisplayListBuildingData needs to be + // set on all the ancestor stacking contexts of the placeholder frame, up + // to the containing block of the OOF frame. This is done to ensure that the + // content that might be behind the OOF frame is built for merging. + nsIFrame* placeholder = currentFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW) + ? currentFrame->GetPlaceholderFrame() + : nullptr; + + if (placeholder) { + nsRect placeholderOverflow = aOverflow; + auto rv = nsLayoutUtils::TransformRect(currentFrame, placeholder, + placeholderOverflow); + if (rv != nsLayoutUtils::TRANSFORM_SUCCEEDED) { + placeholderOverflow = nsRect(); + } + + CRR_LOG("Processing placeholder %p for OOF frame %p\n", placeholder, + currentFrame); + + CRR_LOG("OOF frame draw area: %d %d %d %d\n", placeholderOverflow.x, + placeholderOverflow.y, placeholderOverflow.width, + placeholderOverflow.height); + + // Tracking AGRs for the placeholder processing is not necessary, as the + // goal is to only modify the DisplayListBuildingData rect. + nsIFrame* dummyAGR = nullptr; + + // Find a common ancestor frame to handle frame continuations. + // TODO: It might be possible to write a more specific and efficient + // function for this. + const nsIFrame* ancestor = nsLayoutUtils::FindNearestCommonAncestorFrame( + currentFrame->GetParent(), placeholder->GetParent()); + + if (!ProcessFrameInternal(placeholder, aBuilder, &dummyAGR, + placeholderOverflow, ancestor, + aOutFramesWithProps, false)) { + return false; + } + } + + // Convert 'aOverflow' into the coordinate space of the nearest stacking + // context or display port ancestor and update 'currentFrame' to point to + // that frame. + aOverflow = nsLayoutUtils::TransformFrameRectToAncestor( + currentFrame, aOverflow, aStopAtFrame, nullptr, nullptr, + /* aStopAtStackingContextAndDisplayPortAndOOFFrame = */ true, + ¤tFrame); + if (IsInPreserve3DContext(currentFrame)) { + return false; + } + + MOZ_ASSERT(currentFrame); + + // Check whether the current frame is a scrollable frame with display port. + nsRect displayPort; + nsIScrollableFrame* sf = do_QueryFrame(currentFrame); + nsIContent* content = sf ? currentFrame->GetContent() : nullptr; + + if (content && DisplayPortUtils::GetDisplayPort(content, &displayPort)) { + CRR_LOG("Frame belongs to displayport frame %p\n", currentFrame); + + // Get overflow relative to the scrollport (from the scrollframe) + nsRect r = aOverflow - sf->GetScrollPortRect().TopLeft(); + r.IntersectRect(r, displayPort); + if (!r.IsEmpty()) { + nsRect* rect = currentFrame->GetProperty( + nsDisplayListBuilder::DisplayListBuildingDisplayPortRect()); + if (!rect) { + rect = new nsRect(); + currentFrame->SetProperty( + nsDisplayListBuilder::DisplayListBuildingDisplayPortRect(), rect); + currentFrame->SetHasOverrideDirtyRegion(true); + aOutFramesWithProps.AppendElement(currentFrame); + } + rect->UnionRect(*rect, r); + CRR_LOG("Adding area to displayport draw area: %d %d %d %d\n", r.x, r.y, + r.width, r.height); + + // TODO: Can we just use MarkFrameForDisplayIfVisible, plus + // MarkFramesForDifferentAGR to ensure that this displayport, plus any + // items that move relative to it get rebuilt, and then not contribute + // to the root dirty area? + aOverflow = sf->GetScrollPortRect(); + } else { + // Don't contribute to the root dirty area at all. + aOverflow.SetEmpty(); + } + } else { + aOverflow.IntersectRect(aOverflow, + currentFrame->InkOverflowRectRelativeToSelf()); + } + + if (aOverflow.IsEmpty()) { + break; + } + + if (CanStoreDisplayListBuildingRect(aBuilder, currentFrame)) { + CRR_LOG("Frame belongs to stacking context frame %p\n", currentFrame); + // If we found an intermediate stacking context with an existing display + // item then we can store the dirty rect there and stop. If we couldn't + // find one then we need to keep bubbling up to the next stacking context. + nsDisplayItem* wrapperItem = + GetFirstDisplayItemWithChildren(currentFrame); + if (!wrapperItem) { + continue; + } + + // Store the stacking context relative dirty area such + // that display list building will pick it up when it + // gets to it. + nsDisplayListBuilder::DisplayListBuildingData* data = + currentFrame->GetProperty( + nsDisplayListBuilder::DisplayListBuildingRect()); + if (!data) { + data = new nsDisplayListBuilder::DisplayListBuildingData(); + currentFrame->SetProperty( + nsDisplayListBuilder::DisplayListBuildingRect(), data); + currentFrame->SetHasOverrideDirtyRegion(true); + aOutFramesWithProps.AppendElement(currentFrame); + } + CRR_LOG("Adding area to stacking context draw area: %d %d %d %d\n", + aOverflow.x, aOverflow.y, aOverflow.width, aOverflow.height); + data->mDirtyRect.UnionRect(data->mDirtyRect, aOverflow); + + if (!aStopAtStackingContext) { + // Continue ascending the frame tree until we reach aStopAtFrame. + continue; + } + + // Grab the visible (display list building) rect for children of this + // wrapper item and convert into into coordinate relative to the current + // frame. + nsRect previousVisible = wrapperItem->GetBuildingRectForChildren(); + if (wrapperItem->ReferenceFrameForChildren() != wrapperItem->Frame()) { + previousVisible -= wrapperItem->ToReferenceFrame(); + } + + if (!previousVisible.Contains(aOverflow)) { + // If the overflow area of the changed frame isn't contained within the + // old item, then we might change the size of the item and need to + // update its sorting accordingly. Keep propagating the overflow area up + // so that we build intersecting items for sorting. + continue; + } + + if (!data->mModifiedAGR) { + data->mModifiedAGR = *aAGR; + } else if (data->mModifiedAGR != *aAGR) { + data->mDirtyRect = currentFrame->InkOverflowRectRelativeToSelf(); + CRR_LOG( + "Found multiple modified AGRs within this stacking context, " + "giving up\n"); + } + + // Don't contribute to the root dirty area at all. + aOverflow.SetEmpty(); + *aAGR = nullptr; + + break; + } + } + return true; +} + +bool RetainedDisplayListBuilder::ProcessFrame( + nsIFrame* aFrame, nsDisplayListBuilder* aBuilder, nsIFrame* aStopAtFrame, + nsTArray<nsIFrame*>& aOutFramesWithProps, const bool aStopAtStackingContext, + nsRect* aOutDirty, nsIFrame** aOutModifiedAGR) { + if (aFrame->HasOverrideDirtyRegion()) { + aOutFramesWithProps.AppendElement(aFrame); + } + + if (aFrame->HasAnyStateBits(NS_FRAME_IN_POPUP)) { + return true; + } + + // TODO: There is almost certainly a faster way of doing this, probably can be + // combined with the ancestor walk for TransformFrameRectToAncestor. + nsIFrame* agrFrame = aBuilder->FindAnimatedGeometryRootFrameFor(aFrame); + + CRR_LOG("Processing frame %p with agr %p\n", aFrame, agr->mFrame); + + // Convert the frame's overflow rect into the coordinate space + // of the nearest stacking context that has an existing display item. + // We store that as a dirty rect on that stacking context so that we build + // all items that intersect the changed frame within the stacking context, + // and then we use MarkFrameForDisplayIfVisible to make sure the stacking + // context itself gets built. We don't need to build items that intersect + // outside of the stacking context, since we know the stacking context item + // exists in the old list, so we can trivially merge without needing other + // items. + nsRect overflow = aFrame->InkOverflowRectRelativeToSelf(); + + // If the modified frame is also a caret frame, include the caret area. + // This is needed because some frames (for example text frames without text) + // might have an empty overflow rect. + if (aFrame == aBuilder->GetCaretFrame()) { + overflow.UnionRect(overflow, aBuilder->GetCaretRect()); + } + + if (!ProcessFrameInternal(aFrame, aBuilder, &agrFrame, overflow, aStopAtFrame, + aOutFramesWithProps, aStopAtStackingContext)) { + return false; + } + + if (!overflow.IsEmpty()) { + aOutDirty->UnionRect(*aOutDirty, overflow); + CRR_LOG("Adding area to root draw area: %d %d %d %d\n", overflow.x, + overflow.y, overflow.width, overflow.height); + + // If we get changed frames from multiple AGRS, then just give up as it gets + // really complex to track which items would need to be marked in + // MarkFramesForDifferentAGR. + if (!*aOutModifiedAGR) { + CRR_LOG("Setting %p as root stacking context AGR\n", agrFrame); + *aOutModifiedAGR = agrFrame; + } else if (agrFrame && *aOutModifiedAGR != agrFrame) { + CRR_LOG("Found multiple AGRs in root stacking context, giving up\n"); + return false; + } + } + return true; +} + +static void AddFramesForContainingBlock(nsIFrame* aBlock, + const nsFrameList& aFrames, + nsTArray<nsIFrame*>& aExtraFrames) { + for (nsIFrame* f : aFrames) { + if (!f->IsFrameModified() && AnyContentAncestorModified(f, aBlock)) { + CRR_LOG("Adding invalid OOF %p\n", f); + aExtraFrames.AppendElement(f); + } + } +} + +// Placeholder descendants of aFrame don't contribute to aFrame's overflow area. +// Find all the containing blocks that might own placeholders under us, walk +// their OOF frames list, and manually invalidate any frames that are +// descendants of a modified frame (us, or another frame we'll get to soon). +// This is combined with the work required for MarkFrameForDisplayIfVisible, +// so that we can avoid an extra ancestor walk, and we can reuse the flag +// to detect when we've already visited an ancestor (and thus all further +// ancestors must also be visited). +static void FindContainingBlocks(nsIFrame* aFrame, + nsTArray<nsIFrame*>& aExtraFrames) { + for (nsIFrame* f = aFrame; f; f = nsLayoutUtils::GetDisplayListParent(f)) { + if (f->ForceDescendIntoIfVisible()) { + return; + } + f->SetForceDescendIntoIfVisible(true); + CRR_LOG("Considering OOFs for %p\n", f); + + AddFramesForContainingBlock(f, f->GetChildList(FrameChildListID::Float), + aExtraFrames); + AddFramesForContainingBlock(f, f->GetChildList(f->GetAbsoluteListID()), + aExtraFrames); + + // This condition must match the condition in + // nsLayoutUtils::GetParentOrPlaceholderFor which is used by + // nsLayoutUtils::GetDisplayListParent + if (f->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW) && !f->GetPrevInFlow()) { + nsIFrame* parent = f->GetParent(); + if (parent && !parent->ForceDescendIntoIfVisible()) { + // If the GetDisplayListParent call is going to walk to a placeholder, + // in rare cases the placeholder might be contained in a different + // continuation from the oof. So we have to make sure to mark the oofs + // parent. In the common case this doesn't make us do any extra work, + // just changes the order in which we visit the frames since walking + // through placeholders will walk through the parent, and we stop when + // we find a ForceDescendIntoIfVisible bit set. + FindContainingBlocks(parent, aExtraFrames); + } + } + } +} + +/** + * Given a list of frames that has been modified, computes the region that we + * need to do display list building for in order to build all modified display + * items. + * + * When a modified frame is within a stacking context (with an existing display + * item), then we only contribute to the build area within the stacking context, + * as well as forcing display list building to descend to the stacking context. + * We don't need to add build area outside of the stacking context (and force + * items above/below the stacking context container item to be built), since + * just matching the position of the stacking context container item is + * sufficient to ensure correct ordering during merging. + * + * We need to rebuild all items that might intersect with the modified frame, + * both now and during async changes on the compositor. We do this by rebuilding + * the area covered by the changed frame, as well as rebuilding all items that + * have a different (async) AGR to the changed frame. If we have changes to + * multiple AGRs (within a stacking context), then we rebuild that stacking + * context entirely. + * + * @param aModifiedFrames The list of modified frames. + * @param aOutDirty The result region to use for display list building. + * @param aOutModifiedAGR The modified AGR for the root stacking context. + * @param aOutFramesWithProps The list of frames to which we attached partial + * build data so that it can be cleaned up. + * + * @return true if we succesfully computed a partial rebuild region, false if a + * full build is required. + */ +bool RetainedDisplayListBuilder::ComputeRebuildRegion( + nsTArray<nsIFrame*>& aModifiedFrames, nsRect* aOutDirty, + nsIFrame** aOutModifiedAGR, nsTArray<nsIFrame*>& aOutFramesWithProps) { + CRR_LOG("Computing rebuild regions for %zu frames:\n", + aModifiedFrames.Length()); + nsTArray<nsIFrame*> extraFrames; + for (nsIFrame* f : aModifiedFrames) { + MOZ_ASSERT(f); + + mBuilder.AddFrameMarkedForDisplayIfVisible(f); + FindContainingBlocks(f, extraFrames); + + if (!ProcessFrame(f, &mBuilder, RootReferenceFrame(), aOutFramesWithProps, + true, aOutDirty, aOutModifiedAGR)) { + return false; + } + } + + // Since we set modified to true on the extraFrames, add them to + // aModifiedFrames so that it will get reverted. + aModifiedFrames.AppendElements(extraFrames); + + for (nsIFrame* f : extraFrames) { + f->SetFrameIsModified(true); + + if (!ProcessFrame(f, &mBuilder, RootReferenceFrame(), aOutFramesWithProps, + true, aOutDirty, aOutModifiedAGR)) { + return false; + } + } + + return true; +} + +bool RetainedDisplayListBuilder::ShouldBuildPartial( + nsTArray<nsIFrame*>& aModifiedFrames) { + if (mList.IsEmpty()) { + // Partial builds without a previous display list do not make sense. + Metrics()->mPartialUpdateFailReason = PartialUpdateFailReason::EmptyList; + return false; + } + + if (aModifiedFrames.Length() > + StaticPrefs::layout_display_list_rebuild_frame_limit()) { + // Computing a dirty rect with too many modified frames can be slow. + Metrics()->mPartialUpdateFailReason = PartialUpdateFailReason::RebuildLimit; + return false; + } + + // We don't support retaining with overlay scrollbars, since they require + // us to look at the display list and pick the highest z-index, which + // we can't do during partial building. + if (mBuilder.DisablePartialUpdates()) { + mBuilder.SetDisablePartialUpdates(false); + Metrics()->mPartialUpdateFailReason = PartialUpdateFailReason::Disabled; + return false; + } + + for (nsIFrame* f : aModifiedFrames) { + MOZ_ASSERT(f); + + const LayoutFrameType type = f->Type(); + + // If we have any modified frames of the following types, it is likely that + // doing a partial rebuild of the display list will be slower than doing a + // full rebuild. + // This is because these frames either intersect or may intersect with most + // of the page content. This is either due to display port size or different + // async AGR. + if (type == LayoutFrameType::Viewport || + type == LayoutFrameType::PageContent || + type == LayoutFrameType::Canvas || type == LayoutFrameType::Scrollbar) { + Metrics()->mPartialUpdateFailReason = PartialUpdateFailReason::FrameType; + return false; + } + + // Detect root scroll frame and do a full rebuild for them too for the same + // reasons as above, but also because top layer items should to be marked + // modified if the root scroll frame is modified. Putting this check here + // means we don't need to check everytime a frame is marked modified though. + if (type == LayoutFrameType::Scroll && f->GetParent() && + !f->GetParent()->GetParent()) { + Metrics()->mPartialUpdateFailReason = PartialUpdateFailReason::FrameType; + return false; + } + } + + return true; +} + +void RetainedDisplayListBuilder::InvalidateCaretFramesIfNeeded() { + if (mPreviousCaret == mBuilder.GetCaretFrame()) { + // The current caret frame is the same as the previous one. + return; + } + + if (mPreviousCaret) { + mPreviousCaret->MarkNeedsDisplayItemRebuild(); + } + + if (mBuilder.GetCaretFrame()) { + mBuilder.GetCaretFrame()->MarkNeedsDisplayItemRebuild(); + } + + mPreviousCaret = mBuilder.GetCaretFrame(); +} + +static void ClearFrameProps(nsTArray<nsIFrame*>& aFrames) { + for (nsIFrame* f : aFrames) { + DL_LOGV("RDL - Clearing modified flags for frame %p", f); + if (f->HasOverrideDirtyRegion()) { + f->SetHasOverrideDirtyRegion(false); + f->RemoveProperty(nsDisplayListBuilder::DisplayListBuildingRect()); + f->RemoveProperty( + nsDisplayListBuilder::DisplayListBuildingDisplayPortRect()); + } + + f->SetFrameIsModified(false); + f->SetHasModifiedDescendants(false); + } +} + +class AutoClearFramePropsArray { + public: + explicit AutoClearFramePropsArray(size_t aCapacity) : mFrames(aCapacity) {} + AutoClearFramePropsArray() = default; + ~AutoClearFramePropsArray() { ClearFrameProps(mFrames); } + + nsTArray<nsIFrame*>& Frames() { return mFrames; } + bool IsEmpty() const { return mFrames.IsEmpty(); } + + private: + nsTArray<nsIFrame*> mFrames; +}; + +void RetainedDisplayListBuilder::ClearFramesWithProps() { + AutoClearFramePropsArray modifiedFrames; + AutoClearFramePropsArray framesWithProps; + GetModifiedAndFramesWithProps(&modifiedFrames.Frames(), + &framesWithProps.Frames()); +} + +void RetainedDisplayListBuilder::ClearRetainedData() { + DL_LOGI("(%p) RDL - Clearing retained display list builder data", this); + List()->DeleteAll(Builder()); + ClearFramesWithProps(); + ClearReuseableDisplayItems(); +} + +namespace RDLUtils { + +MOZ_NEVER_INLINE_DEBUG void AssertFrameSubtreeUnmodified( + const nsIFrame* aFrame) { + MOZ_ASSERT(!aFrame->IsFrameModified()); + MOZ_ASSERT(!aFrame->HasModifiedDescendants()); + + for (const auto& childList : aFrame->ChildLists()) { + for (nsIFrame* child : childList.mList) { + AssertFrameSubtreeUnmodified(child); + } + } +} + +MOZ_NEVER_INLINE_DEBUG void AssertDisplayListUnmodified(nsDisplayList* aList) { + for (nsDisplayItem* item : *aList) { + AssertDisplayItemUnmodified(item); + } +} + +MOZ_NEVER_INLINE_DEBUG void AssertDisplayItemUnmodified(nsDisplayItem* aItem) { + MOZ_ASSERT(!aItem->HasDeletedFrame()); + MOZ_ASSERT(!AnyContentAncestorModified(aItem->FrameForInvalidation())); + + if (aItem->GetChildren()) { + AssertDisplayListUnmodified(aItem->GetChildren()); + } +} + +} // namespace RDLUtils + +namespace RDL { + +void MarkAncestorFrames(nsIFrame* aFrame, + nsTArray<nsIFrame*>& aOutFramesWithProps) { + nsIFrame* frame = nsLayoutUtils::GetDisplayListParent(aFrame); + while (frame && !frame->HasModifiedDescendants()) { + aOutFramesWithProps.AppendElement(frame); + frame->SetHasModifiedDescendants(true); + frame = nsLayoutUtils::GetDisplayListParent(frame); + } +} + +/** + * Iterates over the modified frames array and updates the frame tree flags + * so that container frames know whether they have modified descendant frames. + * Frames that were marked modified are added to |aOutFramesWithProps|, so that + * the modified status can be cleared after the display list build. + */ +void MarkAllAncestorFrames(const nsTArray<nsIFrame*>& aModifiedFrames, + nsTArray<nsIFrame*>& aOutFramesWithProps) { + nsAutoString frameName; + DL_LOGI("RDL - Modified frames: %zu", aModifiedFrames.Length()); + for (nsIFrame* frame : aModifiedFrames) { +#ifdef DEBUG + frame->GetFrameName(frameName); +#endif + DL_LOGV("RDL - Processing modified frame: %p (%s)", frame, + NS_ConvertUTF16toUTF8(frameName).get()); + + MarkAncestorFrames(frame, aOutFramesWithProps); + } +} + +/** + * Marks the given display item |aItem| as reuseable container, and updates the + * bounds in case some child items were destroyed. + */ +MOZ_NEVER_INLINE_DEBUG void ReuseStackingContextItem( + nsDisplayListBuilder* aBuilder, nsDisplayItem* aItem) { + aItem->SetPreProcessed(); + + if (aItem->HasChildren()) { + aItem->UpdateBounds(aBuilder); + } + + aBuilder->AddReusableDisplayItem(aItem); + DL_LOGD("Reusing display item %p", aItem); +} + +bool IsSupportedFrameType(const nsIFrame* aFrame) { + // The way table backgrounds are handled makes these frames incompatible with + // this retained display list approach. + if (aFrame->IsTableColFrame()) { + return false; + } + + if (aFrame->IsTableColGroupFrame()) { + return false; + } + + if (aFrame->IsTableRowFrame()) { + return false; + } + + if (aFrame->IsTableRowGroupFrame()) { + return false; + } + + if (aFrame->IsTableCellFrame()) { + return false; + } + + // Everything else should work. + return true; +} + +bool IsReuseableStackingContextItem(nsDisplayItem* aItem) { + if (!IsSupportedFrameType(aItem->Frame())) { + return false; + } + + if (!aItem->IsReusable()) { + return false; + } + + const nsIFrame* frame = aItem->FrameForInvalidation(); + return !frame->HasModifiedDescendants() && !frame->GetPrevContinuation() && + !frame->GetNextContinuation(); +} + +/** + * Recursively visits every display item of the display list and destroys all + * display items that depend on deleted or modified frames. + * The stacking context display items for unmodified frame subtrees are kept + * linked and collected in given |aOutItems| array. + */ +void CollectStackingContextItems(nsDisplayListBuilder* aBuilder, + nsDisplayList* aList, nsIFrame* aOuterFrame, + int aDepth = 0, bool aParentReused = false) { + for (nsDisplayItem* item : aList->TakeItems()) { + if (DL_LOG_TEST(LogLevel::Debug)) { + DL_LOGD( + "%*s Preprocessing item %p (%s) (frame: %p) " + "(children: %zu) (depth: %d) (parentReused: %d)", + aDepth, "", item, item->Name(), + item->HasDeletedFrame() ? nullptr : item->Frame(), + item->GetChildren() ? item->GetChildren()->Length() : 0, aDepth, + aParentReused); + } + + if (!item->CanBeReused() || item->HasDeletedFrame() || + AnyContentAncestorModified(item->FrameForInvalidation(), aOuterFrame)) { + DL_LOGD("%*s Deleted modified or temporary item %p", aDepth, "", item); + item->Destroy(aBuilder); + continue; + } + + MOZ_ASSERT(!AnyContentAncestorModified(item->FrameForInvalidation())); + MOZ_ASSERT(!item->IsPreProcessed()); + item->InvalidateCachedChildInfo(aBuilder); +#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + item->SetMergedPreProcessed(false, true); +#endif + item->SetReused(true); + + const bool isStackingContextItem = IsReuseableStackingContextItem(item); + + if (item->GetChildren()) { + CollectStackingContextItems(aBuilder, item->GetChildren(), item->Frame(), + aDepth + 1, + aParentReused || isStackingContextItem); + } + + if (aParentReused) { + // Keep the contents of the current container item linked. +#ifdef DEBUG + RDLUtils::AssertDisplayItemUnmodified(item); +#endif + aList->AppendToTop(item); + } else if (isStackingContextItem) { + // |item| is a stacking context item that can be reused. + ReuseStackingContextItem(aBuilder, item); + } else { + // |item| is inside a container item that will be destroyed later. + DL_LOGD("%*s Deleted unused item %p", aDepth, "", item); + item->Destroy(aBuilder); + continue; + } + + if (item->GetType() == DisplayItemType::TYPE_SUBDOCUMENT) { + IncrementPresShellPaintCount(aBuilder, item); + } + } +} + +} // namespace RDL + +bool RetainedDisplayListBuilder::TrySimpleUpdate( + const nsTArray<nsIFrame*>& aModifiedFrames, + nsTArray<nsIFrame*>& aOutFramesWithProps) { + if (!mBuilder.IsReusingStackingContextItems()) { + return false; + } + + RDL::MarkAllAncestorFrames(aModifiedFrames, aOutFramesWithProps); + RDL::CollectStackingContextItems(&mBuilder, &mList, RootReferenceFrame()); + + return true; +} + +PartialUpdateResult RetainedDisplayListBuilder::AttemptPartialUpdate( + nscolor aBackstop) { + DL_LOGI("(%p) RDL - AttemptPartialUpdate, root frame: %p", this, + RootReferenceFrame()); + + mBuilder.RemoveModifiedWindowRegions(); + + if (mBuilder.ShouldSyncDecodeImages()) { + DL_LOGI("RDL - Sync decoding images"); + MarkFramesWithItemsAndImagesModified(&mList); + } + + InvalidateCaretFramesIfNeeded(); + + // We set the override dirty regions during ComputeRebuildRegion or in + // DisplayPortUtils::InvalidateForDisplayPortChange. The display port change + // also marks the frame modified, so those regions are cleared here as well. + AutoClearFramePropsArray modifiedFrames(64); + AutoClearFramePropsArray framesWithProps(64); + GetModifiedAndFramesWithProps(&modifiedFrames.Frames(), + &framesWithProps.Frames()); + + if (!ShouldBuildPartial(modifiedFrames.Frames())) { + // Do not allow partial builds if the |ShouldBuildPartial()| heuristic + // fails. + mBuilder.SetPartialBuildFailed(true); + return PartialUpdateResult::Failed; + } + + nsRect modifiedDirty; + nsDisplayList modifiedDL(&mBuilder); + nsIFrame* modifiedAGR = nullptr; + PartialUpdateResult result = PartialUpdateResult::NoChange; + const bool simpleUpdate = + TrySimpleUpdate(modifiedFrames.Frames(), framesWithProps.Frames()); + + mBuilder.EnterPresShell(RootReferenceFrame()); + + if (!simpleUpdate) { + if (!ComputeRebuildRegion(modifiedFrames.Frames(), &modifiedDirty, + &modifiedAGR, framesWithProps.Frames()) || + !PreProcessDisplayList(&mList, modifiedAGR, result, + RootReferenceFrame(), nullptr)) { + DL_LOGI("RDL - Partial update aborted"); + mBuilder.SetPartialBuildFailed(true); + mBuilder.LeavePresShell(RootReferenceFrame(), nullptr); + mList.DeleteAll(&mBuilder); + return PartialUpdateResult::Failed; + } + } else { + modifiedDirty = mBuilder.GetVisibleRect(); + } + + // This is normally handled by EnterPresShell, but we skipped it so that we + // didn't call MarkFrameForDisplayIfVisible before ComputeRebuildRegion. + nsIScrollableFrame* sf = + RootReferenceFrame()->PresShell()->GetRootScrollFrameAsScrollable(); + if (sf) { + nsCanvasFrame* canvasFrame = do_QueryFrame(sf->GetScrolledFrame()); + if (canvasFrame) { + mBuilder.MarkFrameForDisplayIfVisible(canvasFrame, RootReferenceFrame()); + } + } + + nsRect rootOverflow = RootOverflowRect(); + modifiedDirty.IntersectRect(modifiedDirty, rootOverflow); + + mBuilder.SetDirtyRect(modifiedDirty); + mBuilder.SetPartialUpdate(true); + mBuilder.SetPartialBuildFailed(false); + + DL_LOGI("RDL - Starting display list build"); + RootReferenceFrame()->BuildDisplayListForStackingContext(&mBuilder, + &modifiedDL); + DL_LOGI("RDL - Finished display list build"); + + if (!modifiedDL.IsEmpty()) { + nsLayoutUtils::AddExtraBackgroundItems( + &mBuilder, &modifiedDL, RootReferenceFrame(), + nsRect(nsPoint(0, 0), rootOverflow.Size()), rootOverflow, aBackstop); + } + mBuilder.SetPartialUpdate(false); + + if (mBuilder.PartialBuildFailed()) { + DL_LOGI("RDL - Partial update failed!"); + mBuilder.LeavePresShell(RootReferenceFrame(), nullptr); + mBuilder.ClearReuseableDisplayItems(); + mList.DeleteAll(&mBuilder); + modifiedDL.DeleteAll(&mBuilder); + Metrics()->mPartialUpdateFailReason = PartialUpdateFailReason::Content; + return PartialUpdateResult::Failed; + } + + // printf_stderr("Painting --- Modified list (dirty %d,%d,%d,%d):\n", + // modifiedDirty.x, modifiedDirty.y, modifiedDirty.width, + // modifiedDirty.height); + // nsIFrame::PrintDisplayList(&mBuilder, modifiedDL); + + // |modifiedDL| can sometimes be empty here. We still perform the + // display list merging to prune unused items (for example, items that + // are not visible anymore) from the old list. + // TODO: Optimization opportunity. In this case, MergeDisplayLists() + // unnecessarily creates a hashtable of the old items. + // TODO: Ideally we could skip this if result is NoChange, but currently when + // we call RestoreState on nsDisplayWrapList it resets the clip to the base + // clip, and we need the UpdateBounds call (within MergeDisplayLists) to + // move it to the correct inner clip. + if (!simpleUpdate) { + Maybe<const ActiveScrolledRoot*> dummy; + if (MergeDisplayLists(&modifiedDL, &mList, &mList, dummy)) { + result = PartialUpdateResult::Updated; + } + } else { + MOZ_ASSERT(mList.IsEmpty()); + mList = std::move(modifiedDL); + mBuilder.ClearReuseableDisplayItems(); + result = PartialUpdateResult::Updated; + } + +#if 0 + if (DL_LOG_TEST(LogLevel::Verbose)) { + printf_stderr("Painting --- Display list:\n"); + nsIFrame::PrintDisplayList(&mBuilder, mList); + } +#endif + + mBuilder.LeavePresShell(RootReferenceFrame(), List()); + return result; +} + +nsRect RetainedDisplayListBuilder::RootOverflowRect() const { + const nsIFrame* rootReferenceFrame = RootReferenceFrame(); + nsRect rootOverflowRect = rootReferenceFrame->InkOverflowRectRelativeToSelf(); + const nsPresContext* presContext = rootReferenceFrame->PresContext(); + if (!rootReferenceFrame->GetParent() && + presContext->IsRootContentDocumentCrossProcess() && + presContext->HasDynamicToolbar()) { + rootOverflowRect.SizeTo(nsLayoutUtils::ExpandHeightForDynamicToolbar( + presContext, rootOverflowRect.Size())); + } + + return rootOverflowRect; +} + +} // namespace mozilla diff --git a/layout/painting/RetainedDisplayListBuilder.h b/layout/painting/RetainedDisplayListBuilder.h new file mode 100644 index 0000000000..98c085fee0 --- /dev/null +++ b/layout/painting/RetainedDisplayListBuilder.h @@ -0,0 +1,287 @@ +/* -*- 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/. */ + +#ifndef RETAINEDDISPLAYLISTBUILDER_H_ +#define RETAINEDDISPLAYLISTBUILDER_H_ + +#include "nsDisplayList.h" +#include "mozilla/Maybe.h" +#include "mozilla/EnumSet.h" + +class nsWindowSizes; + +namespace mozilla { + +class nsDisplayItem; +class nsDisplayList; + +/** + * RetainedDisplayListData contains frame invalidation information. + * Currently this is implemented as a map of frame pointers to flags. + */ +struct RetainedDisplayListData { + enum class FrameFlag : uint8_t { Modified, HasProps, HadWillChange }; + using FrameFlags = mozilla::EnumSet<FrameFlag, uint8_t>; + + RetainedDisplayListData(); + + /** + * Adds the frame to modified frames list. + */ + void AddModifiedFrame(nsIFrame* aFrame); + + /** + * Removes all the frames from this RetainedDisplayListData. + */ + void Clear() { + mFrames.Clear(); + mModifiedFrameCount = 0; + } + + /** + * Returns a mutable reference to flags set for the given |aFrame|. + */ + FrameFlags& Flags(nsIFrame* aFrame) { return mFrames.LookupOrInsert(aFrame); } + + /** + * Returns flags set for the given |aFrame|, or FrameFlags::None if the frame + * is not in this RetainedDisplayListData. + */ + FrameFlags GetFlags(nsIFrame* aFrame) const { return mFrames.Get(aFrame); } + + bool IsModified(nsIFrame* aFrame) const { + return GetFlags(aFrame).contains(FrameFlag::Modified); + } + + bool HasProps(nsIFrame* aFrame) const { + return GetFlags(aFrame).contains(FrameFlag::HasProps); + } + + bool HadWillChange(nsIFrame* aFrame) const { + return GetFlags(aFrame).contains(FrameFlag::HadWillChange); + } + + /** + * Returns an iterator to the underlying frame storage. + */ + auto ConstIterator() { return mFrames.ConstIter(); } + + /** + * Returns true if the modified frame limit has been reached. + */ + bool AtModifiedFrameLimit() { + return mModifiedFrameCount >= mModifiedFrameLimit; + } + + /** + * Removes the given |aFrame| from this RetainedDisplayListData. + */ + bool Remove(nsIFrame* aFrame) { return mFrames.Remove(aFrame); } + + private: + nsTHashMap<nsPtrHashKey<nsIFrame>, FrameFlags> mFrames; + uint32_t mModifiedFrameCount = 0; + uint32_t mModifiedFrameLimit; // initialized to a pref value in constructor +}; + +enum class PartialUpdateResult { Failed, NoChange, Updated }; + +enum class PartialUpdateFailReason { + NA, + EmptyList, + RebuildLimit, + FrameType, + Disabled, + Content, + VisibleRect, +}; + +struct RetainedDisplayListMetrics { + RetainedDisplayListMetrics() { Reset(); } + + void Reset() { + mNewItems = 0; + mRebuiltItems = 0; + mRemovedItems = 0; + mReusedItems = 0; + mTotalItems = 0; + mPartialBuildDuration = 0; + mFullBuildDuration = 0; + mPartialUpdateFailReason = PartialUpdateFailReason::NA; + mPartialUpdateResult = PartialUpdateResult::NoChange; + } + + void StartBuild() { mStartTime = mozilla::TimeStamp::Now(); } + + void EndFullBuild() { mFullBuildDuration = Elapsed(); } + + void EndPartialBuild(PartialUpdateResult aResult) { + mPartialBuildDuration = Elapsed(); + mPartialUpdateResult = aResult; + } + + double Elapsed() { + return (mozilla::TimeStamp::Now() - mStartTime).ToMilliseconds(); + } + + const char* FailReasonString() const { + switch (mPartialUpdateFailReason) { + case PartialUpdateFailReason::NA: + return "N/A"; + case PartialUpdateFailReason::EmptyList: + return "Empty list"; + case PartialUpdateFailReason::RebuildLimit: + return "Rebuild limit"; + case PartialUpdateFailReason::FrameType: + return "Frame type"; + case PartialUpdateFailReason::Disabled: + return "Disabled"; + case PartialUpdateFailReason::Content: + return "Content"; + case PartialUpdateFailReason::VisibleRect: + return "VisibleRect"; + default: + MOZ_ASSERT_UNREACHABLE("Enum value not handled!"); + } + } + + unsigned int mNewItems; + unsigned int mRebuiltItems; + unsigned int mRemovedItems; + unsigned int mReusedItems; + unsigned int mTotalItems; + + mozilla::TimeStamp mStartTime; + double mPartialBuildDuration; + double mFullBuildDuration; + PartialUpdateFailReason mPartialUpdateFailReason; + PartialUpdateResult mPartialUpdateResult; +}; + +class RetainedDisplayListBuilder { + public: + RetainedDisplayListBuilder(nsIFrame* aReferenceFrame, + nsDisplayListBuilderMode aMode, bool aBuildCaret) + : mBuilder(aReferenceFrame, aMode, aBuildCaret, true), mList(&mBuilder) {} + ~RetainedDisplayListBuilder() { mList.DeleteAll(&mBuilder); } + + nsDisplayListBuilder* Builder() { return &mBuilder; } + + nsDisplayList* List() { return &mList; } + + RetainedDisplayListMetrics* Metrics() { return &mMetrics; } + + RetainedDisplayListData* Data() { return &mData; } + + PartialUpdateResult AttemptPartialUpdate(nscolor aBackstop); + + /** + * Clears the modified state for frames in the retained display list data. + */ + void ClearFramesWithProps(); + + void ClearRetainedData(); + + void ClearReuseableDisplayItems() { mBuilder.ClearReuseableDisplayItems(); } + + void AddSizeOfIncludingThis(nsWindowSizes&) const; + + NS_DECLARE_FRAME_PROPERTY_DELETABLE(Cached, RetainedDisplayListBuilder) + + private: + void GetModifiedAndFramesWithProps(nsTArray<nsIFrame*>* aOutModifiedFrames, + nsTArray<nsIFrame*>* aOutFramesWithProps); + + void IncrementSubDocPresShellPaintCount(nsDisplayItem* aItem); + + /** + * Invalidates the current and previous caret frame if they have changed. + */ + void InvalidateCaretFramesIfNeeded(); + + /** + * A simple early exit heuristic to avoid slow partial display list rebuilds. + * Returns true if a partial display list build should be attempted. + */ + bool ShouldBuildPartial(nsTArray<nsIFrame*>& aModifiedFrames); + + /** + * Recursively pre-processes the old display list tree before building the + * new partial display lists, and serializes the old list into an array, + * recording indices on items for fast lookup during merging. Builds an + * initial linear DAG for the list if we don't have an existing one. Finds + * items that have a different AGR from the specified one, and marks them to + * also be built so that we get relative ordering correct. Passes + * aKeepLinked=true internally for sub-lists that can't be changed to keep the + * original list structure linked for fast re-use. + */ + bool PreProcessDisplayList( + RetainedDisplayList* aList, nsIFrame* aAGR, PartialUpdateResult& aUpdated, + nsIFrame* aAsyncAncestor, const ActiveScrolledRoot* aAsyncAncestorASR, + nsIFrame* aOuterFrame = nullptr, uint32_t aCallerKey = 0, + uint32_t aNestingDepth = 0, bool aKeepLinked = false); + + /** + * Merges items from aNewList into non-invalidated items from aOldList and + * stores the result in aOutList. + * + * aOuterItem is a pointer to an item that owns one of the lists, if + * available. If both lists are populated, then both outer items must not be + * invalidated, and identical, so either can be passed here. + * + * Returns true if changes were made, and the resulting display list (in + * aOutList) is different from aOldList. + */ + bool MergeDisplayLists( + nsDisplayList* aNewList, RetainedDisplayList* aOldList, + RetainedDisplayList* aOutList, + mozilla::Maybe<const mozilla::ActiveScrolledRoot*>& aOutContainerASR, + nsDisplayItem* aOuterItem = nullptr); + + bool ComputeRebuildRegion(nsTArray<nsIFrame*>& aModifiedFrames, + nsRect* aOutDirty, nsIFrame** aOutModifiedAGR, + nsTArray<nsIFrame*>& aOutFramesWithProps); + + bool ProcessFrame(nsIFrame* aFrame, nsDisplayListBuilder* aBuilder, + nsIFrame* aStopAtFrame, + nsTArray<nsIFrame*>& aOutFramesWithProps, + const bool aStopAtStackingContext, nsRect* aOutDirty, + nsIFrame** aOutModifiedAGR); + + nsIFrame* RootReferenceFrame() { return mBuilder.RootReferenceFrame(); } + const nsIFrame* RootReferenceFrame() const { + return mBuilder.RootReferenceFrame(); + } + + nsRect RootOverflowRect() const; + + /** + * Tries to perform a simple partial display list build without display list + * merging. In this mode, only the top-level stacking context items and their + * contents are reused, when the frame subtree has not been modified. + */ + bool TrySimpleUpdate(const nsTArray<nsIFrame*>& aModifiedFrames, + nsTArray<nsIFrame*>& aOutFramesWithProps); + + friend class MergeState; + + nsDisplayListBuilder mBuilder; + RetainedDisplayList mList; + WeakFrame mPreviousCaret; + RetainedDisplayListMetrics mMetrics; + RetainedDisplayListData mData; +}; + +namespace RDLUtils { + +void AssertFrameSubtreeUnmodified(const nsIFrame* aFrame); +void AssertDisplayItemUnmodified(nsDisplayItem* aItem); +void AssertDisplayListUnmodified(nsDisplayList* aList); + +} // namespace RDLUtils +} // namespace mozilla + +#endif // RETAINEDDISPLAYLISTBUILDER_H_ diff --git a/layout/painting/RetainedDisplayListHelpers.h b/layout/painting/RetainedDisplayListHelpers.h new file mode 100644 index 0000000000..933ab31a7a --- /dev/null +++ b/layout/painting/RetainedDisplayListHelpers.h @@ -0,0 +1,187 @@ +/* -*- 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/. */ + +#ifndef RETAINEDDISPLAYLISTHELPERS_H_ +#define RETAINEDDISPLAYLISTHELPERS_H_ + +#include "mozilla/Span.h" +#include "PLDHashTable.h" + +class nsIFrame; + +namespace mozilla { + +struct DisplayItemKey { + bool operator==(const DisplayItemKey& aOther) const { + return mFrame == aOther.mFrame && mPerFrameKey == aOther.mPerFrameKey; + } + + nsIFrame* mFrame; + uint32_t mPerFrameKey; +}; + +class DisplayItemHashEntry : public PLDHashEntryHdr { + public: + typedef DisplayItemKey KeyType; + typedef const DisplayItemKey* KeyTypePointer; + + explicit DisplayItemHashEntry(KeyTypePointer aKey) : mKey(*aKey) {} + DisplayItemHashEntry(DisplayItemHashEntry&&) = default; + + ~DisplayItemHashEntry() = default; + + KeyType GetKey() const { return mKey; } + bool KeyEquals(KeyTypePointer aKey) const { return mKey == *aKey; } + + static KeyTypePointer KeyToPointer(KeyType& aKey) { return &aKey; } + static PLDHashNumber HashKey(KeyTypePointer aKey) { + if (!aKey) { + return 0; + } + + return mozilla::HashGeneric(aKey->mFrame, aKey->mPerFrameKey); + } + enum { ALLOW_MEMMOVE = true }; + + DisplayItemKey mKey; +}; + +template <typename T> +bool SpanContains(mozilla::Span<const T>& aSpan, T aItem) { + for (const T& i : aSpan) { + if (i == aItem) { + return true; + } + } + return false; +} + +class OldListUnits {}; +class MergedListUnits {}; + +template <typename Units> +struct Index { + Index() : val(0) {} + explicit Index(size_t aVal) : val(aVal) { + MOZ_RELEASE_ASSERT(aVal < std::numeric_limits<uint32_t>::max(), + "List index overflowed"); + } + + bool operator==(const Index<Units>& aOther) const { + return val == aOther.val; + } + + uint32_t val; +}; +typedef Index<OldListUnits> OldListIndex; +typedef Index<MergedListUnits> MergedListIndex; + +template <typename T> +class DirectedAcyclicGraph { + public: + DirectedAcyclicGraph() = default; + DirectedAcyclicGraph(DirectedAcyclicGraph&& aOther) + : mNodesInfo(std::move(aOther.mNodesInfo)), + mDirectPredecessorList(std::move(aOther.mDirectPredecessorList)) {} + + DirectedAcyclicGraph& operator=(DirectedAcyclicGraph&& aOther) { + mNodesInfo = std::move(aOther.mNodesInfo); + mDirectPredecessorList = std::move(aOther.mDirectPredecessorList); + return *this; + } + + Index<T> AddNode( + mozilla::Span<const Index<T>> aDirectPredecessors, + const mozilla::Maybe<Index<T>>& aExtraPredecessor = mozilla::Nothing()) { + size_t index = mNodesInfo.Length(); + mNodesInfo.AppendElement(NodeInfo(mDirectPredecessorList.Length(), + aDirectPredecessors.Length())); + if (aExtraPredecessor && + !SpanContains(aDirectPredecessors, aExtraPredecessor.value())) { + mNodesInfo.LastElement().mDirectPredecessorCount++; + mDirectPredecessorList.SetCapacity(mDirectPredecessorList.Length() + + aDirectPredecessors.Length() + 1); + mDirectPredecessorList.AppendElements(aDirectPredecessors); + mDirectPredecessorList.AppendElement(aExtraPredecessor.value()); + } else { + mDirectPredecessorList.AppendElements(aDirectPredecessors); + } + return Index<T>(index); + } + + size_t Length() { return mNodesInfo.Length(); } + + mozilla::Span<Index<T>> GetDirectPredecessors(Index<T> aNodeIndex) { + NodeInfo& node = mNodesInfo[aNodeIndex.val]; + const auto span = mozilla::Span{mDirectPredecessorList}; + return span.Subspan(node.mIndexInDirectPredecessorList, + node.mDirectPredecessorCount); + } + + template <typename OtherUnits> + void EnsureCapacityFor(const DirectedAcyclicGraph<OtherUnits>& aOther) { + mNodesInfo.SetCapacity(aOther.mNodesInfo.Length()); + mDirectPredecessorList.SetCapacity(aOther.mDirectPredecessorList.Length()); + } + + void Clear() { + mNodesInfo.Clear(); + mDirectPredecessorList.Clear(); + } + + struct NodeInfo { + NodeInfo(size_t aIndexInDirectPredecessorList, + size_t aDirectPredecessorCount) + : mIndexInDirectPredecessorList(aIndexInDirectPredecessorList), + mDirectPredecessorCount(aDirectPredecessorCount) {} + size_t mIndexInDirectPredecessorList; + size_t mDirectPredecessorCount; + }; + + nsTArray<NodeInfo> mNodesInfo; + nsTArray<Index<T>> mDirectPredecessorList; +}; + +class RetainedDisplayListBuilder; +class nsDisplayItem; + +struct OldItemInfo { + explicit OldItemInfo(nsDisplayItem* aItem); + + void AddedToMergedList(MergedListIndex aIndex) { + MOZ_ASSERT(!IsUsed()); + mUsed = true; + mIndex = aIndex; + mItem = nullptr; + } + + void AddedMatchToMergedList(RetainedDisplayListBuilder* aBuilder, + MergedListIndex aIndex); + void Discard(RetainedDisplayListBuilder* aBuilder, + nsTArray<MergedListIndex>&& aDirectPredecessors); + bool IsUsed() { return mUsed; } + + bool IsDiscarded() { + MOZ_ASSERT(IsUsed()); + return mDiscarded; + } + + bool IsChanged(); + + nsDisplayItem* mItem; + nsTArray<MergedListIndex> mDirectPredecessors; + MergedListIndex mIndex; + bool mUsed; + bool mDiscarded; + bool mOwnsItem; +}; + +bool AnyContentAncestorModified(nsIFrame* aFrame, + nsIFrame* aStopAtFrame = nullptr); + +} // namespace mozilla + +#endif // RETAINEDDISPLAYLISTHELPERS_H_ diff --git a/layout/painting/TransformClipNode.h b/layout/painting/TransformClipNode.h new file mode 100644 index 0000000000..a28dc3dbc0 --- /dev/null +++ b/layout/painting/TransformClipNode.h @@ -0,0 +1,138 @@ +/* -*- 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/. */ + +#ifndef MOZILLA_PAINTING_TRANSFORMCLIPNODE_H +#define MOZILLA_PAINTING_TRANSFORMCLIPNODE_H + +#include "mozilla/gfx/MatrixFwd.h" +#include "mozilla/gfx/Rect.h" +#include "mozilla/Maybe.h" +#include "nsISupports.h" +#include "nsRegionFwd.h" + +namespace mozilla { + +/** + * TransformClipNode stores a transformation matrix and a post-transform + * clip rect. + * They can be used to transform and clip a display item inside a flattened + * nsDisplayTransform to the coordinate space of that nsDisplayTransform. + */ +class TransformClipNode { + NS_INLINE_DECL_REFCOUNTING(TransformClipNode); + + public: + TransformClipNode(const RefPtr<TransformClipNode>& aParent, + const gfx::Matrix4x4Flagged& aTransform, + const Maybe<gfx::IntRect>& aClip) + : mParent(aParent), mTransform(aTransform), mClip(aClip) { + MOZ_COUNT_CTOR(TransformClipNode); + } + + /** + * Returns the parent node, or nullptr if this is the root node. + */ + const RefPtr<TransformClipNode>& Parent() const { return mParent; } + + /** + * Transforms and clips |aRect| up to the root transform node. + * |aRect| is expected to be in app units. + */ + nsRect TransformRect(const nsRect& aRect, const int32_t aA2D) const { + if (aRect.IsEmpty()) { + return aRect; + } + + gfx::Rect result(NSAppUnitsToFloatPixels(aRect.x, aA2D), + NSAppUnitsToFloatPixels(aRect.y, aA2D), + NSAppUnitsToFloatPixels(aRect.width, aA2D), + NSAppUnitsToFloatPixels(aRect.height, aA2D)); + TransformRect(result); + return nsRect(NSFloatPixelsToAppUnits(result.x, aA2D), + NSFloatPixelsToAppUnits(result.y, aA2D), + NSFloatPixelsToAppUnits(result.width, aA2D), + NSFloatPixelsToAppUnits(result.height, aA2D)); + } + + /** + * Transforms and clips |aRect| up to the root transform node. + * |aRect| is expected to be in integer pixels. + */ + gfx::IntRect TransformRect(const gfx::IntRect& aRect) const { + if (aRect.IsEmpty()) { + return aRect; + } + + gfx::Rect result(IntRectToRect(aRect)); + TransformRect(result); + return RoundedToInt(result); + } + + /** + * Transforms and clips |aRegion| up to the root transform node. + * |aRegion| is expected be in integer pixels. + */ + nsIntRegion TransformRegion(const nsIntRegion& aRegion) { + if (aRegion.IsEmpty()) { + return aRegion; + } + + nsIntRegion result = aRegion; + + const TransformClipNode* node = this; + while (node) { + const gfx::Matrix4x4Flagged& transform = node->Transform(); + result = result.Transform(transform.GetMatrix()); + + if (node->Clip()) { + const gfx::IntRect clipRect = *node->Clip(); + result.AndWith(clipRect); + } + + node = node->Parent(); + } + + return result; + } + + protected: + /** + * Returns the post-transform clip, if there is one. + */ + const Maybe<gfx::IntRect>& Clip() const { return mClip; } + + /** + * Returns the matrix that transforms the item bounds to the coordinate space + * of the flattened nsDisplayTransform. + */ + const gfx::Matrix4x4Flagged& Transform() const { return mTransform; } + + void TransformRect(gfx::Rect& aRect) const { + const TransformClipNode* node = this; + while (node) { + const gfx::Matrix4x4Flagged& transform = node->Transform(); + gfx::Rect maxBounds = gfx::Rect::MaxIntRect(); + + if (node->Clip()) { + maxBounds = IntRectToRect(*node->Clip()); + } + + aRect = transform.TransformAndClipBounds(aRect, maxBounds); + node = node->Parent(); + } + } + + private: + MOZ_COUNTED_DTOR(TransformClipNode) + + const RefPtr<TransformClipNode> mParent; + const gfx::Matrix4x4Flagged mTransform; + const Maybe<gfx::IntRect> mClip; +}; + +} // namespace mozilla + +#endif /* MOZILLA_PAINTING_TRANSFORMCLIPNODE_H */ diff --git a/layout/painting/WindowRenderer.cpp b/layout/painting/WindowRenderer.cpp new file mode 100644 index 0000000000..d695ef8eba --- /dev/null +++ b/layout/painting/WindowRenderer.cpp @@ -0,0 +1,229 @@ +/* -*- 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/. */ + +#include "WindowRenderer.h" + +#include "gfxPlatform.h" +#include "mozilla/dom/Animation.h" // for Animation +#include "mozilla/dom/AnimationEffect.h" +#include "mozilla/EffectSet.h" +#include "mozilla/layers/PersistentBufferProvider.h" // for PersistentBufferProviderBasic, PersistentBufferProvider (ptr only) +#include "nsDisplayList.h" + +using namespace mozilla::gfx; +using namespace mozilla::layers; + +namespace mozilla { + +/** + * StartFrameTimeRecording, together with StopFrameTimeRecording + * enable recording of frame intervals. + * + * To allow concurrent consumers, a cyclic array is used which serves all + * consumers, practically stateless with regard to consumers. + * + * To save resources, the buffer is allocated on first call to + * StartFrameTimeRecording and recording is paused if no consumer which called + * StartFrameTimeRecording is able to get valid results (because the cyclic + * buffer was overwritten since that call). + * + * To determine availability of the data upon StopFrameTimeRecording: + * - mRecording.mNextIndex increases on each RecordFrame, and never resets. + * - Cyclic buffer position is realized as mNextIndex % bufferSize. + * - StartFrameTimeRecording returns mNextIndex. When StopFrameTimeRecording is + * called, the required start index is passed as an arg, and we're able to + * calculate the required length. If this length is bigger than bufferSize, it + * means data was overwritten. otherwise, we can return the entire sequence. + * - To determine if we need to pause, mLatestStartIndex is updated to + * mNextIndex on each call to StartFrameTimeRecording. If this index gets + * overwritten, it means that all earlier start indices obtained via + * StartFrameTimeRecording were also overwritten, hence, no point in + * recording, so pause. + * - mCurrentRunStartIndex indicates the oldest index of the recording after + * which the recording was not paused. If StopFrameTimeRecording is invoked + * with a start index older than this, it means that some frames were not + * recorded, so data is invalid. + */ +uint32_t FrameRecorder::StartFrameTimeRecording(int32_t aBufferSize) { + if (mRecording.mIsPaused) { + mRecording.mIsPaused = false; + + if (!mRecording.mIntervals.Length()) { // Initialize recording buffers + mRecording.mIntervals.SetLength(aBufferSize); + } + + // After being paused, recent values got invalid. Update them to now. + mRecording.mLastFrameTime = TimeStamp::Now(); + + // Any recording which started before this is invalid, since we were paused. + mRecording.mCurrentRunStartIndex = mRecording.mNextIndex; + } + + // If we'll overwrite this index, there are no more consumers with aStartIndex + // for which we're able to provide the full recording, so no point in keep + // recording. + mRecording.mLatestStartIndex = mRecording.mNextIndex; + return mRecording.mNextIndex; +} + +void FrameRecorder::RecordFrame() { + if (!mRecording.mIsPaused) { + TimeStamp now = TimeStamp::Now(); + uint32_t i = mRecording.mNextIndex % mRecording.mIntervals.Length(); + mRecording.mIntervals[i] = + static_cast<float>((now - mRecording.mLastFrameTime).ToMilliseconds()); + mRecording.mNextIndex++; + mRecording.mLastFrameTime = now; + + if (mRecording.mNextIndex > + (mRecording.mLatestStartIndex + mRecording.mIntervals.Length())) { + // We've just overwritten the most recent recording start -> pause. + mRecording.mIsPaused = true; + } + } +} + +void FrameRecorder::StopFrameTimeRecording(uint32_t aStartIndex, + nsTArray<float>& aFrameIntervals) { + uint32_t bufferSize = mRecording.mIntervals.Length(); + uint32_t length = mRecording.mNextIndex - aStartIndex; + if (mRecording.mIsPaused || length > bufferSize || + aStartIndex < mRecording.mCurrentRunStartIndex) { + // aStartIndex is too old. Also if aStartIndex was issued before + // mRecordingNextIndex overflowed (uint32_t) + // and stopped after the overflow (would happen once every 828 days of + // constant 60fps). + length = 0; + } + + if (!length) { + aFrameIntervals.Clear(); + return; // empty recording, return empty arrays. + } + // Set length in advance to avoid possibly repeated reallocations + aFrameIntervals.SetLength(length); + + uint32_t cyclicPos = aStartIndex % bufferSize; + for (uint32_t i = 0; i < length; i++, cyclicPos++) { + if (cyclicPos == bufferSize) { + cyclicPos = 0; + } + aFrameIntervals[i] = mRecording.mIntervals[cyclicPos]; + } +} + +already_AddRefed<PersistentBufferProvider> +WindowRenderer::CreatePersistentBufferProvider( + const mozilla::gfx::IntSize& aSize, mozilla::gfx::SurfaceFormat aFormat) { + RefPtr<PersistentBufferProviderBasic> bufferProvider; + // If we are using remote canvas we don't want to use acceleration in + // non-remote layer managers, so we always use the fallback software one. + if (!gfxPlatform::UseRemoteCanvas() || + !gfxPlatform::IsBackendAccelerated( + gfxPlatform::GetPlatform()->GetPreferredCanvasBackend())) { + bufferProvider = PersistentBufferProviderBasic::Create( + aSize, aFormat, + gfxPlatform::GetPlatform()->GetPreferredCanvasBackend()); + } + + if (!bufferProvider) { + bufferProvider = PersistentBufferProviderBasic::Create( + aSize, aFormat, gfxPlatform::GetPlatform()->GetFallbackCanvasBackend()); + } + + return bufferProvider.forget(); +} + +void WindowRenderer::AddPartialPrerenderedAnimation( + uint64_t aCompositorAnimationId, dom::Animation* aAnimation) { + mPartialPrerenderedAnimations.InsertOrUpdate(aCompositorAnimationId, + RefPtr{aAnimation}); + aAnimation->SetPartialPrerendered(aCompositorAnimationId); +} +void WindowRenderer::RemovePartialPrerenderedAnimation( + uint64_t aCompositorAnimationId, dom::Animation* aAnimation) { + MOZ_ASSERT(aAnimation); +#ifdef DEBUG + RefPtr<dom::Animation> animation; + if (mPartialPrerenderedAnimations.Remove(aCompositorAnimationId, + getter_AddRefs(animation)) && + // It may be possible that either animation's effect has already been + // nulled out via Animation::SetEffect() so ignore such cases. + aAnimation->GetEffect() && aAnimation->GetEffect()->AsKeyframeEffect() && + animation->GetEffect() && animation->GetEffect()->AsKeyframeEffect()) { + MOZ_ASSERT( + EffectSet::GetForEffect(aAnimation->GetEffect()->AsKeyframeEffect()) == + EffectSet::GetForEffect(animation->GetEffect()->AsKeyframeEffect())); + } +#else + mPartialPrerenderedAnimations.Remove(aCompositorAnimationId); +#endif + aAnimation->ResetPartialPrerendered(); +} +void WindowRenderer::UpdatePartialPrerenderedAnimations( + const nsTArray<uint64_t>& aJankedAnimations) { + for (uint64_t id : aJankedAnimations) { + RefPtr<dom::Animation> animation; + if (mPartialPrerenderedAnimations.Remove(id, getter_AddRefs(animation))) { + animation->UpdatePartialPrerendered(); + } + } +} + +void FallbackRenderer::SetTarget(gfxContext* aTarget, + layers::BufferMode aDoubleBuffering) { + mTarget = aTarget; + mBufferMode = aDoubleBuffering; +} + +bool FallbackRenderer::BeginTransaction(const nsCString& aURL) { + if (!mTarget) { + return false; + } + + return true; +} + +void FallbackRenderer::EndTransactionWithColor(const nsIntRect& aRect, + const gfx::DeviceColor& aColor) { + mTarget->GetDrawTarget()->FillRect(Rect(aRect), ColorPattern(aColor)); + mAnimationReadyTime = TimeStamp::Now(); +} + +void FallbackRenderer::EndTransactionWithList(nsDisplayListBuilder* aBuilder, + nsDisplayList* aList, + int32_t aAppUnitsPerDevPixel, + EndTransactionFlags aFlags) { + if (aFlags & EndTransactionFlags::END_NO_COMPOSITE) { + return; + } + + DrawTarget* dt = mTarget->GetDrawTarget(); + + BackendType backend = gfxPlatform::GetPlatform()->GetContentBackendFor( + LayersBackend::LAYERS_NONE); + RefPtr<DrawTarget> dest = + gfxPlatform::GetPlatform()->CreateDrawTargetForBackend( + backend, dt->GetSize(), dt->GetFormat()); + if (dest) { + gfxContext ctx(dest, /* aPreserveTransform */ true); + + nsRegion opaque = aList->GetOpaqueRegion(aBuilder); + if (opaque.Contains(aList->GetComponentAlphaBounds(aBuilder))) { + dest->SetPermitSubpixelAA(true); + } + + aList->Paint(aBuilder, &ctx, aAppUnitsPerDevPixel); + + RefPtr<SourceSurface> snapshot = dest->Snapshot(); + dt->DrawSurface(snapshot, Rect(dest->GetRect()), Rect(dest->GetRect()), + DrawSurfaceOptions(), + DrawOptions(1.0f, CompositionOp::OP_SOURCE)); + } + mAnimationReadyTime = TimeStamp::Now(); +} + +} // namespace mozilla diff --git a/layout/painting/WindowRenderer.h b/layout/painting/WindowRenderer.h new file mode 100644 index 0000000000..e018a4cf56 --- /dev/null +++ b/layout/painting/WindowRenderer.h @@ -0,0 +1,284 @@ +/* -*- 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/. */ + +#ifndef MOZILLA_PAINTING_WINDOWRENDERER_H +#define MOZILLA_PAINTING_WINDOWRENDERER_H + +#include "mozilla/webrender/webrender_ffi.h" +#include "mozilla/layers/LayersTypes.h" +#include "mozilla/dom/Animation.h" // for Animation +#include "mozilla/layers/ScrollableLayerGuid.h" // for ScrollableLayerGuid, ScrollableLayerGuid::ViewID +#include "mozilla/ScrollPositionUpdate.h" // for ScrollPositionUpdate +#include "nsRefPtrHashtable.h" // for nsRefPtrHashtable +#include "gfxContext.h" + +namespace mozilla { +namespace layers { +class LayerManager; +class WebRenderLayerManager; +class KnowsCompositor; +class CompositorBridgeChild; +class FrameUniformityData; +class PersistentBufferProvider; +} // namespace layers +class FallbackRenderer; +class nsDisplayListBuilder; +class nsDisplayList; + +class FrameRecorder { + public: + /** + * Record (and return) frame-intervals and paint-times for frames which were + * presented between calling StartFrameTimeRecording and + * StopFrameTimeRecording. + * + * - Uses a cyclic buffer and serves concurrent consumers, so if Stop is + * called too late + * (elements were overwritten since Start), result is considered invalid + * and hence empty.) + * - Buffer is capable of holding 10 seconds @ 60fps (or more if frames were + * less frequent). + * Can be changed (up to 1 hour) via pref: + * toolkit.framesRecording.bufferSize. + * - Note: the first frame-interval may be longer than expected because last + * frame + * might have been presented some time before calling + * StartFrameTimeRecording. + */ + + /** + * Returns a handle which represents current recording start position. + */ + virtual uint32_t StartFrameTimeRecording(int32_t aBufferSize); + + /** + * Clears, then populates aFrameIntervals with the recorded frame timing + * data. The array will be empty if data was overwritten since + * aStartIndex was obtained. + */ + virtual void StopFrameTimeRecording(uint32_t aStartIndex, + nsTArray<float>& aFrameIntervals); + + void RecordFrame(); + + private: + struct FramesTimingRecording { + // Stores state and data for frame intervals and paint times recording. + // see LayerManager::StartFrameTimeRecording() at Layers.cpp for more + // details. + FramesTimingRecording() + : mNextIndex(0), + mLatestStartIndex(0), + mCurrentRunStartIndex(0), + mIsPaused(true) {} + nsTArray<float> mIntervals; + TimeStamp mLastFrameTime; + uint32_t mNextIndex; + uint32_t mLatestStartIndex; + uint32_t mCurrentRunStartIndex; + bool mIsPaused; + }; + FramesTimingRecording mRecording; +}; + +/** + * WindowRenderer is the retained rendering object owned by an nsIWidget for + * drawing the contents of that window, the role previously handled by + * LayerManager. + * + * It can be WebRender, (deprecated) Layers, or an immediate-mode + * FallbackRenderer. + * + * The intention is for LayerManager to be removed entirely in the near future, + * with WebRender inheriting directly from this class. It is likely that more + * cleanup can be done once that happens. + */ +class WindowRenderer : public FrameRecorder { + NS_INLINE_DECL_REFCOUNTING(WindowRenderer) + + public: + // Cast to implementation types. + virtual layers::WebRenderLayerManager* AsWebRender() { return nullptr; } + virtual FallbackRenderer* AsFallback() { return nullptr; } + + // Required functionality + + /** + * Start a new transaction. Nested transactions are not allowed so + * there must be no transaction currently in progress. + * This transaction will update the state of the window from which + * this LayerManager was obtained. + */ + virtual bool BeginTransaction(const nsCString& aURL = nsCString()) = 0; + + enum EndTransactionFlags { + END_DEFAULT = 0, + END_NO_IMMEDIATE_REDRAW = 1 << 0, // Do not perform the drawing phase + END_NO_COMPOSITE = + 1 << 1, // Do not composite after drawing painted layer contents. + END_NO_REMOTE_COMPOSITE = 1 << 2 // Do not schedule a composition with a + // remote Compositor, if one exists. + }; + + /** + * Attempts to end an "empty transaction". There must have been no + * changes to the layer tree since the BeginTransaction(). + * It's possible for this to fail; PaintedLayers may need to be updated + * due to VRAM data being lost, for example. In such cases this method + * returns false, and the caller must proceed with a normal layer tree + * update and EndTransaction. + */ + virtual bool EndEmptyTransaction( + EndTransactionFlags aFlags = END_DEFAULT) = 0; + + virtual void Destroy() {} + + /** + * Type of layer manager this is. This is to be used sparsely in order to + * avoid a lot of Layers backend specific code. It should be used only when + * Layers backend specific functionality is necessary. + */ + virtual layers::LayersBackend GetBackendType() = 0; + + /** + * Type of layers backend that will be used to composite this layer tree. + * When compositing is done remotely, then this returns the layers type + * of the compositor. + */ + virtual layers::LayersBackend GetCompositorBackendType() { + return GetBackendType(); + } + + /** + * Checks if we need to invalidate the OS widget to trigger + * painting when updating this renderer. + */ + virtual bool NeedsWidgetInvalidation() { return true; } + + /** + * Make sure that the previous transaction has been entirely + * completed. + * + * Note: This may sychronously wait on a remote compositor + * to complete rendering. + */ + virtual void FlushRendering(wr::RenderReasons aReasons) {} + + /** + * Make sure that the previous transaction has been + * received. This will synchronsly wait on a remote compositor. + */ + virtual void WaitOnTransactionProcessed() {} + + virtual bool IsCompositingCheap() { return true; } + + /** + * returns the maximum texture size on this layer backend, or INT32_MAX + * if there is no maximum + */ + virtual int32_t GetMaxTextureSize() const { return INT32_MAX; } + + /** + * Return the name of the layer manager's backend. + */ + virtual void GetBackendName(nsAString& aName) = 0; + + virtual void GetFrameUniformity(layers::FrameUniformityData* aOutData) {} + + virtual bool AddPendingScrollUpdateForNextTransaction( + layers::ScrollableLayerGuid::ViewID aScrollId, + const ScrollPositionUpdate& aUpdateInfo) { + return false; + } + + /** + * Creates a PersistentBufferProvider for use with canvas which is optimized + * for inter-operating with this layermanager. + */ + virtual already_AddRefed<layers::PersistentBufferProvider> + CreatePersistentBufferProvider(const mozilla::gfx::IntSize& aSize, + mozilla::gfx::SurfaceFormat aFormat); + + // Helper wrappers around cast to impl and then cast again. + + virtual layers::KnowsCompositor* AsKnowsCompositor() { return nullptr; } + + virtual layers::CompositorBridgeChild* GetCompositorBridgeChild() { + return nullptr; + } + + // Provided functionality + + void AddPartialPrerenderedAnimation(uint64_t aCompositorAnimationId, + dom::Animation* aAnimation); + void RemovePartialPrerenderedAnimation(uint64_t aCompositorAnimationId, + dom::Animation* aAnimation); + void UpdatePartialPrerenderedAnimations( + const nsTArray<uint64_t>& aJankedAnimations); + + const TimeStamp& GetAnimationReadyTime() const { return mAnimationReadyTime; } + + protected: + virtual ~WindowRenderer() = default; + + // Transform animations which are not fully pre-rendered because it's on a + // large frame. We need to update the pre-rendered area once after we tried + // to composite area which is outside of the pre-rendered area on the + // compositor. + nsRefPtrHashtable<nsUint64HashKey, dom::Animation> + mPartialPrerenderedAnimations; + + // The time when painting most recently finished. This is recorded so that + // we can time any play-pending animations from this point. + TimeStamp mAnimationReadyTime; +}; + +/** + * FallbackRenderer is non-retained renderer that acts as a direct wrapper + * around calling Paint on the provided DisplayList. This is used for cases + * where initializing WebRender is too costly, and we don't need + * retaining/invalidation (like small popup windows). + * + * It doesn't support any sort of EmptyTransaction, and only draws during + * EndTransaction if a composite is requested (no END_NO_COMPOSITE flag + * provided) + */ +class FallbackRenderer : public WindowRenderer { + public: + FallbackRenderer* AsFallback() override { return this; } + + void SetTarget(gfxContext* aContext, layers::BufferMode aDoubleBuffering); + + bool BeginTransaction(const nsCString& aURL = nsCString()) override; + + bool EndEmptyTransaction(EndTransactionFlags aFlags = END_DEFAULT) override { + return false; + } + + layers::LayersBackend GetBackendType() override { + return layers::LayersBackend::LAYERS_NONE; + } + + virtual void GetBackendName(nsAString& name) override { + name.AssignLiteral("Fallback"); + } + + bool IsCompositingCheap() override { return false; } + + void EndTransactionWithColor(const nsIntRect& aRect, + const gfx::DeviceColor& aColor); + void EndTransactionWithList(nsDisplayListBuilder* aBuilder, + nsDisplayList* aList, + int32_t aAppUnitsPerDevPixel, + EndTransactionFlags aFlags); + + gfxContext* mTarget; + layers::BufferMode mBufferMode; +}; + +} // namespace mozilla + +#endif /* MOZILLA_PAINTING_WINDOWRENDERER_H */ diff --git a/layout/painting/crashtests/1402183-1.html b/layout/painting/crashtests/1402183-1.html new file mode 100644 index 0000000000..ea54512894 --- /dev/null +++ b/layout/painting/crashtests/1402183-1.html @@ -0,0 +1,20 @@ +<!DOCTYPE html> +<html> +<head> +<title>Bug 1402183</title> +<style type="text/css"> +:not(article) { + -webkit-text-stroke-width: 1px; + box-decoration-break: clone; + clip-path: polygon(69px 1px, 49px 1px, 0px 0px); +} +</style> +</head> +<body> +<ins> +<p> +r6O#i/q] +</p> +</ins> +</body> +</html> diff --git a/layout/painting/crashtests/1405881-1.html b/layout/painting/crashtests/1405881-1.html new file mode 100644 index 0000000000..44c8ab1ac0 --- /dev/null +++ b/layout/painting/crashtests/1405881-1.html @@ -0,0 +1,24 @@ +<style type="text/css">
+ #container {
+ height: 300px;
+ width: 300px;
+ }
+ #box {
+ height: 100px;
+ width: 100px;
+ background: red;
+ animation: 2s anim;
+ }
+ @keyframes anim {
+ from {
+ -moz-transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 10%, 10%, 0, 1);
+ }
+ to {
+ -moz-transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 40%, 40%, 0, 1);
+ }
+ }
+
+</style>
+<div id=container>
+ <div id=box></div>
+</div>
diff --git a/layout/painting/crashtests/1407470-1.html b/layout/painting/crashtests/1407470-1.html new file mode 100644 index 0000000000..0ddf957182 --- /dev/null +++ b/layout/painting/crashtests/1407470-1.html @@ -0,0 +1,19 @@ +<body id='test_body'> +<script> +let o = []; +o[0] = document.createElement("tt"); +test_body.appendChild(o[0]); +o[1] = document.createElement("center"); +o[2] = document.createElement("footer"); +o[0].appendChild(o[2]); +o[1].animate([{ + "padding": "80.40vw 0.0vmax", + "transform": "matrix(6287.56,268.76,237.34,222.80,186.72,287.94) rotateX(2.046rad)" + }], { duration:3845.75 }); +o[2].appendChild(o[1]); +test_body.animate([{ + "transform": "scale3d(0.0,5961462.820,250.41)", + "outline": "auto thick", + "mask": "exclude no-clip url(data:image/gif;base64,R0lGODlhAQABAAD/ACH/C05FVFNDQVBFMi4wAwEAAAAsAAAAAAEAAQAAAgAsAAAAAAEAAQAAAgA7),exclude", + }], 3617.63433129); +</script>
\ No newline at end of file diff --git a/layout/painting/crashtests/1413073-1.html b/layout/painting/crashtests/1413073-1.html new file mode 100644 index 0000000000..ddecfb2424 --- /dev/null +++ b/layout/painting/crashtests/1413073-1.html @@ -0,0 +1,15 @@ +<html class="reftest-wait"> +<body> +<div style="position:fixed; width:200px; height:200px; background-color:blue"> + <div style="position:fixed; width:200px; height:200px; left:400px; background-color:red" id="inner"></div> +</div> +</body> +<script> + function doTest() { + var d = document.getElementById("inner"); + d.style.backgroundColor = "green"; + document.documentElement.className = ""; + } + window.addEventListener("MozReftestInvalidate", doTest); +</script> +</html> diff --git a/layout/painting/crashtests/1413073-2.html b/layout/painting/crashtests/1413073-2.html new file mode 100644 index 0000000000..01d1718745 --- /dev/null +++ b/layout/painting/crashtests/1413073-2.html @@ -0,0 +1,18 @@ +<html class="reftest-wait"> +<body> +<div style="position:absolute; width:200px; height:200px; opacity:0.5"> + <div style="opacity:0.9"> + <div style="position:fixed; width:200px; height:200px; left:400px; background-color:blue" id="fixed"></div> + </div> + <div style="width:200px; height:200px; background-color:red" id="inner"></div> +</div> +</body> +<script> + function doTest() { + var d = document.getElementById("inner"); + d.style.backgroundColor = "green"; + document.documentElement.className = ""; + } + window.addEventListener("MozReftestInvalidate", doTest); +</script> +</html> diff --git a/layout/painting/crashtests/1418177-1.html b/layout/painting/crashtests/1418177-1.html new file mode 100644 index 0000000000..6420814bce --- /dev/null +++ b/layout/painting/crashtests/1418177-1.html @@ -0,0 +1,34 @@ +<html> + <head> + <style> + * { + background: url(16.png), url(16.png) transparent; + -webkit-background-clip: text, text; + -moz-tab-size: calc(1 + 1) !important; + background-blend-mode: screen; + align-content: baseline; + background-color: green; + } + </style> + <script> + function fun_0() { + try { o3 = document.createElement('th') } catch (e) {}; + try { o1.appendChild(o3) } catch (e) {} + } + + try { o1 = document.createElement('tr') } catch (e) {} + try { o2 = document.createElement('ol') } catch (e) {} + try { xhr = new XMLHttpRequest({mozAnon: false }) } catch (e) {} + try { document.documentElement.appendChild(o1) } catch (e) {} + try { document.documentElement.appendChild(o2) } catch (e) {} + for (let i = 0; i < 100; i++) { + try { xhr.open('GET', 'data:text/html,1', false); } catch (e) {}; + try { xhr.send(); } catch (e) {}; + try { fuzzPriv.GC(); fuzzPriv.CC(); fuzzPriv.GC(); fuzzPriv.CC(); } catch (e) {}; + try { xhr.addEventListener('readystatechange', fun_0, true) } catch (e) {}; + try { o2.offsetLeft } catch (e) {}; + try { document.styleSheets[0].cssRules[0].style['background-origin'] = 'border-box, border-box' } catch (e) {} + } + </script> + </head> +</html> diff --git a/layout/painting/crashtests/1418722-1.html b/layout/painting/crashtests/1418722-1.html new file mode 100644 index 0000000000..93415d3012 --- /dev/null +++ b/layout/painting/crashtests/1418722-1.html @@ -0,0 +1,17 @@ +<html class="reftest-wait"> +<body> + <div style="width:200px; height:200px; perspective:1000px"> + <div style="width:200px; height:200px; transform:translateZ(2px); background-color:green" id="transformed"></div> + </div> + <div style="width: 200px; height:200px; background-color:red" id="helper"></div> +</body> +<script> + function doTest() { + var element = document.getElementById("transformed"); + element.parentNode.removeChild(element); + document.getElementById("helper").style.backgroundColor = "blue"; + document.documentElement.className = ""; + } + window.addEventListener("MozReftestInvalidate", doTest); +</script> +</html> diff --git a/layout/painting/crashtests/1419917.html b/layout/painting/crashtests/1419917.html new file mode 100644 index 0000000000..a60dd15403 --- /dev/null +++ b/layout/painting/crashtests/1419917.html @@ -0,0 +1,15 @@ +<html> + <head> + <style> + :nth-last-child(2) { -moz-appearance:listbox } + </style> + <script> + try { o1 = document.createElement('tr') } catch(e) { } + try { o2 = document.createElement('th') } catch(e) { } + try { o3 = document.createElement('canvas') } catch(e) { } + try { document.documentElement.appendChild(o1) } catch(e) { } + try { o1.appendChild(o2) } catch(e) { } + try { o1.appendChild(o3) } catch(e) { } + </script> + </head> +</html> diff --git a/layout/painting/crashtests/1425271-1.html b/layout/painting/crashtests/1425271-1.html new file mode 100644 index 0000000000..164cb0f11f --- /dev/null +++ b/layout/painting/crashtests/1425271-1.html @@ -0,0 +1,54 @@ +<!DOCTYPE html> +<html class="reftest-wait"> + +<head> + <meta http-equiv="content-type" content="text/html; charset=UTF-8"> + <meta charset="utf-8"> +</head> + +<body> +<div id="container"> + <div id="element"> + <!-- + The HTML code for this element has no other meaning than to create + display items that are merged together. + --> + <div style="column-count:2; column-count:2; width:300px; height:100px;"> + <div id="o" style="opacity:0.5; width:100px; height:200px; background:lime;"> + <div id="d" style="height:50px; width:80px; background:red; padding:2px">Text</div> + </div> + </div> + </div> +</div> + +<script type="text/javascript"> +function redirect() { + document.documentElement.removeAttribute("class"); + + // Trigger root frame deletion. + window.location.replace("about:blank"); +} + +function removeElements(container) { + document.body.removeChild(container); + + setTimeout(redirect, 0); +} + +function createElements() { + var c = document.getElementById("container"); + var e = document.getElementById("element"); + for (var i = 0; i < 1000; ++i) { + // Populate the container with elements that cause display item merges. + c.appendChild(e.cloneNode(true)); + } + + setTimeout(() => removeElements(c), 0); +} + +document.addEventListener("MozReftestInvalidate", createElements); +// window.addEventListener("load", createElements); +</script> + +</body> +</html> diff --git a/layout/painting/crashtests/1428906-1.html b/layout/painting/crashtests/1428906-1.html new file mode 100644 index 0000000000..03e6d3aaf8 --- /dev/null +++ b/layout/painting/crashtests/1428906-1.html @@ -0,0 +1,16 @@ +<html><head> +<meta http-equiv="content-type" content="text/html; charset=UTF-8"><style> +*,m{background:url()repeat center top fixed} +#a{transform:scale(1)} +</style> +<script> +function eh1(){ + try{c=a.insertRow()}catch(e){} + try{c.appendChild(b)}catch(e){} +} +</script> +</head><body><table id="a"> +<tbody><tr><d id="b"> +<video> +<source onerror="eh1()"> +</video></d></tr></tbody></table></body></html> diff --git a/layout/painting/crashtests/1430589-1.html b/layout/painting/crashtests/1430589-1.html new file mode 100644 index 0000000000..88bb0494a7 --- /dev/null +++ b/layout/painting/crashtests/1430589-1.html @@ -0,0 +1,55 @@ +<html class="reftest-wait"><head> +<meta http-equiv="content-type" content="text/html; charset=UTF-8"> + <style> + :root { + flex-wrap: wrap-reverse; + } + menuitem { + -webkit-mask-image: url(data:image/gif;base64,R0lGODlhEAAQAMQAAORHHOVSKudfOulrSOp3WOyDZu6QdvCchPGolfO0o/XBs/fNwfjZ0frl3/zy7////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAkAABAALAAAAAAQABAAAAVVICSOZGlCQAosJ6mu7fiyZeKqNKToQGDsM8hBADgUXoGAiqhSvp5QAnQKGIgUhwFUYLCVDFCrKUE1lBavAViFIDlTImbKC5Gm2hB0SlBCBMQiB0UjIQA7); + } + + * { + margin-right: -1px; + display: -webkit-box; + overflow: scroll; + } + + + .css1 { + -webkit-border-bottom-left-radius: 10px; + } + + + </style> +<script> +function BOOM(){ + setTimeout(() => { + var elem = document.getElementById("vuln"); + elem.style.width = 600; + elem.style.height = 600; + window.requestAnimationFrame(() => { + window.requestAnimationFrame(() => { + document.documentElement.removeAttribute("class"); + }); + }); + }, 0); +} + +document.addEventListener("MozReftestInvalidate", BOOM); +//window.addEventListener("load", BOOM); +</script></head> + +<body> + <footer> + <textarea id="vuln" style="width: 500px; height: 500px;">Ashitaka</textarea> + </footer> + <menu> + <menuitem> + <iframe></iframe> + </menuitem></menu> + <menuitem> + <hr> + <dialog class="css1"></dialog> + + +</menuitem></body></html> diff --git a/layout/painting/crashtests/1454105-1.html b/layout/painting/crashtests/1454105-1.html new file mode 100644 index 0000000000..946d45992b --- /dev/null +++ b/layout/painting/crashtests/1454105-1.html @@ -0,0 +1,22 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="UTF-8"> +<style> +#a { + filter: brightness(1); + border-style: solid; +} +#b { + opacity: 0.2; +} +</style> +</head> +<body> +<span id="b"> + <span id="a"> + <div></div> + </span> +</span> +</body> +</html> diff --git a/layout/painting/crashtests/1455944-1.html b/layout/painting/crashtests/1455944-1.html new file mode 100644 index 0000000000..c115f4dd8e --- /dev/null +++ b/layout/painting/crashtests/1455944-1.html @@ -0,0 +1,14 @@ +<!DOCTYPE html> +<html> +<head> +<meta http-equiv="content-type" content="text/html; charset=UTF-8"> +<meta charset="utf-8"> +</head> + +<body> +<div style="opacity: 0.9;"> + <iframe style="border: none;" src=""></iframe> +</div> + +</body> +</html> diff --git a/layout/painting/crashtests/1458145.html b/layout/painting/crashtests/1458145.html new file mode 100644 index 0000000000..dc5f07c204 --- /dev/null +++ b/layout/painting/crashtests/1458145.html @@ -0,0 +1,11 @@ +<style> +* { position: fixed; } +#a { +perspective: 0px; +overflow: scroll; +} +.cl2 { -webkit-transform-style: preserve-3d; } +.cl1 { border-bottom-right-radius: 0px 1px; } +</style> +<del id="a" class="cl1"> +<dl class="cl2">#_k</dl> diff --git a/layout/painting/crashtests/1465305-1.html b/layout/painting/crashtests/1465305-1.html new file mode 100644 index 0000000000..084f524fba --- /dev/null +++ b/layout/painting/crashtests/1465305-1.html @@ -0,0 +1,8 @@ +<style> +:not(cursor) { + -webkit-background-clip: text; + border-top-right-radius: 0vh; +} +</style> +><input> +<dialog open=""> diff --git a/layout/painting/crashtests/1468124-1.html b/layout/painting/crashtests/1468124-1.html new file mode 100644 index 0000000000..9acf1427db --- /dev/null +++ b/layout/painting/crashtests/1468124-1.html @@ -0,0 +1,27 @@ +<html class="reftest-wait"> +<style> +:not(feFuncB) { + position: fixed; +} +a:last-child { + -webkit-transform-style: preserve-3d; +} +* { + -webkit-backface-visibility: hidden; +</style> +<script> +window.requestIdleCallback(function() { + document.documentElement.getBoundingClientRect(); +}); +function go() { + var c = document.createElement("a") + c.text = "-"; + try { c.replaceChild(b, c.childNodes[0]); } catch(e) { } + try { document.body.appendChild(c); } catch(e) { } + document.documentElement.className = ""; +} +</script> +<body onload=go()> +<d id="b">| +<audio controls=""> +</html> diff --git a/layout/painting/crashtests/1469472.html b/layout/painting/crashtests/1469472.html new file mode 100644 index 0000000000..f53d027704 --- /dev/null +++ b/layout/painting/crashtests/1469472.html @@ -0,0 +1,7 @@ +<style> +:not(polygon) { +background-image: url(#x); +background-blend-mode: hue, normal; +</style> +<table> +<th> diff --git a/layout/painting/crashtests/1477831-1.html b/layout/painting/crashtests/1477831-1.html new file mode 100644 index 0000000000..d6483bde02 --- /dev/null +++ b/layout/painting/crashtests/1477831-1.html @@ -0,0 +1,11 @@ +<style> +* { + margin-left: 1vw; + columns: 2; + opacity: 0.2; + -webkit-transform: rotate(0deg); +} +#a { float: left; } +</style> +<content id="a"> +<dd>A</dd> diff --git a/layout/painting/crashtests/1504033.html b/layout/painting/crashtests/1504033.html new file mode 100644 index 0000000000..9aeb7973be --- /dev/null +++ b/layout/painting/crashtests/1504033.html @@ -0,0 +1,13 @@ +<style> +* { -webkit-transform-style: preserve-3d } +</style> +<script> +function go() { + a.append("x"); +} +</script> +<body onload=go()> +<svg overflow="auto"> +<use xlink:href="#b" style="-webkit-transform-style: flat"/> +<use id="b" xlink:href="#a"> +<text id="a"> diff --git a/layout/painting/crashtests/1514544-1.html b/layout/painting/crashtests/1514544-1.html new file mode 100644 index 0000000000..8e9efdc3c4 --- /dev/null +++ b/layout/painting/crashtests/1514544-1.html @@ -0,0 +1,15 @@ +<html class="reftest-wait"> +<script> +setTimeout(function() { + a.appendChild(b); + document.documentElement.className = ""; +}, 100) +</script> +<style> +:root { opacity: 0 } +</style> +A +<textarea id="a" hidden=""></textarea> +<object id="b" > +A +</html> diff --git a/layout/painting/crashtests/1547420-1.html b/layout/painting/crashtests/1547420-1.html new file mode 100644 index 0000000000..ce54d67d09 --- /dev/null +++ b/layout/painting/crashtests/1547420-1.html @@ -0,0 +1,20 @@ +<script></script> +<style> +* { + text-align-last: right; + min-height: max-content; + min-width: 1vmin; + writing-mode: vertical-rl; +} +</style> +<q style="writing-mode: lr"> +<marquee></marquee> +<style></style> +</q> +<dl style="-webkit-transform: skew(0deg); mso-ignore: colspan"> +<dd> +<table> +<dt style="margin-left: 67%; scale: 7 46 0.006057077979"> +</dt> +<marquee bgcolor="-moz-mac-accentdarkestshadow"> +<button autofocus="autofocus"> diff --git a/layout/painting/crashtests/1549909.html b/layout/painting/crashtests/1549909.html new file mode 100644 index 0000000000..0542ee91a8 --- /dev/null +++ b/layout/painting/crashtests/1549909.html @@ -0,0 +1,9 @@ +<style> +* { + -webkit-column-break-after: always; + float: left; + column-width: 0px; +} +</style> +} +<video controls="controls"> diff --git a/layout/painting/crashtests/1551389-1.html b/layout/painting/crashtests/1551389-1.html new file mode 100644 index 0000000000..4ee12c1ce9 --- /dev/null +++ b/layout/painting/crashtests/1551389-1.html @@ -0,0 +1,6 @@ +<style> +dl::first-letter { float: right } +* { -webkit-box-shadow: -moz-cellhighlighttext 0px 34px 1px } +</style> +<s dir="RTL"> +<dl style="break-inside: avoid">AA</iframe> diff --git a/layout/painting/crashtests/1555819-1.html b/layout/painting/crashtests/1555819-1.html new file mode 100644 index 0000000000..0327e3690f --- /dev/null +++ b/layout/painting/crashtests/1555819-1.html @@ -0,0 +1,12 @@ +<style> +body { + width: 1px; + height: 5vmax; + -webkit-filter: brightness(1); +} +* { + grid-template-areas: ''; + columns: 1px; +} +</style> +<keygen autofocus="">aaaa diff --git a/layout/painting/crashtests/1574392.html b/layout/painting/crashtests/1574392.html new file mode 100644 index 0000000000..2e8c4d15aa --- /dev/null +++ b/layout/painting/crashtests/1574392.html @@ -0,0 +1 @@ +<u>🏴</u> diff --git a/layout/painting/crashtests/1589800-1.html b/layout/painting/crashtests/1589800-1.html new file mode 100644 index 0000000000..8af28028a0 --- /dev/null +++ b/layout/painting/crashtests/1589800-1.html @@ -0,0 +1,47 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta http-equiv="content-type" content="text/html; charset=utf-8"> + <style> + #parent { + transform: rotateZ(1deg); + position: absolute; + opacity: 0.5; + overflow-x: hidden; + } + + #child { + width: 10px; + height: 10px; + } + + .blend { + mix-blend-mode: color-burn; + } + </style> +</head> + +<body> + <div id="parent"> + <div id="child">a</div> + </div> + <div> + <div id="blend">a</div> + </div> + <script type="text/javascript"> + function modify() { + var e = document.getElementById("child"); + e.style.backgroundColor = "red"; + setTimeout(addBlend, 0); + } + + function addBlend() { + var e = document.getElementById("blend"); + e.classList.add("blend"); + } + + // setTimeout(modify, 3000); + window.addEventListener("MozAfterPaint", modify); + </script> +</body> +</html> diff --git a/layout/painting/crashtests/1667503-1.html b/layout/painting/crashtests/1667503-1.html new file mode 100644 index 0000000000..70f99b3f37 --- /dev/null +++ b/layout/painting/crashtests/1667503-1.html @@ -0,0 +1,16 @@ +<script> +function go() { + b.src = "x:" + b.requestFullscreen() +} +function fuzz() { + a.close("") + b.src = "x" + a.showModal() + a.appendChild(document.createElement("b")) +} +</script> +<body onload=go()> +<dialog id="a"> +<nav>x</nav> +<video id="b" onerror="fuzz()"></video> diff --git a/layout/painting/crashtests/1713880-1.html b/layout/painting/crashtests/1713880-1.html new file mode 100644 index 0000000000..1ccd59743a --- /dev/null +++ b/layout/painting/crashtests/1713880-1.html @@ -0,0 +1,10 @@ +<script> +window.onload = () => { + let a = document.createElement("dt") + c.appendChild(a) + let b = document.createElement("button") + a.appendChild(b) + b.animate([{"all": "revert"}], {delay: 9.04}) +} +</script> +<body id='c'></body> diff --git a/layout/painting/crashtests/1714584-1.html b/layout/painting/crashtests/1714584-1.html new file mode 100644 index 0000000000..1ef2812a2d --- /dev/null +++ b/layout/painting/crashtests/1714584-1.html @@ -0,0 +1,5 @@ +<style> +output:only-of-type { opacity: 0 } +</style> +<output> +<iframe src="x">x</iframe> diff --git a/layout/painting/crashtests/1717655-1.html b/layout/painting/crashtests/1717655-1.html new file mode 100644 index 0000000000..940dac6dfb --- /dev/null +++ b/layout/painting/crashtests/1717655-1.html @@ -0,0 +1,19 @@ +<!DOCTYPE html> +<html> +<head> + <style> + * { + transform: perspective(2337713741.755817Q) !important; + height: 2637992280%; + } + + * { + border-width: medium; + display: table; + } + </style> +</head> +<table border="32767" rules="all"> + <caption></caption> +</table> +</html> diff --git a/layout/painting/crashtests/1763006-1.html b/layout/painting/crashtests/1763006-1.html new file mode 100644 index 0000000000..080c88e7d8 --- /dev/null +++ b/layout/painting/crashtests/1763006-1.html @@ -0,0 +1,23 @@ +<style> +#a { + -webkit-filter: invert(10%); + background: url(data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=) repeat-x center top fixed +} +</style> +<script> +function go() { + window.find("1") + requestIdleCallback(() => { + a.insertCell().appendChild(b) + var x = window.getSelection() + x.extend(b) + x.deleteFromDocument() + }) +} +</script> +<body onload=go()> +<table> +<colgroup>Z1xNfp~Zdx</colgroup> +<tr id="a"> +<th></th> +<li id="b"> diff --git a/layout/painting/crashtests/1819957-1.html b/layout/painting/crashtests/1819957-1.html new file mode 100644 index 0000000000..647c9b7f12 --- /dev/null +++ b/layout/painting/crashtests/1819957-1.html @@ -0,0 +1,14 @@ +<style> +::selection { + text-shadow: 0 1px 0px -moz-mac-menushadow; +} +</style> +<script> +document.addEventListener('DOMContentLoaded', () => { + document.execCommand("selectAll", false) +}) +</script> +<dir> +<menu id="foo"></menu> +<menu> +<link>a</link> diff --git a/layout/painting/crashtests/crashtests.list b/layout/painting/crashtests/crashtests.list new file mode 100644 index 0000000000..7bc56b335c --- /dev/null +++ b/layout/painting/crashtests/crashtests.list @@ -0,0 +1,32 @@ +load 1402183-1.html +load 1407470-1.html +load 1413073-1.html +load 1413073-2.html +load 1405881-1.html +load 1418177-1.html +load 1418722-1.html +load 1419917.html +load 1425271-1.html +load 1428906-1.html +pref(widget.windows.window_occlusion_tracking.enabled,false) load 1430589-1.html # Bug 1819154 +load 1454105-1.html +load 1455944-1.html +load 1458145.html +load 1465305-1.html +load 1468124-1.html +load 1469472.html +load 1477831-1.html +load 1504033.html +load 1514544-1.html +asserts(0-1) asserts-if(Android,0-2) load 1547420-1.html +load 1549909.html +load 1551389-1.html +asserts(0-2) load 1555819-1.html +load 1574392.html +load 1589800-1.html +load 1667503-1.html +load 1713880-1.html +load 1717655-1.html +load 1714584-1.html +load 1763006-1.html +load 1819957-1.html diff --git a/layout/painting/moz.build b/layout/painting/moz.build new file mode 100644 index 0000000000..b5f1d02a89 --- /dev/null +++ b/layout/painting/moz.build @@ -0,0 +1,72 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +with Files("**"): + BUG_COMPONENT = ("Core", "Web Painting") + +EXPORTS += [ + "ActiveLayerTracker.h", + "DisplayItemClip.h", + "DisplayItemClipChain.h", + "DisplayListClipState.h", + "HitTestInfo.h", + "MatrixStack.h", + "nsCSSRendering.h", + "nsCSSRenderingBorders.h", + "nsCSSRenderingGradients.h", + "nsDisplayItemTypes.h", + "nsDisplayItemTypesList.h", + "nsDisplayList.h", + "nsDisplayListArenaTypes.h", + "nsDisplayListInvalidation.h", + "nsImageRenderer.h", + "RetainedDisplayListBuilder.h", + "RetainedDisplayListHelpers.h", + "TransformClipNode.h", + "WindowRenderer.h", +] + +EXPORTS.mozilla += [ + "PaintTracker.h", +] + +UNIFIED_SOURCES += [ + "ActiveLayerTracker.cpp", + "DashedCornerFinder.cpp", + "DisplayItemClip.cpp", + "DisplayItemClipChain.cpp", + "DisplayListClipState.cpp", + "DottedCornerFinder.cpp", + "HitTestInfo.cpp", + "MaskLayerImageCache.cpp", + "nsCSSRendering.cpp", + "nsCSSRenderingBorders.cpp", + "nsCSSRenderingGradients.cpp", + "nsDisplayList.cpp", + "nsDisplayListInvalidation.cpp", + "nsImageRenderer.cpp", + "PaintTracker.cpp", + "RetainedDisplayListBuilder.cpp", + "WindowRenderer.cpp", +] + +include("/ipc/chromium/chromium-config.mozbuild") + +LOCAL_INCLUDES += [ + "/docshell/base", + "/dom/base", + "/gfx/2d", + "/gfx/cairo/cairo/src", + "/layout/base", + "/layout/generic", + "/layout/style", + "/layout/tables", + "/layout/xul", +] + +LOCAL_INCLUDES += CONFIG["SKIA_INCLUDES"] + +FINAL_LIBRARY = "xul" diff --git a/layout/painting/nsCSSRendering.cpp b/layout/painting/nsCSSRendering.cpp new file mode 100644 index 0000000000..1a1bc3d01f --- /dev/null +++ b/layout/painting/nsCSSRendering.cpp @@ -0,0 +1,4944 @@ +/* -*- 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/. */ + +/* utility functions for drawing borders and backgrounds */ + +#include "nsCSSRendering.h" + +#include <ctime> + +#include "gfx2DGlue.h" +#include "gfxContext.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/ComputedStyle.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/StaticPrefs_layout.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/Helpers.h" +#include "mozilla/gfx/Logging.h" +#include "mozilla/gfx/PathHelpers.h" +#include "mozilla/HashFunctions.h" +#include "mozilla/MathAlgorithms.h" +#include "mozilla/PresShell.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/SVGImageContext.h" +#include "gfxFont.h" +#include "ScaledFontBase.h" +#include "skia/include/core/SkTextBlob.h" + +#include "BorderConsts.h" +#include "nsCanvasFrame.h" +#include "nsStyleConsts.h" +#include "nsPresContext.h" +#include "nsIFrame.h" +#include "nsIFrameInlines.h" +#include "nsPageSequenceFrame.h" +#include "nsPoint.h" +#include "nsRect.h" +#include "nsFrameManager.h" +#include "nsGkAtoms.h" +#include "nsCSSAnonBoxes.h" +#include "nsIContent.h" +#include "mozilla/dom/DocumentInlines.h" +#include "nsIScrollableFrame.h" +#include "imgIContainer.h" +#include "ImageOps.h" +#include "nsCSSColorUtils.h" +#include "nsITheme.h" +#include "nsLayoutUtils.h" +#include "nsBlockFrame.h" +#include "nsStyleStructInlines.h" +#include "nsCSSFrameConstructor.h" +#include "nsCSSProps.h" +#include "nsContentUtils.h" +#include "gfxDrawable.h" +#include "nsCSSRenderingBorders.h" +#include "mozilla/css/ImageLoader.h" +#include "ImageContainer.h" +#include "mozilla/ProfilerLabels.h" +#include "mozilla/StaticPrefs_layout.h" +#include "mozilla/Telemetry.h" +#include "gfxUtils.h" +#include "gfxGradientCache.h" +#include "nsInlineFrame.h" +#include "nsRubyTextContainerFrame.h" +#include <algorithm> +#include "TextDrawTarget.h" + +using namespace mozilla; +using namespace mozilla::css; +using namespace mozilla::gfx; +using namespace mozilla::image; +using mozilla::CSSSizeOrRatio; +using mozilla::dom::Document; + +static int gFrameTreeLockCount = 0; + +// To avoid storing this data on nsInlineFrame (bloat) and to avoid +// recalculating this for each frame in a continuation (perf), hold +// a cache of various coordinate information that we need in order +// to paint inline backgrounds. +struct InlineBackgroundData { + InlineBackgroundData() + : mFrame(nullptr), + mLineContainer(nullptr), + mContinuationPoint(0), + mUnbrokenMeasure(0), + mLineContinuationPoint(0), + mPIStartBorderData{}, + mBidiEnabled(false), + mVertical(false) {} + + ~InlineBackgroundData() = default; + + void Reset() { + mBoundingBox.SetRect(0, 0, 0, 0); + mContinuationPoint = mLineContinuationPoint = mUnbrokenMeasure = 0; + mFrame = mLineContainer = nullptr; + mPIStartBorderData.Reset(); + } + + /** + * Return a continuous rect for (an inline) aFrame relative to the + * continuation that draws the left-most part of the background. + * This is used when painting backgrounds. + */ + nsRect GetContinuousRect(nsIFrame* aFrame) { + MOZ_ASSERT(static_cast<nsInlineFrame*>(do_QueryFrame(aFrame))); + + SetFrame(aFrame); + + nscoord pos; // an x coordinate if writing-mode is horizontal; + // y coordinate if vertical + if (mBidiEnabled) { + pos = mLineContinuationPoint; + + // Scan continuations on the same line as aFrame and accumulate the widths + // of frames that are to the left (if this is an LTR block) or right + // (if it's RTL) of the current one. + bool isRtlBlock = (mLineContainer->StyleVisibility()->mDirection == + StyleDirection::Rtl); + nscoord curOffset = mVertical ? aFrame->GetOffsetTo(mLineContainer).y + : aFrame->GetOffsetTo(mLineContainer).x; + + // If the continuation is fluid we know inlineFrame is not on the same + // line. If it's not fluid, we need to test further to be sure. + nsIFrame* inlineFrame = aFrame->GetPrevContinuation(); + while (inlineFrame && !inlineFrame->GetNextInFlow() && + AreOnSameLine(aFrame, inlineFrame)) { + nscoord frameOffset = mVertical + ? inlineFrame->GetOffsetTo(mLineContainer).y + : inlineFrame->GetOffsetTo(mLineContainer).x; + if (isRtlBlock == (frameOffset >= curOffset)) { + pos += mVertical ? inlineFrame->GetSize().height + : inlineFrame->GetSize().width; + } + inlineFrame = inlineFrame->GetPrevContinuation(); + } + + inlineFrame = aFrame->GetNextContinuation(); + while (inlineFrame && !inlineFrame->GetPrevInFlow() && + AreOnSameLine(aFrame, inlineFrame)) { + nscoord frameOffset = mVertical + ? inlineFrame->GetOffsetTo(mLineContainer).y + : inlineFrame->GetOffsetTo(mLineContainer).x; + if (isRtlBlock == (frameOffset >= curOffset)) { + pos += mVertical ? inlineFrame->GetSize().height + : inlineFrame->GetSize().width; + } + inlineFrame = inlineFrame->GetNextContinuation(); + } + if (isRtlBlock) { + // aFrame itself is also to the right of its left edge, so add its + // width. + pos += mVertical ? aFrame->GetSize().height : aFrame->GetSize().width; + // pos is now the distance from the left [top] edge of aFrame to the + // right [bottom] edge of the unbroken content. Change it to indicate + // the distance from the left [top] edge of the unbroken content to the + // left [top] edge of aFrame. + pos = mUnbrokenMeasure - pos; + } + } else { + pos = mContinuationPoint; + } + + // Assume background-origin: border and return a rect with offsets + // relative to (0,0). If we have a different background-origin, + // then our rect should be deflated appropriately by our caller. + return mVertical + ? nsRect(0, -pos, mFrame->GetSize().width, mUnbrokenMeasure) + : nsRect(-pos, 0, mUnbrokenMeasure, mFrame->GetSize().height); + } + + /** + * Return a continuous rect for (an inline) aFrame relative to the + * continuation that should draw the left[top]-border. This is used when + * painting borders and clipping backgrounds. This may NOT be the same + * continuous rect as for drawing backgrounds; the continuation with the + * left[top]-border might be somewhere in the middle of that rect (e.g. BIDI), + * in those cases we need the reverse background order starting at the + * left[top]-border continuation. + */ + nsRect GetBorderContinuousRect(nsIFrame* aFrame, nsRect aBorderArea) { + // Calling GetContinuousRect(aFrame) here may lead to Reset/Init which + // resets our mPIStartBorderData so we save it ... + PhysicalInlineStartBorderData saved(mPIStartBorderData); + nsRect joinedBorderArea = GetContinuousRect(aFrame); + if (!saved.mIsValid || saved.mFrame != mPIStartBorderData.mFrame) { + if (aFrame == mPIStartBorderData.mFrame) { + if (mVertical) { + mPIStartBorderData.SetCoord(joinedBorderArea.y); + } else { + mPIStartBorderData.SetCoord(joinedBorderArea.x); + } + } else if (mPIStartBorderData.mFrame) { + // Copy data to a temporary object so that computing the + // continous rect here doesn't clobber our normal state. + InlineBackgroundData temp = *this; + if (mVertical) { + mPIStartBorderData.SetCoord( + temp.GetContinuousRect(mPIStartBorderData.mFrame).y); + } else { + mPIStartBorderData.SetCoord( + temp.GetContinuousRect(mPIStartBorderData.mFrame).x); + } + } + } else { + // ... and restore it when possible. + mPIStartBorderData.SetCoord(saved.mCoord); + } + if (mVertical) { + if (joinedBorderArea.y > mPIStartBorderData.mCoord) { + joinedBorderArea.y = + -(mUnbrokenMeasure + joinedBorderArea.y - aBorderArea.height); + } else { + joinedBorderArea.y -= mPIStartBorderData.mCoord; + } + } else { + if (joinedBorderArea.x > mPIStartBorderData.mCoord) { + joinedBorderArea.x = + -(mUnbrokenMeasure + joinedBorderArea.x - aBorderArea.width); + } else { + joinedBorderArea.x -= mPIStartBorderData.mCoord; + } + } + return joinedBorderArea; + } + + nsRect GetBoundingRect(nsIFrame* aFrame) { + SetFrame(aFrame); + + // Move the offsets relative to (0,0) which puts the bounding box into + // our coordinate system rather than our parent's. We do this by + // moving it the back distance from us to the bounding box. + // This also assumes background-origin: border, so our caller will + // need to deflate us if needed. + nsRect boundingBox(mBoundingBox); + nsPoint point = mFrame->GetPosition(); + boundingBox.MoveBy(-point.x, -point.y); + + return boundingBox; + } + + protected: + // This is a coordinate on the inline axis, but is not a true logical inline- + // coord because it is always measured from left to right (if horizontal) or + // from top to bottom (if vertical), ignoring any bidi RTL directionality. + // We'll call this "physical inline start", or PIStart for short. + struct PhysicalInlineStartBorderData { + nsIFrame* mFrame; // the continuation that may have a left-border + nscoord mCoord; // cached GetContinuousRect(mFrame).x or .y + bool mIsValid; // true if mCoord is valid + void Reset() { + mFrame = nullptr; + mIsValid = false; + } + void SetCoord(nscoord aCoord) { + mCoord = aCoord; + mIsValid = true; + } + }; + + nsIFrame* mFrame; + nsIFrame* mLineContainer; + nsRect mBoundingBox; + nscoord mContinuationPoint; + nscoord mUnbrokenMeasure; + nscoord mLineContinuationPoint; + PhysicalInlineStartBorderData mPIStartBorderData; + bool mBidiEnabled; + bool mVertical; + + void SetFrame(nsIFrame* aFrame) { + MOZ_ASSERT(aFrame, "Need a frame"); + NS_ASSERTION(gFrameTreeLockCount > 0, + "Can't call this when frame tree is not locked"); + + if (aFrame == mFrame) { + return; + } + + nsIFrame* prevContinuation = GetPrevContinuation(aFrame); + + if (!prevContinuation || mFrame != prevContinuation) { + // Ok, we've got the wrong frame. We have to start from scratch. + Reset(); + Init(aFrame); + return; + } + + // Get our last frame's size and add its width to our continuation + // point before we cache the new frame. + mContinuationPoint += + mVertical ? mFrame->GetSize().height : mFrame->GetSize().width; + + // If this a new line, update mLineContinuationPoint. + if (mBidiEnabled && + (aFrame->GetPrevInFlow() || !AreOnSameLine(mFrame, aFrame))) { + mLineContinuationPoint = mContinuationPoint; + } + + mFrame = aFrame; + } + + nsIFrame* GetPrevContinuation(nsIFrame* aFrame) { + nsIFrame* prevCont = aFrame->GetPrevContinuation(); + if (!prevCont && aFrame->HasAnyStateBits(NS_FRAME_PART_OF_IBSPLIT)) { + nsIFrame* block = aFrame->GetProperty(nsIFrame::IBSplitPrevSibling()); + if (block) { + // The {ib} properties are only stored on first continuations + NS_ASSERTION(!block->GetPrevContinuation(), + "Incorrect value for IBSplitPrevSibling"); + prevCont = block->GetProperty(nsIFrame::IBSplitPrevSibling()); + NS_ASSERTION(prevCont, "How did that happen?"); + } + } + return prevCont; + } + + nsIFrame* GetNextContinuation(nsIFrame* aFrame) { + nsIFrame* nextCont = aFrame->GetNextContinuation(); + if (!nextCont && aFrame->HasAnyStateBits(NS_FRAME_PART_OF_IBSPLIT)) { + // The {ib} properties are only stored on first continuations + aFrame = aFrame->FirstContinuation(); + nsIFrame* block = aFrame->GetProperty(nsIFrame::IBSplitSibling()); + if (block) { + nextCont = block->GetProperty(nsIFrame::IBSplitSibling()); + NS_ASSERTION(nextCont, "How did that happen?"); + } + } + return nextCont; + } + + void Init(nsIFrame* aFrame) { + mPIStartBorderData.Reset(); + mBidiEnabled = aFrame->PresContext()->BidiEnabled(); + if (mBidiEnabled) { + // Find the line container frame + mLineContainer = aFrame; + while (mLineContainer && + mLineContainer->IsFrameOfType(nsIFrame::eLineParticipant)) { + mLineContainer = mLineContainer->GetParent(); + } + + MOZ_ASSERT(mLineContainer, "Cannot find line containing frame."); + MOZ_ASSERT(mLineContainer != aFrame, + "line container frame " + "should be an ancestor of the target frame."); + } + + mVertical = aFrame->GetWritingMode().IsVertical(); + + // Start with the previous flow frame as our continuation point + // is the total of the widths of the previous frames. + nsIFrame* inlineFrame = GetPrevContinuation(aFrame); + bool changedLines = false; + while (inlineFrame) { + if (!mPIStartBorderData.mFrame && + !(mVertical ? inlineFrame->GetSkipSides().Top() + : inlineFrame->GetSkipSides().Left())) { + mPIStartBorderData.mFrame = inlineFrame; + } + nsRect rect = inlineFrame->GetRect(); + mContinuationPoint += mVertical ? rect.height : rect.width; + if (mBidiEnabled && + (changedLines || !AreOnSameLine(aFrame, inlineFrame))) { + mLineContinuationPoint += mVertical ? rect.height : rect.width; + changedLines = true; + } + mUnbrokenMeasure += mVertical ? rect.height : rect.width; + mBoundingBox.UnionRect(mBoundingBox, rect); + inlineFrame = GetPrevContinuation(inlineFrame); + } + + // Next add this frame and subsequent frames to the bounding box and + // unbroken width. + inlineFrame = aFrame; + while (inlineFrame) { + if (!mPIStartBorderData.mFrame && + !(mVertical ? inlineFrame->GetSkipSides().Top() + : inlineFrame->GetSkipSides().Left())) { + mPIStartBorderData.mFrame = inlineFrame; + } + nsRect rect = inlineFrame->GetRect(); + mUnbrokenMeasure += mVertical ? rect.height : rect.width; + mBoundingBox.UnionRect(mBoundingBox, rect); + inlineFrame = GetNextContinuation(inlineFrame); + } + + mFrame = aFrame; + } + + bool AreOnSameLine(nsIFrame* aFrame1, nsIFrame* aFrame2) { + if (nsBlockFrame* blockFrame = do_QueryFrame(mLineContainer)) { + bool isValid1, isValid2; + nsBlockInFlowLineIterator it1(blockFrame, aFrame1, &isValid1); + nsBlockInFlowLineIterator it2(blockFrame, aFrame2, &isValid2); + return isValid1 && isValid2 && + // Make sure aFrame1 and aFrame2 are in the same continuation of + // blockFrame. + it1.GetContainer() == it2.GetContainer() && + // And on the same line in it + it1.GetLine().get() == it2.GetLine().get(); + } + if (nsRubyTextContainerFrame* rtcFrame = do_QueryFrame(mLineContainer)) { + nsBlockFrame* block = nsLayoutUtils::FindNearestBlockAncestor(rtcFrame); + // Ruby text container can only hold one line of text, so if they + // are in the same continuation, they are in the same line. Since + // ruby text containers are bidi isolate, they are never split for + // bidi reordering, which means being in different continuation + // indicates being in different lines. + for (nsIFrame* frame = rtcFrame->FirstContinuation(); frame; + frame = frame->GetNextContinuation()) { + bool isDescendant1 = + nsLayoutUtils::IsProperAncestorFrame(frame, aFrame1, block); + bool isDescendant2 = + nsLayoutUtils::IsProperAncestorFrame(frame, aFrame2, block); + if (isDescendant1 && isDescendant2) { + return true; + } + if (isDescendant1 || isDescendant2) { + return false; + } + } + MOZ_ASSERT_UNREACHABLE("None of the frames is a descendant of this rtc?"); + } + MOZ_ASSERT_UNREACHABLE("Do we have any other type of line container?"); + return false; + } +}; + +static StaticAutoPtr<InlineBackgroundData> gInlineBGData; + +// Initialize any static variables used by nsCSSRendering. +void nsCSSRendering::Init() { + NS_ASSERTION(!gInlineBGData, "Init called twice"); + gInlineBGData = new InlineBackgroundData(); +} + +// Clean up any global variables used by nsCSSRendering. +void nsCSSRendering::Shutdown() { gInlineBGData = nullptr; } + +/** + * Make a bevel color + */ +static nscolor MakeBevelColor(mozilla::Side whichSide, StyleBorderStyle style, + nscolor aBorderColor) { + nscolor colors[2]; + nscolor theColor; + + // Given a background color and a border color + // calculate the color used for the shading + NS_GetSpecial3DColors(colors, aBorderColor); + + if ((style == StyleBorderStyle::Outset) || + (style == StyleBorderStyle::Ridge)) { + // Flip colors for these two border styles + switch (whichSide) { + case eSideBottom: + whichSide = eSideTop; + break; + case eSideRight: + whichSide = eSideLeft; + break; + case eSideTop: + whichSide = eSideBottom; + break; + case eSideLeft: + whichSide = eSideRight; + break; + } + } + + switch (whichSide) { + case eSideBottom: + theColor = colors[1]; + break; + case eSideRight: + theColor = colors[1]; + break; + case eSideTop: + theColor = colors[0]; + break; + case eSideLeft: + default: + theColor = colors[0]; + break; + } + return theColor; +} + +static bool GetRadii(nsIFrame* aForFrame, const nsStyleBorder& aBorder, + const nsRect& aOrigBorderArea, const nsRect& aBorderArea, + nscoord aRadii[8]) { + bool haveRoundedCorners; + nsSize sz = aBorderArea.Size(); + nsSize frameSize = aForFrame->GetSize(); + if (&aBorder == aForFrame->StyleBorder() && + frameSize == aOrigBorderArea.Size()) { + haveRoundedCorners = aForFrame->GetBorderRadii(sz, sz, Sides(), aRadii); + } else { + haveRoundedCorners = nsIFrame::ComputeBorderRadii( + aBorder.mBorderRadius, frameSize, sz, Sides(), aRadii); + } + + return haveRoundedCorners; +} + +static bool GetRadii(nsIFrame* aForFrame, const nsStyleBorder& aBorder, + const nsRect& aOrigBorderArea, const nsRect& aBorderArea, + RectCornerRadii* aBgRadii) { + nscoord radii[8]; + bool haveRoundedCorners = + GetRadii(aForFrame, aBorder, aOrigBorderArea, aBorderArea, radii); + + if (haveRoundedCorners) { + auto d2a = aForFrame->PresContext()->AppUnitsPerDevPixel(); + nsCSSRendering::ComputePixelRadii(radii, d2a, aBgRadii); + } + return haveRoundedCorners; +} + +static nsRect JoinBoxesForBlockAxisSlice(nsIFrame* aFrame, + const nsRect& aBorderArea) { + // Inflate the block-axis size as if our continuations were laid out + // adjacent in that axis. Note that we don't touch the inline size. + const auto wm = aFrame->GetWritingMode(); + const nsSize dummyContainerSize; + LogicalRect borderArea(wm, aBorderArea, dummyContainerSize); + nscoord bSize = 0; + nsIFrame* f = aFrame->GetNextContinuation(); + for (; f; f = f->GetNextContinuation()) { + bSize += f->BSize(wm); + } + borderArea.BSize(wm) += bSize; + bSize = 0; + f = aFrame->GetPrevContinuation(); + for (; f; f = f->GetPrevContinuation()) { + bSize += f->BSize(wm); + } + borderArea.BStart(wm) -= bSize; + borderArea.BSize(wm) += bSize; + return borderArea.GetPhysicalRect(wm, dummyContainerSize); +} + +/** + * Inflate aBorderArea which is relative to aFrame's origin to calculate + * a hypothetical non-split frame area for all the continuations. + * See "Joining Boxes for 'slice'" in + * http://dev.w3.org/csswg/css-break/#break-decoration + */ +enum InlineBoxOrder { eForBorder, eForBackground }; +static nsRect JoinBoxesForSlice(nsIFrame* aFrame, const nsRect& aBorderArea, + InlineBoxOrder aOrder) { + if (static_cast<nsInlineFrame*>(do_QueryFrame(aFrame))) { + return (aOrder == eForBorder + ? gInlineBGData->GetBorderContinuousRect(aFrame, aBorderArea) + : gInlineBGData->GetContinuousRect(aFrame)) + + aBorderArea.TopLeft(); + } + return JoinBoxesForBlockAxisSlice(aFrame, aBorderArea); +} + +/* static */ +bool nsCSSRendering::IsBoxDecorationSlice(const nsStyleBorder& aStyleBorder) { + return aStyleBorder.mBoxDecorationBreak == StyleBoxDecorationBreak::Slice; +} + +/* static */ +nsRect nsCSSRendering::BoxDecorationRectForBorder( + nsIFrame* aFrame, const nsRect& aBorderArea, Sides aSkipSides, + const nsStyleBorder* aStyleBorder) { + if (!aStyleBorder) { + aStyleBorder = aFrame->StyleBorder(); + } + // If aSkipSides.IsEmpty() then there are no continuations, or it's + // a ::first-letter that wants all border sides on the first continuation. + return IsBoxDecorationSlice(*aStyleBorder) && !aSkipSides.IsEmpty() + ? ::JoinBoxesForSlice(aFrame, aBorderArea, eForBorder) + : aBorderArea; +} + +/* static */ +nsRect nsCSSRendering::BoxDecorationRectForBackground( + nsIFrame* aFrame, const nsRect& aBorderArea, Sides aSkipSides, + const nsStyleBorder* aStyleBorder) { + if (!aStyleBorder) { + aStyleBorder = aFrame->StyleBorder(); + } + // If aSkipSides.IsEmpty() then there are no continuations, or it's + // a ::first-letter that wants all border sides on the first continuation. + return IsBoxDecorationSlice(*aStyleBorder) && !aSkipSides.IsEmpty() + ? ::JoinBoxesForSlice(aFrame, aBorderArea, eForBackground) + : aBorderArea; +} + +//---------------------------------------------------------------------- +// Thebes Border Rendering Code Start + +/* + * Compute the float-pixel radii that should be used for drawing + * this border/outline, given the various input bits. + */ +/* static */ +void nsCSSRendering::ComputePixelRadii(const nscoord* aAppUnitsRadii, + nscoord aAppUnitsPerPixel, + RectCornerRadii* oBorderRadii) { + Float radii[8]; + for (const auto corner : mozilla::AllPhysicalHalfCorners()) { + radii[corner] = Float(aAppUnitsRadii[corner]) / aAppUnitsPerPixel; + } + + (*oBorderRadii)[C_TL] = Size(radii[eCornerTopLeftX], radii[eCornerTopLeftY]); + (*oBorderRadii)[C_TR] = + Size(radii[eCornerTopRightX], radii[eCornerTopRightY]); + (*oBorderRadii)[C_BR] = + Size(radii[eCornerBottomRightX], radii[eCornerBottomRightY]); + (*oBorderRadii)[C_BL] = + Size(radii[eCornerBottomLeftX], radii[eCornerBottomLeftY]); +} + +static Maybe<nsStyleBorder> GetBorderIfVisited(const ComputedStyle& aStyle) { + Maybe<nsStyleBorder> result; + // Don't check RelevantLinkVisited here, since we want to take the + // same amount of time whether or not it's true. + const ComputedStyle* styleIfVisited = aStyle.GetStyleIfVisited(); + if (MOZ_LIKELY(!styleIfVisited)) { + return result; + } + + result.emplace(*aStyle.StyleBorder()); + auto& newBorder = result.ref(); + for (const auto side : mozilla::AllPhysicalSides()) { + nscolor color = aStyle.GetVisitedDependentColor( + nsStyleBorder::BorderColorFieldFor(side)); + newBorder.BorderColorFor(side) = StyleColor::FromColor(color); + } + + return result; +} + +ImgDrawResult nsCSSRendering::PaintBorder( + nsPresContext* aPresContext, gfxContext& aRenderingContext, + nsIFrame* aForFrame, const nsRect& aDirtyRect, const nsRect& aBorderArea, + ComputedStyle* aStyle, PaintBorderFlags aFlags, Sides aSkipSides) { + AUTO_PROFILER_LABEL("nsCSSRendering::PaintBorder", GRAPHICS); + + Maybe<nsStyleBorder> visitedBorder = GetBorderIfVisited(*aStyle); + return PaintBorderWithStyleBorder( + aPresContext, aRenderingContext, aForFrame, aDirtyRect, aBorderArea, + visitedBorder.refOr(*aStyle->StyleBorder()), aStyle, aFlags, aSkipSides); +} + +Maybe<nsCSSBorderRenderer> nsCSSRendering::CreateBorderRenderer( + nsPresContext* aPresContext, DrawTarget* aDrawTarget, nsIFrame* aForFrame, + const nsRect& aDirtyRect, const nsRect& aBorderArea, ComputedStyle* aStyle, + bool* aOutBorderIsEmpty, Sides aSkipSides) { + Maybe<nsStyleBorder> visitedBorder = GetBorderIfVisited(*aStyle); + return CreateBorderRendererWithStyleBorder( + aPresContext, aDrawTarget, aForFrame, aDirtyRect, aBorderArea, + visitedBorder.refOr(*aStyle->StyleBorder()), aStyle, aOutBorderIsEmpty, + aSkipSides); +} + +ImgDrawResult nsCSSRendering::CreateWebRenderCommandsForBorder( + nsDisplayItem* aItem, nsIFrame* aForFrame, const nsRect& aBorderArea, + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const mozilla::layers::StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + const auto* style = aForFrame->Style(); + Maybe<nsStyleBorder> visitedBorder = GetBorderIfVisited(*style); + return nsCSSRendering::CreateWebRenderCommandsForBorderWithStyleBorder( + aItem, aForFrame, aBorderArea, aBuilder, aResources, aSc, aManager, + aDisplayListBuilder, visitedBorder.refOr(*style->StyleBorder())); +} + +void nsCSSRendering::CreateWebRenderCommandsForNullBorder( + nsDisplayItem* aItem, nsIFrame* aForFrame, const nsRect& aBorderArea, + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const mozilla::layers::StackingContextHelper& aSc, + const nsStyleBorder& aStyleBorder) { + bool borderIsEmpty = false; + Maybe<nsCSSBorderRenderer> br = + nsCSSRendering::CreateNullBorderRendererWithStyleBorder( + aForFrame->PresContext(), nullptr, aForFrame, nsRect(), aBorderArea, + aStyleBorder, aForFrame->Style(), &borderIsEmpty, + aForFrame->GetSkipSides()); + if (!borderIsEmpty && br) { + br->CreateWebRenderCommands(aItem, aBuilder, aResources, aSc); + } +} + +ImgDrawResult nsCSSRendering::CreateWebRenderCommandsForBorderWithStyleBorder( + nsDisplayItem* aItem, nsIFrame* aForFrame, const nsRect& aBorderArea, + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const mozilla::layers::StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder, + const nsStyleBorder& aStyleBorder) { + auto& borderImage = aStyleBorder.mBorderImageSource; + // First try to create commands for simple borders. + if (borderImage.IsNone()) { + CreateWebRenderCommandsForNullBorder( + aItem, aForFrame, aBorderArea, aBuilder, aResources, aSc, aStyleBorder); + return ImgDrawResult::SUCCESS; + } + + // Next we try image and gradient borders. Gradients are not supported at + // this very moment. + if (!borderImage.IsImageRequestType()) { + return ImgDrawResult::NOT_SUPPORTED; + } + + if (aStyleBorder.mBorderImageRepeatH == StyleBorderImageRepeat::Space || + aStyleBorder.mBorderImageRepeatV == StyleBorderImageRepeat::Space) { + return ImgDrawResult::NOT_SUPPORTED; + } + + uint32_t flags = 0; + if (aDisplayListBuilder->IsPaintingToWindow()) { + flags |= nsImageRenderer::FLAG_PAINTING_TO_WINDOW; + } + if (aDisplayListBuilder->ShouldSyncDecodeImages()) { + flags |= nsImageRenderer::FLAG_SYNC_DECODE_IMAGES; + } + + bool dummy; + image::ImgDrawResult result; + Maybe<nsCSSBorderImageRenderer> bir = + nsCSSBorderImageRenderer::CreateBorderImageRenderer( + aForFrame->PresContext(), aForFrame, aBorderArea, aStyleBorder, + aItem->GetBounds(aDisplayListBuilder, &dummy), + aForFrame->GetSkipSides(), flags, &result); + + if (!bir) { + // We aren't ready. Try to fallback to the null border image if present but + // return the draw result for the border image renderer. + CreateWebRenderCommandsForNullBorder( + aItem, aForFrame, aBorderArea, aBuilder, aResources, aSc, aStyleBorder); + return result; + } + + return bir->CreateWebRenderCommands(aItem, aForFrame, aBuilder, aResources, + aSc, aManager, aDisplayListBuilder); +} + +static nsCSSBorderRenderer ConstructBorderRenderer( + nsPresContext* aPresContext, ComputedStyle* aStyle, DrawTarget* aDrawTarget, + nsIFrame* aForFrame, const nsRect& aDirtyRect, const nsRect& aBorderArea, + const nsStyleBorder& aStyleBorder, Sides aSkipSides, bool* aNeedsClip) { + nsMargin border = aStyleBorder.GetComputedBorder(); + + // Compute the outermost boundary of the area that might be painted. + // Same coordinate space as aBorderArea & aBGClipRect. + nsRect joinedBorderArea = nsCSSRendering::BoxDecorationRectForBorder( + aForFrame, aBorderArea, aSkipSides, &aStyleBorder); + RectCornerRadii bgRadii; + ::GetRadii(aForFrame, aStyleBorder, aBorderArea, joinedBorderArea, &bgRadii); + + PrintAsFormatString(" joinedBorderArea: %d %d %d %d\n", joinedBorderArea.x, + joinedBorderArea.y, joinedBorderArea.width, + joinedBorderArea.height); + + // start drawing + if (nsCSSRendering::IsBoxDecorationSlice(aStyleBorder)) { + if (joinedBorderArea.IsEqualEdges(aBorderArea)) { + // No need for a clip, just skip the sides we don't want. + border.ApplySkipSides(aSkipSides); + } else { + // We're drawing borders around the joined continuation boxes so we need + // to clip that to the slice that we want for this frame. + *aNeedsClip = true; + } + } else { + MOZ_ASSERT(joinedBorderArea.IsEqualEdges(aBorderArea), + "Should use aBorderArea for box-decoration-break:clone"); + MOZ_ASSERT( + aForFrame->GetSkipSides().IsEmpty() || + aForFrame->IsTrueOverflowContainer() || + aForFrame->IsColumnSetFrame(), // a little broader than column-rule + "Should not skip sides for box-decoration-break:clone except " + "::first-letter/line continuations or other frame types that " + "don't have borders but those shouldn't reach this point. " + "Overflow containers do reach this point though, as does " + "column-rule drawing (which always involves a columnset)."); + border.ApplySkipSides(aSkipSides); + } + + // Convert to dev pixels. + nscoord oneDevPixel = aPresContext->DevPixelsToAppUnits(1); + Rect joinedBorderAreaPx = NSRectToRect(joinedBorderArea, oneDevPixel); + Float borderWidths[4] = { + Float(border.top) / oneDevPixel, Float(border.right) / oneDevPixel, + Float(border.bottom) / oneDevPixel, Float(border.left) / oneDevPixel}; + Rect dirtyRect = NSRectToRect(aDirtyRect, oneDevPixel); + + StyleBorderStyle borderStyles[4]; + nscolor borderColors[4]; + + // pull out styles, colors + for (const auto i : mozilla::AllPhysicalSides()) { + borderStyles[i] = aStyleBorder.GetBorderStyle(i); + borderColors[i] = aStyleBorder.BorderColorFor(i).CalcColor(*aStyle); + } + + PrintAsFormatString( + " borderStyles: %d %d %d %d\n", static_cast<int>(borderStyles[0]), + static_cast<int>(borderStyles[1]), static_cast<int>(borderStyles[2]), + static_cast<int>(borderStyles[3])); + + return nsCSSBorderRenderer( + aPresContext, aDrawTarget, dirtyRect, joinedBorderAreaPx, borderStyles, + borderWidths, bgRadii, borderColors, !aForFrame->BackfaceIsHidden(), + *aNeedsClip ? Some(NSRectToRect(aBorderArea, oneDevPixel)) : Nothing()); +} + +ImgDrawResult nsCSSRendering::PaintBorderWithStyleBorder( + nsPresContext* aPresContext, gfxContext& aRenderingContext, + nsIFrame* aForFrame, const nsRect& aDirtyRect, const nsRect& aBorderArea, + const nsStyleBorder& aStyleBorder, ComputedStyle* aStyle, + PaintBorderFlags aFlags, Sides aSkipSides) { + DrawTarget& aDrawTarget = *aRenderingContext.GetDrawTarget(); + + PrintAsStringNewline("++ PaintBorder"); + + // Check to see if we have an appearance defined. If so, we let the theme + // renderer draw the border. DO not get the data from aForFrame, since the + // passed in ComputedStyle may be different! Always use |aStyle|! + StyleAppearance appearance = aStyle->StyleDisplay()->EffectiveAppearance(); + if (appearance != StyleAppearance::None) { + nsITheme* theme = aPresContext->Theme(); + if (theme->ThemeSupportsWidget(aPresContext, aForFrame, appearance)) { + return ImgDrawResult::SUCCESS; // Let the theme handle it. + } + } + + if (!aStyleBorder.mBorderImageSource.IsNone()) { + ImgDrawResult result = ImgDrawResult::SUCCESS; + + uint32_t irFlags = 0; + if (aFlags & PaintBorderFlags::SyncDecodeImages) { + irFlags |= nsImageRenderer::FLAG_SYNC_DECODE_IMAGES; + } + + // Creating the border image renderer will request a decode, and we rely on + // that happening. + Maybe<nsCSSBorderImageRenderer> renderer = + nsCSSBorderImageRenderer::CreateBorderImageRenderer( + aPresContext, aForFrame, aBorderArea, aStyleBorder, aDirtyRect, + aSkipSides, irFlags, &result); + // renderer was created successfully, which means border image is ready to + // be used. + if (renderer) { + MOZ_ASSERT(result == ImgDrawResult::SUCCESS); + return renderer->DrawBorderImage(aPresContext, aRenderingContext, + aForFrame, aDirtyRect); + } + } + + ImgDrawResult result = ImgDrawResult::SUCCESS; + + // If we had a border-image, but it wasn't loaded, then we should return + // ImgDrawResult::NOT_READY; we'll want to try again if we do a paint with + // sync decoding enabled. + if (!aStyleBorder.mBorderImageSource.IsNone()) { + result = ImgDrawResult::NOT_READY; + } + + nsMargin border = aStyleBorder.GetComputedBorder(); + if (0 == border.left && 0 == border.right && 0 == border.top && + 0 == border.bottom) { + // Empty border area + return result; + } + + bool needsClip = false; + nsCSSBorderRenderer br = ConstructBorderRenderer( + aPresContext, aStyle, &aDrawTarget, aForFrame, aDirtyRect, aBorderArea, + aStyleBorder, aSkipSides, &needsClip); + if (needsClip) { + aDrawTarget.PushClipRect(NSRectToSnappedRect( + aBorderArea, aForFrame->PresContext()->AppUnitsPerDevPixel(), + aDrawTarget)); + } + + br.DrawBorders(); + + if (needsClip) { + aDrawTarget.PopClip(); + } + + PrintAsStringNewline(); + + return result; +} + +Maybe<nsCSSBorderRenderer> nsCSSRendering::CreateBorderRendererWithStyleBorder( + nsPresContext* aPresContext, DrawTarget* aDrawTarget, nsIFrame* aForFrame, + const nsRect& aDirtyRect, const nsRect& aBorderArea, + const nsStyleBorder& aStyleBorder, ComputedStyle* aStyle, + bool* aOutBorderIsEmpty, Sides aSkipSides) { + if (!aStyleBorder.mBorderImageSource.IsNone()) { + return Nothing(); + } + return CreateNullBorderRendererWithStyleBorder( + aPresContext, aDrawTarget, aForFrame, aDirtyRect, aBorderArea, + aStyleBorder, aStyle, aOutBorderIsEmpty, aSkipSides); +} + +Maybe<nsCSSBorderRenderer> +nsCSSRendering::CreateNullBorderRendererWithStyleBorder( + nsPresContext* aPresContext, DrawTarget* aDrawTarget, nsIFrame* aForFrame, + const nsRect& aDirtyRect, const nsRect& aBorderArea, + const nsStyleBorder& aStyleBorder, ComputedStyle* aStyle, + bool* aOutBorderIsEmpty, Sides aSkipSides) { + StyleAppearance appearance = aStyle->StyleDisplay()->EffectiveAppearance(); + if (appearance != StyleAppearance::None) { + nsITheme* theme = aPresContext->Theme(); + if (theme->ThemeSupportsWidget(aPresContext, aForFrame, appearance)) { + // The border will be draw as part of the themed background item created + // for this same frame. If no themed background item was created then not + // drawing also matches that we do without webrender and what + // nsDisplayBorder does for themed borders. + if (aOutBorderIsEmpty) { + *aOutBorderIsEmpty = true; + } + return Nothing(); + } + } + + nsMargin border = aStyleBorder.GetComputedBorder(); + if (0 == border.left && 0 == border.right && 0 == border.top && + 0 == border.bottom) { + // Empty border area + if (aOutBorderIsEmpty) { + *aOutBorderIsEmpty = true; + } + return Nothing(); + } + + bool needsClip = false; + nsCSSBorderRenderer br = ConstructBorderRenderer( + aPresContext, aStyle, aDrawTarget, aForFrame, aDirtyRect, aBorderArea, + aStyleBorder, aSkipSides, &needsClip); + return Some(br); +} + +Maybe<nsCSSBorderRenderer> +nsCSSRendering::CreateBorderRendererForNonThemedOutline( + nsPresContext* aPresContext, DrawTarget* aDrawTarget, nsIFrame* aForFrame, + const nsRect& aDirtyRect, const nsRect& aInnerRect, ComputedStyle* aStyle) { + // Get our ComputedStyle's color struct. + const nsStyleOutline* ourOutline = aStyle->StyleOutline(); + if (!ourOutline->ShouldPaintOutline()) { + // Empty outline + return Nothing(); + } + + const nscoord offset = ourOutline->mOutlineOffset.ToAppUnits(); + nsRect innerRect = aInnerRect; + innerRect.Inflate(offset); + + // If the dirty rect is completely inside the border area (e.g., only the + // content is being painted), then we can skip out now + // XXX this isn't exactly true for rounded borders, where the inside curves + // may encroach into the content area. A safer calculation would be to + // shorten insideRect by the radius one each side before performing this test. + if (innerRect.Contains(aDirtyRect)) { + return Nothing(); + } + + nscoord width = ourOutline->GetOutlineWidth(); + + StyleBorderStyle outlineStyle; + // Themed outlines are handled by our callers, if supported. + if (ourOutline->mOutlineStyle.IsAuto()) { + if (width == 0) { + return Nothing(); // empty outline + } + // http://dev.w3.org/csswg/css-ui/#outline + // "User agents may treat 'auto' as 'solid'." + outlineStyle = StyleBorderStyle::Solid; + } else { + outlineStyle = ourOutline->mOutlineStyle.AsBorderStyle(); + } + + RectCornerRadii outlineRadii; + nsRect outerRect = innerRect; + outerRect.Inflate(width); + + const nscoord oneDevPixel = aPresContext->AppUnitsPerDevPixel(); + Rect oRect(NSRectToRect(outerRect, oneDevPixel)); + + const Float outlineWidths[4] = { + Float(width) / oneDevPixel, Float(width) / oneDevPixel, + Float(width) / oneDevPixel, Float(width) / oneDevPixel}; + + // convert the radii + nscoord twipsRadii[8]; + + // get the radius for our outline + if (aForFrame->GetBorderRadii(twipsRadii)) { + RectCornerRadii innerRadii; + ComputePixelRadii(twipsRadii, oneDevPixel, &innerRadii); + + Float devPixelOffset = aPresContext->AppUnitsToFloatDevPixels(offset); + const Float widths[4] = { + outlineWidths[0] + devPixelOffset, outlineWidths[1] + devPixelOffset, + outlineWidths[2] + devPixelOffset, outlineWidths[3] + devPixelOffset}; + nsCSSBorderRenderer::ComputeOuterRadii(innerRadii, widths, &outlineRadii); + } + + StyleBorderStyle outlineStyles[4] = {outlineStyle, outlineStyle, outlineStyle, + outlineStyle}; + + // This handles treating the initial color as 'currentColor'; if we + // ever want 'invert' back we'll need to do a bit of work here too. + nscolor outlineColor = + aStyle->GetVisitedDependentColor(&nsStyleOutline::mOutlineColor); + nscolor outlineColors[4] = {outlineColor, outlineColor, outlineColor, + outlineColor}; + + Rect dirtyRect = NSRectToRect(aDirtyRect, oneDevPixel); + + return Some(nsCSSBorderRenderer( + aPresContext, aDrawTarget, dirtyRect, oRect, outlineStyles, outlineWidths, + outlineRadii, outlineColors, !aForFrame->BackfaceIsHidden(), Nothing())); +} + +void nsCSSRendering::PaintNonThemedOutline(nsPresContext* aPresContext, + gfxContext& aRenderingContext, + nsIFrame* aForFrame, + const nsRect& aDirtyRect, + const nsRect& aInnerRect, + ComputedStyle* aStyle) { + Maybe<nsCSSBorderRenderer> br = CreateBorderRendererForNonThemedOutline( + aPresContext, aRenderingContext.GetDrawTarget(), aForFrame, aDirtyRect, + aInnerRect, aStyle); + if (!br) { + return; + } + + // start drawing + br->DrawBorders(); + + PrintAsStringNewline(); +} + +void nsCSSRendering::PaintFocus(nsPresContext* aPresContext, + DrawTarget* aDrawTarget, + const nsRect& aFocusRect, nscolor aColor) { + nscoord oneCSSPixel = nsPresContext::CSSPixelsToAppUnits(1); + nscoord oneDevPixel = aPresContext->DevPixelsToAppUnits(1); + + Rect focusRect(NSRectToRect(aFocusRect, oneDevPixel)); + + RectCornerRadii focusRadii; + { + nscoord twipsRadii[8] = {0, 0, 0, 0, 0, 0, 0, 0}; + ComputePixelRadii(twipsRadii, oneDevPixel, &focusRadii); + } + Float focusWidths[4] = { + Float(oneCSSPixel) / oneDevPixel, Float(oneCSSPixel) / oneDevPixel, + Float(oneCSSPixel) / oneDevPixel, Float(oneCSSPixel) / oneDevPixel}; + + StyleBorderStyle focusStyles[4] = { + StyleBorderStyle::Dotted, StyleBorderStyle::Dotted, + StyleBorderStyle::Dotted, StyleBorderStyle::Dotted}; + nscolor focusColors[4] = {aColor, aColor, aColor, aColor}; + + // Because this renders a dotted border, the background color + // should not be used. Therefore, we provide a value that will + // be blatantly wrong if it ever does get used. (If this becomes + // something that CSS can style, this function will then have access + // to a ComputedStyle and can use the same logic that PaintBorder + // and PaintOutline do.) + // + // WebRender layers-free mode don't use PaintFocus function. Just assign + // the backface-visibility to true for this case. + nsCSSBorderRenderer br(aPresContext, aDrawTarget, focusRect, focusRect, + focusStyles, focusWidths, focusRadii, focusColors, + true, Nothing()); + br.DrawBorders(); + + PrintAsStringNewline(); +} + +// Thebes Border Rendering Code End +//---------------------------------------------------------------------- + +//---------------------------------------------------------------------- + +/** + * Helper for ComputeObjectAnchorPoint; parameters are the same as for + * that function, except they're for a single coordinate / a single size + * dimension. (so, x/width vs. y/height) + */ +static void ComputeObjectAnchorCoord(const LengthPercentage& aCoord, + const nscoord aOriginBounds, + const nscoord aImageSize, + nscoord* aTopLeftCoord, + nscoord* aAnchorPointCoord) { + nscoord extraSpace = aOriginBounds - aImageSize; + + // The anchor-point doesn't care about our image's size; just the size + // of the region we're rendering into. + *aAnchorPointCoord = aCoord.Resolve( + aOriginBounds, static_cast<nscoord (*)(float)>(NSToCoordRoundWithClamp)); + // Adjust aTopLeftCoord by the specified % of the extra space. + *aTopLeftCoord = aCoord.Resolve( + extraSpace, static_cast<nscoord (*)(float)>(NSToCoordRoundWithClamp)); +} + +void nsImageRenderer::ComputeObjectAnchorPoint(const Position& aPos, + const nsSize& aOriginBounds, + const nsSize& aImageSize, + nsPoint* aTopLeft, + nsPoint* aAnchorPoint) { + ComputeObjectAnchorCoord(aPos.horizontal, aOriginBounds.width, + aImageSize.width, &aTopLeft->x, &aAnchorPoint->x); + + ComputeObjectAnchorCoord(aPos.vertical, aOriginBounds.height, + aImageSize.height, &aTopLeft->y, &aAnchorPoint->y); +} + +// In print / print preview we have multiple canvas frames (one for each page, +// and one for the document as a whole). For the topmost one, we really want the +// page sequence page background, not the root or body's background. +static nsIFrame* GetPageSequenceForCanvas(const nsIFrame* aCanvasFrame) { + MOZ_ASSERT(aCanvasFrame->IsCanvasFrame(), "not a canvas frame"); + nsPresContext* pc = aCanvasFrame->PresContext(); + if (!pc->IsPrintingOrPrintPreview()) { + return nullptr; + } + auto* ps = pc->PresShell()->GetPageSequenceFrame(); + if (!ps) { + return nullptr; + } + if (ps->GetParent() != aCanvasFrame) { + return nullptr; + } + return ps; +} + +auto nsCSSRendering::FindEffectiveBackgroundColor(nsIFrame* aFrame, + bool aStopAtThemed, + bool aPreferBodyToCanvas) + -> EffectiveBackgroundColor { + MOZ_ASSERT(aFrame); + nsPresContext* pc = aFrame->PresContext(); + auto BgColorIfNotTransparent = [&](nsIFrame* aFrame) -> Maybe<nscolor> { + nscolor c = + aFrame->GetVisitedDependentColor(&nsStyleBackground::mBackgroundColor); + if (NS_GET_A(c) == 255) { + return Some(c); + } + if (NS_GET_A(c)) { + // TODO(emilio): We should maybe just blend with ancestor bg colors and + // such, but this is probably good enough for now, matches pre-existing + // behavior. + const nscolor defaultBg = pc->DefaultBackgroundColor(); + MOZ_ASSERT(NS_GET_A(defaultBg) == 255, "PreferenceSheet guarantees this"); + return Some(NS_ComposeColors(defaultBg, c)); + } + return Nothing(); + }; + + for (nsIFrame* frame = aFrame; frame; + frame = nsLayoutUtils::GetParentOrPlaceholderForCrossDoc(frame)) { + if (auto bg = BgColorIfNotTransparent(frame)) { + return {*bg}; + } + + if (aStopAtThemed && frame->IsThemed()) { + return {NS_TRANSPARENT, true}; + } + + if (frame->IsCanvasFrame()) { + if (aPreferBodyToCanvas && !GetPageSequenceForCanvas(frame)) { + if (auto* body = pc->Document()->GetBodyElement()) { + if (nsIFrame* f = body->GetPrimaryFrame()) { + if (auto bg = BgColorIfNotTransparent(f)) { + return {*bg}; + } + } + } + } + if (nsIFrame* bgFrame = FindBackgroundFrame(frame)) { + if (auto bg = BgColorIfNotTransparent(bgFrame)) { + return {*bg}; + } + } + } + } + + return {pc->DefaultBackgroundColor()}; +} + +nsIFrame* nsCSSRendering::FindBackgroundStyleFrame(nsIFrame* aForFrame) { + const nsStyleBackground* result = aForFrame->StyleBackground(); + + // Check if we need to do propagation from BODY rather than HTML. + if (!result->IsTransparent(aForFrame)) { + return aForFrame; + } + + nsIContent* content = aForFrame->GetContent(); + // The root element content can't be null. We wouldn't know what + // frame to create for aFrame. + // Use |OwnerDoc| so it works during destruction. + if (!content) { + return aForFrame; + } + + Document* document = content->OwnerDoc(); + + dom::Element* bodyContent = document->GetBodyElement(); + // We need to null check the body node (bug 118829) since + // there are cases, thanks to the fix for bug 5569, where we + // will reflow a document with no body. In particular, if a + // SCRIPT element in the head blocks the parser and then has a + // SCRIPT that does "document.location.href = 'foo'", then + // nsParser::Terminate will call |DidBuildModel| methods + // through to the content sink, which will call |StartLayout| + // and thus |Initialize| on the pres shell. See bug 119351 + // for the ugly details. + if (!bodyContent || aForFrame->StyleDisplay()->IsContainAny()) { + return aForFrame; + } + + nsIFrame* bodyFrame = bodyContent->GetPrimaryFrame(); + if (!bodyFrame || bodyFrame->StyleDisplay()->IsContainAny()) { + return aForFrame; + } + + return nsLayoutUtils::GetStyleFrame(bodyFrame); +} + +/** + * |FindBackground| finds the correct style data to use to paint the + * background. It is responsible for handling the following two + * statements in section 14.2 of CSS2: + * + * The background of the box generated by the root element covers the + * entire canvas. + * + * For HTML documents, however, we recommend that authors specify the + * background for the BODY element rather than the HTML element. User + * agents should observe the following precedence rules to fill in the + * background: if the value of the 'background' property for the HTML + * element is different from 'transparent' then use it, else use the + * value of the 'background' property for the BODY element. If the + * resulting value is 'transparent', the rendering is undefined. + * + * Thus, in our implementation, it is responsible for ensuring that: + * + we paint the correct background on the |nsCanvasFrame| or |nsPageFrame|, + * + we don't paint the background on the root element, and + * + we don't paint the background on the BODY element in *some* cases, + * and for SGML-based HTML documents only. + * + * |FindBackground| checks whether a background should be painted. If yes, it + * returns the resulting ComputedStyle to use for the background information; + * Otherwise, it returns nullptr. + */ +ComputedStyle* nsCSSRendering::FindRootFrameBackground(nsIFrame* aForFrame) { + return FindBackgroundStyleFrame(aForFrame)->Style(); +} + +static nsIFrame* FindCanvasBackgroundFrame(const nsIFrame* aForFrame, + nsIFrame* aRootElementFrame) { + MOZ_ASSERT(aForFrame->IsCanvasFrame(), "not a canvas frame"); + if (auto* ps = GetPageSequenceForCanvas(aForFrame)) { + return ps; + } + if (aRootElementFrame) { + return nsCSSRendering::FindBackgroundStyleFrame(aRootElementFrame); + } + // This should always give transparent, so we'll fill it in with the default + // color if needed. This seems to happen a bit while a page is being loaded. + return const_cast<nsIFrame*>(aForFrame); +} + +// Helper for FindBackgroundFrame. Returns true if aForFrame has a meaningful +// background that it should draw (i.e. that it hasn't propagated to another +// frame). See documentation for FindBackground. +inline bool FrameHasMeaningfulBackground(const nsIFrame* aForFrame, + nsIFrame* aRootElementFrame) { + MOZ_ASSERT(!aForFrame->IsCanvasFrame(), + "FindBackgroundFrame handles canvas frames before calling us, " + "so we don't need to consider them here"); + + if (aForFrame == aRootElementFrame) { + // We must have propagated our background to the viewport or canvas. Abort. + return false; + } + + // Return true unless the frame is for a BODY element whose background + // was propagated to the viewport. + + nsIContent* content = aForFrame->GetContent(); + if (!content || content->NodeInfo()->NameAtom() != nsGkAtoms::body) { + return true; // not frame for a "body" element + } + // It could be a non-HTML "body" element but that's OK, we'd fail the + // bodyContent check below + + if (aForFrame->Style()->GetPseudoType() != PseudoStyleType::NotPseudo || + aForFrame->StyleDisplay()->IsContainAny()) { + return true; // A pseudo-element frame, or contained. + } + + // We should only look at the <html> background if we're in an HTML document + Document* document = content->OwnerDoc(); + + dom::Element* bodyContent = document->GetBodyElement(); + if (bodyContent != content) { + return true; // this wasn't the background that was propagated + } + + // This can be called even when there's no root element yet, during frame + // construction, via nsLayoutUtils::FrameHasTransparency and + // nsContainerFrame::SyncFrameViewProperties. + if (!aRootElementFrame || aRootElementFrame->StyleDisplay()->IsContainAny()) { + return true; + } + + const nsStyleBackground* htmlBG = aRootElementFrame->StyleBackground(); + return !htmlBG->IsTransparent(aRootElementFrame); +} + +nsIFrame* nsCSSRendering::FindBackgroundFrame(const nsIFrame* aForFrame) { + nsIFrame* rootElementFrame = + aForFrame->PresShell()->FrameConstructor()->GetRootElementStyleFrame(); + if (aForFrame->IsCanvasFrame()) { + return FindCanvasBackgroundFrame(aForFrame, rootElementFrame); + } + + if (FrameHasMeaningfulBackground(aForFrame, rootElementFrame)) { + return const_cast<nsIFrame*>(aForFrame); + } + + return nullptr; +} + +ComputedStyle* nsCSSRendering::FindBackground(const nsIFrame* aForFrame) { + if (auto* backgroundFrame = FindBackgroundFrame(aForFrame)) { + return backgroundFrame->Style(); + } + return nullptr; +} + +void nsCSSRendering::BeginFrameTreesLocked() { ++gFrameTreeLockCount; } + +void nsCSSRendering::EndFrameTreesLocked() { + NS_ASSERTION(gFrameTreeLockCount > 0, "Unbalanced EndFrameTreeLocked"); + --gFrameTreeLockCount; + if (gFrameTreeLockCount == 0) { + gInlineBGData->Reset(); + } +} + +bool nsCSSRendering::HasBoxShadowNativeTheme(nsIFrame* aFrame, + bool& aMaybeHasBorderRadius) { + const nsStyleDisplay* styleDisplay = aFrame->StyleDisplay(); + nsITheme::Transparency transparency; + if (aFrame->IsThemed(styleDisplay, &transparency)) { + aMaybeHasBorderRadius = false; + // For opaque (rectangular) theme widgets we can take the generic + // border-box path with border-radius disabled. + return transparency != nsITheme::eOpaque; + } + + aMaybeHasBorderRadius = true; + return false; +} + +gfx::sRGBColor nsCSSRendering::GetShadowColor(const StyleSimpleShadow& aShadow, + nsIFrame* aFrame, + float aOpacity) { + // Get the shadow color; if not specified, use the foreground color + nscolor shadowColor = aShadow.color.CalcColor(aFrame); + sRGBColor color = sRGBColor::FromABGR(shadowColor); + color.a *= aOpacity; + return color; +} + +nsRect nsCSSRendering::GetShadowRect(const nsRect& aFrameArea, + bool aNativeTheme, nsIFrame* aForFrame) { + nsRect frameRect = aNativeTheme ? aForFrame->InkOverflowRectRelativeToSelf() + + aFrameArea.TopLeft() + : aFrameArea; + Sides skipSides = aForFrame->GetSkipSides(); + frameRect = BoxDecorationRectForBorder(aForFrame, frameRect, skipSides); + + // Explicitly do not need to account for the spread radius here + // Webrender does it for us or PaintBoxShadow will for non-WR + return frameRect; +} + +bool nsCSSRendering::GetBorderRadii(const nsRect& aFrameRect, + const nsRect& aBorderRect, nsIFrame* aFrame, + RectCornerRadii& aOutRadii) { + const nscoord oneDevPixel = aFrame->PresContext()->DevPixelsToAppUnits(1); + nscoord twipsRadii[8]; + NS_ASSERTION( + aBorderRect.Size() == aFrame->VisualBorderRectRelativeToSelf().Size(), + "unexpected size"); + nsSize sz = aFrameRect.Size(); + bool hasBorderRadius = aFrame->GetBorderRadii(sz, sz, Sides(), twipsRadii); + if (hasBorderRadius) { + ComputePixelRadii(twipsRadii, oneDevPixel, &aOutRadii); + } + + return hasBorderRadius; +} + +void nsCSSRendering::PaintBoxShadowOuter(nsPresContext* aPresContext, + gfxContext& aRenderingContext, + nsIFrame* aForFrame, + const nsRect& aFrameArea, + const nsRect& aDirtyRect, + float aOpacity) { + DrawTarget& aDrawTarget = *aRenderingContext.GetDrawTarget(); + auto shadows = aForFrame->StyleEffects()->mBoxShadow.AsSpan(); + if (shadows.IsEmpty()) { + return; + } + + bool hasBorderRadius; + // mutually exclusive with hasBorderRadius + bool nativeTheme = HasBoxShadowNativeTheme(aForFrame, hasBorderRadius); + const nsStyleDisplay* styleDisplay = aForFrame->StyleDisplay(); + + nsRect frameRect = GetShadowRect(aFrameArea, nativeTheme, aForFrame); + + // Get any border radius, since box-shadow must also have rounded corners if + // the frame does. + RectCornerRadii borderRadii; + const nscoord oneDevPixel = aPresContext->DevPixelsToAppUnits(1); + if (hasBorderRadius) { + nscoord twipsRadii[8]; + NS_ASSERTION( + aFrameArea.Size() == aForFrame->VisualBorderRectRelativeToSelf().Size(), + "unexpected size"); + nsSize sz = frameRect.Size(); + hasBorderRadius = aForFrame->GetBorderRadii(sz, sz, Sides(), twipsRadii); + if (hasBorderRadius) { + ComputePixelRadii(twipsRadii, oneDevPixel, &borderRadii); + } + } + + // We don't show anything that intersects with the frame we're blurring on. So + // tell the blurrer not to do unnecessary work there. + gfxRect skipGfxRect = ThebesRect(NSRectToRect(frameRect, oneDevPixel)); + skipGfxRect.Round(); + bool useSkipGfxRect = true; + if (nativeTheme) { + // Optimize non-leaf native-themed frames by skipping computing pixels + // in the padding-box. We assume the padding-box is going to be painted + // opaquely for non-leaf frames. + // XXX this may not be a safe assumption; we should make this go away + // by optimizing box-shadow drawing more for the cases where we don't have a + // skip-rect. + useSkipGfxRect = !aForFrame->IsLeaf(); + nsRect paddingRect = + aForFrame->GetPaddingRectRelativeToSelf() + aFrameArea.TopLeft(); + skipGfxRect = nsLayoutUtils::RectToGfxRect(paddingRect, oneDevPixel); + } else if (hasBorderRadius) { + skipGfxRect.Deflate(gfxMargin( + std::max(borderRadii[C_TL].height, borderRadii[C_TR].height), 0, + std::max(borderRadii[C_BL].height, borderRadii[C_BR].height), 0)); + } + + for (const StyleBoxShadow& shadow : Reversed(shadows)) { + if (shadow.inset) { + continue; + } + + nsRect shadowRect = frameRect; + nsPoint shadowOffset(shadow.base.horizontal.ToAppUnits(), + shadow.base.vertical.ToAppUnits()); + shadowRect.MoveBy(shadowOffset); + nscoord shadowSpread = shadow.spread.ToAppUnits(); + if (!nativeTheme) { + shadowRect.Inflate(shadowSpread); + } + + // shadowRect won't include the blur, so make an extra rect here that + // includes the blur for use in the even-odd rule below. + nsRect shadowRectPlusBlur = shadowRect; + nscoord blurRadius = shadow.base.blur.ToAppUnits(); + shadowRectPlusBlur.Inflate( + nsContextBoxBlur::GetBlurRadiusMargin(blurRadius, oneDevPixel)); + + Rect shadowGfxRectPlusBlur = NSRectToRect(shadowRectPlusBlur, oneDevPixel); + shadowGfxRectPlusBlur.RoundOut(); + MaybeSnapToDevicePixels(shadowGfxRectPlusBlur, aDrawTarget, true); + + sRGBColor gfxShadowColor = GetShadowColor(shadow.base, aForFrame, aOpacity); + + if (nativeTheme) { + nsContextBoxBlur blurringArea; + + // When getting the widget shape from the native theme, we're going + // to draw the widget into the shadow surface to create a mask. + // We need to ensure that there actually *is* a shadow surface + // and that we're not going to draw directly into aRenderingContext. + gfxContext* shadowContext = blurringArea.Init( + shadowRect, shadowSpread, blurRadius, oneDevPixel, &aRenderingContext, + aDirtyRect, useSkipGfxRect ? &skipGfxRect : nullptr, + nsContextBoxBlur::FORCE_MASK); + if (!shadowContext) continue; + + MOZ_ASSERT(shadowContext == blurringArea.GetContext()); + + aRenderingContext.Save(); + aRenderingContext.SetColor(gfxShadowColor); + + // Draw the shape of the frame so it can be blurred. Recall how + // nsContextBoxBlur doesn't make any temporary surfaces if blur is 0 and + // it just returns the original surface? If we have no blur, we're + // painting this fill on the actual content surface (aRenderingContext == + // shadowContext) which is why we set up the color and clip before doing + // this. + + // We don't clip the border-box from the shadow, nor any other box. + // We assume that the native theme is going to paint over the shadow. + + // Draw the widget shape + gfxContextMatrixAutoSaveRestore save(shadowContext); + gfxPoint devPixelOffset = nsLayoutUtils::PointToGfxPoint( + shadowOffset, aPresContext->AppUnitsPerDevPixel()); + shadowContext->SetMatrixDouble( + shadowContext->CurrentMatrixDouble().PreTranslate(devPixelOffset)); + + nsRect nativeRect = aDirtyRect; + nativeRect.MoveBy(-shadowOffset); + nativeRect.IntersectRect(frameRect, nativeRect); + aPresContext->Theme()->DrawWidgetBackground( + shadowContext, aForFrame, styleDisplay->EffectiveAppearance(), + aFrameArea, nativeRect, nsITheme::DrawOverflow::No); + + blurringArea.DoPaint(); + aRenderingContext.Restore(); + } else { + aRenderingContext.Save(); + + { + Rect innerClipRect = NSRectToRect(frameRect, oneDevPixel); + if (!MaybeSnapToDevicePixels(innerClipRect, aDrawTarget, true)) { + innerClipRect.Round(); + } + + // Clip out the interior of the frame's border edge so that the shadow + // is only painted outside that area. + RefPtr<PathBuilder> builder = + aDrawTarget.CreatePathBuilder(FillRule::FILL_EVEN_ODD); + AppendRectToPath(builder, shadowGfxRectPlusBlur); + if (hasBorderRadius) { + AppendRoundedRectToPath(builder, innerClipRect, borderRadii); + } else { + AppendRectToPath(builder, innerClipRect); + } + RefPtr<Path> path = builder->Finish(); + aRenderingContext.Clip(path); + } + + // Clip the shadow so that we only get the part that applies to aForFrame. + nsRect fragmentClip = shadowRectPlusBlur; + Sides skipSides = aForFrame->GetSkipSides(); + if (!skipSides.IsEmpty()) { + if (skipSides.Left()) { + nscoord xmost = fragmentClip.XMost(); + fragmentClip.x = aFrameArea.x; + fragmentClip.width = xmost - fragmentClip.x; + } + if (skipSides.Right()) { + nscoord xmost = fragmentClip.XMost(); + nscoord overflow = xmost - aFrameArea.XMost(); + if (overflow > 0) { + fragmentClip.width -= overflow; + } + } + if (skipSides.Top()) { + nscoord ymost = fragmentClip.YMost(); + fragmentClip.y = aFrameArea.y; + fragmentClip.height = ymost - fragmentClip.y; + } + if (skipSides.Bottom()) { + nscoord ymost = fragmentClip.YMost(); + nscoord overflow = ymost - aFrameArea.YMost(); + if (overflow > 0) { + fragmentClip.height -= overflow; + } + } + } + fragmentClip = fragmentClip.Intersect(aDirtyRect); + aRenderingContext.Clip(NSRectToSnappedRect( + fragmentClip, aForFrame->PresContext()->AppUnitsPerDevPixel(), + aDrawTarget)); + + RectCornerRadii clipRectRadii; + if (hasBorderRadius) { + Float spreadDistance = Float(shadowSpread / oneDevPixel); + + Float borderSizes[4]; + + borderSizes[eSideLeft] = spreadDistance; + borderSizes[eSideTop] = spreadDistance; + borderSizes[eSideRight] = spreadDistance; + borderSizes[eSideBottom] = spreadDistance; + + nsCSSBorderRenderer::ComputeOuterRadii(borderRadii, borderSizes, + &clipRectRadii); + } + nsContextBoxBlur::BlurRectangle( + &aRenderingContext, shadowRect, oneDevPixel, + hasBorderRadius ? &clipRectRadii : nullptr, blurRadius, + gfxShadowColor, aDirtyRect, skipGfxRect); + aRenderingContext.Restore(); + } + } +} + +nsRect nsCSSRendering::GetBoxShadowInnerPaddingRect(nsIFrame* aFrame, + const nsRect& aFrameArea) { + Sides skipSides = aFrame->GetSkipSides(); + nsRect frameRect = BoxDecorationRectForBorder(aFrame, aFrameArea, skipSides); + + nsRect paddingRect = frameRect; + nsMargin border = aFrame->GetUsedBorder(); + paddingRect.Deflate(border); + return paddingRect; +} + +bool nsCSSRendering::ShouldPaintBoxShadowInner(nsIFrame* aFrame) { + const Span<const StyleBoxShadow> shadows = + aFrame->StyleEffects()->mBoxShadow.AsSpan(); + if (shadows.IsEmpty()) { + return false; + } + + if (aFrame->IsThemed() && aFrame->GetContent() && + !nsContentUtils::IsChromeDoc(aFrame->GetContent()->GetComposedDoc())) { + // There's no way of getting hold of a shape corresponding to a + // "padding-box" for native-themed widgets, so just don't draw + // inner box-shadows for them. But we allow chrome to paint inner + // box shadows since chrome can be aware of the platform theme. + return false; + } + + return true; +} + +bool nsCSSRendering::GetShadowInnerRadii(nsIFrame* aFrame, + const nsRect& aFrameArea, + RectCornerRadii& aOutInnerRadii) { + // Get any border radius, since box-shadow must also have rounded corners + // if the frame does. + nscoord twipsRadii[8]; + nsRect frameRect = + BoxDecorationRectForBorder(aFrame, aFrameArea, aFrame->GetSkipSides()); + nsSize sz = frameRect.Size(); + nsMargin border = aFrame->GetUsedBorder(); + aFrame->GetBorderRadii(sz, sz, Sides(), twipsRadii); + const nscoord oneDevPixel = aFrame->PresContext()->DevPixelsToAppUnits(1); + + RectCornerRadii borderRadii; + + const bool hasBorderRadius = + GetBorderRadii(frameRect, aFrameArea, aFrame, borderRadii); + + if (hasBorderRadius) { + ComputePixelRadii(twipsRadii, oneDevPixel, &borderRadii); + + Float borderSizes[4] = { + Float(border.top) / oneDevPixel, Float(border.right) / oneDevPixel, + Float(border.bottom) / oneDevPixel, Float(border.left) / oneDevPixel}; + nsCSSBorderRenderer::ComputeInnerRadii(borderRadii, borderSizes, + &aOutInnerRadii); + } + + return hasBorderRadius; +} + +void nsCSSRendering::PaintBoxShadowInner(nsPresContext* aPresContext, + gfxContext& aRenderingContext, + nsIFrame* aForFrame, + const nsRect& aFrameArea) { + if (!ShouldPaintBoxShadowInner(aForFrame)) { + return; + } + + const Span<const StyleBoxShadow> shadows = + aForFrame->StyleEffects()->mBoxShadow.AsSpan(); + NS_ASSERTION( + aForFrame->IsFieldSetFrame() || aFrameArea.Size() == aForFrame->GetSize(), + "unexpected size"); + + nsRect paddingRect = GetBoxShadowInnerPaddingRect(aForFrame, aFrameArea); + + RectCornerRadii innerRadii; + bool hasBorderRadius = GetShadowInnerRadii(aForFrame, aFrameArea, innerRadii); + + const nscoord oneDevPixel = aPresContext->DevPixelsToAppUnits(1); + + for (const StyleBoxShadow& shadow : Reversed(shadows)) { + if (!shadow.inset) { + continue; + } + + // shadowPaintRect: the area to paint on the temp surface + // shadowClipRect: the area on the temporary surface within shadowPaintRect + // that we will NOT paint in + nscoord blurRadius = shadow.base.blur.ToAppUnits(); + nsMargin blurMargin = + nsContextBoxBlur::GetBlurRadiusMargin(blurRadius, oneDevPixel); + nsRect shadowPaintRect = paddingRect; + shadowPaintRect.Inflate(blurMargin); + + // Round the spread radius to device pixels (by truncation). + // This mostly matches what we do for borders, except that we don't round + // up values between zero and one device pixels to one device pixel. + // This way of rounding is symmetric around zero, which makes sense for + // the spread radius. + int32_t spreadDistance = shadow.spread.ToAppUnits() / oneDevPixel; + nscoord spreadDistanceAppUnits = + aPresContext->DevPixelsToAppUnits(spreadDistance); + + nsRect shadowClipRect = paddingRect; + shadowClipRect.MoveBy(shadow.base.horizontal.ToAppUnits(), + shadow.base.vertical.ToAppUnits()); + shadowClipRect.Deflate(spreadDistanceAppUnits, spreadDistanceAppUnits); + + Rect shadowClipGfxRect = NSRectToRect(shadowClipRect, oneDevPixel); + shadowClipGfxRect.Round(); + + RectCornerRadii clipRectRadii; + if (hasBorderRadius) { + // Calculate the radii the inner clipping rect will have + Float borderSizes[4] = {0, 0, 0, 0}; + + // See PaintBoxShadowOuter and bug 514670 + if (innerRadii[C_TL].width > 0 || innerRadii[C_BL].width > 0) { + borderSizes[eSideLeft] = spreadDistance; + } + + if (innerRadii[C_TL].height > 0 || innerRadii[C_TR].height > 0) { + borderSizes[eSideTop] = spreadDistance; + } + + if (innerRadii[C_TR].width > 0 || innerRadii[C_BR].width > 0) { + borderSizes[eSideRight] = spreadDistance; + } + + if (innerRadii[C_BL].height > 0 || innerRadii[C_BR].height > 0) { + borderSizes[eSideBottom] = spreadDistance; + } + + nsCSSBorderRenderer::ComputeInnerRadii(innerRadii, borderSizes, + &clipRectRadii); + } + + // Set the "skip rect" to the area within the frame that we don't paint in, + // including after blurring. + nsRect skipRect = shadowClipRect; + skipRect.Deflate(blurMargin); + gfxRect skipGfxRect = nsLayoutUtils::RectToGfxRect(skipRect, oneDevPixel); + if (hasBorderRadius) { + skipGfxRect.Deflate(gfxMargin( + std::max(clipRectRadii[C_TL].height, clipRectRadii[C_TR].height), 0, + std::max(clipRectRadii[C_BL].height, clipRectRadii[C_BR].height), 0)); + } + + // When there's a blur radius, gfxAlphaBoxBlur leaves the skiprect area + // unchanged. And by construction the gfxSkipRect is not touched by the + // rendered shadow (even after blurring), so those pixels must be completely + // transparent in the shadow, so drawing them changes nothing. + DrawTarget* drawTarget = aRenderingContext.GetDrawTarget(); + + // Clip the context to the area of the frame's padding rect, so no part of + // the shadow is painted outside. Also cut out anything beyond where the + // inset shadow will be. + Rect shadowGfxRect = NSRectToRect(paddingRect, oneDevPixel); + shadowGfxRect.Round(); + + sRGBColor shadowColor = GetShadowColor(shadow.base, aForFrame, 1.0); + aRenderingContext.Save(); + + // This clips the outside border radius. + // clipRectRadii is the border radius inside the inset shadow. + if (hasBorderRadius) { + RefPtr<Path> roundedRect = + MakePathForRoundedRect(*drawTarget, shadowGfxRect, innerRadii); + aRenderingContext.Clip(roundedRect); + } else { + aRenderingContext.Clip(shadowGfxRect); + } + + nsContextBoxBlur insetBoxBlur; + gfxRect destRect = + nsLayoutUtils::RectToGfxRect(shadowPaintRect, oneDevPixel); + Point shadowOffset(shadow.base.horizontal.ToAppUnits() / oneDevPixel, + shadow.base.vertical.ToAppUnits() / oneDevPixel); + + insetBoxBlur.InsetBoxBlur( + &aRenderingContext, ToRect(destRect), shadowClipGfxRect, shadowColor, + blurRadius, spreadDistanceAppUnits, oneDevPixel, hasBorderRadius, + clipRectRadii, ToRect(skipGfxRect), shadowOffset); + aRenderingContext.Restore(); + } +} + +/* static */ +nsCSSRendering::PaintBGParams nsCSSRendering::PaintBGParams::ForAllLayers( + nsPresContext& aPresCtx, const nsRect& aDirtyRect, + const nsRect& aBorderArea, nsIFrame* aFrame, uint32_t aPaintFlags, + float aOpacity) { + MOZ_ASSERT(aFrame); + + PaintBGParams result(aPresCtx, aDirtyRect, aBorderArea, aFrame, aPaintFlags, + -1, CompositionOp::OP_OVER, aOpacity); + + return result; +} + +/* static */ +nsCSSRendering::PaintBGParams nsCSSRendering::PaintBGParams::ForSingleLayer( + nsPresContext& aPresCtx, const nsRect& aDirtyRect, + const nsRect& aBorderArea, nsIFrame* aFrame, uint32_t aPaintFlags, + int32_t aLayer, CompositionOp aCompositionOp, float aOpacity) { + MOZ_ASSERT(aFrame && (aLayer != -1)); + + PaintBGParams result(aPresCtx, aDirtyRect, aBorderArea, aFrame, aPaintFlags, + aLayer, aCompositionOp, aOpacity); + + return result; +} + +ImgDrawResult nsCSSRendering::PaintStyleImageLayer(const PaintBGParams& aParams, + gfxContext& aRenderingCtx) { + AUTO_PROFILER_LABEL("nsCSSRendering::PaintStyleImageLayer", GRAPHICS); + + MOZ_ASSERT(aParams.frame, + "Frame is expected to be provided to PaintStyleImageLayer"); + + const ComputedStyle* sc = FindBackground(aParams.frame); + if (!sc) { + // We don't want to bail out if moz-appearance is set on a root + // node. If it has a parent content node, bail because it's not + // a root, otherwise keep going in order to let the theme stuff + // draw the background. The canvas really should be drawing the + // bg, but there's no way to hook that up via css. + if (!aParams.frame->StyleDisplay()->HasAppearance()) { + return ImgDrawResult::SUCCESS; + } + + nsIContent* content = aParams.frame->GetContent(); + if (!content || content->GetParent()) { + return ImgDrawResult::SUCCESS; + } + + sc = aParams.frame->Style(); + } + + return PaintStyleImageLayerWithSC(aParams, aRenderingCtx, sc, + *aParams.frame->StyleBorder()); +} + +bool nsCSSRendering::CanBuildWebRenderDisplayItemsForStyleImageLayer( + WebRenderLayerManager* aManager, nsPresContext& aPresCtx, nsIFrame* aFrame, + const nsStyleBackground* aBackgroundStyle, int32_t aLayer, + uint32_t aPaintFlags) { + if (!aBackgroundStyle) { + return false; + } + + MOZ_ASSERT(aFrame && aLayer >= 0 && + (uint32_t)aLayer < aBackgroundStyle->mImage.mLayers.Length()); + + // We cannot draw native themed backgrounds + StyleAppearance appearance = aFrame->StyleDisplay()->EffectiveAppearance(); + if (appearance != StyleAppearance::None) { + nsITheme* theme = aPresCtx.Theme(); + if (theme->ThemeSupportsWidget(&aPresCtx, aFrame, appearance)) { + return false; + } + } + + // We only support painting gradients and image for a single style image + // layer, and we don't support crop-rects. + const auto& styleImage = + aBackgroundStyle->mImage.mLayers[aLayer].mImage.FinalImage(); + if (styleImage.IsImageRequestType()) { + if (styleImage.IsRect()) { + return false; + } + + imgRequestProxy* requestProxy = styleImage.GetImageRequest(); + if (!requestProxy) { + return false; + } + + uint32_t imageFlags = imgIContainer::FLAG_NONE; + if (aPaintFlags & nsCSSRendering::PAINTBG_SYNC_DECODE_IMAGES) { + imageFlags |= imgIContainer::FLAG_SYNC_DECODE; + } + + nsCOMPtr<imgIContainer> srcImage; + requestProxy->GetImage(getter_AddRefs(srcImage)); + if (!srcImage || + !srcImage->IsImageContainerAvailable(aManager, imageFlags)) { + return false; + } + + return true; + } + + if (styleImage.IsGradient()) { + return true; + } + + return false; +} + +ImgDrawResult nsCSSRendering::BuildWebRenderDisplayItemsForStyleImageLayer( + const PaintBGParams& aParams, mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const mozilla::layers::StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, nsDisplayItem* aItem) { + MOZ_ASSERT(aParams.frame, + "Frame is expected to be provided to " + "BuildWebRenderDisplayItemsForStyleImageLayer"); + + ComputedStyle* sc = FindBackground(aParams.frame); + if (!sc) { + // We don't want to bail out if moz-appearance is set on a root + // node. If it has a parent content node, bail because it's not + // a root, otherwise keep going in order to let the theme stuff + // draw the background. The canvas really should be drawing the + // bg, but there's no way to hook that up via css. + if (!aParams.frame->StyleDisplay()->HasAppearance()) { + return ImgDrawResult::SUCCESS; + } + + nsIContent* content = aParams.frame->GetContent(); + if (!content || content->GetParent()) { + return ImgDrawResult::SUCCESS; + } + + sc = aParams.frame->Style(); + } + return BuildWebRenderDisplayItemsForStyleImageLayerWithSC( + aParams, aBuilder, aResources, aSc, aManager, aItem, sc, + *aParams.frame->StyleBorder()); +} + +static bool IsOpaqueBorderEdge(const nsStyleBorder& aBorder, + mozilla::Side aSide) { + if (aBorder.GetComputedBorder().Side(aSide) == 0) return true; + switch (aBorder.GetBorderStyle(aSide)) { + case StyleBorderStyle::Solid: + case StyleBorderStyle::Groove: + case StyleBorderStyle::Ridge: + case StyleBorderStyle::Inset: + case StyleBorderStyle::Outset: + break; + default: + return false; + } + + // If we're using a border image, assume it's not fully opaque, + // because we may not even have the image loaded at this point, and + // even if we did, checking whether the relevant tile is fully + // opaque would be too much work. + if (!aBorder.mBorderImageSource.IsNone()) { + return false; + } + + StyleColor color = aBorder.BorderColorFor(aSide); + // We don't know the foreground color here, so if it's being used + // we must assume it might be transparent. + return !color.MaybeTransparent(); +} + +/** + * Returns true if all border edges are either missing or opaque. + */ +static bool IsOpaqueBorder(const nsStyleBorder& aBorder) { + for (const auto i : mozilla::AllPhysicalSides()) { + if (!IsOpaqueBorderEdge(aBorder, i)) { + return false; + } + } + return true; +} + +static inline void SetupDirtyRects(const nsRect& aBGClipArea, + const nsRect& aCallerDirtyRect, + nscoord aAppUnitsPerPixel, + /* OUT: */ + nsRect* aDirtyRect, gfxRect* aDirtyRectGfx) { + aDirtyRect->IntersectRect(aBGClipArea, aCallerDirtyRect); + + // Compute the Thebes equivalent of the dirtyRect. + *aDirtyRectGfx = nsLayoutUtils::RectToGfxRect(*aDirtyRect, aAppUnitsPerPixel); + NS_WARNING_ASSERTION(aDirtyRect->IsEmpty() || !aDirtyRectGfx->IsEmpty(), + "converted dirty rect should not be empty"); + MOZ_ASSERT(!aDirtyRect->IsEmpty() || aDirtyRectGfx->IsEmpty(), + "second should be empty if first is"); +} + +static bool IsSVGStyleGeometryBox(StyleGeometryBox aBox) { + return (aBox == StyleGeometryBox::FillBox || + aBox == StyleGeometryBox::StrokeBox || + aBox == StyleGeometryBox::ViewBox); +} + +static bool IsHTMLStyleGeometryBox(StyleGeometryBox aBox) { + return (aBox == StyleGeometryBox::ContentBox || + aBox == StyleGeometryBox::PaddingBox || + aBox == StyleGeometryBox::BorderBox || + aBox == StyleGeometryBox::MarginBox); +} + +static StyleGeometryBox ComputeBoxValue(nsIFrame* aForFrame, + StyleGeometryBox aBox) { + if (!aForFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) { + // For elements with associated CSS layout box, the values fill-box, + // stroke-box and view-box compute to the initial value of mask-clip. + if (IsSVGStyleGeometryBox(aBox)) { + return StyleGeometryBox::BorderBox; + } + } else { + // For SVG elements without associated CSS layout box, the values + // content-box, padding-box, border-box and margin-box compute to fill-box. + if (IsHTMLStyleGeometryBox(aBox)) { + return StyleGeometryBox::FillBox; + } + } + + return aBox; +} + +bool nsCSSRendering::ImageLayerClipState::IsValid() const { + // mDirtyRectInDevPx comes from mDirtyRectInAppUnits. mDirtyRectInAppUnits + // can not be empty if mDirtyRectInDevPx is not. + if (!mDirtyRectInDevPx.IsEmpty() && mDirtyRectInAppUnits.IsEmpty()) { + return false; + } + + if (mHasRoundedCorners == mClippedRadii.IsEmpty()) { + return false; + } + + return true; +} + +/* static */ +void nsCSSRendering::GetImageLayerClip( + const nsStyleImageLayers::Layer& aLayer, nsIFrame* aForFrame, + const nsStyleBorder& aBorder, const nsRect& aBorderArea, + const nsRect& aCallerDirtyRect, bool aWillPaintBorder, + nscoord aAppUnitsPerPixel, + /* out */ ImageLayerClipState* aClipState) { + StyleGeometryBox layerClip = ComputeBoxValue(aForFrame, aLayer.mClip); + if (IsSVGStyleGeometryBox(layerClip)) { + MOZ_ASSERT(aForFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)); + + // The coordinate space of clipArea is svg user space. + nsRect clipArea = nsLayoutUtils::ComputeGeometryBox(aForFrame, layerClip); + + nsRect strokeBox = (layerClip == StyleGeometryBox::StrokeBox) + ? clipArea + : nsLayoutUtils::ComputeGeometryBox( + aForFrame, StyleGeometryBox::StrokeBox); + nsRect clipAreaRelativeToStrokeBox = clipArea - strokeBox.TopLeft(); + + // aBorderArea is the stroke-box area in a coordinate space defined by + // the caller. This coordinate space can be svg user space of aForFrame, + // the space of aForFrame's reference-frame, or anything else. + // + // Which coordinate space chosen for aBorderArea is not matter. What + // matter is to ensure returning aClipState->mBGClipArea in the consistent + // coordiante space with aBorderArea. So we evaluate the position of clip + // area base on the position of aBorderArea here. + aClipState->mBGClipArea = + clipAreaRelativeToStrokeBox + aBorderArea.TopLeft(); + + SetupDirtyRects(aClipState->mBGClipArea, aCallerDirtyRect, + aAppUnitsPerPixel, &aClipState->mDirtyRectInAppUnits, + &aClipState->mDirtyRectInDevPx); + MOZ_ASSERT(aClipState->IsValid()); + return; + } + + if (layerClip == StyleGeometryBox::NoClip) { + aClipState->mBGClipArea = aCallerDirtyRect; + + SetupDirtyRects(aClipState->mBGClipArea, aCallerDirtyRect, + aAppUnitsPerPixel, &aClipState->mDirtyRectInAppUnits, + &aClipState->mDirtyRectInDevPx); + MOZ_ASSERT(aClipState->IsValid()); + return; + } + + MOZ_ASSERT(!aForFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)); + + // Compute the outermost boundary of the area that might be painted. + // Same coordinate space as aBorderArea. + Sides skipSides = aForFrame->GetSkipSides(); + nsRect clipBorderArea = + BoxDecorationRectForBorder(aForFrame, aBorderArea, skipSides, &aBorder); + + bool haveRoundedCorners = false; + LayoutFrameType fType = aForFrame->Type(); + if (fType != LayoutFrameType::TableColGroup && + fType != LayoutFrameType::TableCol && + fType != LayoutFrameType::TableRow && + fType != LayoutFrameType::TableRowGroup) { + haveRoundedCorners = GetRadii(aForFrame, aBorder, aBorderArea, + clipBorderArea, aClipState->mRadii); + } + bool isSolidBorder = aWillPaintBorder && IsOpaqueBorder(aBorder); + if (isSolidBorder && layerClip == StyleGeometryBox::BorderBox) { + // If we have rounded corners, we need to inflate the background + // drawing area a bit to avoid seams between the border and + // background. + layerClip = haveRoundedCorners ? StyleGeometryBox::MozAlmostPadding + : StyleGeometryBox::PaddingBox; + } + + aClipState->mBGClipArea = clipBorderArea; + + if (aForFrame->IsScrollFrame() && + StyleImageLayerAttachment::Local == aLayer.mAttachment) { + // As of this writing, this is still in discussion in the CSS Working Group + // http://lists.w3.org/Archives/Public/www-style/2013Jul/0250.html + + // The rectangle for 'background-clip' scrolls with the content, + // but the background is also clipped at a non-scrolling 'padding-box' + // like the content. (See below.) + // Therefore, only 'content-box' makes a difference here. + if (layerClip == StyleGeometryBox::ContentBox) { + nsIScrollableFrame* scrollableFrame = do_QueryFrame(aForFrame); + // Clip at a rectangle attached to the scrolled content. + aClipState->mHasAdditionalBGClipArea = true; + aClipState->mAdditionalBGClipArea = + nsRect(aClipState->mBGClipArea.TopLeft() + + scrollableFrame->GetScrolledFrame()->GetPosition() + // For the dir=rtl case: + + scrollableFrame->GetScrollRange().TopLeft(), + scrollableFrame->GetScrolledRect().Size()); + nsMargin padding = aForFrame->GetUsedPadding(); + // padding-bottom is ignored on scrollable frames: + // https://bugzilla.mozilla.org/show_bug.cgi?id=748518 + padding.bottom = 0; + padding.ApplySkipSides(skipSides); + aClipState->mAdditionalBGClipArea.Deflate(padding); + } + + // Also clip at a non-scrolling, rounded-corner 'padding-box', + // same as the scrolled content because of the 'overflow' property. + layerClip = StyleGeometryBox::PaddingBox; + } + + // See the comment of StyleGeometryBox::Margin. + // Hitting this assertion means we decide to turn on margin-box support for + // positioned mask from CSS parser and style system. In this case, you + // should *inflate* mBGClipArea by the margin returning from + // aForFrame->GetUsedMargin() in the code chunk bellow. + MOZ_ASSERT(layerClip != StyleGeometryBox::MarginBox, + "StyleGeometryBox::MarginBox rendering is not supported yet.\n"); + + if (layerClip != StyleGeometryBox::BorderBox && + layerClip != StyleGeometryBox::Text) { + nsMargin border = aForFrame->GetUsedBorder(); + if (layerClip == StyleGeometryBox::MozAlmostPadding) { + // Reduce |border| by 1px (device pixels) on all sides, if + // possible, so that we don't get antialiasing seams between the + // {background|mask} and border. + border.top = std::max(0, border.top - aAppUnitsPerPixel); + border.right = std::max(0, border.right - aAppUnitsPerPixel); + border.bottom = std::max(0, border.bottom - aAppUnitsPerPixel); + border.left = std::max(0, border.left - aAppUnitsPerPixel); + } else if (layerClip != StyleGeometryBox::PaddingBox) { + NS_ASSERTION(layerClip == StyleGeometryBox::ContentBox, + "unexpected background-clip"); + border += aForFrame->GetUsedPadding(); + } + border.ApplySkipSides(skipSides); + aClipState->mBGClipArea.Deflate(border); + + if (haveRoundedCorners) { + nsIFrame::AdjustBorderRadii(aClipState->mRadii, -border); + } + } + + if (haveRoundedCorners) { + auto d2a = aForFrame->PresContext()->AppUnitsPerDevPixel(); + nsCSSRendering::ComputePixelRadii(aClipState->mRadii, d2a, + &aClipState->mClippedRadii); + aClipState->mHasRoundedCorners = !aClipState->mClippedRadii.IsEmpty(); + } + + if (!haveRoundedCorners && aClipState->mHasAdditionalBGClipArea) { + // Do the intersection here to account for the fast path(?) below. + aClipState->mBGClipArea = + aClipState->mBGClipArea.Intersect(aClipState->mAdditionalBGClipArea); + aClipState->mHasAdditionalBGClipArea = false; + } + + SetupDirtyRects(aClipState->mBGClipArea, aCallerDirtyRect, aAppUnitsPerPixel, + &aClipState->mDirtyRectInAppUnits, + &aClipState->mDirtyRectInDevPx); + + MOZ_ASSERT(aClipState->IsValid()); +} + +static void SetupImageLayerClip(nsCSSRendering::ImageLayerClipState& aClipState, + gfxContext* aCtx, nscoord aAppUnitsPerPixel, + gfxContextAutoSaveRestore* aAutoSR) { + if (aClipState.mDirtyRectInDevPx.IsEmpty()) { + // Our caller won't draw anything under this condition, so no need + // to set more up. + return; + } + + if (aClipState.mCustomClip) { + // We don't support custom clips and rounded corners, arguably a bug, but + // table painting seems to depend on it. + return; + } + + // If we have rounded corners, clip all subsequent drawing to the + // rounded rectangle defined by bgArea and bgRadii (we don't know + // whether the rounded corners intrude on the dirtyRect or not). + // Do not do this if we have a caller-provided clip rect -- + // as above with bgArea, arguably a bug, but table painting seems + // to depend on it. + + if (aClipState.mHasAdditionalBGClipArea) { + gfxRect bgAreaGfx = nsLayoutUtils::RectToGfxRect( + aClipState.mAdditionalBGClipArea, aAppUnitsPerPixel); + bgAreaGfx.Round(); + gfxUtils::ConditionRect(bgAreaGfx); + + aAutoSR->EnsureSaved(aCtx); + aCtx->SnappedClip(bgAreaGfx); + } + + if (aClipState.mHasRoundedCorners) { + Rect bgAreaGfx = NSRectToRect(aClipState.mBGClipArea, aAppUnitsPerPixel); + bgAreaGfx.Round(); + + if (bgAreaGfx.IsEmpty()) { + // I think it's become possible to hit this since + // https://hg.mozilla.org/mozilla-central/rev/50e934e4979b landed. + NS_WARNING("converted background area should not be empty"); + // Make our caller not do anything. + aClipState.mDirtyRectInDevPx.SizeTo(gfxSize(0.0, 0.0)); + return; + } + + aAutoSR->EnsureSaved(aCtx); + + RefPtr<Path> roundedRect = MakePathForRoundedRect( + *aCtx->GetDrawTarget(), bgAreaGfx, aClipState.mClippedRadii); + aCtx->Clip(roundedRect); + } +} + +static void DrawBackgroundColor(nsCSSRendering::ImageLayerClipState& aClipState, + gfxContext* aCtx, nscoord aAppUnitsPerPixel) { + if (aClipState.mDirtyRectInDevPx.IsEmpty()) { + // Our caller won't draw anything under this condition, so no need + // to set more up. + return; + } + + DrawTarget* drawTarget = aCtx->GetDrawTarget(); + + // We don't support custom clips and rounded corners, arguably a bug, but + // table painting seems to depend on it. + if (!aClipState.mHasRoundedCorners || aClipState.mCustomClip) { + aCtx->NewPath(); + aCtx->SnappedRectangle(aClipState.mDirtyRectInDevPx); + aCtx->Fill(); + return; + } + + Rect bgAreaGfx = NSRectToRect(aClipState.mBGClipArea, aAppUnitsPerPixel); + bgAreaGfx.Round(); + + if (bgAreaGfx.IsEmpty()) { + // I think it's become possible to hit this since + // https://hg.mozilla.org/mozilla-central/rev/50e934e4979b landed. + NS_WARNING("converted background area should not be empty"); + // Make our caller not do anything. + aClipState.mDirtyRectInDevPx.SizeTo(gfxSize(0.0, 0.0)); + return; + } + + aCtx->Save(); + gfxRect dirty = ThebesRect(bgAreaGfx).Intersect(aClipState.mDirtyRectInDevPx); + + aCtx->SnappedClip(dirty); + + if (aClipState.mHasAdditionalBGClipArea) { + gfxRect bgAdditionalAreaGfx = nsLayoutUtils::RectToGfxRect( + aClipState.mAdditionalBGClipArea, aAppUnitsPerPixel); + bgAdditionalAreaGfx.Round(); + gfxUtils::ConditionRect(bgAdditionalAreaGfx); + aCtx->SnappedClip(bgAdditionalAreaGfx); + } + + RefPtr<Path> roundedRect = + MakePathForRoundedRect(*drawTarget, bgAreaGfx, aClipState.mClippedRadii); + aCtx->SetPath(roundedRect); + aCtx->Fill(); + aCtx->Restore(); +} + +enum class ScrollbarColorKind { + Thumb, + Track, +}; + +static Maybe<nscolor> CalcScrollbarColor(nsIFrame* aFrame, + ScrollbarColorKind aKind) { + ComputedStyle* scrollbarStyle = nsLayoutUtils::StyleForScrollbar(aFrame); + const auto& colors = scrollbarStyle->StyleUI()->mScrollbarColor; + if (colors.IsAuto()) { + return Nothing(); + } + const auto& color = aKind == ScrollbarColorKind::Thumb + ? colors.AsColors().thumb + : colors.AsColors().track; + return Some(color.CalcColor(*scrollbarStyle)); +} + +static nscolor GetBackgroundColor(nsIFrame* aFrame, + const ComputedStyle* aStyle) { + switch (aStyle->StyleDisplay()->EffectiveAppearance()) { + case StyleAppearance::ScrollbarthumbVertical: + case StyleAppearance::ScrollbarthumbHorizontal: { + if (Maybe<nscolor> overrideColor = + CalcScrollbarColor(aFrame, ScrollbarColorKind::Thumb)) { + return *overrideColor; + } + break; + } + case StyleAppearance::ScrollbarVertical: + case StyleAppearance::ScrollbarHorizontal: + case StyleAppearance::Scrollcorner: { + if (Maybe<nscolor> overrideColor = + CalcScrollbarColor(aFrame, ScrollbarColorKind::Track)) { + return *overrideColor; + } + break; + } + default: + break; + } + return aStyle->GetVisitedDependentColor(&nsStyleBackground::mBackgroundColor); +} + +nscolor nsCSSRendering::DetermineBackgroundColor(nsPresContext* aPresContext, + const ComputedStyle* aStyle, + nsIFrame* aFrame, + bool& aDrawBackgroundImage, + bool& aDrawBackgroundColor) { + auto shouldPaint = aFrame->ComputeShouldPaintBackground(); + aDrawBackgroundImage = shouldPaint.mImage; + aDrawBackgroundColor = shouldPaint.mColor; + + const nsStyleBackground* bg = aStyle->StyleBackground(); + nscolor bgColor; + if (aDrawBackgroundColor) { + bgColor = GetBackgroundColor(aFrame, aStyle); + if (NS_GET_A(bgColor) == 0) { + aDrawBackgroundColor = false; + } + } else { + // If GetBackgroundColorDraw() is false, we are still expected to + // draw color in the background of any frame that's not completely + // transparent, but we are expected to use white instead of whatever + // color was specified. + bgColor = NS_RGB(255, 255, 255); + if (aDrawBackgroundImage || !bg->IsTransparent(aStyle)) { + aDrawBackgroundColor = true; + } else { + bgColor = NS_RGBA(0, 0, 0, 0); + } + } + + // We can skip painting the background color if a background image is opaque. + nsStyleImageLayers::Repeat repeat = bg->BottomLayer().mRepeat; + bool xFullRepeat = repeat.mXRepeat == StyleImageLayerRepeat::Repeat || + repeat.mXRepeat == StyleImageLayerRepeat::Round; + bool yFullRepeat = repeat.mYRepeat == StyleImageLayerRepeat::Repeat || + repeat.mYRepeat == StyleImageLayerRepeat::Round; + if (aDrawBackgroundColor && xFullRepeat && yFullRepeat && + bg->BottomLayer().mImage.IsOpaque() && + bg->BottomLayer().mBlendMode == StyleBlend::Normal) { + aDrawBackgroundColor = false; + } + + return bgColor; +} + +static CompositionOp DetermineCompositionOp( + const nsCSSRendering::PaintBGParams& aParams, + const nsStyleImageLayers& aLayers, uint32_t aLayerIndex) { + if (aParams.layer >= 0) { + // When drawing a single layer, use the specified composition op. + return aParams.compositionOp; + } + + const nsStyleImageLayers::Layer& layer = aLayers.mLayers[aLayerIndex]; + // When drawing all layers, get the compositon op from each image layer. + if (aParams.paintFlags & nsCSSRendering::PAINTBG_MASK_IMAGE) { + // Always using OP_OVER mode while drawing the bottom mask layer. + if (aLayerIndex == (aLayers.mImageCount - 1)) { + return CompositionOp::OP_OVER; + } + + return nsCSSRendering::GetGFXCompositeMode(layer.mComposite); + } + + return nsCSSRendering::GetGFXBlendMode(layer.mBlendMode); +} + +ImgDrawResult nsCSSRendering::PaintStyleImageLayerWithSC( + const PaintBGParams& aParams, gfxContext& aRenderingCtx, + const ComputedStyle* aBackgroundSC, const nsStyleBorder& aBorder) { + MOZ_ASSERT(aParams.frame, + "Frame is expected to be provided to PaintStyleImageLayerWithSC"); + + // If we're drawing all layers, aCompositonOp is ignored, so make sure that + // it was left at its default value. + MOZ_ASSERT(aParams.layer != -1 || + aParams.compositionOp == CompositionOp::OP_OVER); + + // Check to see if we have an appearance defined. If so, we let the theme + // renderer draw the background and bail out. + // XXXzw this ignores aParams.bgClipRect. + StyleAppearance appearance = + aParams.frame->StyleDisplay()->EffectiveAppearance(); + if (appearance != StyleAppearance::None) { + nsITheme* theme = aParams.presCtx.Theme(); + if (theme->ThemeSupportsWidget(&aParams.presCtx, aParams.frame, + appearance)) { + nsRect drawing(aParams.borderArea); + theme->GetWidgetOverflow(aParams.presCtx.DeviceContext(), aParams.frame, + appearance, &drawing); + drawing.IntersectRect(drawing, aParams.dirtyRect); + theme->DrawWidgetBackground(&aRenderingCtx, aParams.frame, appearance, + aParams.borderArea, drawing); + return ImgDrawResult::SUCCESS; + } + } + + // For canvas frames (in the CSS sense) we draw the background color using + // a solid color item that gets added in nsLayoutUtils::PaintFrame, + // or nsSubDocumentFrame::BuildDisplayList (bug 488242). (The solid + // color may be moved into nsDisplayCanvasBackground by + // PresShell::AddCanvasBackgroundColorItem(), and painted by + // nsDisplayCanvasBackground directly.) Either way we don't need to + // paint the background color here. + bool isCanvasFrame = aParams.frame->IsCanvasFrame(); + const bool paintMask = aParams.paintFlags & PAINTBG_MASK_IMAGE; + + // Determine whether we are drawing background images and/or + // background colors. + bool drawBackgroundImage = true; + bool drawBackgroundColor = !paintMask; + nscolor bgColor = NS_RGBA(0, 0, 0, 0); + if (!paintMask) { + bgColor = + DetermineBackgroundColor(&aParams.presCtx, aBackgroundSC, aParams.frame, + drawBackgroundImage, drawBackgroundColor); + } + + // Masks shouldn't be suppressed for print. + MOZ_ASSERT_IF(paintMask, drawBackgroundImage); + + const nsStyleImageLayers& layers = + paintMask ? aBackgroundSC->StyleSVGReset()->mMask + : aBackgroundSC->StyleBackground()->mImage; + // If we're drawing a specific layer, we don't want to draw the + // background color. + if (drawBackgroundColor && aParams.layer >= 0) { + drawBackgroundColor = false; + } + + // At this point, drawBackgroundImage and drawBackgroundColor are + // true if and only if we are actually supposed to paint an image or + // color into aDirtyRect, respectively. + if (!drawBackgroundImage && !drawBackgroundColor) { + return ImgDrawResult::SUCCESS; + } + + // The 'bgClipArea' (used only by the image tiling logic, far below) + // is the caller-provided aParams.bgClipRect if any, or else the area + // determined by the value of 'background-clip' in + // SetupCurrentBackgroundClip. (Arguably it should be the + // intersection, but that breaks the table painter -- in particular, + // taking the intersection breaks reftests/bugs/403249-1[ab].) + nscoord appUnitsPerPixel = aParams.presCtx.AppUnitsPerDevPixel(); + ImageLayerClipState clipState; + if (aParams.bgClipRect) { + clipState.mBGClipArea = *aParams.bgClipRect; + clipState.mCustomClip = true; + clipState.mHasRoundedCorners = false; + SetupDirtyRects(clipState.mBGClipArea, aParams.dirtyRect, appUnitsPerPixel, + &clipState.mDirtyRectInAppUnits, + &clipState.mDirtyRectInDevPx); + } else { + GetImageLayerClip(layers.BottomLayer(), aParams.frame, aBorder, + aParams.borderArea, aParams.dirtyRect, + (aParams.paintFlags & PAINTBG_WILL_PAINT_BORDER), + appUnitsPerPixel, &clipState); + } + + // If we might be using a background color, go ahead and set it now. + if (drawBackgroundColor && !isCanvasFrame) { + aRenderingCtx.SetColor(sRGBColor::FromABGR(bgColor)); + } + + // If there is no background image, draw a color. (If there is + // neither a background image nor a color, we wouldn't have gotten + // this far.) + if (!drawBackgroundImage) { + if (!isCanvasFrame) { + DrawBackgroundColor(clipState, &aRenderingCtx, appUnitsPerPixel); + } + return ImgDrawResult::SUCCESS; + } + + if (layers.mImageCount < 1) { + // Return if there are no background layers, all work from this point + // onwards happens iteratively on these. + return ImgDrawResult::SUCCESS; + } + + MOZ_ASSERT((aParams.layer < 0) || + (layers.mImageCount > uint32_t(aParams.layer))); + + // The background color is rendered over the entire dirty area, + // even if the image isn't. + if (drawBackgroundColor && !isCanvasFrame) { + DrawBackgroundColor(clipState, &aRenderingCtx, appUnitsPerPixel); + } + + // Compute the outermost boundary of the area that might be painted. + // Same coordinate space as aParams.borderArea & aParams.bgClipRect. + Sides skipSides = aParams.frame->GetSkipSides(); + nsRect paintBorderArea = BoxDecorationRectForBackground( + aParams.frame, aParams.borderArea, skipSides, &aBorder); + nsRect clipBorderArea = BoxDecorationRectForBorder( + aParams.frame, aParams.borderArea, skipSides, &aBorder); + + ImgDrawResult result = ImgDrawResult::SUCCESS; + StyleGeometryBox currentBackgroundClip = StyleGeometryBox::BorderBox; + const bool drawAllLayers = (aParams.layer < 0); + uint32_t count = drawAllLayers + ? layers.mImageCount // iterate all image layers. + : layers.mImageCount - + aParams.layer; // iterate from the bottom layer to + // the 'aParams.layer-th' layer. + NS_FOR_VISIBLE_IMAGE_LAYERS_BACK_TO_FRONT_WITH_RANGE( + i, layers, layers.mImageCount - 1, count) { + // NOTE: no Save() yet, we do that later by calling autoSR.EnsureSaved(ctx) + // in the cases we need it. + gfxContextAutoSaveRestore autoSR; + const nsStyleImageLayers::Layer& layer = layers.mLayers[i]; + + ImageLayerClipState currentLayerClipState = clipState; + if (!aParams.bgClipRect) { + bool isBottomLayer = (i == layers.mImageCount - 1); + if (currentBackgroundClip != layer.mClip || isBottomLayer) { + currentBackgroundClip = layer.mClip; + if (!isBottomLayer) { + currentLayerClipState = {}; + // For the bottom layer, we already called GetImageLayerClip above + // and it stored its results in clipState. + GetImageLayerClip(layer, aParams.frame, aBorder, aParams.borderArea, + aParams.dirtyRect, + (aParams.paintFlags & PAINTBG_WILL_PAINT_BORDER), + appUnitsPerPixel, ¤tLayerClipState); + } + SetupImageLayerClip(currentLayerClipState, &aRenderingCtx, + appUnitsPerPixel, &autoSR); + if (!clipBorderArea.IsEqualEdges(aParams.borderArea)) { + // We're drawing the background for the joined continuation boxes + // so we need to clip that to the slice that we want for this + // frame. + gfxRect clip = nsLayoutUtils::RectToGfxRect(aParams.borderArea, + appUnitsPerPixel); + autoSR.EnsureSaved(&aRenderingCtx); + aRenderingCtx.SnappedClip(clip); + } + } + } + + // Skip the following layer preparing and painting code if the current + // layer is not selected for drawing. + if (aParams.layer >= 0 && i != (uint32_t)aParams.layer) { + continue; + } + nsBackgroundLayerState state = PrepareImageLayer( + &aParams.presCtx, aParams.frame, aParams.paintFlags, paintBorderArea, + currentLayerClipState.mBGClipArea, layer, nullptr); + result &= state.mImageRenderer.PrepareResult(); + + // Skip the layer painting code if we found the dirty region is empty. + if (currentLayerClipState.mDirtyRectInDevPx.IsEmpty()) { + continue; + } + + if (!state.mFillArea.IsEmpty()) { + CompositionOp co = DetermineCompositionOp(aParams, layers, i); + if (co != CompositionOp::OP_OVER) { + NS_ASSERTION(aRenderingCtx.CurrentOp() == CompositionOp::OP_OVER, + "It is assumed the initial op is OP_OVER, when it is " + "restored later"); + aRenderingCtx.SetOp(co); + } + + result &= state.mImageRenderer.DrawLayer( + &aParams.presCtx, aRenderingCtx, state.mDestArea, state.mFillArea, + state.mAnchor + paintBorderArea.TopLeft(), + currentLayerClipState.mDirtyRectInAppUnits, state.mRepeatSize, + aParams.opacity); + + if (co != CompositionOp::OP_OVER) { + aRenderingCtx.SetOp(CompositionOp::OP_OVER); + } + } + } + + return result; +} + +ImgDrawResult +nsCSSRendering::BuildWebRenderDisplayItemsForStyleImageLayerWithSC( + const PaintBGParams& aParams, mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const mozilla::layers::StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, nsDisplayItem* aItem, + ComputedStyle* aBackgroundSC, const nsStyleBorder& aBorder) { + MOZ_ASSERT(!(aParams.paintFlags & PAINTBG_MASK_IMAGE)); + + nscoord appUnitsPerPixel = aParams.presCtx.AppUnitsPerDevPixel(); + ImageLayerClipState clipState; + + clipState.mBGClipArea = *aParams.bgClipRect; + clipState.mCustomClip = true; + clipState.mHasRoundedCorners = false; + SetupDirtyRects(clipState.mBGClipArea, aParams.dirtyRect, appUnitsPerPixel, + &clipState.mDirtyRectInAppUnits, + &clipState.mDirtyRectInDevPx); + + // Compute the outermost boundary of the area that might be painted. + // Same coordinate space as aParams.borderArea & aParams.bgClipRect. + Sides skipSides = aParams.frame->GetSkipSides(); + nsRect paintBorderArea = BoxDecorationRectForBackground( + aParams.frame, aParams.borderArea, skipSides, &aBorder); + + const nsStyleImageLayers& layers = aBackgroundSC->StyleBackground()->mImage; + const nsStyleImageLayers::Layer& layer = layers.mLayers[aParams.layer]; + + // Skip the following layer painting code if we found the dirty region is + // empty or the current layer is not selected for drawing. + if (clipState.mDirtyRectInDevPx.IsEmpty()) { + return ImgDrawResult::SUCCESS; + } + + ImgDrawResult result = ImgDrawResult::SUCCESS; + nsBackgroundLayerState state = + PrepareImageLayer(&aParams.presCtx, aParams.frame, aParams.paintFlags, + paintBorderArea, clipState.mBGClipArea, layer, nullptr); + result &= state.mImageRenderer.PrepareResult(); + + if (!state.mFillArea.IsEmpty()) { + result &= state.mImageRenderer.BuildWebRenderDisplayItemsForLayer( + &aParams.presCtx, aBuilder, aResources, aSc, aManager, aItem, + state.mDestArea, state.mFillArea, + state.mAnchor + paintBorderArea.TopLeft(), + clipState.mDirtyRectInAppUnits, state.mRepeatSize, aParams.opacity); + } + + return result; +} + +nsRect nsCSSRendering::ComputeImageLayerPositioningArea( + nsPresContext* aPresContext, nsIFrame* aForFrame, const nsRect& aBorderArea, + const nsStyleImageLayers::Layer& aLayer, nsIFrame** aAttachedToFrame, + bool* aOutIsTransformedFixed) { + // Compute {background|mask} origin area relative to aBorderArea now as we + // may need it to compute the effective image size for a CSS gradient. + nsRect positionArea; + + StyleGeometryBox layerOrigin = ComputeBoxValue(aForFrame, aLayer.mOrigin); + + if (IsSVGStyleGeometryBox(layerOrigin)) { + MOZ_ASSERT(aForFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)); + *aAttachedToFrame = aForFrame; + + positionArea = nsLayoutUtils::ComputeGeometryBox(aForFrame, layerOrigin); + + nsPoint toStrokeBoxOffset = nsPoint(0, 0); + if (layerOrigin != StyleGeometryBox::StrokeBox) { + nsRect strokeBox = nsLayoutUtils::ComputeGeometryBox( + aForFrame, StyleGeometryBox::StrokeBox); + toStrokeBoxOffset = positionArea.TopLeft() - strokeBox.TopLeft(); + } + + // For SVG frames, the return value is relative to the stroke box + return nsRect(toStrokeBoxOffset, positionArea.Size()); + } + + MOZ_ASSERT(!aForFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)); + + LayoutFrameType frameType = aForFrame->Type(); + nsIFrame* geometryFrame = aForFrame; + if (MOZ_UNLIKELY(frameType == LayoutFrameType::Scroll && + StyleImageLayerAttachment::Local == aLayer.mAttachment)) { + nsIScrollableFrame* scrollableFrame = do_QueryFrame(aForFrame); + positionArea = nsRect(scrollableFrame->GetScrolledFrame()->GetPosition() + // For the dir=rtl case: + + scrollableFrame->GetScrollRange().TopLeft(), + scrollableFrame->GetScrolledRect().Size()); + // The ScrolledRect’s size does not include the borders or scrollbars, + // reverse the handling of background-origin + // compared to the common case below. + if (layerOrigin == StyleGeometryBox::BorderBox) { + nsMargin border = geometryFrame->GetUsedBorder(); + border.ApplySkipSides(geometryFrame->GetSkipSides()); + positionArea.Inflate(border); + positionArea.Inflate(scrollableFrame->GetActualScrollbarSizes()); + } else if (layerOrigin != StyleGeometryBox::PaddingBox) { + nsMargin padding = geometryFrame->GetUsedPadding(); + padding.ApplySkipSides(geometryFrame->GetSkipSides()); + positionArea.Deflate(padding); + NS_ASSERTION(layerOrigin == StyleGeometryBox::ContentBox, + "unknown background-origin value"); + } + *aAttachedToFrame = aForFrame; + return positionArea; + } + + if (MOZ_UNLIKELY(frameType == LayoutFrameType::Canvas)) { + geometryFrame = aForFrame->PrincipalChildList().FirstChild(); + // geometryFrame might be null if this canvas is a page created + // as an overflow container (e.g. the in-flow content has already + // finished and this page only displays the continuations of + // absolutely positioned content). + if (geometryFrame) { + positionArea = + nsPlaceholderFrame::GetRealFrameFor(geometryFrame)->GetRect(); + } + } else { + positionArea = nsRect(nsPoint(0, 0), aBorderArea.Size()); + } + + // See the comment of StyleGeometryBox::MarginBox. + // Hitting this assertion means we decide to turn on margin-box support for + // positioned mask from CSS parser and style system. In this case, you + // should *inflate* positionArea by the margin returning from + // geometryFrame->GetUsedMargin() in the code chunk bellow. + MOZ_ASSERT(aLayer.mOrigin != StyleGeometryBox::MarginBox, + "StyleGeometryBox::MarginBox rendering is not supported yet.\n"); + + // {background|mask} images are tiled over the '{background|mask}-clip' area + // but the origin of the tiling is based on the '{background|mask}-origin' + // area. + if (layerOrigin != StyleGeometryBox::BorderBox && geometryFrame) { + nsMargin border = geometryFrame->GetUsedBorder(); + if (layerOrigin != StyleGeometryBox::PaddingBox) { + border += geometryFrame->GetUsedPadding(); + NS_ASSERTION(layerOrigin == StyleGeometryBox::ContentBox, + "unknown background-origin value"); + } + positionArea.Deflate(border); + } + + nsIFrame* attachedToFrame = aForFrame; + if (StyleImageLayerAttachment::Fixed == aLayer.mAttachment) { + // If it's a fixed background attachment, then the image is placed + // relative to the viewport, which is the area of the root frame + // in a screen context or the page content frame in a print context. + attachedToFrame = aPresContext->PresShell()->GetRootFrame(); + NS_ASSERTION(attachedToFrame, "no root frame"); + nsIFrame* pageContentFrame = nullptr; + if (aPresContext->IsPaginated()) { + pageContentFrame = nsLayoutUtils::GetClosestFrameOfType( + aForFrame, LayoutFrameType::PageContent); + if (pageContentFrame) { + attachedToFrame = pageContentFrame; + } + // else this is an embedded shell and its root frame is what we want + } + + // If the background is affected by a transform, treat is as if it + // wasn't fixed. + if (nsLayoutUtils::IsTransformed(aForFrame, attachedToFrame)) { + attachedToFrame = aForFrame; + *aOutIsTransformedFixed = true; + } else { + // Set the background positioning area to the viewport's area + // (relative to aForFrame) + positionArea = nsRect(-aForFrame->GetOffsetTo(attachedToFrame), + attachedToFrame->GetSize()); + + if (!pageContentFrame) { + // Subtract the size of scrollbars. + nsIScrollableFrame* scrollableFrame = + aPresContext->PresShell()->GetRootScrollFrameAsScrollable(); + if (scrollableFrame) { + nsMargin scrollbars = scrollableFrame->GetActualScrollbarSizes(); + positionArea.Deflate(scrollbars); + } + } + + // If we have the dynamic toolbar, we need to expand the image area to + // include the region under the dynamic toolbar, otherwise we will see a + // blank space under the toolbar. + if (aPresContext->IsRootContentDocumentCrossProcess() && + aPresContext->HasDynamicToolbar()) { + positionArea.SizeTo(nsLayoutUtils::ExpandHeightForDynamicToolbar( + aPresContext, positionArea.Size())); + } + } + } + *aAttachedToFrame = attachedToFrame; + + return positionArea; +} + +/* static */ +nscoord nsCSSRendering::ComputeRoundedSize(nscoord aCurrentSize, + nscoord aPositioningSize) { + float repeatCount = NS_roundf(float(aPositioningSize) / float(aCurrentSize)); + if (repeatCount < 1.0f) { + return aPositioningSize; + } + return nscoord(NS_lround(float(aPositioningSize) / repeatCount)); +} + +// Apply the CSS image sizing algorithm as it applies to background images. +// See http://www.w3.org/TR/css3-background/#the-background-size . +// aIntrinsicSize is the size that the background image 'would like to be'. +// It can be found by calling nsImageRenderer::ComputeIntrinsicSize. +static nsSize ComputeDrawnSizeForBackground( + const CSSSizeOrRatio& aIntrinsicSize, const nsSize& aBgPositioningArea, + const StyleBackgroundSize& aLayerSize, StyleImageLayerRepeat aXRepeat, + StyleImageLayerRepeat aYRepeat) { + nsSize imageSize; + + // Size is dictated by cover or contain rules. + if (aLayerSize.IsContain() || aLayerSize.IsCover()) { + nsImageRenderer::FitType fitType = aLayerSize.IsCover() + ? nsImageRenderer::COVER + : nsImageRenderer::CONTAIN; + imageSize = nsImageRenderer::ComputeConstrainedSize( + aBgPositioningArea, aIntrinsicSize.mRatio, fitType); + } else { + MOZ_ASSERT(aLayerSize.IsExplicitSize()); + const auto& width = aLayerSize.explicit_size.width; + const auto& height = aLayerSize.explicit_size.height; + // No cover/contain constraint, use default algorithm. + CSSSizeOrRatio specifiedSize; + if (width.IsLengthPercentage()) { + specifiedSize.SetWidth( + width.AsLengthPercentage().Resolve(aBgPositioningArea.width)); + } + if (height.IsLengthPercentage()) { + specifiedSize.SetHeight( + height.AsLengthPercentage().Resolve(aBgPositioningArea.height)); + } + + imageSize = nsImageRenderer::ComputeConcreteSize( + specifiedSize, aIntrinsicSize, aBgPositioningArea); + } + + // See https://www.w3.org/TR/css3-background/#background-size . + // "If 'background-repeat' is 'round' for one (or both) dimensions, there is a + // second + // step. The UA must scale the image in that dimension (or both dimensions) + // so that it fits a whole number of times in the background positioning + // area." + // "If 'background-repeat' is 'round' for one dimension only and if + // 'background-size' + // is 'auto' for the other dimension, then there is a third step: that other + // dimension is scaled so that the original aspect ratio is restored." + bool isRepeatRoundInBothDimensions = + aXRepeat == StyleImageLayerRepeat::Round && + aYRepeat == StyleImageLayerRepeat::Round; + + // Calculate the rounded size only if the background-size computation + // returned a correct size for the image. + if (imageSize.width && aXRepeat == StyleImageLayerRepeat::Round) { + imageSize.width = nsCSSRendering::ComputeRoundedSize( + imageSize.width, aBgPositioningArea.width); + if (!isRepeatRoundInBothDimensions && aLayerSize.IsExplicitSize() && + aLayerSize.explicit_size.height.IsAuto()) { + // Restore intrinsic ratio + if (aIntrinsicSize.mRatio) { + imageSize.height = + aIntrinsicSize.mRatio.Inverted().ApplyTo(imageSize.width); + } + } + } + + // Calculate the rounded size only if the background-size computation + // returned a correct size for the image. + if (imageSize.height && aYRepeat == StyleImageLayerRepeat::Round) { + imageSize.height = nsCSSRendering::ComputeRoundedSize( + imageSize.height, aBgPositioningArea.height); + if (!isRepeatRoundInBothDimensions && aLayerSize.IsExplicitSize() && + aLayerSize.explicit_size.width.IsAuto()) { + // Restore intrinsic ratio + if (aIntrinsicSize.mRatio) { + imageSize.width = aIntrinsicSize.mRatio.ApplyTo(imageSize.height); + } + } + } + + return imageSize; +} + +/* ComputeSpacedRepeatSize + * aImageDimension: the image width/height + * aAvailableSpace: the background positioning area width/height + * aRepeat: determine whether the image is repeated + * Returns the image size plus gap size of app units for use as spacing + */ +static nscoord ComputeSpacedRepeatSize(nscoord aImageDimension, + nscoord aAvailableSpace, bool& aRepeat) { + float ratio = static_cast<float>(aAvailableSpace) / aImageDimension; + + if (ratio < 2.0f) { // If you can't repeat at least twice, then don't repeat. + aRepeat = false; + return aImageDimension; + } + + aRepeat = true; + return (aAvailableSpace - aImageDimension) / (NSToIntFloor(ratio) - 1); +} + +/* static */ +nscoord nsCSSRendering::ComputeBorderSpacedRepeatSize(nscoord aImageDimension, + nscoord aAvailableSpace, + nscoord& aSpace) { + int32_t count = aImageDimension ? (aAvailableSpace / aImageDimension) : 0; + aSpace = (aAvailableSpace - aImageDimension * count) / (count + 1); + return aSpace + aImageDimension; +} + +nsBackgroundLayerState nsCSSRendering::PrepareImageLayer( + nsPresContext* aPresContext, nsIFrame* aForFrame, uint32_t aFlags, + const nsRect& aBorderArea, const nsRect& aBGClipRect, + const nsStyleImageLayers::Layer& aLayer, bool* aOutIsTransformedFixed) { + /* + * The properties we need to keep in mind when drawing style image + * layers are: + * + * background-image/ mask-image + * background-repeat/ mask-repeat + * background-attachment + * background-position/ mask-position + * background-clip/ mask-clip + * background-origin/ mask-origin + * background-size/ mask-size + * background-blend-mode + * box-decoration-break + * mask-mode + * mask-composite + * + * (background-color applies to the entire element and not to individual + * layers, so it is irrelevant to this method.) + * + * These properties have the following dependencies upon each other when + * determining rendering: + * + * background-image/ mask-image + * no dependencies + * background-repeat/ mask-repeat + * no dependencies + * background-attachment + * no dependencies + * background-position/ mask-position + * depends upon background-size/mask-size (for the image's scaled size) + * and background-break (for the background positioning area) + * background-clip/ mask-clip + * no dependencies + * background-origin/ mask-origin + * depends upon background-attachment (only in the case where that value + * is 'fixed') + * background-size/ mask-size + * depends upon box-decoration-break (for the background positioning area + * for resolving percentages), background-image (for the image's intrinsic + * size), background-repeat (if that value is 'round'), and + * background-origin (for the background painting area, when + * background-repeat is 'round') + * background-blend-mode + * no dependencies + * mask-mode + * no dependencies + * mask-composite + * no dependencies + * box-decoration-break + * no dependencies + * + * As a result of only-if dependencies we don't strictly do a topological + * sort of the above properties when processing, but it's pretty close to one: + * + * background-clip/mask-clip (by caller) + * background-image/ mask-image + * box-decoration-break, background-origin/ mask origin + * background-attachment (postfix for background-origin if 'fixed') + * background-size/ mask-size + * background-position/ mask-position + * background-repeat/ mask-repeat + */ + + uint32_t irFlags = 0; + if (aFlags & nsCSSRendering::PAINTBG_SYNC_DECODE_IMAGES) { + irFlags |= nsImageRenderer::FLAG_SYNC_DECODE_IMAGES; + } + if (aFlags & nsCSSRendering::PAINTBG_TO_WINDOW) { + irFlags |= nsImageRenderer::FLAG_PAINTING_TO_WINDOW; + } + if (aFlags & nsCSSRendering::PAINTBG_HIGH_QUALITY_SCALING) { + irFlags |= nsImageRenderer::FLAG_HIGH_QUALITY_SCALING; + } + // Only do partial bg image drawing in content documents: non-content + // documents are viewed as UI of the browser and a partial draw of a bg image + // might look weird in that context. + if (StaticPrefs::layout_display_partial_background_images() && + XRE_IsContentProcess() && !aPresContext->IsChrome()) { + irFlags |= nsImageRenderer::FLAG_DRAW_PARTIAL_FRAMES; + } + + nsBackgroundLayerState state(aForFrame, &aLayer.mImage, irFlags); + if (!state.mImageRenderer.PrepareImage()) { + // There's no image or it's not ready to be painted. + if (aOutIsTransformedFixed && + StyleImageLayerAttachment::Fixed == aLayer.mAttachment) { + nsIFrame* attachedToFrame = aPresContext->PresShell()->GetRootFrame(); + NS_ASSERTION(attachedToFrame, "no root frame"); + nsIFrame* pageContentFrame = nullptr; + if (aPresContext->IsPaginated()) { + pageContentFrame = nsLayoutUtils::GetClosestFrameOfType( + aForFrame, LayoutFrameType::PageContent); + if (pageContentFrame) { + attachedToFrame = pageContentFrame; + } + // else this is an embedded shell and its root frame is what we want + } + + *aOutIsTransformedFixed = + nsLayoutUtils::IsTransformed(aForFrame, attachedToFrame); + } + return state; + } + + // The frame to which the background is attached + nsIFrame* attachedToFrame = aForFrame; + // Is the background marked 'fixed', but affected by a transform? + bool transformedFixed = false; + // Compute background origin area relative to aBorderArea now as we may need + // it to compute the effective image size for a CSS gradient. + nsRect positionArea = ComputeImageLayerPositioningArea( + aPresContext, aForFrame, aBorderArea, aLayer, &attachedToFrame, + &transformedFixed); + if (aOutIsTransformedFixed) { + *aOutIsTransformedFixed = transformedFixed; + } + + // For background-attachment:fixed backgrounds, we'll override the area + // where the background can be drawn to the viewport. + nsRect bgClipRect = aBGClipRect; + + if (StyleImageLayerAttachment::Fixed == aLayer.mAttachment && + !transformedFixed && (aFlags & nsCSSRendering::PAINTBG_TO_WINDOW)) { + bgClipRect = positionArea + aBorderArea.TopLeft(); + } + + StyleImageLayerRepeat repeatX = aLayer.mRepeat.mXRepeat; + StyleImageLayerRepeat repeatY = aLayer.mRepeat.mYRepeat; + + // Scale the image as specified for background-size and background-repeat. + // Also as required for proper background positioning when background-position + // is defined with percentages. + CSSSizeOrRatio intrinsicSize = state.mImageRenderer.ComputeIntrinsicSize(); + nsSize bgPositionSize = positionArea.Size(); + nsSize imageSize = ComputeDrawnSizeForBackground( + intrinsicSize, bgPositionSize, aLayer.mSize, repeatX, repeatY); + + if (imageSize.width <= 0 || imageSize.height <= 0) return state; + + state.mImageRenderer.SetPreferredSize(intrinsicSize, imageSize); + + // Compute the anchor point. + // + // relative to aBorderArea.TopLeft() (which is where the top-left + // of aForFrame's border-box will be rendered) + nsPoint imageTopLeft; + + // Compute the position of the background now that the background's size is + // determined. + nsImageRenderer::ComputeObjectAnchorPoint(aLayer.mPosition, bgPositionSize, + imageSize, &imageTopLeft, + &state.mAnchor); + state.mRepeatSize = imageSize; + if (repeatX == StyleImageLayerRepeat::Space) { + bool isRepeat; + state.mRepeatSize.width = ComputeSpacedRepeatSize( + imageSize.width, bgPositionSize.width, isRepeat); + if (isRepeat) { + imageTopLeft.x = 0; + state.mAnchor.x = 0; + } else { + repeatX = StyleImageLayerRepeat::NoRepeat; + } + } + + if (repeatY == StyleImageLayerRepeat::Space) { + bool isRepeat; + state.mRepeatSize.height = ComputeSpacedRepeatSize( + imageSize.height, bgPositionSize.height, isRepeat); + if (isRepeat) { + imageTopLeft.y = 0; + state.mAnchor.y = 0; + } else { + repeatY = StyleImageLayerRepeat::NoRepeat; + } + } + + imageTopLeft += positionArea.TopLeft(); + state.mAnchor += positionArea.TopLeft(); + state.mDestArea = nsRect(imageTopLeft + aBorderArea.TopLeft(), imageSize); + state.mFillArea = state.mDestArea; + + ExtendMode repeatMode = ExtendMode::CLAMP; + if (repeatX == StyleImageLayerRepeat::Repeat || + repeatX == StyleImageLayerRepeat::Round || + repeatX == StyleImageLayerRepeat::Space) { + state.mFillArea.x = bgClipRect.x; + state.mFillArea.width = bgClipRect.width; + repeatMode = ExtendMode::REPEAT_X; + } + if (repeatY == StyleImageLayerRepeat::Repeat || + repeatY == StyleImageLayerRepeat::Round || + repeatY == StyleImageLayerRepeat::Space) { + state.mFillArea.y = bgClipRect.y; + state.mFillArea.height = bgClipRect.height; + + /*** + * We're repeating on the X axis already, + * so if we have to repeat in the Y axis, + * we really need to repeat in both directions. + */ + if (repeatMode == ExtendMode::REPEAT_X) { + repeatMode = ExtendMode::REPEAT; + } else { + repeatMode = ExtendMode::REPEAT_Y; + } + } + state.mImageRenderer.SetExtendMode(repeatMode); + state.mImageRenderer.SetMaskOp(aLayer.mMaskMode); + + state.mFillArea.IntersectRect(state.mFillArea, bgClipRect); + + return state; +} + +nsRect nsCSSRendering::GetBackgroundLayerRect( + nsPresContext* aPresContext, nsIFrame* aForFrame, const nsRect& aBorderArea, + const nsRect& aClipRect, const nsStyleImageLayers::Layer& aLayer, + uint32_t aFlags) { + Sides skipSides = aForFrame->GetSkipSides(); + nsRect borderArea = + BoxDecorationRectForBackground(aForFrame, aBorderArea, skipSides); + nsBackgroundLayerState state = PrepareImageLayer( + aPresContext, aForFrame, aFlags, borderArea, aClipRect, aLayer); + return state.mFillArea; +} + +// Begin table border-collapsing section +// These functions were written to not disrupt the normal ones and yet satisfy +// some additional requirements At some point, all functions should be unified +// to include the additional functionality that these provide + +static nscoord RoundIntToPixel(nscoord aValue, nscoord aOneDevPixel, + bool aRoundDown = false) { + if (aOneDevPixel <= 0) { + // We must be rendering to a device that has a resolution greater than + // one device pixel! + // In that case, aValue is as accurate as it's going to get. + return aValue; + } + + nscoord halfPixel = NSToCoordRound(aOneDevPixel / 2.0f); + nscoord extra = aValue % aOneDevPixel; + nscoord finalValue = (!aRoundDown && (extra >= halfPixel)) + ? aValue + (aOneDevPixel - extra) + : aValue - extra; + return finalValue; +} + +static nscoord RoundFloatToPixel(float aValue, nscoord aOneDevPixel, + bool aRoundDown = false) { + return RoundIntToPixel(NSToCoordRound(aValue), aOneDevPixel, aRoundDown); +} + +static void SetPoly(const Rect& aRect, Point* poly) { + poly[0].x = aRect.x; + poly[0].y = aRect.y; + poly[1].x = aRect.x + aRect.width; + poly[1].y = aRect.y; + poly[2].x = aRect.x + aRect.width; + poly[2].y = aRect.y + aRect.height; + poly[3].x = aRect.x; + poly[3].y = aRect.y + aRect.height; +} + +static void DrawDashedSegment(DrawTarget& aDrawTarget, nsRect aRect, + nscoord aDashLength, nscolor aColor, + int32_t aAppUnitsPerDevPixel, bool aHorizontal) { + ColorPattern color(ToDeviceColor(aColor)); + DrawOptions drawOptions(1.f, CompositionOp::OP_OVER, AntialiasMode::NONE); + StrokeOptions strokeOptions; + + Float dash[2]; + dash[0] = Float(aDashLength) / aAppUnitsPerDevPixel; + dash[1] = dash[0]; + + strokeOptions.mDashPattern = dash; + strokeOptions.mDashLength = MOZ_ARRAY_LENGTH(dash); + + if (aHorizontal) { + nsPoint left = (aRect.TopLeft() + aRect.BottomLeft()) / 2; + nsPoint right = (aRect.TopRight() + aRect.BottomRight()) / 2; + strokeOptions.mLineWidth = Float(aRect.height) / aAppUnitsPerDevPixel; + StrokeLineWithSnapping(left, right, aAppUnitsPerDevPixel, aDrawTarget, + color, strokeOptions, drawOptions); + } else { + nsPoint top = (aRect.TopLeft() + aRect.TopRight()) / 2; + nsPoint bottom = (aRect.BottomLeft() + aRect.BottomRight()) / 2; + strokeOptions.mLineWidth = Float(aRect.width) / aAppUnitsPerDevPixel; + StrokeLineWithSnapping(top, bottom, aAppUnitsPerDevPixel, aDrawTarget, + color, strokeOptions, drawOptions); + } +} + +static void DrawSolidBorderSegment( + DrawTarget& aDrawTarget, nsRect aRect, nscolor aColor, + int32_t aAppUnitsPerDevPixel, + mozilla::Side aStartBevelSide = mozilla::eSideTop, + nscoord aStartBevelOffset = 0, + mozilla::Side aEndBevelSide = mozilla::eSideTop, + nscoord aEndBevelOffset = 0) { + ColorPattern color(ToDeviceColor(aColor)); + DrawOptions drawOptions(1.f, CompositionOp::OP_OVER, AntialiasMode::NONE); + + nscoord oneDevPixel = NSIntPixelsToAppUnits(1, aAppUnitsPerDevPixel); + // We don't need to bevel single pixel borders + if ((aRect.width == oneDevPixel) || (aRect.height == oneDevPixel) || + ((0 == aStartBevelOffset) && (0 == aEndBevelOffset))) { + // simple rectangle + aDrawTarget.FillRect( + NSRectToSnappedRect(aRect, aAppUnitsPerDevPixel, aDrawTarget), color, + drawOptions); + } else { + // polygon with beveling + Point poly[4]; + SetPoly(NSRectToSnappedRect(aRect, aAppUnitsPerDevPixel, aDrawTarget), + poly); + + Float startBevelOffset = + NSAppUnitsToFloatPixels(aStartBevelOffset, aAppUnitsPerDevPixel); + switch (aStartBevelSide) { + case eSideTop: + poly[0].x += startBevelOffset; + break; + case eSideBottom: + poly[3].x += startBevelOffset; + break; + case eSideRight: + poly[1].y += startBevelOffset; + break; + case eSideLeft: + poly[0].y += startBevelOffset; + } + + Float endBevelOffset = + NSAppUnitsToFloatPixels(aEndBevelOffset, aAppUnitsPerDevPixel); + switch (aEndBevelSide) { + case eSideTop: + poly[1].x -= endBevelOffset; + break; + case eSideBottom: + poly[2].x -= endBevelOffset; + break; + case eSideRight: + poly[2].y -= endBevelOffset; + break; + case eSideLeft: + poly[3].y -= endBevelOffset; + } + + RefPtr<PathBuilder> builder = aDrawTarget.CreatePathBuilder(); + builder->MoveTo(poly[0]); + builder->LineTo(poly[1]); + builder->LineTo(poly[2]); + builder->LineTo(poly[3]); + builder->Close(); + RefPtr<Path> path = builder->Finish(); + aDrawTarget.Fill(path, color, drawOptions); + } +} + +static void GetDashInfo(nscoord aBorderLength, nscoord aDashLength, + nscoord aOneDevPixel, int32_t& aNumDashSpaces, + nscoord& aStartDashLength, nscoord& aEndDashLength) { + aNumDashSpaces = 0; + if (aStartDashLength + aDashLength + aEndDashLength >= aBorderLength) { + aStartDashLength = aBorderLength; + aEndDashLength = 0; + } else { + aNumDashSpaces = + (aBorderLength - aDashLength) / (2 * aDashLength); // round down + nscoord extra = aBorderLength - aStartDashLength - aEndDashLength - + (((2 * aNumDashSpaces) - 1) * aDashLength); + if (extra > 0) { + nscoord half = RoundIntToPixel(extra / 2, aOneDevPixel); + aStartDashLength += half; + aEndDashLength += (extra - half); + } + } +} + +void nsCSSRendering::DrawTableBorderSegment( + DrawTarget& aDrawTarget, StyleBorderStyle aBorderStyle, + nscolor aBorderColor, const nsRect& aBorder, int32_t aAppUnitsPerDevPixel, + mozilla::Side aStartBevelSide, nscoord aStartBevelOffset, + mozilla::Side aEndBevelSide, nscoord aEndBevelOffset) { + bool horizontal = + ((eSideTop == aStartBevelSide) || (eSideBottom == aStartBevelSide)); + nscoord oneDevPixel = NSIntPixelsToAppUnits(1, aAppUnitsPerDevPixel); + + if ((oneDevPixel >= aBorder.width) || (oneDevPixel >= aBorder.height) || + (StyleBorderStyle::Dashed == aBorderStyle) || + (StyleBorderStyle::Dotted == aBorderStyle)) { + // no beveling for 1 pixel border, dash or dot + aStartBevelOffset = 0; + aEndBevelOffset = 0; + } + + switch (aBorderStyle) { + case StyleBorderStyle::None: + case StyleBorderStyle::Hidden: + // NS_ASSERTION(false, "style of none or hidden"); + break; + case StyleBorderStyle::Dotted: + case StyleBorderStyle::Dashed: { + nscoord dashLength = + (StyleBorderStyle::Dashed == aBorderStyle) ? DASH_LENGTH : DOT_LENGTH; + // make the dash length proportional to the border thickness + dashLength *= (horizontal) ? aBorder.height : aBorder.width; + // make the min dash length for the ends 1/2 the dash length + nscoord minDashLength = + (StyleBorderStyle::Dashed == aBorderStyle) + ? RoundFloatToPixel(((float)dashLength) / 2.0f, + aAppUnitsPerDevPixel) + : dashLength; + minDashLength = std::max(minDashLength, oneDevPixel); + nscoord numDashSpaces = 0; + nscoord startDashLength = minDashLength; + nscoord endDashLength = minDashLength; + if (horizontal) { + GetDashInfo(aBorder.width, dashLength, aAppUnitsPerDevPixel, + numDashSpaces, startDashLength, endDashLength); + nsRect rect(aBorder.x, aBorder.y, startDashLength, aBorder.height); + DrawSolidBorderSegment(aDrawTarget, rect, aBorderColor, + aAppUnitsPerDevPixel); + + rect.x += startDashLength + dashLength; + rect.width = + aBorder.width - (startDashLength + endDashLength + dashLength); + DrawDashedSegment(aDrawTarget, rect, dashLength, aBorderColor, + aAppUnitsPerDevPixel, horizontal); + + rect.x += rect.width; + rect.width = endDashLength; + DrawSolidBorderSegment(aDrawTarget, rect, aBorderColor, + aAppUnitsPerDevPixel); + } else { + GetDashInfo(aBorder.height, dashLength, aAppUnitsPerDevPixel, + numDashSpaces, startDashLength, endDashLength); + nsRect rect(aBorder.x, aBorder.y, aBorder.width, startDashLength); + DrawSolidBorderSegment(aDrawTarget, rect, aBorderColor, + aAppUnitsPerDevPixel); + + rect.y += rect.height + dashLength; + rect.height = + aBorder.height - (startDashLength + endDashLength + dashLength); + DrawDashedSegment(aDrawTarget, rect, dashLength, aBorderColor, + aAppUnitsPerDevPixel, horizontal); + + rect.y += rect.height; + rect.height = endDashLength; + DrawSolidBorderSegment(aDrawTarget, rect, aBorderColor, + aAppUnitsPerDevPixel); + } + } break; + default: + AutoTArray<SolidBeveledBorderSegment, 3> segments; + GetTableBorderSolidSegments( + segments, aBorderStyle, aBorderColor, aBorder, aAppUnitsPerDevPixel, + aStartBevelSide, aStartBevelOffset, aEndBevelSide, aEndBevelOffset); + for (const auto& segment : segments) { + DrawSolidBorderSegment( + aDrawTarget, segment.mRect, segment.mColor, aAppUnitsPerDevPixel, + segment.mStartBevel.mSide, segment.mStartBevel.mOffset, + segment.mEndBevel.mSide, segment.mEndBevel.mOffset); + } + break; + } +} + +void nsCSSRendering::GetTableBorderSolidSegments( + nsTArray<SolidBeveledBorderSegment>& aSegments, + StyleBorderStyle aBorderStyle, nscolor aBorderColor, const nsRect& aBorder, + int32_t aAppUnitsPerDevPixel, mozilla::Side aStartBevelSide, + nscoord aStartBevelOffset, mozilla::Side aEndBevelSide, + nscoord aEndBevelOffset) { + const bool horizontal = + eSideTop == aStartBevelSide || eSideBottom == aStartBevelSide; + const nscoord oneDevPixel = NSIntPixelsToAppUnits(1, aAppUnitsPerDevPixel); + + switch (aBorderStyle) { + case StyleBorderStyle::None: + case StyleBorderStyle::Hidden: + return; + case StyleBorderStyle::Dotted: + case StyleBorderStyle::Dashed: + MOZ_ASSERT_UNREACHABLE("Caller should have checked"); + return; + case StyleBorderStyle::Groove: + case StyleBorderStyle::Ridge: + if ((horizontal && (oneDevPixel >= aBorder.height)) || + (!horizontal && (oneDevPixel >= aBorder.width))) { + aSegments.AppendElement( + SolidBeveledBorderSegment{aBorder, + aBorderColor, + {aStartBevelSide, aStartBevelOffset}, + {aEndBevelSide, aEndBevelOffset}}); + } else { + nscoord startBevel = + (aStartBevelOffset > 0) + ? RoundFloatToPixel(0.5f * (float)aStartBevelOffset, + aAppUnitsPerDevPixel, true) + : 0; + nscoord endBevel = + (aEndBevelOffset > 0) + ? RoundFloatToPixel(0.5f * (float)aEndBevelOffset, + aAppUnitsPerDevPixel, true) + : 0; + mozilla::Side ridgeGrooveSide = (horizontal) ? eSideTop : eSideLeft; + // FIXME: In theory, this should use the visited-dependent + // background color, but I don't care. + nscolor bevelColor = + MakeBevelColor(ridgeGrooveSide, aBorderStyle, aBorderColor); + nsRect rect(aBorder); + nscoord half; + if (horizontal) { // top, bottom + half = RoundFloatToPixel(0.5f * (float)aBorder.height, + aAppUnitsPerDevPixel); + rect.height = half; + if (eSideTop == aStartBevelSide) { + rect.x += startBevel; + rect.width -= startBevel; + } + if (eSideTop == aEndBevelSide) { + rect.width -= endBevel; + } + aSegments.AppendElement( + SolidBeveledBorderSegment{rect, + bevelColor, + {aStartBevelSide, startBevel}, + {aEndBevelSide, endBevel}}); + } else { // left, right + half = RoundFloatToPixel(0.5f * (float)aBorder.width, + aAppUnitsPerDevPixel); + rect.width = half; + if (eSideLeft == aStartBevelSide) { + rect.y += startBevel; + rect.height -= startBevel; + } + if (eSideLeft == aEndBevelSide) { + rect.height -= endBevel; + } + aSegments.AppendElement( + SolidBeveledBorderSegment{rect, + bevelColor, + {aStartBevelSide, startBevel}, + {aEndBevelSide, endBevel}}); + } + + rect = aBorder; + ridgeGrooveSide = + (eSideTop == ridgeGrooveSide) ? eSideBottom : eSideRight; + // FIXME: In theory, this should use the visited-dependent + // background color, but I don't care. + bevelColor = + MakeBevelColor(ridgeGrooveSide, aBorderStyle, aBorderColor); + if (horizontal) { + rect.y = rect.y + half; + rect.height = aBorder.height - half; + if (eSideBottom == aStartBevelSide) { + rect.x += startBevel; + rect.width -= startBevel; + } + if (eSideBottom == aEndBevelSide) { + rect.width -= endBevel; + } + aSegments.AppendElement( + SolidBeveledBorderSegment{rect, + bevelColor, + {aStartBevelSide, startBevel}, + {aEndBevelSide, endBevel}}); + } else { + rect.x = rect.x + half; + rect.width = aBorder.width - half; + if (eSideRight == aStartBevelSide) { + rect.y += aStartBevelOffset - startBevel; + rect.height -= startBevel; + } + if (eSideRight == aEndBevelSide) { + rect.height -= endBevel; + } + aSegments.AppendElement( + SolidBeveledBorderSegment{rect, + bevelColor, + {aStartBevelSide, startBevel}, + {aEndBevelSide, endBevel}}); + } + } + break; + case StyleBorderStyle::Double: + // We can only do "double" borders if the thickness of the border + // is more than 2px. Otherwise, we fall through to painting a + // solid border. + if ((aBorder.width > 2 * oneDevPixel || horizontal) && + (aBorder.height > 2 * oneDevPixel || !horizontal)) { + nscoord startBevel = + (aStartBevelOffset > 0) + ? RoundFloatToPixel(0.333333f * (float)aStartBevelOffset, + aAppUnitsPerDevPixel) + : 0; + nscoord endBevel = + (aEndBevelOffset > 0) + ? RoundFloatToPixel(0.333333f * (float)aEndBevelOffset, + aAppUnitsPerDevPixel) + : 0; + if (horizontal) { // top, bottom + nscoord thirdHeight = RoundFloatToPixel( + 0.333333f * (float)aBorder.height, aAppUnitsPerDevPixel); + + // draw the top line or rect + nsRect topRect(aBorder.x, aBorder.y, aBorder.width, thirdHeight); + if (eSideTop == aStartBevelSide) { + topRect.x += aStartBevelOffset - startBevel; + topRect.width -= aStartBevelOffset - startBevel; + } + if (eSideTop == aEndBevelSide) { + topRect.width -= aEndBevelOffset - endBevel; + } + + aSegments.AppendElement( + SolidBeveledBorderSegment{topRect, + aBorderColor, + {aStartBevelSide, startBevel}, + {aEndBevelSide, endBevel}}); + + // draw the botom line or rect + nscoord heightOffset = aBorder.height - thirdHeight; + nsRect bottomRect(aBorder.x, aBorder.y + heightOffset, aBorder.width, + aBorder.height - heightOffset); + if (eSideBottom == aStartBevelSide) { + bottomRect.x += aStartBevelOffset - startBevel; + bottomRect.width -= aStartBevelOffset - startBevel; + } + if (eSideBottom == aEndBevelSide) { + bottomRect.width -= aEndBevelOffset - endBevel; + } + aSegments.AppendElement( + SolidBeveledBorderSegment{bottomRect, + aBorderColor, + {aStartBevelSide, startBevel}, + {aEndBevelSide, endBevel}}); + } else { // left, right + nscoord thirdWidth = RoundFloatToPixel( + 0.333333f * (float)aBorder.width, aAppUnitsPerDevPixel); + + nsRect leftRect(aBorder.x, aBorder.y, thirdWidth, aBorder.height); + if (eSideLeft == aStartBevelSide) { + leftRect.y += aStartBevelOffset - startBevel; + leftRect.height -= aStartBevelOffset - startBevel; + } + if (eSideLeft == aEndBevelSide) { + leftRect.height -= aEndBevelOffset - endBevel; + } + + aSegments.AppendElement( + SolidBeveledBorderSegment{leftRect, + aBorderColor, + {aStartBevelSide, startBevel}, + {aEndBevelSide, endBevel}}); + + nscoord widthOffset = aBorder.width - thirdWidth; + nsRect rightRect(aBorder.x + widthOffset, aBorder.y, + aBorder.width - widthOffset, aBorder.height); + if (eSideRight == aStartBevelSide) { + rightRect.y += aStartBevelOffset - startBevel; + rightRect.height -= aStartBevelOffset - startBevel; + } + if (eSideRight == aEndBevelSide) { + rightRect.height -= aEndBevelOffset - endBevel; + } + aSegments.AppendElement( + SolidBeveledBorderSegment{rightRect, + aBorderColor, + {aStartBevelSide, startBevel}, + {aEndBevelSide, endBevel}}); + } + break; + } + // else fall through to solid + [[fallthrough]]; + case StyleBorderStyle::Solid: + aSegments.AppendElement( + SolidBeveledBorderSegment{aBorder, + aBorderColor, + {aStartBevelSide, aStartBevelOffset}, + {aEndBevelSide, aEndBevelOffset}}); + break; + case StyleBorderStyle::Outset: + case StyleBorderStyle::Inset: + MOZ_ASSERT_UNREACHABLE( + "inset, outset should have been converted to groove, ridge"); + break; + } +} + +// End table border-collapsing section + +Rect nsCSSRendering::ExpandPaintingRectForDecorationLine( + nsIFrame* aFrame, const StyleTextDecorationStyle aStyle, + const Rect& aClippedRect, const Float aICoordInFrame, + const Float aCycleLength, bool aVertical) { + switch (aStyle) { + case StyleTextDecorationStyle::Dotted: + case StyleTextDecorationStyle::Dashed: + case StyleTextDecorationStyle::Wavy: + break; + default: + NS_ERROR("Invalid style was specified"); + return aClippedRect; + } + + nsBlockFrame* block = nullptr; + // Note that when we paint the decoration lines in relative positioned + // box, we should paint them like all of the boxes are positioned as static. + nscoord framePosInBlockAppUnits = 0; + for (nsIFrame* f = aFrame; f; f = f->GetParent()) { + block = do_QueryFrame(f); + if (block) { + break; + } + framePosInBlockAppUnits += + aVertical ? f->GetNormalPosition().y : f->GetNormalPosition().x; + } + + NS_ENSURE_TRUE(block, aClippedRect); + + nsPresContext* pc = aFrame->PresContext(); + Float framePosInBlock = + Float(pc->AppUnitsToGfxUnits(framePosInBlockAppUnits)); + int32_t rectPosInBlock = int32_t(NS_round(framePosInBlock + aICoordInFrame)); + int32_t extraStartEdge = + rectPosInBlock - (rectPosInBlock / int32_t(aCycleLength) * aCycleLength); + Rect rect(aClippedRect); + if (aVertical) { + rect.y -= extraStartEdge; + rect.height += extraStartEdge; + } else { + rect.x -= extraStartEdge; + rect.width += extraStartEdge; + } + return rect; +} + +// Converts a GfxFont to an SkFont +// Either returns true if it was successful, or false if something went wrong +static bool GetSkFontFromGfxFont(DrawTarget& aDrawTarget, gfxFont* aFont, + SkFont& aSkFont) { + RefPtr<ScaledFont> scaledFont = aFont->GetScaledFont(&aDrawTarget); + if (!scaledFont) { + return false; + } + + ScaledFontBase* fontBase = static_cast<ScaledFontBase*>(scaledFont.get()); + + SkTypeface* typeface = fontBase->GetSkTypeface(); + if (!typeface) { + return false; + } + + aSkFont = SkFont(sk_ref_sp(typeface), SkFloatToScalar(fontBase->GetSize())); + return true; +} + +// Computes data used to position the decoration line within a +// SkTextBlob, data is returned through aBounds +static void GetPositioning( + const nsCSSRendering::PaintDecorationLineParams& aParams, const Rect& aRect, + Float aOneCSSPixel, Float aCenterBaselineOffset, SkScalar aBounds[]) { + /** + * How Positioning in Skia Works + * Take the letter "n" for example + * We set textPos as 0, 0 + * This is represented in Skia like so (not to scale) + * ^ + * -10px | _ __ + * | | '_ \ + * -5px | | | | | + * y-axis | |_| |_| + * (0,0) -----------------------> + * | 5px 10px + * 5px | + * | + * 10px | + * v + * 0 on the x axis is a line that touches the bottom of the n + * (0,0) is the bottom left-hand corner of the n character + * Moving "up" from the n is going in a negative y direction + * Moving "down" from the n is going in a positive y direction + * + * The intercepts that are returned in this arrangement will be + * offset by the original point it starts at. (This happens in + * the SkipInk function below). + * + * In Skia, text MUST be laid out such that the next character + * in the RunBuffer is further along the x-axis than the previous + * character, otherwise there is undefined/strange behavior. + */ + + Float rectThickness = aParams.vertical ? aRect.Width() : aRect.Height(); + + // the upper and lower lines/edges of the under or over line + SkScalar upperLine, lowerLine; + if (aParams.decoration == mozilla::StyleTextDecorationLine::OVERLINE) { + lowerLine = + -aParams.offset + aParams.defaultLineThickness - aCenterBaselineOffset; + upperLine = lowerLine - rectThickness; + } else { + // underlines in vertical text are offset from the center of + // the text, and not the baseline + // Skia sets the text at it's baseline so we have to offset it + // for text in vertical-* writing modes + upperLine = -aParams.offset - aCenterBaselineOffset; + lowerLine = upperLine + rectThickness; + } + + // set up the bounds, add in a little padding to the thickness of the line + // (unless the line is <= 1 CSS pixel thick) + Float lineThicknessPadding = aParams.lineSize.height > aOneCSSPixel + ? 0.25f * aParams.lineSize.height + : 0; + // don't allow padding greater than 0.75 CSS pixel + lineThicknessPadding = std::min(lineThicknessPadding, 0.75f * aOneCSSPixel); + aBounds[0] = upperLine - lineThicknessPadding; + aBounds[1] = lowerLine + lineThicknessPadding; +} + +// positions an individual glyph according to the given offset +static SkPoint GlyphPosition(const gfxTextRun::DetailedGlyph& aGlyph, + const SkPoint& aTextPos, + int32_t aAppUnitsPerDevPixel) { + SkPoint point = {aGlyph.mOffset.x, aGlyph.mOffset.y}; + + // convert to device pixels + point.fX /= (float)aAppUnitsPerDevPixel; + point.fY /= (float)aAppUnitsPerDevPixel; + + // add offsets + point.fX += aTextPos.fX; + point.fY += aTextPos.fY; + return point; +} + +// returns a count of all the glyphs that will be rendered +// excludes ligature continuations, includes the number of individual +// glyph records. This includes the number of DetailedGlyphs that a single +// CompressedGlyph record points to. This function is necessary because Skia +// needs the total length of glyphs to add to it's run buffer before it creates +// the RunBuffer object, and this cannot be resized later. +static uint32_t CountAllGlyphs( + const gfxTextRun* aTextRun, + const gfxTextRun::CompressedGlyph* aCompressedGlyph, uint32_t aStringStart, + uint32_t aStringEnd) { + uint32_t totalGlyphCount = 0; + + for (const gfxTextRun::CompressedGlyph* cg = aCompressedGlyph + aStringStart; + cg < aCompressedGlyph + aStringEnd; ++cg) { + totalGlyphCount += cg->IsSimpleGlyph() ? 1 : cg->GetGlyphCount(); + } + + return totalGlyphCount; +} + +static void AddDetailedGlyph(const SkTextBlobBuilder::RunBuffer& aRunBuffer, + const gfxTextRun::DetailedGlyph& aGlyph, + int aIndex, float aAppUnitsPerDevPixel, + SkPoint& aTextPos) { + // add glyph ID to the run buffer at i + aRunBuffer.glyphs[aIndex] = aGlyph.mGlyphID; + + // position the glyph correctly using the detailed offsets + SkPoint position = GlyphPosition(aGlyph, aTextPos, aAppUnitsPerDevPixel); + aRunBuffer.pos[2 * aIndex] = position.fX; + aRunBuffer.pos[(2 * aIndex) + 1] = position.fY; + + // increase aTextPos.fx by the advance + aTextPos.fX += ((float)aGlyph.mAdvance / aAppUnitsPerDevPixel); +} + +static void AddSimpleGlyph(const SkTextBlobBuilder::RunBuffer& aRunBuffer, + const gfxTextRun::CompressedGlyph& aGlyph, + int aIndex, float aAppUnitsPerDevPixel, + SkPoint& aTextPos) { + aRunBuffer.glyphs[aIndex] = aGlyph.GetSimpleGlyph(); + + // simple glyphs are offset from 0, so we'll just use textPos + aRunBuffer.pos[2 * aIndex] = aTextPos.fX; + aRunBuffer.pos[(2 * aIndex) + 1] = aTextPos.fY; + + // increase aTextPos.fX by the advance + aTextPos.fX += ((float)aGlyph.GetSimpleAdvance() / aAppUnitsPerDevPixel); +} + +// Sets up a Skia TextBlob of the specified font, text position, and made up of +// the glyphs between aStringStart and aStringEnd. Handles RTL and LTR text +// and positions each glyph within the text blob +static sk_sp<const SkTextBlob> CreateTextBlob( + const gfxTextRun* aTextRun, + const gfxTextRun::CompressedGlyph* aCompressedGlyph, const SkFont& aFont, + const gfxTextRun::PropertyProvider::Spacing* aSpacing, + uint32_t aStringStart, uint32_t aStringEnd, float aAppUnitsPerDevPixel, + SkPoint& aTextPos, int32_t& aSpacingOffset) { + // allocate space for the run buffer, then fill it with the glyphs + uint32_t len = + CountAllGlyphs(aTextRun, aCompressedGlyph, aStringStart, aStringEnd); + if (len <= 0) { + return nullptr; + } + + SkTextBlobBuilder builder; + const SkTextBlobBuilder::RunBuffer& run = builder.allocRunPos(aFont, len); + + // RTL text should be read in by glyph starting at aStringEnd - 1 down until + // aStringStart. + bool isRTL = aTextRun->IsRightToLeft(); + uint32_t currIndex = isRTL ? aStringEnd - 1 : aStringStart; // textRun index + // currIndex will be advanced by |step| until it reaches |limit|, which is the + // final index to be handled (NOT one beyond the final index) + int step = isRTL ? -1 : 1; + uint32_t limit = isRTL ? aStringStart : aStringEnd - 1; + + uint32_t i = 0; // index into the SkTextBlob we're building + while (true) { + // Loop exit test is below, just before we update currIndex. + aTextPos.fX += + isRTL ? aSpacing[aSpacingOffset].mAfter / aAppUnitsPerDevPixel + : aSpacing[aSpacingOffset].mBefore / aAppUnitsPerDevPixel; + + if (aCompressedGlyph[currIndex].IsSimpleGlyph()) { + MOZ_ASSERT(i < len, "glyph count error!"); + AddSimpleGlyph(run, aCompressedGlyph[currIndex], i, aAppUnitsPerDevPixel, + aTextPos); + i++; + } else { + // if it's detailed, potentially add multiple into run.glyphs + uint32_t count = aCompressedGlyph[currIndex].GetGlyphCount(); + if (count > 0) { + gfxTextRun::DetailedGlyph* detailGlyph = + aTextRun->GetDetailedGlyphs(currIndex); + for (uint32_t d = isRTL ? count - 1 : 0; count; count--, d += step) { + MOZ_ASSERT(i < len, "glyph count error!"); + AddDetailedGlyph(run, detailGlyph[d], i, aAppUnitsPerDevPixel, + aTextPos); + i++; + } + } + } + aTextPos.fX += isRTL + ? aSpacing[aSpacingOffset].mBefore / aAppUnitsPerDevPixel + : aSpacing[aSpacingOffset].mAfter / aAppUnitsPerDevPixel; + aSpacingOffset += step; + + if (currIndex == limit) { + break; + } + currIndex += step; + } + + MOZ_ASSERT(i == len, "glyph count error!"); + + return builder.make(); +} + +// Given a TextBlob, the bounding lines, and the set of current intercepts this +// function adds the intercepts for the current TextBlob into the given set of +// previoulsy calculated intercepts. This set is either of length 0, or a +// multiple of 2 (since every intersection with a piece of text results in two +// intercepts: entering/exiting) +static void GetTextIntercepts(const sk_sp<const SkTextBlob>& aBlob, + const SkScalar aBounds[], + nsTArray<SkScalar>& aIntercepts) { + // It's possible that we'll encounter a Windows exception deep inside + // Skia's DirectWrite code while trying to get the intercepts. To avoid + // crashing in this case, catch any such exception here and discard the + // newly-added (and incompletely filled in) elements. + int count = 0; + MOZ_SEH_TRY { + // https://skia.org/user/api/SkTextBlob_Reference#Text_Blob_Text_Intercepts + count = aBlob->getIntercepts(aBounds, nullptr); + if (count < 2) { + return; + } + aBlob->getIntercepts(aBounds, aIntercepts.AppendElements(count)); + } + MOZ_SEH_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { + gfxCriticalNote << "Exception occurred getting text intercepts"; + aIntercepts.TruncateLength(aIntercepts.Length() - count); + } +} + +// This function, given a set of intercepts that represent each intersection +// between an under/overline and text, makes a series of calls to +// PaintDecorationLineInternal that paints a series of clip rects which +// implement the text-decoration-skip-ink property +// Logic for where to place each clipped rect, and the length of each rect is +// included here +static void SkipInk(nsIFrame* aFrame, DrawTarget& aDrawTarget, + const nsCSSRendering::PaintDecorationLineParams& aParams, + const nsTArray<SkScalar>& aIntercepts, Float aPadding, + Rect& aRect) { + nsCSSRendering::PaintDecorationLineParams clipParams = aParams; + int length = aIntercepts.Length(); + + SkScalar startIntercept = 0; + SkScalar endIntercept = 0; + + // keep track of the direction we are drawing the clipped rects in + // for sideways text, our intercepts from the first glyph are actually + // decreasing (towards the top edge of the page), so we use a negative + // direction + Float dir = 1.0f; + Float lineStart = aParams.vertical ? aParams.pt.y : aParams.pt.x; + Float lineEnd = lineStart + aParams.lineSize.width; + if (aParams.sidewaysLeft) { + dir = -1.0f; + std::swap(lineStart, lineEnd); + } + + for (int i = 0; i <= length; i += 2) { + // handle start/end edge cases and set up general case + startIntercept = (i > 0) ? (dir * aIntercepts[i - 1]) + lineStart + : lineStart - (dir * aPadding); + endIntercept = (i < length) ? (dir * aIntercepts[i]) + lineStart + : lineEnd + (dir * aPadding); + + // remove padding at both ends for width + // the start of the line is calculated so the padding removes just + // enough so that the line starts at its normal position + clipParams.lineSize.width = + (dir * (endIntercept - startIntercept)) - (2.0 * aPadding); + + // Don't draw decoration lines that have a smaller width than 1, or half + // the line-end padding dimension. + if (clipParams.lineSize.width < std::max(aPadding * 0.5, 1.0)) { + continue; + } + + // Start the line right after the intercept's location plus room for + // padding; snap the rect edges to device pixels for consistent rendering + // of dots across separate fragments of a dotted line. + if (aParams.vertical) { + clipParams.pt.y = aParams.sidewaysLeft ? endIntercept + aPadding + : startIntercept + aPadding; + aRect.y = std::floor(clipParams.pt.y + 0.5); + aRect.SetBottomEdge( + std::floor(clipParams.pt.y + clipParams.lineSize.width + 0.5)); + } else { + clipParams.pt.x = startIntercept + aPadding; + aRect.x = std::floor(clipParams.pt.x + 0.5); + aRect.SetRightEdge( + std::floor(clipParams.pt.x + clipParams.lineSize.width + 0.5)); + } + + nsCSSRendering::PaintDecorationLineInternal(aFrame, aDrawTarget, clipParams, + aRect); + } +} + +void nsCSSRendering::PaintDecorationLine( + nsIFrame* aFrame, DrawTarget& aDrawTarget, + const PaintDecorationLineParams& aParams) { + NS_ASSERTION(aParams.style != StyleTextDecorationStyle::None, + "aStyle is none"); + + Rect rect = ToRect(GetTextDecorationRectInternal(aParams.pt, aParams)); + if (rect.IsEmpty() || !rect.Intersects(aParams.dirtyRect)) { + return; + } + + if (aParams.decoration != StyleTextDecorationLine::UNDERLINE && + aParams.decoration != StyleTextDecorationLine::OVERLINE && + aParams.decoration != StyleTextDecorationLine::LINE_THROUGH) { + MOZ_ASSERT_UNREACHABLE("Invalid text decoration value"); + return; + } + + // Check if decoration line will skip past ascenders/descenders + // text-decoration-skip-ink only applies to overlines/underlines + mozilla::StyleTextDecorationSkipInk skipInk = + aFrame->StyleText()->mTextDecorationSkipInk; + bool skipInkEnabled = + skipInk != mozilla::StyleTextDecorationSkipInk::None && + aParams.decoration != StyleTextDecorationLine::LINE_THROUGH; + + if (!skipInkEnabled || aParams.glyphRange.Length() == 0) { + PaintDecorationLineInternal(aFrame, aDrawTarget, aParams, rect); + return; + } + + // check if the frame is a text frame or not + nsTextFrame* textFrame = nullptr; + if (aFrame->IsTextFrame()) { + textFrame = static_cast<nsTextFrame*>(aFrame); + } else { + PaintDecorationLineInternal(aFrame, aDrawTarget, aParams, rect); + return; + } + + // get text run and current text offset (for line wrapping) + gfxTextRun* textRun = + textFrame->GetTextRun(nsTextFrame::TextRunType::eInflated); + + // used for conversions from app units to device pixels + int32_t appUnitsPerDevPixel = aFrame->PresContext()->AppUnitsPerDevPixel(); + + // pointer to the array of glyphs for this TextRun + gfxTextRun::CompressedGlyph* characterGlyphs = textRun->GetCharacterGlyphs(); + + // get positioning info + SkPoint textPos = {0, aParams.baselineOffset}; + SkScalar bounds[] = {0, 0}; + Float oneCSSPixel = aFrame->PresContext()->CSSPixelsToDevPixels(1.0f); + if (!textRun->UseCenterBaseline()) { + GetPositioning(aParams, rect, oneCSSPixel, 0, bounds); + } + + // array for the text intercepts + AutoTArray<SkScalar, 256> intercepts; + + // array for spacing data + AutoTArray<gfxTextRun::PropertyProvider::Spacing, 64> spacing; + spacing.SetLength(aParams.glyphRange.Length()); + if (aParams.provider != nullptr) { + aParams.provider->GetSpacing(aParams.glyphRange, spacing.Elements()); + } + + // loop through each glyph run + // in most cases there will only be one + bool isRTL = textRun->IsRightToLeft(); + int32_t spacingOffset = isRTL ? aParams.glyphRange.Length() - 1 : 0; + gfxTextRun::GlyphRunIterator iter(textRun, aParams.glyphRange, isRTL); + + // For any glyph run where we don't actually do skipping, we'll need to + // advance the current position by its width. + // (For runs we do process, CreateTextBlob will update the position.) + auto currentGlyphRunAdvance = [&]() { + return textRun->GetAdvanceWidth( + gfxTextRun::Range(iter.StringStart(), iter.StringEnd()), + aParams.provider) / + appUnitsPerDevPixel; + }; + + for (; !iter.AtEnd(); iter.NextRun()) { + if (iter.GlyphRun()->mOrientation == + mozilla::gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT || + (iter.GlyphRun()->mIsCJK && + skipInk == mozilla::StyleTextDecorationSkipInk::Auto)) { + // We don't support upright text in vertical modes currently + // (see https://bugzilla.mozilla.org/show_bug.cgi?id=1572294), + // but we do need to update textPos so that following runs will be + // correctly positioned. + // We also don't apply skip-ink to CJK text runs because many fonts + // have an underline that looks really bad if this is done + // (see https://bugzilla.mozilla.org/show_bug.cgi?id=1573249), + // when skip-ink is set to 'auto'. + textPos.fX += currentGlyphRunAdvance(); + continue; + } + + gfxFont* font = iter.GlyphRun()->mFont; + // Don't try to apply skip-ink to 'sbix' fonts like Apple Color Emoji, + // because old macOS (10.9) may crash trying to retrieve glyph paths + // that don't exist. + if (font->GetFontEntry()->HasFontTable(TRUETYPE_TAG('s', 'b', 'i', 'x'))) { + textPos.fX += currentGlyphRunAdvance(); + continue; + } + + // get a Skia version of the glyph run's font + SkFont skiafont; + if (!GetSkFontFromGfxFont(aDrawTarget, font, skiafont)) { + PaintDecorationLineInternal(aFrame, aDrawTarget, aParams, rect); + return; + } + + // Create a text blob with correctly positioned glyphs. This also updates + // textPos.fX with the advance of the glyphs. + sk_sp<const SkTextBlob> textBlob = + CreateTextBlob(textRun, characterGlyphs, skiafont, spacing.Elements(), + iter.StringStart(), iter.StringEnd(), + (float)appUnitsPerDevPixel, textPos, spacingOffset); + + if (!textBlob) { + textPos.fX += currentGlyphRunAdvance(); + continue; + } + + if (textRun->UseCenterBaseline()) { + // writing modes that use a center baseline need to be adjusted on a + // font-by-font basis since Skia lines up the text on a alphabetic + // baseline, but for some vertical-* writing modes the offset is from the + // center. + gfxFont::Metrics metrics = font->GetMetrics(nsFontMetrics::eHorizontal); + Float centerToBaseline = (metrics.emAscent - metrics.emDescent) / 2.0f; + GetPositioning(aParams, rect, oneCSSPixel, centerToBaseline, bounds); + } + + // compute the text intercepts that need to be skipped + GetTextIntercepts(textBlob, bounds, intercepts); + } + bool needsSkipInk = intercepts.Length() > 0; + + if (needsSkipInk) { + // Padding between glyph intercepts and the decoration line: we use the + // decoration line thickness, clamped to a minimum of 1px and a maximum + // of 0.2em. + Float padding = + std::min(std::max(aParams.lineSize.height, oneCSSPixel), + Float(textRun->GetFontGroup()->GetStyle()->size / 5.0)); + SkipInk(aFrame, aDrawTarget, aParams, intercepts, padding, rect); + } else { + PaintDecorationLineInternal(aFrame, aDrawTarget, aParams, rect); + } +} + +void nsCSSRendering::PaintDecorationLineInternal( + nsIFrame* aFrame, DrawTarget& aDrawTarget, + const PaintDecorationLineParams& aParams, Rect aRect) { + Float lineThickness = std::max(NS_round(aParams.lineSize.height), 1.0); + + DeviceColor color = ToDeviceColor(aParams.color); + ColorPattern colorPat(color); + StrokeOptions strokeOptions(lineThickness); + DrawOptions drawOptions; + + Float dash[2]; + + AutoPopClips autoPopClips(&aDrawTarget); + + mozilla::layout::TextDrawTarget* textDrawer = nullptr; + if (aDrawTarget.GetBackendType() == BackendType::WEBRENDER_TEXT) { + textDrawer = static_cast<mozilla::layout::TextDrawTarget*>(&aDrawTarget); + } + + switch (aParams.style) { + case StyleTextDecorationStyle::Solid: + case StyleTextDecorationStyle::Double: + break; + case StyleTextDecorationStyle::Dashed: { + autoPopClips.PushClipRect(aRect); + Float dashWidth = lineThickness * DOT_LENGTH * DASH_LENGTH; + dash[0] = dashWidth; + dash[1] = dashWidth; + strokeOptions.mDashPattern = dash; + strokeOptions.mDashLength = MOZ_ARRAY_LENGTH(dash); + strokeOptions.mLineCap = CapStyle::BUTT; + aRect = ExpandPaintingRectForDecorationLine( + aFrame, aParams.style, aRect, aParams.icoordInFrame, dashWidth * 2, + aParams.vertical); + // We should continue to draw the last dash even if it is not in the rect. + aRect.width += dashWidth; + break; + } + case StyleTextDecorationStyle::Dotted: { + autoPopClips.PushClipRect(aRect); + Float dashWidth = lineThickness * DOT_LENGTH; + if (lineThickness > 2.0) { + dash[0] = 0.f; + dash[1] = dashWidth * 2.f; + strokeOptions.mLineCap = CapStyle::ROUND; + } else { + dash[0] = dashWidth; + dash[1] = dashWidth; + } + strokeOptions.mDashPattern = dash; + strokeOptions.mDashLength = MOZ_ARRAY_LENGTH(dash); + aRect = ExpandPaintingRectForDecorationLine( + aFrame, aParams.style, aRect, aParams.icoordInFrame, dashWidth * 2, + aParams.vertical); + // We should continue to draw the last dot even if it is not in the rect. + aRect.width += dashWidth; + break; + } + case StyleTextDecorationStyle::Wavy: + autoPopClips.PushClipRect(aRect); + if (lineThickness > 2.0) { + drawOptions.mAntialiasMode = AntialiasMode::SUBPIXEL; + } else { + // Don't use anti-aliasing here. Because looks like lighter color wavy + // line at this case. And probably, users don't think the + // non-anti-aliased wavy line is not pretty. + drawOptions.mAntialiasMode = AntialiasMode::NONE; + } + break; + default: + NS_ERROR("Invalid style value!"); + return; + } + + // The block-direction position should be set to the middle of the line. + if (aParams.vertical) { + aRect.x += lineThickness / 2; + } else { + aRect.y += lineThickness / 2; + } + + switch (aParams.style) { + case StyleTextDecorationStyle::Solid: + case StyleTextDecorationStyle::Dotted: + case StyleTextDecorationStyle::Dashed: { + Point p1 = aRect.TopLeft(); + Point p2 = aParams.vertical ? aRect.BottomLeft() : aRect.TopRight(); + if (textDrawer) { + textDrawer->AppendDecoration(p1, p2, lineThickness, aParams.vertical, + color, aParams.style); + } else { + aDrawTarget.StrokeLine(p1, p2, colorPat, strokeOptions, drawOptions); + } + return; + } + case StyleTextDecorationStyle::Double: { + /** + * We are drawing double line as: + * + * +-------------------------------------------+ + * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| ^ + * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| | lineThickness + * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| v + * | | + * | | + * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| ^ + * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| | lineThickness + * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| v + * +-------------------------------------------+ + */ + Point p1a = aRect.TopLeft(); + Point p2a = aParams.vertical ? aRect.BottomLeft() : aRect.TopRight(); + + if (aParams.vertical) { + aRect.width -= lineThickness; + } else { + aRect.height -= lineThickness; + } + + Point p1b = aParams.vertical ? aRect.TopRight() : aRect.BottomLeft(); + Point p2b = aRect.BottomRight(); + + if (textDrawer) { + textDrawer->AppendDecoration(p1a, p2a, lineThickness, aParams.vertical, + color, StyleTextDecorationStyle::Solid); + textDrawer->AppendDecoration(p1b, p2b, lineThickness, aParams.vertical, + color, StyleTextDecorationStyle::Solid); + } else { + aDrawTarget.StrokeLine(p1a, p2a, colorPat, strokeOptions, drawOptions); + aDrawTarget.StrokeLine(p1b, p2b, colorPat, strokeOptions, drawOptions); + } + return; + } + case StyleTextDecorationStyle::Wavy: { + /** + * We are drawing wavy line as: + * + * P: Path, X: Painted pixel + * + * +---------------------------------------+ + * XX|X XXXXXX XXXXXX | + * PP|PX XPPPPPPX XPPPPPPX | ^ + * XX|XPX XPXXXXXXPX XPXXXXXXPX| | + * | XPX XPX XPX XPX XP|X |adv + * | XPXXXXXXPX XPXXXXXXPX X|PX | + * | XPPPPPPX XPPPPPPX |XPX v + * | XXXXXX XXXXXX | XX + * +---------------------------------------+ + * <---><---> ^ + * adv flatLengthAtVertex rightMost + * + * 1. Always starts from top-left of the drawing area, however, we need + * to draw the line from outside of the rect. Because the start + * point of the line is not good style if we draw from inside it. + * 2. First, draw horizontal line from outside the rect to top-left of + * the rect; + * 3. Goes down to bottom of the area at 45 degrees. + * 4. Slides to right horizontaly, see |flatLengthAtVertex|. + * 5. Goes up to top of the area at 45 degrees. + * 6. Slides to right horizontaly. + * 7. Repeat from 2 until reached to right-most edge of the area. + * + * In the vertical case, swap horizontal and vertical coordinates and + * directions in the above description. + */ + + Float& rectICoord = aParams.vertical ? aRect.y : aRect.x; + Float& rectISize = aParams.vertical ? aRect.height : aRect.width; + const Float rectBSize = aParams.vertical ? aRect.width : aRect.height; + + const Float adv = rectBSize - lineThickness; + const Float flatLengthAtVertex = + std::max((lineThickness - 1.0) * 2.0, 1.0); + + // Align the start of wavy lines to the nearest ancestor block. + const Float cycleLength = 2 * (adv + flatLengthAtVertex); + aRect = ExpandPaintingRectForDecorationLine( + aFrame, aParams.style, aRect, aParams.icoordInFrame, cycleLength, + aParams.vertical); + + if (textDrawer) { + // Undo attempted centering + Float& rectBCoord = aParams.vertical ? aRect.x : aRect.y; + rectBCoord -= lineThickness / 2; + + textDrawer->AppendWavyDecoration(aRect, lineThickness, aParams.vertical, + color); + return; + } + + // figure out if we can trim whole cycles from the left and right edges + // of the line, to try and avoid creating an unnecessarily long and + // complex path (but don't do this for webrender, ) + const Float dirtyRectICoord = + aParams.vertical ? aParams.dirtyRect.y : aParams.dirtyRect.x; + int32_t skipCycles = floor((dirtyRectICoord - rectICoord) / cycleLength); + if (skipCycles > 0) { + rectICoord += skipCycles * cycleLength; + rectISize -= skipCycles * cycleLength; + } + + rectICoord += lineThickness / 2.0; + + Point pt(aRect.TopLeft()); + Float& ptICoord = aParams.vertical ? pt.y.value : pt.x.value; + Float& ptBCoord = aParams.vertical ? pt.x.value : pt.y.value; + if (aParams.vertical) { + ptBCoord += adv; + } + Float iCoordLimit = ptICoord + rectISize + lineThickness; + + const Float dirtyRectIMost = aParams.vertical ? aParams.dirtyRect.YMost() + : aParams.dirtyRect.XMost(); + skipCycles = floor((iCoordLimit - dirtyRectIMost) / cycleLength); + if (skipCycles > 0) { + iCoordLimit -= skipCycles * cycleLength; + } + + RefPtr<PathBuilder> builder = aDrawTarget.CreatePathBuilder(); + RefPtr<Path> path; + + ptICoord -= lineThickness; + builder->MoveTo(pt); // 1 + + ptICoord = rectICoord; + builder->LineTo(pt); // 2 + + // In vertical mode, to go "down" relative to the text we need to + // decrease the block coordinate, whereas in horizontal we increase + // it. So the sense of this flag is effectively inverted. + bool goDown = !aParams.vertical; + uint32_t iter = 0; + while (ptICoord < iCoordLimit) { + if (++iter > 1000) { + // stroke the current path and start again, to avoid pathological + // behavior in cairo with huge numbers of path segments + path = builder->Finish(); + aDrawTarget.Stroke(path, colorPat, strokeOptions, drawOptions); + builder = aDrawTarget.CreatePathBuilder(); + builder->MoveTo(pt); + iter = 0; + } + ptICoord += adv; + ptBCoord += goDown ? adv : -adv; + + builder->LineTo(pt); // 3 and 5 + + ptICoord += flatLengthAtVertex; + builder->LineTo(pt); // 4 and 6 + + goDown = !goDown; + } + path = builder->Finish(); + aDrawTarget.Stroke(path, colorPat, strokeOptions, drawOptions); + return; + } + default: + NS_ERROR("Invalid style value!"); + } +} + +Rect nsCSSRendering::DecorationLineToPath( + const PaintDecorationLineParams& aParams) { + NS_ASSERTION(aParams.style != StyleTextDecorationStyle::None, + "aStyle is none"); + + Rect path; // To benefit from RVO, we return this from all return points + + Rect rect = ToRect(GetTextDecorationRectInternal(aParams.pt, aParams)); + if (rect.IsEmpty() || !rect.Intersects(aParams.dirtyRect)) { + return path; + } + + if (aParams.decoration != StyleTextDecorationLine::UNDERLINE && + aParams.decoration != StyleTextDecorationLine::OVERLINE && + aParams.decoration != StyleTextDecorationLine::LINE_THROUGH) { + MOZ_ASSERT_UNREACHABLE("Invalid text decoration value"); + return path; + } + + if (aParams.style != StyleTextDecorationStyle::Solid) { + // For the moment, we support only solid text decorations. + return path; + } + + Float lineThickness = std::max(NS_round(aParams.lineSize.height), 1.0); + + // The block-direction position should be set to the middle of the line. + if (aParams.vertical) { + rect.x += lineThickness / 2; + path = Rect(rect.TopLeft() - Point(lineThickness / 2, 0.0), + Size(lineThickness, rect.Height())); + } else { + rect.y += lineThickness / 2; + path = Rect(rect.TopLeft() - Point(0.0, lineThickness / 2), + Size(rect.Width(), lineThickness)); + } + + return path; +} + +nsRect nsCSSRendering::GetTextDecorationRect( + nsPresContext* aPresContext, const DecorationRectParams& aParams) { + NS_ASSERTION(aPresContext, "aPresContext is null"); + NS_ASSERTION(aParams.style != StyleTextDecorationStyle::None, + "aStyle is none"); + + gfxRect rect = GetTextDecorationRectInternal(Point(0, 0), aParams); + // The rect values are already rounded to nearest device pixels. + nsRect r; + r.x = aPresContext->GfxUnitsToAppUnits(rect.X()); + r.y = aPresContext->GfxUnitsToAppUnits(rect.Y()); + r.width = aPresContext->GfxUnitsToAppUnits(rect.Width()); + r.height = aPresContext->GfxUnitsToAppUnits(rect.Height()); + return r; +} + +gfxRect nsCSSRendering::GetTextDecorationRectInternal( + const Point& aPt, const DecorationRectParams& aParams) { + NS_ASSERTION(aParams.style <= StyleTextDecorationStyle::Wavy, + "Invalid aStyle value"); + + if (aParams.style == StyleTextDecorationStyle::None) { + return gfxRect(0, 0, 0, 0); + } + + bool canLiftUnderline = aParams.descentLimit >= 0.0; + + gfxFloat iCoord = aParams.vertical ? aPt.y : aPt.x; + gfxFloat bCoord = aParams.vertical ? aPt.x : aPt.y; + + // 'left' and 'right' are relative to the line, so for vertical writing modes + // they will actually become top and bottom of the rendered line. + // Similarly, aLineSize.width and .height are actually length and thickness + // of the line, which runs horizontally or vertically according to aVertical. + const gfxFloat left = floor(iCoord + 0.5), + right = floor(iCoord + aParams.lineSize.width + 0.5); + + // We compute |r| as if for a horizontal text run, and then swap vertical + // and horizontal coordinates at the end if vertical was requested. + gfxRect r(left, 0, right - left, 0); + + gfxFloat lineThickness = NS_round(aParams.lineSize.height); + lineThickness = std::max(lineThickness, 1.0); + gfxFloat defaultLineThickness = NS_round(aParams.defaultLineThickness); + defaultLineThickness = std::max(defaultLineThickness, 1.0); + + gfxFloat ascent = NS_round(aParams.ascent); + gfxFloat descentLimit = floor(aParams.descentLimit); + + gfxFloat suggestedMaxRectHeight = + std::max(std::min(ascent, descentLimit), 1.0); + r.height = lineThickness; + if (aParams.style == StyleTextDecorationStyle::Double) { + /** + * We will draw double line as: + * + * +-------------------------------------------+ + * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| ^ + * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| | lineThickness + * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| v + * | | ^ + * | | | gap + * | | v + * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| ^ + * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| | lineThickness + * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| v + * +-------------------------------------------+ + */ + gfxFloat gap = NS_round(lineThickness / 2.0); + gap = std::max(gap, 1.0); + r.height = lineThickness * 2.0 + gap; + if (canLiftUnderline) { + if (r.Height() > suggestedMaxRectHeight) { + // Don't shrink the line height, because the thickness has some meaning. + // We can just shrink the gap at this time. + r.height = std::max(suggestedMaxRectHeight, lineThickness * 2.0 + 1.0); + } + } + } else if (aParams.style == StyleTextDecorationStyle::Wavy) { + /** + * We will draw wavy line as: + * + * +-------------------------------------------+ + * |XXXXX XXXXXX XXXXXX | ^ + * |XXXXXX XXXXXXXX XXXXXXXX | | lineThickness + * |XXXXXXX XXXXXXXXXX XXXXXXXXXX| v + * | XXX XXX XXX XXX XX| + * | XXXXXXXXXX XXXXXXXXXX X| + * | XXXXXXXX XXXXXXXX | + * | XXXXXX XXXXXX | + * +-------------------------------------------+ + */ + r.height = lineThickness > 2.0 ? lineThickness * 4.0 : lineThickness * 3.0; + if (canLiftUnderline) { + if (r.Height() > suggestedMaxRectHeight) { + // Don't shrink the line height even if there is not enough space, + // because the thickness has some meaning. E.g., the 1px wavy line and + // 2px wavy line can be used for different meaning in IME selections + // at same time. + r.height = std::max(suggestedMaxRectHeight, lineThickness * 2.0); + } + } + } + + gfxFloat baseline = floor(bCoord + aParams.ascent + 0.5); + + // Calculate adjusted offset based on writing-mode/orientation and thickness + // of decoration line. The input value aParams.offset is the nominal position + // (offset from baseline) where we would draw a single, infinitely-thin line; + // but for a wavy or double line, we'll need to move the bounding rect of the + // decoration outwards from the baseline so that an underline remains below + // the glyphs, and an overline above them, despite the increased block-dir + // extent of the decoration. + // + // So adjustments by r.Height() are used to make the wider line styles (wavy + // and double) "grow" in the appropriate direction compared to the basic + // single line. + // + // Note that at this point, the decoration rect is being calculated in line- + // relative coordinates, where 'x' is line-rightwards, and 'y' is line- + // upwards. We'll swap them to be physical coords at the end. + gfxFloat offset = 0.0; + + if (aParams.decoration == StyleTextDecorationLine::UNDERLINE) { + offset = aParams.offset; + if (canLiftUnderline) { + if (descentLimit < -offset + r.Height()) { + // If we can ignore the offset and the decoration line is overflowing, + // we should align the bottom edge of the decoration line rect if it's + // possible. Otherwise, we should lift up the top edge of the rect as + // far as possible. + gfxFloat offsetBottomAligned = -descentLimit + r.Height(); + gfxFloat offsetTopAligned = 0.0; + offset = std::min(offsetBottomAligned, offsetTopAligned); + } + } + } else if (aParams.decoration == StyleTextDecorationLine::OVERLINE) { + // For overline, we adjust the offset by defaultlineThickness (the default + // thickness of a single decoration line) because empirically it looks + // better to draw the overline just inside rather than outside the font's + // ascent, which is what nsTextFrame passes as aParams.offset (as fonts + // don't provide an explicit overline-offset). + offset = aParams.offset - defaultLineThickness + r.Height(); + } else if (aParams.decoration == StyleTextDecorationLine::LINE_THROUGH) { + // To maintain a consistent mid-point for line-through decorations, + // we adjust the offset by half of the decoration rect's height. + gfxFloat extra = floor(r.Height() / 2.0 + 0.5); + extra = std::max(extra, lineThickness); + // computes offset for when user specifies a decoration width since + // aParams.offset is derived from the font metric's line height + gfxFloat decorationThicknessOffset = + (lineThickness - defaultLineThickness) / 2.0; + offset = aParams.offset - lineThickness + extra + decorationThicknessOffset; + } else { + MOZ_ASSERT_UNREACHABLE("Invalid text decoration value"); + } + + // Convert line-relative coordinate system (x = line-right, y = line-up) + // to physical coords, and move the decoration rect to the calculated + // offset from baseline. + if (aParams.vertical) { + std::swap(r.x, r.y); + std::swap(r.width, r.height); + // line-upwards in vertical mode = physical-right, so we /add/ offset + // to baseline. Except in sideways-lr mode, where line-upwards will be + // physical leftwards. + if (aParams.sidewaysLeft) { + r.x = baseline - floor(offset + 0.5); + } else { + r.x = baseline + floor(offset - r.Width() + 0.5); + } + } else { + // line-upwards in horizontal mode = physical-up, but our physical coord + // system works downwards, so we /subtract/ offset from baseline. + r.y = baseline - floor(offset + 0.5); + } + + return r; +} + +#define MAX_BLUR_RADIUS 300 +#define MAX_SPREAD_RADIUS 50 + +static inline gfxPoint ComputeBlurStdDev(nscoord aBlurRadius, + int32_t aAppUnitsPerDevPixel, + gfxFloat aScaleX, gfxFloat aScaleY) { + // http://dev.w3.org/csswg/css3-background/#box-shadow says that the + // standard deviation of the blur should be half the given blur value. + gfxFloat blurStdDev = gfxFloat(aBlurRadius) / gfxFloat(aAppUnitsPerDevPixel); + + return gfxPoint( + std::min((blurStdDev * aScaleX), gfxFloat(MAX_BLUR_RADIUS)) / 2.0, + std::min((blurStdDev * aScaleY), gfxFloat(MAX_BLUR_RADIUS)) / 2.0); +} + +static inline IntSize ComputeBlurRadius(nscoord aBlurRadius, + int32_t aAppUnitsPerDevPixel, + gfxFloat aScaleX = 1.0, + gfxFloat aScaleY = 1.0) { + gfxPoint scaledBlurStdDev = + ComputeBlurStdDev(aBlurRadius, aAppUnitsPerDevPixel, aScaleX, aScaleY); + return gfxAlphaBoxBlur::CalculateBlurRadius(scaledBlurStdDev); +} + +// ----- +// nsContextBoxBlur +// ----- +gfxContext* nsContextBoxBlur::Init(const nsRect& aRect, nscoord aSpreadRadius, + nscoord aBlurRadius, + int32_t aAppUnitsPerDevPixel, + gfxContext* aDestinationCtx, + const nsRect& aDirtyRect, + const gfxRect* aSkipRect, uint32_t aFlags) { + if (aRect.IsEmpty()) { + mContext = nullptr; + return nullptr; + } + + IntSize blurRadius; + IntSize spreadRadius; + GetBlurAndSpreadRadius(aDestinationCtx->GetDrawTarget(), aAppUnitsPerDevPixel, + aBlurRadius, aSpreadRadius, blurRadius, spreadRadius); + + mDestinationCtx = aDestinationCtx; + + // If not blurring, draw directly onto the destination device + if (blurRadius.width <= 0 && blurRadius.height <= 0 && + spreadRadius.width <= 0 && spreadRadius.height <= 0 && + !(aFlags & FORCE_MASK)) { + mContext = aDestinationCtx; + return mContext; + } + + // Convert from app units to device pixels + gfxRect rect = nsLayoutUtils::RectToGfxRect(aRect, aAppUnitsPerDevPixel); + + gfxRect dirtyRect = + nsLayoutUtils::RectToGfxRect(aDirtyRect, aAppUnitsPerDevPixel); + dirtyRect.RoundOut(); + + gfxMatrix transform = aDestinationCtx->CurrentMatrixDouble(); + rect = transform.TransformBounds(rect); + + mPreTransformed = !transform.IsIdentity(); + + // Create the temporary surface for blurring + dirtyRect = transform.TransformBounds(dirtyRect); + bool useHardwareAccel = !(aFlags & DISABLE_HARDWARE_ACCELERATION_BLUR); + if (aSkipRect) { + gfxRect skipRect = transform.TransformBounds(*aSkipRect); + mOwnedContext = + mAlphaBoxBlur.Init(aDestinationCtx, rect, spreadRadius, blurRadius, + &dirtyRect, &skipRect, useHardwareAccel); + } else { + mOwnedContext = + mAlphaBoxBlur.Init(aDestinationCtx, rect, spreadRadius, blurRadius, + &dirtyRect, nullptr, useHardwareAccel); + } + mContext = mOwnedContext.get(); + + if (mContext) { + // we don't need to blur if skipRect is equal to rect + // and mContext will be nullptr + mContext->Multiply(transform); + } + return mContext; +} + +void nsContextBoxBlur::DoPaint() { + if (mContext == mDestinationCtx) { + return; + } + + gfxContextMatrixAutoSaveRestore saveMatrix(mDestinationCtx); + + if (mPreTransformed) { + mDestinationCtx->SetMatrix(Matrix()); + } + + mAlphaBoxBlur.Paint(mDestinationCtx); +} + +gfxContext* nsContextBoxBlur::GetContext() { return mContext; } + +/* static */ +nsMargin nsContextBoxBlur::GetBlurRadiusMargin(nscoord aBlurRadius, + int32_t aAppUnitsPerDevPixel) { + IntSize blurRadius = ComputeBlurRadius(aBlurRadius, aAppUnitsPerDevPixel); + + nsMargin result; + result.top = result.bottom = blurRadius.height * aAppUnitsPerDevPixel; + result.left = result.right = blurRadius.width * aAppUnitsPerDevPixel; + return result; +} + +/* static */ +void nsContextBoxBlur::BlurRectangle( + gfxContext* aDestinationCtx, const nsRect& aRect, + int32_t aAppUnitsPerDevPixel, RectCornerRadii* aCornerRadii, + nscoord aBlurRadius, const sRGBColor& aShadowColor, + const nsRect& aDirtyRect, const gfxRect& aSkipRect) { + DrawTarget& aDestDrawTarget = *aDestinationCtx->GetDrawTarget(); + + if (aRect.IsEmpty()) { + return; + } + + Rect shadowGfxRect = NSRectToRect(aRect, aAppUnitsPerDevPixel); + + if (aBlurRadius <= 0) { + ColorPattern color(ToDeviceColor(aShadowColor)); + if (aCornerRadii) { + RefPtr<Path> roundedRect = + MakePathForRoundedRect(aDestDrawTarget, shadowGfxRect, *aCornerRadii); + aDestDrawTarget.Fill(roundedRect, color); + } else { + aDestDrawTarget.FillRect(shadowGfxRect, color); + } + return; + } + + gfxFloat scaleX = 1; + gfxFloat scaleY = 1; + + // Do blurs in device space when possible. + // Chrome/Skia always does the blurs in device space + // and will sometimes get incorrect results (e.g. rotated blurs) + gfxMatrix transform = aDestinationCtx->CurrentMatrixDouble(); + // XXX: we could probably handle negative scales but for now it's easier just + // to fallback + if (!transform.HasNonAxisAlignedTransform() && transform._11 > 0.0 && + transform._22 > 0.0) { + scaleX = transform._11; + scaleY = transform._22; + aDestinationCtx->SetMatrix(Matrix()); + } else { + transform = gfxMatrix(); + } + + gfxPoint blurStdDev = + ComputeBlurStdDev(aBlurRadius, aAppUnitsPerDevPixel, scaleX, scaleY); + + gfxRect dirtyRect = + nsLayoutUtils::RectToGfxRect(aDirtyRect, aAppUnitsPerDevPixel); + dirtyRect.RoundOut(); + + gfxRect shadowThebesRect = + transform.TransformBounds(ThebesRect(shadowGfxRect)); + dirtyRect = transform.TransformBounds(dirtyRect); + gfxRect skipRect = transform.TransformBounds(aSkipRect); + + if (aCornerRadii) { + aCornerRadii->Scale(scaleX, scaleY); + } + + gfxAlphaBoxBlur::BlurRectangle(aDestinationCtx, shadowThebesRect, + aCornerRadii, blurStdDev, aShadowColor, + dirtyRect, skipRect); +} + +/* static */ +void nsContextBoxBlur::GetBlurAndSpreadRadius( + DrawTarget* aDestDrawTarget, int32_t aAppUnitsPerDevPixel, + nscoord aBlurRadius, nscoord aSpreadRadius, IntSize& aOutBlurRadius, + IntSize& aOutSpreadRadius, bool aConstrainSpreadRadius) { + // Do blurs in device space when possible. + // Chrome/Skia always does the blurs in device space + // and will sometimes get incorrect results (e.g. rotated blurs) + Matrix transform = aDestDrawTarget->GetTransform(); + // XXX: we could probably handle negative scales but for now it's easier just + // to fallback + gfxFloat scaleX, scaleY; + if (transform.HasNonAxisAlignedTransform() || transform._11 <= 0.0 || + transform._22 <= 0.0) { + scaleX = 1; + scaleY = 1; + } else { + scaleX = transform._11; + scaleY = transform._22; + } + + // compute a large or smaller blur radius + aOutBlurRadius = + ComputeBlurRadius(aBlurRadius, aAppUnitsPerDevPixel, scaleX, scaleY); + aOutSpreadRadius = + IntSize(int32_t(aSpreadRadius * scaleX / aAppUnitsPerDevPixel), + int32_t(aSpreadRadius * scaleY / aAppUnitsPerDevPixel)); + + if (aConstrainSpreadRadius) { + aOutSpreadRadius.width = + std::min(aOutSpreadRadius.width, int32_t(MAX_SPREAD_RADIUS)); + aOutSpreadRadius.height = + std::min(aOutSpreadRadius.height, int32_t(MAX_SPREAD_RADIUS)); + } +} + +/* static */ +bool nsContextBoxBlur::InsetBoxBlur( + gfxContext* aDestinationCtx, Rect aDestinationRect, Rect aShadowClipRect, + sRGBColor& aShadowColor, nscoord aBlurRadiusAppUnits, + nscoord aSpreadDistanceAppUnits, int32_t aAppUnitsPerDevPixel, + bool aHasBorderRadius, RectCornerRadii& aInnerClipRectRadii, Rect aSkipRect, + Point aShadowOffset) { + if (aDestinationRect.IsEmpty()) { + mContext = nullptr; + return false; + } + + gfxContextAutoSaveRestore autoRestore(aDestinationCtx); + + IntSize blurRadius; + IntSize spreadRadius; + // Convert the blur and spread radius to device pixels + bool constrainSpreadRadius = false; + GetBlurAndSpreadRadius(aDestinationCtx->GetDrawTarget(), aAppUnitsPerDevPixel, + aBlurRadiusAppUnits, aSpreadDistanceAppUnits, + blurRadius, spreadRadius, constrainSpreadRadius); + + // The blur and spread radius are scaled already, so scale all + // input data to the blur. This way, we don't have to scale the min + // inset blur to the invert of the dest context, then rescale it back + // when we draw to the destination surface. + auto scale = aDestinationCtx->CurrentMatrix().ScaleFactors(); + Matrix transform = aDestinationCtx->CurrentMatrix(); + + // XXX: we could probably handle negative scales but for now it's easier just + // to fallback + if (!transform.HasNonAxisAlignedTransform() && transform._11 > 0.0 && + transform._22 > 0.0) { + // If we don't have a rotation, we're pre-transforming all the rects. + aDestinationCtx->SetMatrix(Matrix()); + } else { + // Don't touch anything, we have a rotation. + transform = Matrix(); + } + + Rect transformedDestRect = transform.TransformBounds(aDestinationRect); + Rect transformedShadowClipRect = transform.TransformBounds(aShadowClipRect); + Rect transformedSkipRect = transform.TransformBounds(aSkipRect); + + transformedDestRect.Round(); + transformedShadowClipRect.Round(); + transformedSkipRect.RoundIn(); + + for (size_t i = 0; i < 4; i++) { + aInnerClipRectRadii[i].width = + std::floor(scale.xScale * aInnerClipRectRadii[i].width); + aInnerClipRectRadii[i].height = + std::floor(scale.yScale * aInnerClipRectRadii[i].height); + } + + mAlphaBoxBlur.BlurInsetBox(aDestinationCtx, transformedDestRect, + transformedShadowClipRect, blurRadius, + aShadowColor, + aHasBorderRadius ? &aInnerClipRectRadii : nullptr, + transformedSkipRect, aShadowOffset); + return true; +} diff --git a/layout/painting/nsCSSRendering.h b/layout/painting/nsCSSRendering.h new file mode 100644 index 0000000000..2e813ff1db --- /dev/null +++ b/layout/painting/nsCSSRendering.h @@ -0,0 +1,924 @@ +/* -*- 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/. */ + +/* utility functions for drawing borders and backgrounds */ + +#ifndef nsCSSRendering_h___ +#define nsCSSRendering_h___ + +#include "gfxBlur.h" +#include "gfxContext.h" +#include "mozilla/gfx/PathHelpers.h" +#include "mozilla/gfx/Rect.h" +#include "mozilla/TypedEnumBits.h" +#include "nsStyleStruct.h" +#include "nsIFrame.h" +#include "nsImageRenderer.h" +#include "nsCSSRenderingBorders.h" +#include "gfxTextRun.h" + +class gfxContext; +class nsPresContext; + +namespace mozilla { + +class ComputedStyle; + +namespace gfx { +struct sRGBColor; +class DrawTarget; +} // namespace gfx + +namespace layers { +class ImageContainer; +class StackingContextHelper; +class WebRenderParentCommand; +class WebRenderLayerManager; +class RenderRootStateManager; +} // namespace layers + +namespace wr { +class DisplayListBuilder; +} // namespace wr + +enum class PaintBorderFlags : uint8_t { SyncDecodeImages = 1 << 0 }; +MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(PaintBorderFlags) + +} // namespace mozilla + +/** + * A struct representing all the information needed to paint a background + * image to some target, taking into account all CSS background-* properties. + * See PrepareImageLayer. + */ +struct nsBackgroundLayerState { + typedef mozilla::gfx::CompositionOp CompositionOp; + typedef mozilla::nsImageRenderer nsImageRenderer; + + /** + * @param aFlags some combination of nsCSSRendering::PAINTBG_* flags + */ + nsBackgroundLayerState(nsIFrame* aForFrame, const mozilla::StyleImage* aImage, + uint32_t aFlags) + : mImageRenderer(aForFrame, aImage, aFlags) {} + + /** + * The nsImageRenderer that will be used to draw the background. + */ + nsImageRenderer mImageRenderer; + /** + * A rectangle that one copy of the image tile is mapped onto. Same + * coordinate system as aBorderArea/aBGClipRect passed into + * PrepareImageLayer. + */ + nsRect mDestArea; + /** + * The actual rectangle that should be filled with (complete or partial) + * image tiles. Same coordinate system as aBorderArea/aBGClipRect passed into + * PrepareImageLayer. + */ + nsRect mFillArea; + /** + * The anchor point that should be snapped to a pixel corner. Same + * coordinate system as aBorderArea/aBGClipRect passed into + * PrepareImageLayer. + */ + nsPoint mAnchor; + /** + * The background-repeat property space keyword computes the + * repeat size which is image size plus spacing. + */ + nsSize mRepeatSize; +}; + +struct nsCSSRendering { + typedef mozilla::gfx::sRGBColor sRGBColor; + typedef mozilla::gfx::CompositionOp CompositionOp; + typedef mozilla::gfx::DrawTarget DrawTarget; + typedef mozilla::gfx::Float Float; + typedef mozilla::gfx::Point Point; + typedef mozilla::gfx::Rect Rect; + typedef mozilla::gfx::Size Size; + typedef mozilla::gfx::RectCornerRadii RectCornerRadii; + typedef mozilla::layers::WebRenderLayerManager WebRenderLayerManager; + typedef mozilla::image::ImgDrawResult ImgDrawResult; + typedef nsIFrame::Sides Sides; + + /** + * Initialize any static variables used by nsCSSRendering. + */ + static void Init(); + + /** + * Clean up any static variables used by nsCSSRendering. + */ + static void Shutdown(); + + static bool IsBoxDecorationSlice(const nsStyleBorder& aStyleBorder); + static nsRect BoxDecorationRectForBorder( + nsIFrame* aFrame, const nsRect& aBorderArea, Sides aSkipSides, + const nsStyleBorder* aStyleBorder = nullptr); + static nsRect BoxDecorationRectForBackground( + nsIFrame* aFrame, const nsRect& aBorderArea, Sides aSkipSides, + const nsStyleBorder* aStyleBorder = nullptr); + + static bool GetShadowInnerRadii(nsIFrame* aFrame, const nsRect& aFrameArea, + RectCornerRadii& aOutInnerRadii); + static nsRect GetBoxShadowInnerPaddingRect(nsIFrame* aFrame, + const nsRect& aFrameArea); + static bool ShouldPaintBoxShadowInner(nsIFrame* aFrame); + static void PaintBoxShadowInner(nsPresContext* aPresContext, + gfxContext& aRenderingContext, + nsIFrame* aForFrame, + const nsRect& aFrameArea); + + static bool GetBorderRadii(const nsRect& aFrameRect, + const nsRect& aBorderRect, nsIFrame* aFrame, + RectCornerRadii& aOutRadii); + static nsRect GetShadowRect(const nsRect& aFrameArea, bool aNativeTheme, + nsIFrame* aForFrame); + static mozilla::gfx::sRGBColor GetShadowColor( + const mozilla::StyleSimpleShadow&, nsIFrame* aFrame, float aOpacity); + // Returns if the frame has a themed frame. + // aMaybeHasBorderRadius will return false if we can early detect + // that we don't have a border radius. + static bool HasBoxShadowNativeTheme(nsIFrame* aFrame, + bool& aMaybeHasBorderRadius); + static void PaintBoxShadowOuter(nsPresContext* aPresContext, + gfxContext& aRenderingContext, + nsIFrame* aForFrame, const nsRect& aFrameArea, + const nsRect& aDirtyRect, + float aOpacity = 1.0); + + static void ComputePixelRadii(const nscoord* aAppUnitsRadii, + nscoord aAppUnitsPerPixel, + RectCornerRadii* oBorderRadii); + + /** + * Render the border for an element using css rendering rules + * for borders. aSkipSides says which sides to skip + * when rendering, the default is to skip none. + */ + static ImgDrawResult PaintBorder( + nsPresContext* aPresContext, gfxContext& aRenderingContext, + nsIFrame* aForFrame, const nsRect& aDirtyRect, const nsRect& aBorderArea, + mozilla::ComputedStyle* aStyle, mozilla::PaintBorderFlags aFlags, + Sides aSkipSides = Sides()); + + /** + * Like PaintBorder, but taking an nsStyleBorder argument instead of + * getting it from aStyle. aSkipSides says which sides to skip + * when rendering, the default is to skip none. + */ + static ImgDrawResult PaintBorderWithStyleBorder( + nsPresContext* aPresContext, gfxContext& aRenderingContext, + nsIFrame* aForFrame, const nsRect& aDirtyRect, const nsRect& aBorderArea, + const nsStyleBorder& aBorderStyle, mozilla::ComputedStyle* aStyle, + mozilla::PaintBorderFlags aFlags, Sides aSkipSides = Sides()); + + static mozilla::Maybe<nsCSSBorderRenderer> CreateBorderRenderer( + nsPresContext* aPresContext, DrawTarget* aDrawTarget, nsIFrame* aForFrame, + const nsRect& aDirtyRect, const nsRect& aBorderArea, + mozilla::ComputedStyle* aStyle, bool* aOutBorderIsEmpty, + Sides aSkipSides = Sides()); + + static mozilla::Maybe<nsCSSBorderRenderer> + CreateBorderRendererWithStyleBorder( + nsPresContext* aPresContext, DrawTarget* aDrawTarget, nsIFrame* aForFrame, + const nsRect& aDirtyRect, const nsRect& aBorderArea, + const nsStyleBorder& aBorderStyle, mozilla::ComputedStyle* aStyle, + bool* aOutBorderIsEmpty, Sides aSkipSides = Sides()); + + static mozilla::Maybe<nsCSSBorderRenderer> + CreateNullBorderRendererWithStyleBorder( + nsPresContext* aPresContext, DrawTarget* aDrawTarget, nsIFrame* aForFrame, + const nsRect& aDirtyRect, const nsRect& aBorderArea, + const nsStyleBorder& aBorderStyle, mozilla::ComputedStyle* aStyle, + bool* aOutBorderIsEmpty, Sides aSkipSides = Sides()); + + static mozilla::Maybe<nsCSSBorderRenderer> + CreateBorderRendererForNonThemedOutline(nsPresContext* aPresContext, + DrawTarget* aDrawTarget, + nsIFrame* aForFrame, + const nsRect& aDirtyRect, + const nsRect& aInnerRect, + mozilla::ComputedStyle* aStyle); + + static ImgDrawResult CreateWebRenderCommandsForBorder( + mozilla::nsDisplayItem* aItem, nsIFrame* aForFrame, + const nsRect& aBorderArea, mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const mozilla::layers::StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, + mozilla::nsDisplayListBuilder* aDisplayListBuilder); + + static void CreateWebRenderCommandsForNullBorder( + mozilla::nsDisplayItem* aItem, nsIFrame* aForFrame, + const nsRect& aBorderArea, mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const mozilla::layers::StackingContextHelper& aSc, + const nsStyleBorder& aStyleBorder); + + static ImgDrawResult CreateWebRenderCommandsForBorderWithStyleBorder( + mozilla::nsDisplayItem* aItem, nsIFrame* aForFrame, + const nsRect& aBorderArea, mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const mozilla::layers::StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, + mozilla::nsDisplayListBuilder* aDisplayListBuilder, + const nsStyleBorder& aStyleBorder); + + /** + * Render the outline for an element using css rendering rules for borders. + */ + static void PaintNonThemedOutline(nsPresContext* aPresContext, + gfxContext& aRenderingContext, + nsIFrame* aForFrame, + const nsRect& aDirtyRect, + const nsRect& aInnerRect, + mozilla::ComputedStyle* aStyle); + + /** + * Render keyboard focus on an element. + * |aFocusRect| is the outer rectangle of the focused element. + * Uses a fixed style equivalent to "1px dotted |aColor|". + * Not used for controls, because the native theme may differ. + */ + static void PaintFocus(nsPresContext* aPresContext, DrawTarget* aDrawTarget, + const nsRect& aFocusRect, nscolor aColor); + + /** + * Render a gradient for an element. + * aDest is the rect for a single tile of the gradient on the destination. + * aFill is the rect on the destination to be covered by repeated tiling of + * the gradient. + * aSrc is the part of the gradient to be rendered into a tile (aDest), if + * aSrc and aDest are different sizes, the image will be scaled to map aSrc + * onto aDest. + * aIntrinsicSize is the size of the source gradient. + */ + static void PaintGradient(nsPresContext* aPresContext, gfxContext& aContext, + const mozilla::StyleGradient& aGradient, + const nsRect& aDirtyRect, const nsRect& aDest, + const nsRect& aFill, const nsSize& aRepeatSize, + const mozilla::CSSIntRect& aSrc, + const nsSize& aIntrinsiceSize, + float aOpacity = 1.0); + + /** + * Find the frame whose background style should be used to draw the + * canvas background. aForFrame must be the frame for the root element + * whose background style should be used. This function will return + * aForFrame unless the <body> background should be propagated, in + * which case we return the frame associated with the <body>'s background. + */ + static nsIFrame* FindBackgroundStyleFrame(nsIFrame* aForFrame); + + /** + * Returns the ComputedStyle to be used to paint the background for the given + * frame, if its element has a meaningful background. This applies the rules + * for propagating backgrounds between BODY, the root element, and the + * canvas. + * + * @return the ComputedStyle (if any) to be used for painting aForFrame's + * background. + */ + static mozilla::ComputedStyle* FindBackground(const nsIFrame* aForFrame); + static nsIFrame* FindBackgroundFrame(const nsIFrame* aForFrame); + + /** + * As FindBackground, but the passed-in frame is known to be a root frame + * (returned from nsCSSFrameConstructor::GetRootElementStyleFrame()) + * and there is always some meaningful background returned. + */ + static mozilla::ComputedStyle* FindRootFrameBackground(nsIFrame* aForFrame); + + /** + * Find a non-transparent background color on an ancestor, for various + * contrast checks. Note that this only accounts for background-color and + * might stop at themed frames (depending on the argument), so it might not be + * what you want. Note that if we stop at themed frames we might, in fact, end + * up returning a transparent color (but then mIsThemed will be set to true). + * + * For semi-transparent colors, right now we blend with the default + * background-color rather than with all ancestor backgrounds. + * + * If aPreferBodyToCanvas is true, we prefer the background color of the + * <body> frame, even though we found a canvas background, because the body + * background color is most likely what will be visible as the background + * color of the page, even if the html element has a different background + * color which prevents that of the body frame to propagate to the viewport. + */ + struct EffectiveBackgroundColor { + nscolor mColor = 0; + bool mIsThemed = false; + }; + static EffectiveBackgroundColor FindEffectiveBackgroundColor( + nsIFrame* aFrame, bool aStopAtThemed = true, + bool aPreferBodyToCanvas = false); + + /** + * Determine the background color to draw taking into account print settings. + */ + static nscolor DetermineBackgroundColor(nsPresContext* aPresContext, + const mozilla::ComputedStyle* aStyle, + nsIFrame* aFrame, + bool& aDrawBackgroundImage, + bool& aDrawBackgroundColor); + + static nsRect ComputeImageLayerPositioningArea( + nsPresContext* aPresContext, nsIFrame* aForFrame, + const nsRect& aBorderArea, const nsStyleImageLayers::Layer& aLayer, + nsIFrame** aAttachedToFrame, bool* aOutTransformedFixed); + + // Implementation of the formula for computation of background-repeat round + // See http://dev.w3.org/csswg/css3-background/#the-background-size + // This function returns the adjusted size of the background image. + static nscoord ComputeRoundedSize(nscoord aCurrentSize, + nscoord aPositioningSize); + + /* ComputeBorderSpacedRepeatSize + * aImageDimension: the image width/height + * aAvailableSpace: the background positioning area width/height + * aSpace: the space between each image + * Returns the image size plus gap size of app units for use as spacing + */ + static nscoord ComputeBorderSpacedRepeatSize(nscoord aImageDimension, + nscoord aAvailableSpace, + nscoord& aSpace); + + static nsBackgroundLayerState PrepareImageLayer( + nsPresContext* aPresContext, nsIFrame* aForFrame, uint32_t aFlags, + const nsRect& aBorderArea, const nsRect& aBGClipRect, + const nsStyleImageLayers::Layer& aLayer, + bool* aOutIsTransformedFixed = nullptr); + + struct ImageLayerClipState { + nsRect mBGClipArea; // Affected by mClippedRadii + nsRect mAdditionalBGClipArea; // Not affected by mClippedRadii + nsRect mDirtyRectInAppUnits; + gfxRect mDirtyRectInDevPx; + + nscoord mRadii[8]; + RectCornerRadii mClippedRadii; + bool mHasRoundedCorners; + bool mHasAdditionalBGClipArea; + + // Whether we are being asked to draw with a caller provided background + // clipping area. If this is true we also disable rounded corners. + bool mCustomClip; + + ImageLayerClipState() + : mHasRoundedCorners(false), + mHasAdditionalBGClipArea(false), + mCustomClip(false) { + memset(mRadii, 0, sizeof(nscoord) * 8); + } + + bool IsValid() const; + }; + + static void GetImageLayerClip(const nsStyleImageLayers::Layer& aLayer, + nsIFrame* aForFrame, + const nsStyleBorder& aBorder, + const nsRect& aBorderArea, + const nsRect& aCallerDirtyRect, + bool aWillPaintBorder, + nscoord aAppUnitsPerPixel, + /* out */ ImageLayerClipState* aClipState); + + /** + * Render the background for an element using css rendering rules + * for backgrounds or mask. + */ + enum { + /** + * When this flag is passed, the element's nsDisplayBorder will be + * painted immediately on top of this background. + */ + PAINTBG_WILL_PAINT_BORDER = 0x01, + /** + * When this flag is passed, images are synchronously decoded. + */ + PAINTBG_SYNC_DECODE_IMAGES = 0x02, + /** + * When this flag is passed, painting will go to the screen so we can + * take advantage of the fact that it will be clipped to the viewport. + */ + PAINTBG_TO_WINDOW = 0x04, + /** + * When this flag is passed, painting will read properties of mask-image + * style, instead of background-image. + */ + PAINTBG_MASK_IMAGE = 0x08, + /** + * When this flag is passed, images are downscaled during decode. This + * is also implied by PAINTBG_TO_WINDOW. + */ + PAINTBG_HIGH_QUALITY_SCALING = 0x10, + }; + + struct PaintBGParams { + nsPresContext& presCtx; + nsRect dirtyRect; + nsRect borderArea; + nsIFrame* frame; + uint32_t paintFlags; + nsRect* bgClipRect = nullptr; + int32_t layer; // -1 means painting all layers; other + // value means painting one specific + // layer only. + CompositionOp compositionOp; + float opacity; + + static PaintBGParams ForAllLayers(nsPresContext& aPresCtx, + const nsRect& aDirtyRect, + const nsRect& aBorderArea, + nsIFrame* aFrame, uint32_t aPaintFlags, + float aOpacity = 1.0); + static PaintBGParams ForSingleLayer( + nsPresContext& aPresCtx, const nsRect& aDirtyRect, + const nsRect& aBorderArea, nsIFrame* aFrame, uint32_t aPaintFlags, + int32_t aLayer, CompositionOp aCompositionOp = CompositionOp::OP_OVER, + float aOpacity = 1.0); + + private: + PaintBGParams(nsPresContext& aPresCtx, const nsRect& aDirtyRect, + const nsRect& aBorderArea, nsIFrame* aFrame, + uint32_t aPaintFlags, int32_t aLayer, + CompositionOp aCompositionOp, float aOpacity) + : presCtx(aPresCtx), + dirtyRect(aDirtyRect), + borderArea(aBorderArea), + frame(aFrame), + paintFlags(aPaintFlags), + layer(aLayer), + compositionOp(aCompositionOp), + opacity(aOpacity) {} + }; + + static ImgDrawResult PaintStyleImageLayer(const PaintBGParams& aParams, + gfxContext& aRenderingCtx); + + /** + * Same as |PaintStyleImageLayer|, except using the provided style structs. + * This short-circuits the code that ensures that the root element's + * {background|mask} is drawn on the canvas. + * The aLayer parameter allows you to paint a single layer of the + * {background|mask}. + * The default value for aLayer, -1, means that all layers will be painted. + * The background color will only be painted if the back-most layer is also + * being painted and (aParams.paintFlags & PAINTBG_MASK_IMAGE) is false. + * aCompositionOp is only respected if a single layer is specified (aLayer != + * -1). If all layers are painted, the image layer's blend mode (or the mask + * layer's composition mode) will be used. + */ + static ImgDrawResult PaintStyleImageLayerWithSC( + const PaintBGParams& aParams, gfxContext& aRenderingCtx, + const mozilla::ComputedStyle* aBackgroundSC, + const nsStyleBorder& aBorder); + + static bool CanBuildWebRenderDisplayItemsForStyleImageLayer( + WebRenderLayerManager* aManager, nsPresContext& aPresCtx, + nsIFrame* aFrame, const nsStyleBackground* aBackgroundStyle, + int32_t aLayer, uint32_t aPaintFlags); + static ImgDrawResult BuildWebRenderDisplayItemsForStyleImageLayer( + const PaintBGParams& aParams, mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const mozilla::layers::StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, + mozilla::nsDisplayItem* aItem); + + static ImgDrawResult BuildWebRenderDisplayItemsForStyleImageLayerWithSC( + const PaintBGParams& aParams, mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const mozilla::layers::StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, + mozilla::nsDisplayItem* aItem, mozilla::ComputedStyle* mBackgroundSC, + const nsStyleBorder& aBorder); + + /** + * Returns the rectangle covered by the given background layer image, taking + * into account background positioning, sizing, and repetition, but not + * clipping. + */ + static nsRect GetBackgroundLayerRect(nsPresContext* aPresContext, + nsIFrame* aForFrame, + const nsRect& aBorderArea, + const nsRect& aClipRect, + const nsStyleImageLayers::Layer& aLayer, + uint32_t aFlags); + + /** + * Called when we start creating a display list. The frame tree will not + * change until a matching EndFrameTreeLocked is called. + */ + static void BeginFrameTreesLocked(); + /** + * Called when we've finished using a display list. When all + * BeginFrameTreeLocked calls have been balanced by an EndFrameTreeLocked, + * the frame tree may start changing again. + */ + static void EndFrameTreesLocked(); + + // Draw a border segment in the table collapsing border model with beveling + // corners. + static void DrawTableBorderSegment( + DrawTarget& aDrawTarget, mozilla::StyleBorderStyle aBorderStyle, + nscolor aBorderColor, const nsRect& aBorderRect, + int32_t aAppUnitsPerDevPixel, mozilla::Side aStartBevelSide, + nscoord aStartBevelOffset, mozilla::Side aEndBevelSide, + nscoord aEndBevelOffset); + + // A single border bevel. + struct Bevel { + mozilla::Side mSide; + nscoord mOffset; + }; + + // A single solid beveled border segment. + struct SolidBeveledBorderSegment { + nsRect mRect; + nscolor mColor; + Bevel mStartBevel; + Bevel mEndBevel; + }; + + // Collect the table border segments with beveling. Can't be called with + // dashed / dotted borders, since we don't support beveling those. + static void GetTableBorderSolidSegments( + nsTArray<SolidBeveledBorderSegment>& aSegments, + mozilla::StyleBorderStyle aBorderStyle, nscolor aBorderColor, + const nsRect& aBorderRect, int32_t aAppUnitsPerDevPixel, + mozilla::Side aStartBevelSide, nscoord aStartBevelOffset, + mozilla::Side aEndBevelSide, nscoord aEndBevelOffset); + + // NOTE: pt, dirtyRect, lineSize, ascent, offset in the following + // structs are non-rounded device pixels, not app units. + struct DecorationRectParams { + // The width [length] and the height [thickness] of the decoration + // line. This is a "logical" size in textRun orientation, so that + // for a vertical textrun, width will actually be a physical height; + // and conversely, height will be a physical width. + Size lineSize; + // The default height [thickness] of the line given by the font metrics. + // This is used for obtaining the correct offset for the decoration line + // when CSS specifies a unique thickness for a text-decoration, + // since the offset given by the font is derived from the font metric's + // assumed line height + Float defaultLineThickness = 0.0f; + // The ascent of the text. + Float ascent = 0.0f; + // The offset of the decoration line from the baseline of the text + // (if the value is positive, the line is lifted up). + Float offset = 0.0f; + // If descentLimit is zero or larger and the underline overflows + // from the descent space, the underline should be lifted up as far + // as possible. Note that this does not mean the underline never + // overflows from this limitation, because if the underline is + // positioned to the baseline or upper, it causes unreadability. + // Note that if this is zero or larger, the underline rect may be + // shrunken if it's possible. Therefore, this value is used for + // strikeout line and overline too. + Float descentLimit = -1.0f; + // Which line will be painted. The value can be + // UNDERLINE or OVERLINE or LINE_THROUGH. + mozilla::StyleTextDecorationLine decoration = + mozilla::StyleTextDecorationLine::UNDERLINE; + // The style of the decoration line + mozilla::StyleTextDecorationStyle style = + mozilla::StyleTextDecorationStyle::None; + bool vertical = false; + bool sidewaysLeft = false; + gfxTextRun::Range glyphRange; + gfxTextRun::PropertyProvider* provider; + }; + + struct PaintDecorationLineParams : DecorationRectParams { + // No need to paint outside this rect. + Rect dirtyRect; + // The top/left edge of the text. + Point pt; + // The color of the decoration line. + nscolor color = NS_RGBA(0, 0, 0, 0); + // The distance between the left edge of the given frame and the + // position of the text as positioned without offset of the shadow. + Float icoordInFrame = 0.0f; + // Baseline offset being applied to this text (block-direction adjustment + // applied to glyph positions when computing skip-ink intercepts). + Float baselineOffset = 0.0f; + }; + + /** + * Function for painting the clipped decoration lines for the text. + * Takes into account the rect clipping that occurs when + * text-decoration-skip-ink is being used for underlines or overlines + * + * input: + * @param aFrame the frame which needs the decoration line + * @param aDrawTarget the target/backend being drawn to + * @param aParams the parameters for the decoration line + * being drawn + * @param aRect the rect representing the decoration line + */ + static void PaintDecorationLineInternal( + nsIFrame* aFrame, DrawTarget& aDrawTarget, + const PaintDecorationLineParams& aParams, Rect aRect); + + /** + * Function for painting the decoration lines for the text. + * + * input: + * @param aFrame the frame which needs the decoration line + * @param aDrawTarget the target/backend being drawn to + * @param aParams the parameters for the decoration line + * being drawn + */ + static void PaintDecorationLine(nsIFrame* aFrame, DrawTarget& aDrawTarget, + const PaintDecorationLineParams& aParams); + + /** + * Returns a Rect corresponding to the outline of the decoration line for the + * given text metrics. Arguments have the same meaning as for + * PaintDecorationLine. Currently this only works for solid + * decorations; for other decoration styles the returned Rect will be empty. + */ + static Rect DecorationLineToPath(const PaintDecorationLineParams& aParams); + + /** + * Function for getting the decoration line rect for the text. + * NOTE: aLineSize, aAscent and aOffset are non-rounded device pixels, + * not app units. + * input: + * @param aPresContext + * output: + * @return the decoration line rect for the input, + * the each values are app units. + */ + static nsRect GetTextDecorationRect(nsPresContext* aPresContext, + const DecorationRectParams& aParams); + + static CompositionOp GetGFXBlendMode(mozilla::StyleBlend aBlendMode) { + switch (aBlendMode) { + case mozilla::StyleBlend::Normal: + return CompositionOp::OP_OVER; + case mozilla::StyleBlend::Multiply: + return CompositionOp::OP_MULTIPLY; + case mozilla::StyleBlend::Screen: + return CompositionOp::OP_SCREEN; + case mozilla::StyleBlend::Overlay: + return CompositionOp::OP_OVERLAY; + case mozilla::StyleBlend::Darken: + return CompositionOp::OP_DARKEN; + case mozilla::StyleBlend::Lighten: + return CompositionOp::OP_LIGHTEN; + case mozilla::StyleBlend::ColorDodge: + return CompositionOp::OP_COLOR_DODGE; + case mozilla::StyleBlend::ColorBurn: + return CompositionOp::OP_COLOR_BURN; + case mozilla::StyleBlend::HardLight: + return CompositionOp::OP_HARD_LIGHT; + case mozilla::StyleBlend::SoftLight: + return CompositionOp::OP_SOFT_LIGHT; + case mozilla::StyleBlend::Difference: + return CompositionOp::OP_DIFFERENCE; + case mozilla::StyleBlend::Exclusion: + return CompositionOp::OP_EXCLUSION; + case mozilla::StyleBlend::Hue: + return CompositionOp::OP_HUE; + case mozilla::StyleBlend::Saturation: + return CompositionOp::OP_SATURATION; + case mozilla::StyleBlend::Color: + return CompositionOp::OP_COLOR; + case mozilla::StyleBlend::Luminosity: + return CompositionOp::OP_LUMINOSITY; + case mozilla::StyleBlend::PlusLighter: + return CompositionOp::OP_ADD; + default: + MOZ_ASSERT(false); + return CompositionOp::OP_OVER; + } + } + + static CompositionOp GetGFXCompositeMode( + mozilla::StyleMaskComposite aCompositeMode) { + switch (aCompositeMode) { + case mozilla::StyleMaskComposite::Add: + return CompositionOp::OP_OVER; + case mozilla::StyleMaskComposite::Subtract: + return CompositionOp::OP_OUT; + case mozilla::StyleMaskComposite::Intersect: + return CompositionOp::OP_IN; + case mozilla::StyleMaskComposite::Exclude: + return CompositionOp::OP_XOR; + default: + MOZ_ASSERT(false); + return CompositionOp::OP_OVER; + } + } + + protected: + static gfxRect GetTextDecorationRectInternal( + const Point& aPt, const DecorationRectParams& aParams); + + /** + * Returns inflated rect for painting a decoration line. + * Complex style decoration lines should be painted from leftmost of nearest + * ancestor block box because that makes better look of connection of lines + * for different nodes. ExpandPaintingRectForDecorationLine() returns + * a rect for actual painting rect for the clipped rect. + * + * input: + * @param aFrame the frame which needs the decoration line. + * @param aStyle the style of the complex decoration line + * @param aClippedRect the clipped rect for the decoration line. + * in other words, visible area of the line. + * @param aICoordInFrame the distance between inline-start edge of aFrame + * and aClippedRect.pos. + * @param aCycleLength the width of one cycle of the line style. + */ + static Rect ExpandPaintingRectForDecorationLine( + nsIFrame* aFrame, const mozilla::StyleTextDecorationStyle aStyle, + const Rect& aClippedRect, const Float aICoordInFrame, + const Float aCycleLength, bool aVertical); +}; + +/* + * nsContextBoxBlur + * Creates an 8-bit alpha channel context for callers to draw in, blurs the + * contents of that context and applies it as a 1-color mask on a + * different existing context. Uses gfxAlphaBoxBlur as its back end. + * + * You must call Init() first to create a suitable temporary surface to draw + * on. You must then draw any desired content onto the given context, then + * call DoPaint() to apply the blurred content as a single-color mask. You + * can only call Init() once, so objects cannot be reused. + * + * This is very useful for creating drop shadows or silhouettes. + */ +class nsContextBoxBlur { + typedef mozilla::gfx::sRGBColor sRGBColor; + typedef mozilla::gfx::DrawTarget DrawTarget; + typedef mozilla::gfx::RectCornerRadii RectCornerRadii; + + public: + enum { FORCE_MASK = 0x01, DISABLE_HARDWARE_ACCELERATION_BLUR = 0x02 }; + /** + * Prepares a gfxContext to draw on. Do not call this twice; if you want + * to get the gfxContext again use GetContext(). + * + * @param aRect The coordinates of the surface to create. + * All coordinates must be in app units. + * This must not include the blur radius, pass + * it as the second parameter and everything + * is taken care of. + * + * @param aBlurRadius The blur radius in app units. + * + * @param aAppUnitsPerDevPixel The number of app units in a device pixel, + * for conversion. Most of the time you'll + * pass this from the current PresContext if + * available. + * + * @param aDestinationCtx The graphics context to apply the blurred + * mask to when you call DoPaint(). Make sure + * it is not destroyed before you call + * DoPaint(). To set the color of the + * resulting blurred graphic mask, you must + * set the color on this context before + * calling Init(). + * + * @param aDirtyRect The absolute dirty rect in app units. Used to + * optimize the temporary surface size and speed + * up blur. + * + * @param aSkipRect An area in device pixels (NOT app units!) to + * avoid blurring over, to prevent unnecessary work. + * + * @param aFlags FORCE_MASK to ensure that the content drawn to + * the returned gfxContext is used as a mask, and not drawn directly to + * aDestinationCtx. + * + * @return A blank 8-bit alpha-channel-only graphics context to + * draw on, or null on error. Must not be freed. The + * context has a device offset applied to it given by + * aRect. This means you can use coordinates as if it + * were at the desired position at aRect and you don't + * need to worry about translating any coordinates to + * draw on this temporary surface. + * + * If aBlurRadius is 0, the returned context is aDestinationCtx and + * DoPaint() does nothing, because no blurring is required. Therefore, you + * should prepare the destination context as if you were going to draw + * directly on it instead of any temporary surface created in this class. + */ + gfxContext* Init(const nsRect& aRect, nscoord aSpreadRadius, + nscoord aBlurRadius, int32_t aAppUnitsPerDevPixel, + gfxContext* aDestinationCtx, const nsRect& aDirtyRect, + const gfxRect* aSkipRect, uint32_t aFlags = 0); + + /** + * Does the actual blurring and mask applying. Users of this object *must* + * have called Init() first, then have drawn whatever they want to be + * blurred onto the internal gfxContext before calling this. + */ + void DoPaint(); + + /** + * Gets the internal gfxContext at any time. Must not be freed. Avoid + * calling this before calling Init() since the context would not be + * constructed at that point. + */ + gfxContext* GetContext(); + + /** + * Get the margin associated with the given blur radius, i.e., the + * additional area that might be painted as a result of it. (The + * margin for a spread radius is itself, on all sides.) + */ + static nsMargin GetBlurRadiusMargin(nscoord aBlurRadius, + int32_t aAppUnitsPerDevPixel); + + /** + * Blurs a coloured rectangle onto aDestinationCtx. This is equivalent + * to calling Init(), drawing a rectangle onto the returned surface + * and then calling DoPaint, but may let us optimize better in the + * backend. + * + * @param aDestinationCtx The destination to blur to. + * @param aRect The rectangle to blur in app units. + * @param aAppUnitsPerDevPixel The number of app units in a device pixel, + * for conversion. Most of the time you'll + * pass this from the current PresContext if + * available. + * @param aCornerRadii Corner radii for aRect, if it is a rounded + * rectangle. + * @param aBlurRadius The blur radius in app units. + * @param aShadowColor The color to draw the blurred shadow. + * @param aDirtyRect The absolute dirty rect in app units. Used to + * optimize the temporary surface size and speed + * up blur. + * @param aSkipRect An area in device pixels (NOT app units!) to + * avoid blurring over, to prevent unnecessary work. + */ + static void BlurRectangle(gfxContext* aDestinationCtx, const nsRect& aRect, + int32_t aAppUnitsPerDevPixel, + RectCornerRadii* aCornerRadii, nscoord aBlurRadius, + const sRGBColor& aShadowColor, + const nsRect& aDirtyRect, const gfxRect& aSkipRect); + + /** + * Draws a blurred inset box shadow shape onto the destination surface. + * Like BlurRectangle, this is equivalent to calling Init(), + * drawing a rectangle onto the returned surface + * and then calling DoPaint, but may let us optimize better in the + * backend. + * + * @param aDestinationCtx The destination to blur to. + * @param aDestinationRect The rectangle to blur in app units. + * @param aShadowClipRect The inside clip rect that creates the path. + * @param aShadowColor The color of the blur + * @param aBlurRadiusAppUnits The blur radius in app units + * @param aSpreadRadiusAppUnits The spread radius in app units. + * @param aAppUnitsPerDevPixel The number of app units in a device pixel, + * for conversion. Most of the time you'll + * pass this from the current PresContext if + * available. + * @param aHasBorderRadius If this inset box blur has a border radius + * @param aInnerClipRectRadii The clip rect radii used for the inside rect's + * path. + * @param aSkipRect An area in device pixels (NOT app units!) to + * avoid blurring over, to prevent unnecessary work. + */ + bool InsetBoxBlur(gfxContext* aDestinationCtx, + mozilla::gfx::Rect aDestinationRect, + mozilla::gfx::Rect aShadowClipRect, + mozilla::gfx::sRGBColor& aShadowColor, + nscoord aBlurRadiusAppUnits, nscoord aSpreadRadiusAppUnits, + int32_t aAppUnitsPerDevPixel, bool aHasBorderRadius, + RectCornerRadii& aInnerClipRectRadii, + mozilla::gfx::Rect aSkipRect, + mozilla::gfx::Point aShadowOffset); + + protected: + static void GetBlurAndSpreadRadius(DrawTarget* aDestDrawTarget, + int32_t aAppUnitsPerDevPixel, + nscoord aBlurRadius, nscoord aSpreadRadius, + mozilla::gfx::IntSize& aOutBlurRadius, + mozilla::gfx::IntSize& aOutSpreadRadius, + bool aConstrainSpreadRadius = true); + + gfxAlphaBoxBlur mAlphaBoxBlur; + mozilla::UniquePtr<gfxContext> mOwnedContext; + gfxContext* mContext; // may be either mOwnedContext or mDestinationContext + gfxContext* mDestinationCtx; + + /* This is true if the blur already has it's content transformed + * by mDestinationCtx's transform */ + bool mPreTransformed; +}; + +#endif /* nsCSSRendering_h___ */ diff --git a/layout/painting/nsCSSRenderingBorders.cpp b/layout/painting/nsCSSRenderingBorders.cpp new file mode 100644 index 0000000000..3f0bee7f85 --- /dev/null +++ b/layout/painting/nsCSSRenderingBorders.cpp @@ -0,0 +1,3891 @@ +/* -*- 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/. */ + +#include "nsCSSRenderingBorders.h" + +#include "gfxUtils.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/Helpers.h" +#include "mozilla/gfx/PathHelpers.h" +#include "BorderConsts.h" +#include "DashedCornerFinder.h" +#include "DottedCornerFinder.h" +#include "ImageRegion.h" +#include "nsLayoutUtils.h" +#include "nsStyleConsts.h" +#include "nsContentUtils.h" +#include "nsCSSColorUtils.h" +#include "nsCSSRendering.h" +#include "nsCSSRenderingGradients.h" +#include "nsDisplayList.h" +#include "nsExpirationTracker.h" +#include "nsIScriptError.h" +#include "nsClassHashtable.h" +#include "nsPresContext.h" +#include "nsStyleStruct.h" +#include "gfx2DGlue.h" +#include "gfxGradientCache.h" +#include "mozilla/image/WebRenderImageProvider.h" +#include "mozilla/layers/StackingContextHelper.h" +#include "mozilla/layers/RenderRootStateManager.h" +#include "mozilla/layers/WebRenderLayerManager.h" +#include "mozilla/ProfilerLabels.h" +#include "mozilla/Range.h" +#include <algorithm> + +using namespace mozilla; +using namespace mozilla::gfx; +using namespace mozilla::image; + +#define MAX_COMPOSITE_BORDER_WIDTH LayoutDeviceIntCoord(10000) + +/** + * nsCSSRendering::PaintBorder + * nsCSSRendering::PaintOutline + * -> DrawBorders + * + * DrawBorders + * -> Ability to use specialized approach? + * |- Draw using specialized function + * |- separate corners? + * |- dashed side mask + * | + * -> can border be drawn in 1 pass? (e.g., solid border same color all + * around) + * |- DrawBorderSides with all 4 sides + * -> more than 1 pass? + * |- for each corner + * |- clip to DoCornerClipSubPath + * |- for each side adjacent to corner + * |- clip to GetSideClipSubPath + * |- DrawBorderSides with one side + * |- for each side + * |- GetSideClipWithoutCornersRect + * |- DrawDashedOrDottedSide || DrawBorderSides with one side + */ + +static void ComputeBorderCornerDimensions(const Float* aBorderWidths, + const RectCornerRadii& aRadii, + RectCornerRadii* aDimsResult); + +// given a side index, get the previous and next side index +#define NEXT_SIDE(_s) mozilla::Side(((_s) + 1) & 3) +#define PREV_SIDE(_s) mozilla::Side(((_s) + 3) & 3) + +// given a corner index, get the previous and next corner index +#define NEXT_CORNER(_s) Corner(((_s) + 1) & 3) +#define PREV_CORNER(_s) Corner(((_s) + 3) & 3) + +// from the given base color and the background color, turn +// color into a color for the given border pattern style +static sRGBColor MakeBorderColor(nscolor aColor, + BorderColorStyle aBorderColorStyle); + +// Given a line index (an index starting from the outside of the +// border going inwards) and an array of line styles, calculate the +// color that that stripe of the border should be rendered in. +static sRGBColor ComputeColorForLine(uint32_t aLineIndex, + const BorderColorStyle* aBorderColorStyle, + uint32_t aBorderColorStyleCount, + nscolor aBorderColor); + +// little helper function to check if the array of 4 floats given are +// equal to the given value +static bool CheckFourFloatsEqual(const Float* vals, Float k) { + return (vals[0] == k && vals[1] == k && vals[2] == k && vals[3] == k); +} + +static bool IsZeroSize(const Size& sz) { + return sz.width == 0.0 || sz.height == 0.0; +} + +/* static */ +bool nsCSSBorderRenderer::AllCornersZeroSize(const RectCornerRadii& corners) { + return IsZeroSize(corners[eCornerTopLeft]) && + IsZeroSize(corners[eCornerTopRight]) && + IsZeroSize(corners[eCornerBottomRight]) && + IsZeroSize(corners[eCornerBottomLeft]); +} + +static mozilla::Side GetHorizontalSide(Corner aCorner) { + return (aCorner == C_TL || aCorner == C_TR) ? eSideTop : eSideBottom; +} + +static mozilla::Side GetVerticalSide(Corner aCorner) { + return (aCorner == C_TL || aCorner == C_BL) ? eSideLeft : eSideRight; +} + +static Corner GetCWCorner(mozilla::Side aSide) { + return Corner(NEXT_SIDE(aSide)); +} + +static Corner GetCCWCorner(mozilla::Side aSide) { return Corner(aSide); } + +static bool IsSingleSide(mozilla::SideBits aSides) { + return aSides == SideBits::eTop || aSides == SideBits::eRight || + aSides == SideBits::eBottom || aSides == SideBits::eLeft; +} + +static bool IsHorizontalSide(mozilla::Side aSide) { + return aSide == eSideTop || aSide == eSideBottom; +} + +typedef enum { + // Normal solid square corner. Will be rectangular, the size of the + // adjacent sides. If the corner has a border radius, the corner + // will always be solid, since we don't do dotted/dashed etc. + CORNER_NORMAL, + + // Paint the corner in whatever style is not dotted/dashed of the + // adjacent corners. + CORNER_SOLID, + + // Paint the corner as a dot, the size of the bigger of the adjacent + // sides. + CORNER_DOT +} CornerStyle; + +nsCSSBorderRenderer::nsCSSBorderRenderer( + nsPresContext* aPresContext, DrawTarget* aDrawTarget, + const Rect& aDirtyRect, Rect& aOuterRect, + const StyleBorderStyle* aBorderStyles, const Float* aBorderWidths, + RectCornerRadii& aBorderRadii, const nscolor* aBorderColors, + bool aBackfaceIsVisible, const Maybe<Rect>& aClipRect) + : mPresContext(aPresContext), + mDrawTarget(aDrawTarget), + mDirtyRect(aDirtyRect), + mOuterRect(aOuterRect), + mBorderRadii(aBorderRadii), + mBackfaceIsVisible(aBackfaceIsVisible), + mLocalClip(aClipRect) { + PodCopy(mBorderStyles, aBorderStyles, 4); + PodCopy(mBorderWidths, aBorderWidths, 4); + PodCopy(mBorderColors, aBorderColors, 4); + mInnerRect = mOuterRect; + mInnerRect.Deflate(Margin( + mBorderStyles[0] != StyleBorderStyle::None ? mBorderWidths[0] : 0, + mBorderStyles[1] != StyleBorderStyle::None ? mBorderWidths[1] : 0, + mBorderStyles[2] != StyleBorderStyle::None ? mBorderWidths[2] : 0, + mBorderStyles[3] != StyleBorderStyle::None ? mBorderWidths[3] : 0)); + + ComputeBorderCornerDimensions(mBorderWidths, mBorderRadii, + &mBorderCornerDimensions); + + mOneUnitBorder = CheckFourFloatsEqual(mBorderWidths, 1.0); + mNoBorderRadius = AllCornersZeroSize(mBorderRadii); + mAllBordersSameStyle = AreBorderSideFinalStylesSame(SideBits::eAll); + mAllBordersSameWidth = AllBordersSameWidth(); + mAvoidStroke = false; +} + +/* static */ +void nsCSSBorderRenderer::ComputeInnerRadii(const RectCornerRadii& aRadii, + const Float* aBorderSizes, + RectCornerRadii* aInnerRadiiRet) { + RectCornerRadii& iRadii = *aInnerRadiiRet; + + iRadii[C_TL].width = + std::max(0.f, aRadii[C_TL].width - aBorderSizes[eSideLeft]); + iRadii[C_TL].height = + std::max(0.f, aRadii[C_TL].height - aBorderSizes[eSideTop]); + + iRadii[C_TR].width = + std::max(0.f, aRadii[C_TR].width - aBorderSizes[eSideRight]); + iRadii[C_TR].height = + std::max(0.f, aRadii[C_TR].height - aBorderSizes[eSideTop]); + + iRadii[C_BR].width = + std::max(0.f, aRadii[C_BR].width - aBorderSizes[eSideRight]); + iRadii[C_BR].height = + std::max(0.f, aRadii[C_BR].height - aBorderSizes[eSideBottom]); + + iRadii[C_BL].width = + std::max(0.f, aRadii[C_BL].width - aBorderSizes[eSideLeft]); + iRadii[C_BL].height = + std::max(0.f, aRadii[C_BL].height - aBorderSizes[eSideBottom]); +} + +/* static */ +void nsCSSBorderRenderer::ComputeOuterRadii(const RectCornerRadii& aRadii, + const Float* aBorderSizes, + RectCornerRadii* aOuterRadiiRet) { + RectCornerRadii& oRadii = *aOuterRadiiRet; + + // default all corners to sharp corners + oRadii = RectCornerRadii(0.f); + + // round the edges that have radii > 0.0 to start with + if (aRadii[C_TL].width > 0.f && aRadii[C_TL].height > 0.f) { + oRadii[C_TL].width = + std::max(0.f, aRadii[C_TL].width + aBorderSizes[eSideLeft]); + oRadii[C_TL].height = + std::max(0.f, aRadii[C_TL].height + aBorderSizes[eSideTop]); + } + + if (aRadii[C_TR].width > 0.f && aRadii[C_TR].height > 0.f) { + oRadii[C_TR].width = + std::max(0.f, aRadii[C_TR].width + aBorderSizes[eSideRight]); + oRadii[C_TR].height = + std::max(0.f, aRadii[C_TR].height + aBorderSizes[eSideTop]); + } + + if (aRadii[C_BR].width > 0.f && aRadii[C_BR].height > 0.f) { + oRadii[C_BR].width = + std::max(0.f, aRadii[C_BR].width + aBorderSizes[eSideRight]); + oRadii[C_BR].height = + std::max(0.f, aRadii[C_BR].height + aBorderSizes[eSideBottom]); + } + + if (aRadii[C_BL].width > 0.f && aRadii[C_BL].height > 0.f) { + oRadii[C_BL].width = + std::max(0.f, aRadii[C_BL].width + aBorderSizes[eSideLeft]); + oRadii[C_BL].height = + std::max(0.f, aRadii[C_BL].height + aBorderSizes[eSideBottom]); + } +} + +/*static*/ void ComputeBorderCornerDimensions(const Float* aBorderWidths, + const RectCornerRadii& aRadii, + RectCornerRadii* aDimsRet) { + Float leftWidth = aBorderWidths[eSideLeft]; + Float topWidth = aBorderWidths[eSideTop]; + Float rightWidth = aBorderWidths[eSideRight]; + Float bottomWidth = aBorderWidths[eSideBottom]; + + if (nsCSSBorderRenderer::AllCornersZeroSize(aRadii)) { + // These will always be in pixel units from CSS + (*aDimsRet)[C_TL] = Size(leftWidth, topWidth); + (*aDimsRet)[C_TR] = Size(rightWidth, topWidth); + (*aDimsRet)[C_BR] = Size(rightWidth, bottomWidth); + (*aDimsRet)[C_BL] = Size(leftWidth, bottomWidth); + } else { + // Always round up to whole pixels for the corners; it's safe to + // make the corners bigger than necessary, and this way we ensure + // that we avoid seams. + (*aDimsRet)[C_TL] = Size(ceil(std::max(leftWidth, aRadii[C_TL].width)), + ceil(std::max(topWidth, aRadii[C_TL].height))); + (*aDimsRet)[C_TR] = Size(ceil(std::max(rightWidth, aRadii[C_TR].width)), + ceil(std::max(topWidth, aRadii[C_TR].height))); + (*aDimsRet)[C_BR] = Size(ceil(std::max(rightWidth, aRadii[C_BR].width)), + ceil(std::max(bottomWidth, aRadii[C_BR].height))); + (*aDimsRet)[C_BL] = Size(ceil(std::max(leftWidth, aRadii[C_BL].width)), + ceil(std::max(bottomWidth, aRadii[C_BL].height))); + } +} + +bool nsCSSBorderRenderer::AreBorderSideFinalStylesSame( + mozilla::SideBits aSides) { + NS_ASSERTION(aSides != SideBits::eNone && + (aSides & ~SideBits::eAll) == SideBits::eNone, + "AreBorderSidesSame: invalid whichSides!"); + + /* First check if the specified styles and colors are the same for all sides + */ + int firstStyle = 0; + for (const auto i : mozilla::AllPhysicalSides()) { + if (firstStyle == i) { + if ((static_cast<mozilla::SideBits>(1 << i) & aSides) == + SideBits::eNone) { + firstStyle++; + } + continue; + } + + if ((static_cast<mozilla::SideBits>(1 << i) & aSides) == SideBits::eNone) { + continue; + } + + if (mBorderStyles[firstStyle] != mBorderStyles[i] || + mBorderColors[firstStyle] != mBorderColors[i]) { + return false; + } + } + + /* Then if it's one of the two-tone styles and we're not + * just comparing the TL or BR sides */ + switch (mBorderStyles[firstStyle]) { + case StyleBorderStyle::Groove: + case StyleBorderStyle::Ridge: + case StyleBorderStyle::Inset: + case StyleBorderStyle::Outset: + return ((aSides & ~(SideBits::eTop | SideBits::eLeft)) == + SideBits::eNone || + (aSides & ~(SideBits::eBottom | SideBits::eRight)) == + SideBits::eNone); + default: + return true; + } +} + +bool nsCSSBorderRenderer::IsSolidCornerStyle(StyleBorderStyle aStyle, + Corner aCorner) { + switch (aStyle) { + case StyleBorderStyle::Solid: + return true; + + case StyleBorderStyle::Inset: + case StyleBorderStyle::Outset: + return (aCorner == eCornerTopLeft || aCorner == eCornerBottomRight); + + case StyleBorderStyle::Groove: + case StyleBorderStyle::Ridge: + return mOneUnitBorder && + (aCorner == eCornerTopLeft || aCorner == eCornerBottomRight); + + case StyleBorderStyle::Double: + return mOneUnitBorder; + + default: + return false; + } +} + +bool nsCSSBorderRenderer::IsCornerMergeable(Corner aCorner) { + // Corner between dotted borders with same width and small radii is + // merged into single dot. + // + // widthH / 2.0 + // |<---------->| + // | | + // |radius.width| + // |<--->| | + // | | | + // | _+------+------------+----- + // | / ###|### | + // |/ #######|####### | + // + #########|######### | + // | ##########|########## | + // | ###########|########### | + // | ###########|########### | + // |############|############| + // +------------+############| + // |#########################| + // | ####################### | + // | ####################### | + // | ##################### | + // | ################### | + // | ############### | + // | ####### | + // +-------------------------+---- + // | | + // | | + mozilla::Side sideH(GetHorizontalSide(aCorner)); + mozilla::Side sideV(GetVerticalSide(aCorner)); + StyleBorderStyle styleH = mBorderStyles[sideH]; + StyleBorderStyle styleV = mBorderStyles[sideV]; + if (styleH != styleV || styleH != StyleBorderStyle::Dotted) { + return false; + } + + Float widthH = mBorderWidths[sideH]; + Float widthV = mBorderWidths[sideV]; + if (widthH != widthV) { + return false; + } + + Size radius = mBorderRadii[aCorner]; + return IsZeroSize(radius) || + (radius.width < widthH / 2.0f && radius.height < widthH / 2.0f); +} + +BorderColorStyle nsCSSBorderRenderer::BorderColorStyleForSolidCorner( + StyleBorderStyle aStyle, Corner aCorner) { + // note that this function assumes that the corner is already solid, + // as per the earlier function + switch (aStyle) { + case StyleBorderStyle::Solid: + case StyleBorderStyle::Double: + return BorderColorStyleSolid; + + case StyleBorderStyle::Inset: + case StyleBorderStyle::Groove: + if (aCorner == eCornerTopLeft) { + return BorderColorStyleDark; + } else if (aCorner == eCornerBottomRight) { + return BorderColorStyleLight; + } + break; + + case StyleBorderStyle::Outset: + case StyleBorderStyle::Ridge: + if (aCorner == eCornerTopLeft) { + return BorderColorStyleLight; + } else if (aCorner == eCornerBottomRight) { + return BorderColorStyleDark; + } + break; + default: + return BorderColorStyleNone; + } + + return BorderColorStyleNone; +} + +Rect nsCSSBorderRenderer::GetCornerRect(Corner aCorner) { + Point offset(0.f, 0.f); + + if (aCorner == C_TR || aCorner == C_BR) + offset.x = mOuterRect.Width() - mBorderCornerDimensions[aCorner].width; + if (aCorner == C_BR || aCorner == C_BL) + offset.y = mOuterRect.Height() - mBorderCornerDimensions[aCorner].height; + + return Rect(mOuterRect.TopLeft() + offset, mBorderCornerDimensions[aCorner]); +} + +Rect nsCSSBorderRenderer::GetSideClipWithoutCornersRect(mozilla::Side aSide) { + Point offset(0.f, 0.f); + + // The offset from the outside rect to the start of this side's + // box. For the top and bottom sides, the height of the box + // must be the border height; the x start must take into account + // the corner size (which may be bigger than the right or left + // side's width). The same applies to the right and left sides. + if (aSide == eSideTop) { + offset.x = mBorderCornerDimensions[C_TL].width; + } else if (aSide == eSideRight) { + offset.x = mOuterRect.Width() - mBorderWidths[eSideRight]; + offset.y = mBorderCornerDimensions[C_TR].height; + } else if (aSide == eSideBottom) { + offset.x = mBorderCornerDimensions[C_BL].width; + offset.y = mOuterRect.Height() - mBorderWidths[eSideBottom]; + } else if (aSide == eSideLeft) { + offset.y = mBorderCornerDimensions[C_TL].height; + } + + // The sum of the width & height of the corners adjacent to the + // side. This relies on the relationship between side indexing and + // corner indexing; that is, 0 == SIDE_TOP and 0 == CORNER_TOP_LEFT, + // with both proceeding clockwise. + Size sideCornerSum = mBorderCornerDimensions[GetCCWCorner(aSide)] + + mBorderCornerDimensions[GetCWCorner(aSide)]; + Rect rect(mOuterRect.TopLeft() + offset, mOuterRect.Size() - sideCornerSum); + + if (IsHorizontalSide(aSide)) + rect.height = mBorderWidths[aSide]; + else + rect.width = mBorderWidths[aSide]; + + return rect; +} + +// The side border type and the adjacent border types are +// examined and one of the different types of clipping (listed +// below) is selected. + +typedef enum { + // clip to the trapezoid formed by the corners of the + // inner and outer rectangles for the given side + // + // +--------------- + // |\%%%%%%%%%%%%%% + // | \%%%%%%%%%%%% + // | \%%%%%%%%%%% + // | \%%%%%%%%% + // | +-------- + // | | + // | | + SIDE_CLIP_TRAPEZOID, + + // clip to the trapezoid formed by the outer rectangle + // corners and the center of the region, making sure + // that diagonal lines all go directly from the outside + // corner to the inside corner, but that they then continue on + // to the middle. + // + // This is needed for correctly clipping rounded borders, + // which might extend past the SIDE_CLIP_TRAPEZOID trap. + // + // +-------__--+--- + // \%%%%_-%%%%%%%% + // \+-%%%%%%%%%% + // / \%%%%%%%%%% + // / \%%%%%%%%% + // | +%%_-+--- + // | +%%%%%% + // | / \%%%%% + // + + \%%% + // | | +- + SIDE_CLIP_TRAPEZOID_FULL, + + // clip to the rectangle formed by the given side including corner. + // This is used by the non-dotted side next to dotted side. + // + // +--------------- + // |%%%%%%%%%%%%%%% + // |%%%%%%%%%%%%%%% + // |%%%%%%%%%%%%%%% + // |%%%%%%%%%%%%%%% + // +------+-------- + // | | + // | | + SIDE_CLIP_RECTANGLE_CORNER, + + // clip to the rectangle formed by the given side excluding corner. + // This is used by the dotted side next to non-dotted side. + // + // +------+-------- + // | |%%%%%%%% + // | |%%%%%%%% + // | |%%%%%%%% + // | |%%%%%%%% + // | +-------- + // | | + // | | + SIDE_CLIP_RECTANGLE_NO_CORNER, +} SideClipType; + +// Given three points, p0, p1, and midPoint, move p1 further in to the +// rectangle (of which aMidPoint is the center) so that it reaches the +// closer of the horizontal or vertical lines intersecting the midpoint, +// while maintaing the slope of the line. If p0 and p1 are the same, +// just move p1 to midPoint (since there's no slope to maintain). +// FIXME: Extending only to the midpoint isn't actually sufficient for +// boxes with asymmetric radii. +static void MaybeMoveToMidPoint(Point& aP0, Point& aP1, + const Point& aMidPoint) { + Point ps = aP1 - aP0; + + if (ps.x == 0.0) { + if (ps.y == 0.0) { + aP1 = aMidPoint; + } else { + aP1.y = aMidPoint.y; + } + } else { + if (ps.y == 0.0) { + aP1.x = aMidPoint.x; + } else { + Float k = + std::min((aMidPoint.x - aP0.x) / ps.x, (aMidPoint.y - aP0.y) / ps.y); + aP1 = aP0 + ps * k; + } + } +} + +already_AddRefed<Path> nsCSSBorderRenderer::GetSideClipSubPath( + mozilla::Side aSide) { + // the clip proceeds clockwise from the top left corner; + // so "start" in each case is the start of the region from that side. + // + // the final path will be formed like: + // s0 ------- e0 + // | / + // s1 ----- e1 + // + // that is, the second point will always be on the inside + + Point start[2]; + Point end[2]; + +#define IS_DOTTED(_s) ((_s) == StyleBorderStyle::Dotted) + bool isDotted = IS_DOTTED(mBorderStyles[aSide]); + bool startIsDotted = IS_DOTTED(mBorderStyles[PREV_SIDE(aSide)]); + bool endIsDotted = IS_DOTTED(mBorderStyles[NEXT_SIDE(aSide)]); +#undef IS_DOTTED + + SideClipType startType = SIDE_CLIP_TRAPEZOID; + SideClipType endType = SIDE_CLIP_TRAPEZOID; + + if (!IsZeroSize(mBorderRadii[GetCCWCorner(aSide)])) { + startType = SIDE_CLIP_TRAPEZOID_FULL; + } else if (startIsDotted && !isDotted) { + startType = SIDE_CLIP_RECTANGLE_CORNER; + } else if (!startIsDotted && isDotted) { + startType = SIDE_CLIP_RECTANGLE_NO_CORNER; + } + + if (!IsZeroSize(mBorderRadii[GetCWCorner(aSide)])) { + endType = SIDE_CLIP_TRAPEZOID_FULL; + } else if (endIsDotted && !isDotted) { + endType = SIDE_CLIP_RECTANGLE_CORNER; + } else if (!endIsDotted && isDotted) { + endType = SIDE_CLIP_RECTANGLE_NO_CORNER; + } + + Point midPoint = mInnerRect.Center(); + + start[0] = mOuterRect.CCWCorner(aSide); + start[1] = mInnerRect.CCWCorner(aSide); + + end[0] = mOuterRect.CWCorner(aSide); + end[1] = mInnerRect.CWCorner(aSide); + + if (startType == SIDE_CLIP_TRAPEZOID_FULL) { + MaybeMoveToMidPoint(start[0], start[1], midPoint); + } else if (startType == SIDE_CLIP_RECTANGLE_CORNER) { + if (IsHorizontalSide(aSide)) { + start[1] = + Point(mOuterRect.CCWCorner(aSide).x, mInnerRect.CCWCorner(aSide).y); + } else { + start[1] = + Point(mInnerRect.CCWCorner(aSide).x, mOuterRect.CCWCorner(aSide).y); + } + } else if (startType == SIDE_CLIP_RECTANGLE_NO_CORNER) { + if (IsHorizontalSide(aSide)) { + start[0] = + Point(mInnerRect.CCWCorner(aSide).x, mOuterRect.CCWCorner(aSide).y); + } else { + start[0] = + Point(mOuterRect.CCWCorner(aSide).x, mInnerRect.CCWCorner(aSide).y); + } + } + + if (endType == SIDE_CLIP_TRAPEZOID_FULL) { + MaybeMoveToMidPoint(end[0], end[1], midPoint); + } else if (endType == SIDE_CLIP_RECTANGLE_CORNER) { + if (IsHorizontalSide(aSide)) { + end[1] = + Point(mOuterRect.CWCorner(aSide).x, mInnerRect.CWCorner(aSide).y); + } else { + end[1] = + Point(mInnerRect.CWCorner(aSide).x, mOuterRect.CWCorner(aSide).y); + } + } else if (endType == SIDE_CLIP_RECTANGLE_NO_CORNER) { + if (IsHorizontalSide(aSide)) { + end[0] = + Point(mInnerRect.CWCorner(aSide).x, mOuterRect.CWCorner(aSide).y); + } else { + end[0] = + Point(mOuterRect.CWCorner(aSide).x, mInnerRect.CWCorner(aSide).y); + } + } + + RefPtr<PathBuilder> builder = mDrawTarget->CreatePathBuilder(); + builder->MoveTo(start[0]); + builder->LineTo(end[0]); + builder->LineTo(end[1]); + builder->LineTo(start[1]); + builder->Close(); + return builder->Finish(); +} + +Point nsCSSBorderRenderer::GetStraightBorderPoint(mozilla::Side aSide, + Corner aCorner, + bool* aIsUnfilled, + Float aDotOffset) { + // Calculate the end point of the side for dashed/dotted border, that is also + // the end point of the corner curve. The point is specified by aSide and + // aCorner. (e.g. eSideTop and C_TL means the left end of border-top) + // + // + // aCorner aSide + // +-------------------- + // | + // | + // | +---------- + // | the end point + // | + // | +---------- + // | | + // | | + // | | + // + // The position of the point depends on the border-style, border-width, and + // border-radius of the side, corner, and the adjacent side beyond the corner, + // to make those sides (and corner) interact well. + // + // If the style of aSide is dotted and the dot at the point should be + // unfilled, true is stored to *aIsUnfilled, otherwise false is stored. + + const Float signsList[4][2] = { + {+1.0f, +1.0f}, {-1.0f, +1.0f}, {-1.0f, -1.0f}, {+1.0f, -1.0f}}; + const Float(&signs)[2] = signsList[aCorner]; + + *aIsUnfilled = false; + + Point P = mOuterRect.AtCorner(aCorner); + StyleBorderStyle style = mBorderStyles[aSide]; + Float borderWidth = mBorderWidths[aSide]; + Size dim = mBorderCornerDimensions[aCorner]; + bool isHorizontal = IsHorizontalSide(aSide); + // + // aCorner aSide + // +-------------- + // | + // | +---------- + // | | + // otherSide | | + // | | + mozilla::Side otherSide = ((uint8_t)aSide == (uint8_t)aCorner) + ? PREV_SIDE(aSide) + : NEXT_SIDE(aSide); + StyleBorderStyle otherStyle = mBorderStyles[otherSide]; + Float otherBorderWidth = mBorderWidths[otherSide]; + Size radius = mBorderRadii[aCorner]; + if (IsZeroSize(radius)) { + radius.width = 0.0f; + radius.height = 0.0f; + } + if (style == StyleBorderStyle::Dotted) { + // Offset the dot's location along the side toward the corner by a + // multiple of its width. + if (isHorizontal) { + P.x -= signs[0] * aDotOffset * borderWidth; + } else { + P.y -= signs[1] * aDotOffset * borderWidth; + } + } + if (style == StyleBorderStyle::Dotted && + otherStyle == StyleBorderStyle::Dotted) { + if (borderWidth == otherBorderWidth) { + if (radius.width < borderWidth / 2.0f && + radius.height < borderWidth / 2.0f) { + // Two dots are merged into one and placed at the corner. + // + // borderWidth / 2.0 + // |<---------->| + // | | + // |radius.width| + // |<--->| | + // | | | + // | _+------+------------+----- + // | / ###|### | + // |/ #######|####### | + // + #########|######### | + // | ##########|########## | + // | ###########|########### | + // | ###########|########### | + // |############|############| + // +------------+############| + // |########### P ###########| + // | ####################### | + // | ####################### | + // | ##################### | + // | ################### | + // | ############### | + // | ####### | + // +-------------------------+---- + // | | + // | | + P.x += signs[0] * borderWidth / 2.0f; + P.y += signs[1] * borderWidth / 2.0f; + } else { + // Two dots are drawn separately. + // + // borderWidth * 1.5 + // |<------------>| + // | | + // |radius.width | + // |<----->| | + // | | | + // | _--+-+----+--- + // | _- | ##|## + // | / | ###|### + // |/ |####|#### + // | |####+#### + // | |### P ### + // + | ###|### + // | | ##|## + // +---------+----+--- + // | ##### | + // | ####### | + // |#########| + // +----+----+ + // |#########| + // | ####### | + // | ##### | + // | | + // + // There should be enough gap between 2 dots even if radius.width is + // small but larger than borderWidth / 2.0. borderWidth * 1.5 is the + // value that there's imaginally unfilled dot at the corner. The + // unfilled dot may overflow from the outer curve, but filled dots + // doesn't, so this could be acceptable solution at least for now. + // We may have to find better model/value. + // + // imaginally unfilled dot at the corner + // | + // v +----+--- + // ***** | ##|## + // ******* | ###|### + // *********|####|#### + // *********|####+#### + // *********|### P ### + // ******* | ###|### + // ***** | ##|## + // +---------+----+--- + // | ##### | + // | ####### | + // |#########| + // +----+----+ + // |#########| + // | ####### | + // | ##### | + // | | + Float minimum = borderWidth * 1.5f; + if (isHorizontal) { + P.x += signs[0] * std::max(radius.width, minimum); + P.y += signs[1] * borderWidth / 2.0f; + } else { + P.x += signs[0] * borderWidth / 2.0f; + P.y += signs[1] * std::max(radius.height, minimum); + } + } + + return P; + } + + if (borderWidth < otherBorderWidth) { + // This side is smaller than other side, other side draws the corner. + // + // otherBorderWidth + borderWidth / 2.0 + // |<---------->| + // | | + // +---------+--+-------- + // | ##### | *|* ### + // | ####### |**|**##### + // |#########|**+**##+## + // |####+####|* P *##### + // |#########| *** ### + // | ####### +----------- + // | ##### | ^ + // | | | + // | | first dot is not filled + // | | + // + // radius.width + // |<----------------->| + // | | + // | ___---+------------- + // | __-- #|# ### + // | _- ##|## ##### + // | / ##+## ##+## + // | / # P # ##### + // | | #|# ### + // | | __--+------------- + // || _- ^ + // || / | + // | / first dot is filled + // | | + // | | + // | ##### | + // | ####### | + // |#########| + // +----+----+ + // |#########| + // | ####### | + // | ##### | + Float minimum = otherBorderWidth + borderWidth / 2.0f; + if (isHorizontal) { + if (radius.width < minimum) { + *aIsUnfilled = true; + P.x += signs[0] * minimum; + } else { + P.x += signs[0] * radius.width; + } + P.y += signs[1] * borderWidth / 2.0f; + } else { + P.x += signs[0] * borderWidth / 2.0f; + if (radius.height < minimum) { + *aIsUnfilled = true; + P.y += signs[1] * minimum; + } else { + P.y += signs[1] * radius.height; + } + } + + return P; + } + + // This side is larger than other side, this side draws the corner. + // + // borderWidth / 2.0 + // |<-->| + // | | + // +----+--------------------- + // | ##|## ##### + // | ###|### ####### + // |####|#### ######### + // |####+#### ####+#### + // |### P ### ######### + // | ####### ####### + // | ##### ##### + // +-----+--------------------- + // | *** | + // |*****| + // |**+**| <-- first dot in other side is not filled + // |*****| + // | *** | + // | ### | + // |#####| + // |##+##| + // |#####| + // | ### | + // | | + if (isHorizontal) { + P.x += signs[0] * std::max(radius.width, borderWidth / 2.0f); + P.y += signs[1] * borderWidth / 2.0f; + } else { + P.x += signs[0] * borderWidth / 2.0f; + P.y += signs[1] * std::max(radius.height, borderWidth / 2.0f); + } + return P; + } + + if (style == StyleBorderStyle::Dotted) { + // If only this side is dotted, other side draws the corner. + // + // otherBorderWidth + borderWidth / 2.0 + // |<------->| + // | | + // +------+--+-------- + // |## ##| *|* ### + // |## ##|**|**##### + // |## ##|**+**##+## + // |## ##|* P *##### + // |## ##| *** ### + // |## ##+----------- + // |## ##| ^ + // |## ##| | + // |## ##| first dot is not filled + // |## ##| + // + // radius.width + // |<----------------->| + // | | + // | ___---+------------- + // | __-- #|# ### + // | _- ##|## ##### + // | / ##+## ##+## + // | / # P # ##### + // | | #|# ### + // | | __--+------------- + // || _- ^ + // || / | + // | / first dot is filled + // | | + // | | + // | | + // | | + // | | + // +------+ + // |## ##| + // |## ##| + // |## ##| + Float minimum = otherBorderWidth + borderWidth / 2.0f; + if (isHorizontal) { + if (radius.width < minimum) { + *aIsUnfilled = true; + P.x += signs[0] * minimum; + } else { + P.x += signs[0] * radius.width; + } + P.y += signs[1] * borderWidth / 2.0f; + } else { + P.x += signs[0] * borderWidth / 2.0f; + if (radius.height < minimum) { + *aIsUnfilled = true; + P.y += signs[1] * minimum; + } else { + P.y += signs[1] * radius.height; + } + } + return P; + } + + if (otherStyle == StyleBorderStyle::Dotted && IsZeroSize(radius)) { + // If other side is dotted and radius=0, draw side to the end of corner. + // + // +------------------------------- + // |########## ########## + // P +########## ########## + // |########## ########## + // +-----+------------------------- + // | *** | + // |*****| + // |**+**| <-- first dot in other side is not filled + // |*****| + // | *** | + // | ### | + // |#####| + // |##+##| + // |#####| + // | ### | + // | | + if (isHorizontal) { + P.y += signs[1] * borderWidth / 2.0f; + } else { + P.x += signs[0] * borderWidth / 2.0f; + } + return P; + } + + // Other cases. + // + // dim.width + // |<----------------->| + // | | + // | ___---+------------------ + // | __-- |####### ### + // | _- P +####### ### + // | / |####### ### + // | / __---+------------------ + // | | __-- + // | | / + // || / + // || | + // | | + // | | + // | | + // | | + // +-+-+ + // |###| + // |###| + // |###| + // |###| + // |###| + // | | + // | | + if (isHorizontal) { + P.x += signs[0] * dim.width; + P.y += signs[1] * borderWidth / 2.0f; + } else { + P.x += signs[0] * borderWidth / 2.0f; + P.y += signs[1] * dim.height; + } + + return P; +} + +void nsCSSBorderRenderer::GetOuterAndInnerBezier(Bezier* aOuterBezier, + Bezier* aInnerBezier, + Corner aCorner) { + // Return bezier control points for outer and inner curve for given corner. + // + // ___---+ outer curve + // __-- | + // _- | + // / | + // / | + // | | + // | __--+ inner curve + // | _- + // | / + // | / + // | | + // | | + // | | + // | | + // | | + // +---------+ + + mozilla::Side sideH(GetHorizontalSide(aCorner)); + mozilla::Side sideV(GetVerticalSide(aCorner)); + + Size outerCornerSize(ceil(mBorderRadii[aCorner].width), + ceil(mBorderRadii[aCorner].height)); + Size innerCornerSize( + ceil(std::max(0.0f, mBorderRadii[aCorner].width - mBorderWidths[sideV])), + ceil( + std::max(0.0f, mBorderRadii[aCorner].height - mBorderWidths[sideH]))); + + GetBezierPointsForCorner(aOuterBezier, aCorner, mOuterRect.AtCorner(aCorner), + outerCornerSize); + + GetBezierPointsForCorner(aInnerBezier, aCorner, mInnerRect.AtCorner(aCorner), + innerCornerSize); +} + +void nsCSSBorderRenderer::FillSolidBorder(const Rect& aOuterRect, + const Rect& aInnerRect, + const RectCornerRadii& aBorderRadii, + const Float* aBorderSizes, + SideBits aSides, + const ColorPattern& aColor) { + // Note that this function is allowed to draw more than just the + // requested sides. + + // If we have a border radius, do full rounded rectangles + // and fill, regardless of what sides we're asked to draw. + if (!AllCornersZeroSize(aBorderRadii)) { + RefPtr<PathBuilder> builder = mDrawTarget->CreatePathBuilder(); + + RectCornerRadii innerRadii; + ComputeInnerRadii(aBorderRadii, aBorderSizes, &innerRadii); + + // do the outer border + AppendRoundedRectToPath(builder, aOuterRect, aBorderRadii, true); + + // then do the inner border CCW + AppendRoundedRectToPath(builder, aInnerRect, innerRadii, false); + + RefPtr<Path> path = builder->Finish(); + + mDrawTarget->Fill(path, aColor); + return; + } + + // If we're asked to draw all sides of an equal-sized border, + // stroking is fastest. This is a fairly common path, but partial + // sides is probably second in the list -- there are a bunch of + // common border styles, such as inset and outset, that are + // top-left/bottom-right split. + if (aSides == SideBits::eAll && + CheckFourFloatsEqual(aBorderSizes, aBorderSizes[0]) && !mAvoidStroke) { + Float strokeWidth = aBorderSizes[0]; + Rect r(aOuterRect); + r.Deflate(strokeWidth / 2.f); + mDrawTarget->StrokeRect(r, aColor, StrokeOptions(strokeWidth)); + return; + } + + // Otherwise, we have unequal sized borders or we're only + // drawing some sides; create rectangles for each side + // and fill them. + + Rect r[4]; + + // compute base rects for each side + if (aSides & SideBits::eTop) { + r[eSideTop] = Rect(aOuterRect.X(), aOuterRect.Y(), aOuterRect.Width(), + aBorderSizes[eSideTop]); + } + + if (aSides & SideBits::eBottom) { + r[eSideBottom] = + Rect(aOuterRect.X(), aOuterRect.YMost() - aBorderSizes[eSideBottom], + aOuterRect.Width(), aBorderSizes[eSideBottom]); + } + + if (aSides & SideBits::eLeft) { + r[eSideLeft] = Rect(aOuterRect.X(), aOuterRect.Y(), aBorderSizes[eSideLeft], + aOuterRect.Height()); + } + + if (aSides & SideBits::eRight) { + r[eSideRight] = + Rect(aOuterRect.XMost() - aBorderSizes[eSideRight], aOuterRect.Y(), + aBorderSizes[eSideRight], aOuterRect.Height()); + } + + // If two sides meet at a corner that we're rendering, then + // make sure that we adjust one of the sides to avoid overlap. + // This is especially important in the case of colors with + // an alpha channel. + + if ((aSides & (SideBits::eTop | SideBits::eLeft)) == + (SideBits::eTop | SideBits::eLeft)) { + // adjust the left's top down a bit + r[eSideLeft].y += aBorderSizes[eSideTop]; + r[eSideLeft].height -= aBorderSizes[eSideTop]; + } + + if ((aSides & (SideBits::eTop | SideBits::eRight)) == + (SideBits::eTop | SideBits::eRight)) { + // adjust the top's left a bit + r[eSideTop].width -= aBorderSizes[eSideRight]; + } + + if ((aSides & (SideBits::eBottom | SideBits::eRight)) == + (SideBits::eBottom | SideBits::eRight)) { + // adjust the right's bottom a bit + r[eSideRight].height -= aBorderSizes[eSideBottom]; + } + + if ((aSides & (SideBits::eBottom | SideBits::eLeft)) == + (SideBits::eBottom | SideBits::eLeft)) { + // adjust the bottom's left a bit + r[eSideBottom].x += aBorderSizes[eSideLeft]; + r[eSideBottom].width -= aBorderSizes[eSideLeft]; + } + + // Filling these one by one is faster than filling them all at once. + for (uint32_t i = 0; i < 4; i++) { + if (aSides & static_cast<mozilla::SideBits>(1 << i)) { + MaybeSnapToDevicePixels(r[i], *mDrawTarget, true); + mDrawTarget->FillRect(r[i], aColor); + } + } +} + +sRGBColor MakeBorderColor(nscolor aColor, BorderColorStyle aBorderColorStyle) { + nscolor colors[2]; + int k = 0; + + switch (aBorderColorStyle) { + case BorderColorStyleNone: + return sRGBColor(0.f, 0.f, 0.f, 0.f); // transparent black + + case BorderColorStyleLight: + k = 1; + [[fallthrough]]; + case BorderColorStyleDark: + NS_GetSpecial3DColors(colors, aColor); + return sRGBColor::FromABGR(colors[k]); + + case BorderColorStyleSolid: + default: + return sRGBColor::FromABGR(aColor); + } +} + +sRGBColor ComputeColorForLine(uint32_t aLineIndex, + const BorderColorStyle* aBorderColorStyle, + uint32_t aBorderColorStyleCount, + nscolor aBorderColor) { + NS_ASSERTION(aLineIndex < aBorderColorStyleCount, "Invalid lineIndex given"); + + return MakeBorderColor(aBorderColor, aBorderColorStyle[aLineIndex]); +} + +void nsCSSBorderRenderer::DrawBorderSides(mozilla::SideBits aSides) { + if (aSides == SideBits::eNone || + (aSides & ~SideBits::eAll) != SideBits::eNone) { + NS_WARNING("DrawBorderSides: invalid sides!"); + return; + } + + StyleBorderStyle borderRenderStyle = StyleBorderStyle::None; + nscolor borderRenderColor; + + uint32_t borderColorStyleCount = 0; + BorderColorStyle borderColorStyleTopLeft[3], borderColorStyleBottomRight[3]; + BorderColorStyle* borderColorStyle = nullptr; + + for (const auto i : mozilla::AllPhysicalSides()) { + if ((aSides & static_cast<mozilla::SideBits>(1 << i)) == SideBits::eNone) { + continue; + } + borderRenderStyle = mBorderStyles[i]; + borderRenderColor = mBorderColors[i]; + break; + } + + if (borderRenderStyle == StyleBorderStyle::None || + borderRenderStyle == StyleBorderStyle::Hidden) { + return; + } + + if (borderRenderStyle == StyleBorderStyle::Dashed || + borderRenderStyle == StyleBorderStyle::Dotted) { + // Draw each corner separately, with the given side's color. + if (aSides & SideBits::eTop) { + DrawDashedOrDottedCorner(eSideTop, C_TL); + } else if (aSides & SideBits::eLeft) { + DrawDashedOrDottedCorner(eSideLeft, C_TL); + } + + if (aSides & SideBits::eTop) { + DrawDashedOrDottedCorner(eSideTop, C_TR); + } else if (aSides & SideBits::eRight) { + DrawDashedOrDottedCorner(eSideRight, C_TR); + } + + if (aSides & SideBits::eBottom) { + DrawDashedOrDottedCorner(eSideBottom, C_BL); + } else if (aSides & SideBits::eLeft) { + DrawDashedOrDottedCorner(eSideLeft, C_BL); + } + + if (aSides & SideBits::eBottom) { + DrawDashedOrDottedCorner(eSideBottom, C_BR); + } else if (aSides & SideBits::eRight) { + DrawDashedOrDottedCorner(eSideRight, C_BR); + } + return; + } + + // The borderColorStyle array goes from the outer to the inner style. + // + // If the border width is 1, we need to change the borderRenderStyle + // a bit to make sure that we get the right colors -- e.g. 'ridge' + // with a 1px border needs to look like solid, not like 'outset'. + if (mOneUnitBorder && (borderRenderStyle == StyleBorderStyle::Ridge || + borderRenderStyle == StyleBorderStyle::Groove || + borderRenderStyle == StyleBorderStyle::Double)) { + borderRenderStyle = StyleBorderStyle::Solid; + } + + switch (borderRenderStyle) { + case StyleBorderStyle::Solid: + borderColorStyleTopLeft[0] = BorderColorStyleSolid; + + borderColorStyleBottomRight[0] = BorderColorStyleSolid; + + borderColorStyleCount = 1; + break; + + case StyleBorderStyle::Groove: + borderColorStyleTopLeft[0] = BorderColorStyleDark; + borderColorStyleTopLeft[1] = BorderColorStyleLight; + + borderColorStyleBottomRight[0] = BorderColorStyleLight; + borderColorStyleBottomRight[1] = BorderColorStyleDark; + + borderColorStyleCount = 2; + break; + + case StyleBorderStyle::Ridge: + borderColorStyleTopLeft[0] = BorderColorStyleLight; + borderColorStyleTopLeft[1] = BorderColorStyleDark; + + borderColorStyleBottomRight[0] = BorderColorStyleDark; + borderColorStyleBottomRight[1] = BorderColorStyleLight; + + borderColorStyleCount = 2; + break; + + case StyleBorderStyle::Double: + borderColorStyleTopLeft[0] = BorderColorStyleSolid; + borderColorStyleTopLeft[1] = BorderColorStyleNone; + borderColorStyleTopLeft[2] = BorderColorStyleSolid; + + borderColorStyleBottomRight[0] = BorderColorStyleSolid; + borderColorStyleBottomRight[1] = BorderColorStyleNone; + borderColorStyleBottomRight[2] = BorderColorStyleSolid; + + borderColorStyleCount = 3; + break; + + case StyleBorderStyle::Inset: + borderColorStyleTopLeft[0] = BorderColorStyleDark; + borderColorStyleBottomRight[0] = BorderColorStyleLight; + + borderColorStyleCount = 1; + break; + + case StyleBorderStyle::Outset: + borderColorStyleTopLeft[0] = BorderColorStyleLight; + borderColorStyleBottomRight[0] = BorderColorStyleDark; + + borderColorStyleCount = 1; + break; + + default: + MOZ_ASSERT_UNREACHABLE("Unhandled border style!!"); + break; + } + + // The only way to get to here is by having a + // borderColorStyleCount < 1 or > 3; this should never happen, + // since -moz-border-colors doesn't get handled here. + NS_ASSERTION(borderColorStyleCount > 0 && borderColorStyleCount < 4, + "Non-border-colors case with borderColorStyleCount < 1 or > 3; " + "what happened?"); + + // The caller should never give us anything with a mix + // of TL/BR if the border style would require a + // TL/BR split. + if (aSides & (SideBits::eBottom | SideBits::eRight)) { + borderColorStyle = borderColorStyleBottomRight; + } else { + borderColorStyle = borderColorStyleTopLeft; + } + + // Distribute the border across the available space. + Float borderWidths[3][4]; + + if (borderColorStyleCount == 1) { + for (const auto i : mozilla::AllPhysicalSides()) { + borderWidths[0][i] = mBorderWidths[i]; + } + } else if (borderColorStyleCount == 2) { + // with 2 color styles, any extra pixel goes to the outside + for (const auto i : mozilla::AllPhysicalSides()) { + borderWidths[0][i] = + int32_t(mBorderWidths[i]) / 2 + int32_t(mBorderWidths[i]) % 2; + borderWidths[1][i] = int32_t(mBorderWidths[i]) / 2; + } + } else if (borderColorStyleCount == 3) { + // with 3 color styles, any extra pixel (or lack of extra pixel) + // goes to the middle + for (const auto i : mozilla::AllPhysicalSides()) { + if (mBorderWidths[i] == 1.0) { + borderWidths[0][i] = 1.f; + borderWidths[1][i] = borderWidths[2][i] = 0.f; + } else { + int32_t rest = int32_t(mBorderWidths[i]) % 3; + borderWidths[0][i] = borderWidths[2][i] = borderWidths[1][i] = + (int32_t(mBorderWidths[i]) - rest) / 3; + + if (rest == 1) { + borderWidths[1][i] += 1.f; + } else if (rest == 2) { + borderWidths[0][i] += 1.f; + borderWidths[2][i] += 1.f; + } + } + } + } + + // make a copy that we can modify + RectCornerRadii radii = mBorderRadii; + + Rect soRect(mOuterRect); + Rect siRect(mOuterRect); + + // If adjacent side is dotted and radius=0, draw side to the end of corner. + // + // +-------------------------------- + // |################################ + // | + // |################################ + // +-----+-------------------------- + // | | + // | | + // | | + // | | + // | | + // | ### | + // |#####| + // |#####| + // |#####| + // | ### | + // | | + bool noMarginTop = false; + bool noMarginRight = false; + bool noMarginBottom = false; + bool noMarginLeft = false; + + // If there is at least one dotted side, every side is rendered separately. + if (IsSingleSide(aSides)) { + if (aSides == SideBits::eTop) { + if (mBorderStyles[eSideRight] == StyleBorderStyle::Dotted && + IsZeroSize(mBorderRadii[C_TR])) { + noMarginRight = true; + } + if (mBorderStyles[eSideLeft] == StyleBorderStyle::Dotted && + IsZeroSize(mBorderRadii[C_TL])) { + noMarginLeft = true; + } + } else if (aSides == SideBits::eRight) { + if (mBorderStyles[eSideTop] == StyleBorderStyle::Dotted && + IsZeroSize(mBorderRadii[C_TR])) { + noMarginTop = true; + } + if (mBorderStyles[eSideBottom] == StyleBorderStyle::Dotted && + IsZeroSize(mBorderRadii[C_BR])) { + noMarginBottom = true; + } + } else if (aSides == SideBits::eBottom) { + if (mBorderStyles[eSideRight] == StyleBorderStyle::Dotted && + IsZeroSize(mBorderRadii[C_BR])) { + noMarginRight = true; + } + if (mBorderStyles[eSideLeft] == StyleBorderStyle::Dotted && + IsZeroSize(mBorderRadii[C_BL])) { + noMarginLeft = true; + } + } else { + if (mBorderStyles[eSideTop] == StyleBorderStyle::Dotted && + IsZeroSize(mBorderRadii[C_TL])) { + noMarginTop = true; + } + if (mBorderStyles[eSideBottom] == StyleBorderStyle::Dotted && + IsZeroSize(mBorderRadii[C_BL])) { + noMarginBottom = true; + } + } + } + + for (unsigned int i = 0; i < borderColorStyleCount; i++) { + // walk siRect inwards at the start of the loop to get the + // correct inner rect. + // + // If noMarginTop is false: + // --------------------+ + // /| + // / | + // L | + // ----------------+ | + // | | + // | | + // + // If noMarginTop is true: + // ----------------+<--+ + // | | + // | | + // | | + // | | + // | | + // | | + siRect.Deflate(Margin(noMarginTop ? 0 : borderWidths[i][0], + noMarginRight ? 0 : borderWidths[i][1], + noMarginBottom ? 0 : borderWidths[i][2], + noMarginLeft ? 0 : borderWidths[i][3])); + + if (borderColorStyle[i] != BorderColorStyleNone) { + sRGBColor c = ComputeColorForLine( + i, borderColorStyle, borderColorStyleCount, borderRenderColor); + ColorPattern color(ToDeviceColor(c)); + + FillSolidBorder(soRect, siRect, radii, borderWidths[i], aSides, color); + } + + ComputeInnerRadii(radii, borderWidths[i], &radii); + + // And now soRect is the same as siRect, for the next line in. + soRect = siRect; + } +} + +void nsCSSBorderRenderer::SetupDashedOptions(StrokeOptions* aStrokeOptions, + Float aDash[2], + mozilla::Side aSide, + Float aBorderLength, + bool isCorner) { + MOZ_ASSERT(mBorderStyles[aSide] == StyleBorderStyle::Dashed || + mBorderStyles[aSide] == StyleBorderStyle::Dotted, + "Style should be dashed or dotted."); + + StyleBorderStyle style = mBorderStyles[aSide]; + Float borderWidth = mBorderWidths[aSide]; + + // Dashed line starts and ends with half segment in most case. + // + // __--+---+---+---+---+---+---+---+---+--__ + // |###| | |###|###| | |###| + // |###| | |###|###| | |###| + // |###| | |###|###| | |###| + // __--+---+---+---+---+---+---+---+---+--__ + // + // If radius=0 and other side is either dotted or 0-width, it starts or ends + // with full segment. + // + // +---+---+---+---+---+---+---+---+---+---+ + // |###|###| | |###|###| | |###|###| + // |###|###| | |###|###| | |###|###| + // |###|###| | |###|###| | |###|###| + // +---++--+---+---+---+---+---+---+--++---+ + // | | | | + // | | | | + // | | | | + // | | | | + // | ## | | ## | + // |####| |####| + // |####| |####| + // | ## | | ## | + // | | | | + bool fullStart = false, fullEnd = false; + Float halfDash; + if (style == StyleBorderStyle::Dashed) { + // If either end of the side is not connecting onto a corner then we want a + // full dash at that end. + // + // Note that in the case that a corner is empty, either the adjacent side + // has zero width, or else DrawBorders() set the corner to be empty + // (it does that if the adjacent side has zero length and the border widths + // of this and the adjacent sides are thin enough that the corner will be + // insignificantly small). + + if (mBorderRadii[GetCCWCorner(aSide)].IsEmpty() && + (mBorderCornerDimensions[GetCCWCorner(aSide)].IsEmpty() || + mBorderStyles[PREV_SIDE(aSide)] == StyleBorderStyle::Dotted || + // XXX why this <=1 check? + borderWidth <= 1.0f)) { + fullStart = true; + } + + if (mBorderRadii[GetCWCorner(aSide)].IsEmpty() && + (mBorderCornerDimensions[GetCWCorner(aSide)].IsEmpty() || + mBorderStyles[NEXT_SIDE(aSide)] == StyleBorderStyle::Dotted)) { + fullEnd = true; + } + + halfDash = borderWidth * DOT_LENGTH * DASH_LENGTH / 2.0f; + } else { + halfDash = borderWidth * DOT_LENGTH / 2.0f; + } + + if (style == StyleBorderStyle::Dashed && aBorderLength > 0.0f) { + // The number of half segments, with maximum dash length. + int32_t count = floor(aBorderLength / halfDash); + Float minHalfDash = borderWidth * DOT_LENGTH / 2.0f; + + if (fullStart && fullEnd) { + // count should be 4n + 2 + // + // 1 + 4 + 4 + 1 + // + // | | | | | + // +---+---+---+---+---+---+---+---+---+---+ + // |###|###| | |###|###| | |###|###| + // |###|###| | |###|###| | |###|###| + // |###|###| | |###|###| | |###|###| + // +---+---+---+---+---+---+---+---+---+---+ + + // If border is too short, draw solid line. + if (aBorderLength < 6.0f * minHalfDash) { + return; + } + + if (count % 4 == 0) { + count += 2; + } else if (count % 4 == 1) { + count += 1; + } else if (count % 4 == 3) { + count += 3; + } + } else if (fullStart || fullEnd) { + // count should be 4n + 1 + // + // 1 + 4 + 4 + // + // | | | | + // +---+---+---+---+---+---+---+---+---+ + // |###|###| | |###|###| | |###| + // |###|###| | |###|###| | |###| + // |###|###| | |###|###| | |###| + // +---+---+---+---+---+---+---+---+---+ + // + // 4 + 4 + 1 + // + // | | | | + // +---+---+---+---+---+---+---+---+---+ + // |###| | |###|###| | |###|###| + // |###| | |###|###| | |###|###| + // |###| | |###|###| | |###|###| + // +---+---+---+---+---+---+---+---+---+ + + // If border is too short, draw solid line. + if (aBorderLength < 5.0f * minHalfDash) { + return; + } + + if (count % 4 == 0) { + count += 1; + } else if (count % 4 == 2) { + count += 3; + } else if (count % 4 == 3) { + count += 2; + } + } else { + // count should be 4n + // + // 4 + 4 + // + // | | | + // +---+---+---+---+---+---+---+---+ + // |###| | |###|###| | |###| + // |###| | |###|###| | |###| + // |###| | |###|###| | |###| + // +---+---+---+---+---+---+---+---+ + + // If border is too short, draw solid line. + if (aBorderLength < 4.0f * minHalfDash) { + return; + } + + if (count % 4 == 1) { + count += 3; + } else if (count % 4 == 2) { + count += 2; + } else if (count % 4 == 3) { + count += 1; + } + } + halfDash = aBorderLength / count; + } + + Float fullDash = halfDash * 2.0f; + + aDash[0] = fullDash; + aDash[1] = fullDash; + + if (style == StyleBorderStyle::Dashed && fullDash > 1.0f) { + if (!fullStart) { + // Draw half segments on both ends. + aStrokeOptions->mDashOffset = halfDash; + } + } else if (style != StyleBorderStyle::Dotted && isCorner) { + // If side ends with filled full segment, corner should start with unfilled + // full segment. Not needed for dotted corners, as they overlap one dot with + // the side's end. + // + // corner side + // ------------>|<--------------------------- + // | + // __+---+---+---+---+---+---+---+---+ + // _+- | |###|###| | |###|###| | + // /##| | |###|###| | |###|###| | + // +####| | |###|###| | |###|###| | + // /#\####| _+--+---+---+---+---+---+---+---+ + // |####\##+- + // |#####+- + // +--###/ + // | --+ + aStrokeOptions->mDashOffset = fullDash; + } + + aStrokeOptions->mDashPattern = aDash; + aStrokeOptions->mDashLength = 2; + + PrintAsFormatString("dash: %f %f\n", aDash[0], aDash[1]); +} + +static Float GetBorderLength(mozilla::Side aSide, const Point& aStart, + const Point& aEnd) { + if (aSide == eSideTop) { + return aEnd.x - aStart.x; + } + if (aSide == eSideRight) { + return aEnd.y - aStart.y; + } + if (aSide == eSideBottom) { + return aStart.x - aEnd.x; + } + return aStart.y - aEnd.y; +} + +void nsCSSBorderRenderer::DrawDashedOrDottedSide(mozilla::Side aSide) { + // Draw dashed/dotted side with following approach. + // + // dashed side + // Draw dashed line along the side, with appropriate dash length and gap + // to make the side symmetric as far as possible. Dash length equals to + // the gap, and the ratio of the dash length to border-width is the maximum + // value in in [1, 3] range. + // In most case, line ends with half segment, to joint with corner easily. + // If adjacent side is dotted or 0px and border-radius for the corner + // between them is 0, the line ends with full segment. + // (see comment for GetStraightBorderPoint for more detail) + // + // dotted side + // If border-width <= 2.0, draw 1:1 dashed line. + // Otherwise, draw circles along the side, with appropriate gap that makes + // the side symmetric as far as possible. The ratio of the gap to + // border-width is the maximum value in [0.5, 1] range in most case. + // if the side is too short and there's only 2 dots, it can be more smaller. + // If there's no space to place 2 dots at the side, draw single dot at the + // middle of the side. + // In most case, line ends with filled dot, to joint with corner easily, + // If adjacent side is dotted with larger border-width, or other style, + // the line ends with unfilled dot. + // (see comment for GetStraightBorderPoint for more detail) + + NS_ASSERTION(mBorderStyles[aSide] == StyleBorderStyle::Dashed || + mBorderStyles[aSide] == StyleBorderStyle::Dotted, + "Style should be dashed or dotted."); + + Float borderWidth = mBorderWidths[aSide]; + if (borderWidth == 0.0f) { + return; + } + + if (mBorderStyles[aSide] == StyleBorderStyle::Dotted && borderWidth > 2.0f) { + DrawDottedSideSlow(aSide); + return; + } + + nscolor borderColor = mBorderColors[aSide]; + bool ignored; + // Get the start and end points of the side, ensuring that any dot origins get + // pushed outward to account for stroking. + Point start = + GetStraightBorderPoint(aSide, GetCCWCorner(aSide), &ignored, 0.5f); + Point end = GetStraightBorderPoint(aSide, GetCWCorner(aSide), &ignored, 0.5f); + if (borderWidth < 2.0f) { + // Round start to draw dot on each pixel. + if (IsHorizontalSide(aSide)) { + start.x = round(start.x); + } else { + start.y = round(start.y); + } + } + + Float borderLength = GetBorderLength(aSide, start, end); + if (borderLength < 0.0f) { + return; + } + + StrokeOptions strokeOptions(borderWidth); + Float dash[2]; + SetupDashedOptions(&strokeOptions, dash, aSide, borderLength, false); + + // For dotted sides that can merge with their prior dotted sides, advance the + // dash offset to measure the distance around the combined path. This prevents + // two dots from bunching together at a corner. + mozilla::Side mergeSide = aSide; + while (IsCornerMergeable(GetCCWCorner(mergeSide))) { + mergeSide = PREV_SIDE(mergeSide); + // If we looped all the way around, measure starting at the top side, since + // we need to pick a fixed location to start measuring distance from still. + if (mergeSide == aSide) { + mergeSide = eSideTop; + break; + } + } + while (mergeSide != aSide) { + // Measure the length of the merged side starting from a possibly + // unmergeable corner up to the merged corner. A merged corner effectively + // has no border radius, so we can just use the cheaper AtCorner to find the + // end point. + Float mergeLength = + GetBorderLength(mergeSide, + GetStraightBorderPoint( + mergeSide, GetCCWCorner(mergeSide), &ignored, 0.5f), + mOuterRect.AtCorner(GetCWCorner(mergeSide))); + // Add in the merged side length. Also offset the dash progress by an extra + // dot's width to avoid drawing a dot that would overdraw where the merged + // side would have ended in a gap, i.e. O_O_ + // O + strokeOptions.mDashOffset += mergeLength + borderWidth; + mergeSide = NEXT_SIDE(mergeSide); + } + + DrawOptions drawOptions; + if (mBorderStyles[aSide] == StyleBorderStyle::Dotted) { + drawOptions.mAntialiasMode = AntialiasMode::NONE; + } + + mDrawTarget->StrokeLine(start, end, ColorPattern(ToDeviceColor(borderColor)), + strokeOptions, drawOptions); +} + +void nsCSSBorderRenderer::DrawDottedSideSlow(mozilla::Side aSide) { + // Draw each circles separately for dotted with borderWidth > 2.0. + // Dashed line with CapStyle::ROUND doesn't render perfect circles. + + NS_ASSERTION(mBorderStyles[aSide] == StyleBorderStyle::Dotted, + "Style should be dotted."); + + Float borderWidth = mBorderWidths[aSide]; + if (borderWidth == 0.0f) { + return; + } + + nscolor borderColor = mBorderColors[aSide]; + bool isStartUnfilled, isEndUnfilled; + Point start = + GetStraightBorderPoint(aSide, GetCCWCorner(aSide), &isStartUnfilled); + Point end = GetStraightBorderPoint(aSide, GetCWCorner(aSide), &isEndUnfilled); + enum { + // Corner is not mergeable. + NO_MERGE, + + // Corner between different colors. + // Two dots are merged into one, and both side draw half dot. + MERGE_HALF, + + // Corner between same colors, CCW corner of the side. + // Two dots are merged into one, and this side draw entire dot. + // + // MERGE_ALL MERGE_NONE + // | | + // v v + // +-----------------------+----+ + // | ## ## ## | ## | + // |#### #### #### |####| + // |#### #### #### |####| + // | ## ## ## | ## | + // +----+------------------+ | + // | | | | + // | | | | + // | | | | + // | ## | | ## | + // |####| |####| + MERGE_ALL, + + // Corner between same colors, CW corner of the side. + // Two dots are merged into one, and this side doesn't draw dot. + MERGE_NONE + } mergeStart = NO_MERGE, + mergeEnd = NO_MERGE; + + if (IsCornerMergeable(GetCCWCorner(aSide))) { + if (borderColor == mBorderColors[PREV_SIDE(aSide)]) { + mergeStart = MERGE_ALL; + } else { + mergeStart = MERGE_HALF; + } + } + + if (IsCornerMergeable(GetCWCorner(aSide))) { + if (borderColor == mBorderColors[NEXT_SIDE(aSide)]) { + mergeEnd = MERGE_NONE; + } else { + mergeEnd = MERGE_HALF; + } + } + + Float borderLength = GetBorderLength(aSide, start, end); + if (borderLength < 0.0f) { + if (isStartUnfilled || isEndUnfilled) { + return; + } + borderLength = 0.0f; + start = end = (start + end) / 2.0f; + } + + Float dotWidth = borderWidth * DOT_LENGTH; + Float radius = borderWidth / 2.0f; + if (borderLength < dotWidth) { + // If dots on start and end may overlap, draw a dot at the middle of them. + // + // ___---+-------+---___ + // __-- | ##### | --__ + // #|#######|# + // ##|#######|## + // ###|#######|### + // ###+###+###+### + // start ## end # + // ##|#######|## + // #|#######|# + // | ##### | + // __--+-------+--__ + // _- -_ + // + // If that circle overflows from outer rect, do not draw it. + // + // +-------+ + // | ##### | + // #|#######|# + // ##|#######|## + // ###|#######|### + // ###|###+###|### + // ###|#######|### + // ##|#######|## + // #|#######|# + // | ##### | + // +--+-+--+ + // | | | | + // | | | | + if (!mOuterRect.Contains(Rect(start.x - radius, start.y - radius, + borderWidth, borderWidth))) { + return; + } + + if (isStartUnfilled || isEndUnfilled) { + return; + } + + Point P = (start + end) / 2; + RefPtr<PathBuilder> builder = mDrawTarget->CreatePathBuilder(); + builder->MoveTo(Point(P.x + radius, P.y)); + builder->Arc(P, radius, 0.0f, Float(2.0 * M_PI)); + RefPtr<Path> path = builder->Finish(); + mDrawTarget->Fill(path, ColorPattern(ToDeviceColor(borderColor))); + return; + } + + if (mergeStart == MERGE_HALF || mergeEnd == MERGE_HALF) { + // MERGE_HALF + // Eo + // -------+----+ + // ##### / + // ######/ + // ######/ + // ####+ + // ##/ end + // / + // / + // --+ + // Ei + // + // other (NO_MERGE, MERGE_ALL, MERGE_NONE) + // Eo + // ------------+ + // ##### | + // ####### | + // #########| + // ####+####| + // ## end ##| + // ####### | + // ##### | + // ------------+ + // Ei + + Point I(0.0f, 0.0f), J(0.0f, 0.0f); + if (aSide == eSideTop) { + I.x = 1.0f; + J.y = 1.0f; + } else if (aSide == eSideRight) { + I.y = 1.0f; + J.x = -1.0f; + } else if (aSide == eSideBottom) { + I.x = -1.0f; + J.y = -1.0f; + } else if (aSide == eSideLeft) { + I.y = -1.0f; + J.x = 1.0f; + } + + Point So, Si, Eo, Ei; + + So = (start + (-I + -J) * borderWidth / 2.0f); + Si = (mergeStart == MERGE_HALF) ? (start + (I + J) * borderWidth / 2.0f) + : (start + (-I + J) * borderWidth / 2.0f); + Eo = (end + (I - J) * borderWidth / 2.0f); + Ei = (mergeEnd == MERGE_HALF) ? (end + (-I + J) * borderWidth / 2.0f) + : (end + (I + J) * borderWidth / 2.0f); + + RefPtr<PathBuilder> builder = mDrawTarget->CreatePathBuilder(); + builder->MoveTo(So); + builder->LineTo(Eo); + builder->LineTo(Ei); + builder->LineTo(Si); + builder->Close(); + RefPtr<Path> path = builder->Finish(); + + mDrawTarget->PushClip(path); + } + + size_t count = round(borderLength / dotWidth); + if (isStartUnfilled == isEndUnfilled) { + // Split into 2n segments. + if (count % 2) { + count++; + } + } else { + // Split into 2n+1 segments. + if (count % 2 == 0) { + count++; + } + } + + // A: radius == borderWidth / 2.0 + // B: borderLength / count == borderWidth * (1 - overlap) + // + // A B B B B A + // |<-->|<------>|<------>|<------>|<------>|<-->| + // | | | | | | | + // +----+--------+--------+--------+--------+----+ + // | ##|## **|** ##|## **|** ##|## | + // | ###|### ***|*** ###|### ***|*** ###|### | + // |####|####****|****####|####****|****####|####| + // |####+####****+****####+####****+****####+####| + // |# start #****|****####|####****|****## end ##| + // | ###|### ***|*** ###|### ***|*** ###|### | + // | ##|## **|** ##|## **|** ##|## | + // +----+----+---+--------+--------+---+----+----+ + // | | | | + // | | | | + + // If isStartUnfilled is true, draw dots on 2j+1 points, if not, draw dots on + // 2j points. + size_t from = isStartUnfilled ? 1 : 0; + + // If mergeEnd == MERGE_NONE, last dot is drawn by next side. + size_t to = count; + if (mergeEnd == MERGE_NONE) { + if (to > 2) { + to -= 2; + } else { + to = 0; + } + } + + Point fromP = (start * (count - from) + end * from) / count; + Point toP = (start * (count - to) + end * to) / count; + // Extend dirty rect to avoid clipping pixel for anti-aliasing. + const Float AA_MARGIN = 2.0f; + + // The following algorithm assumes the border's rect and the dirty rect + // intersect. + MOZ_ASSERT(mDirtyRect.Intersects(mOuterRect)); + + if (aSide == eSideTop) { + // Tweak |from| and |to| to fit into |mDirtyRect + radius margin|, + // to render only paths that may overlap mDirtyRect. + // + // mDirtyRect + radius margin + // +--+---------------------+--+ + // | | + // | mDirtyRect | + // + +---------------------+ + + // from ===> |from to | <=== to + // +-----+-----+-----+-----+-----+-----+-----+-----+ + // ### |### ### ###| ### + // ##### ##### ##### ##### ##### + // ##### ##### ##### ##### ##### + // ##### ##### ##### ##### ##### + // ### |### ### ###| ### + // | | | | + // + +---------------------+ + + // | | + // | | + // +--+---------------------+--+ + + Float left = mDirtyRect.x - radius - AA_MARGIN; + if (fromP.x < left) { + size_t tmp = ceil(count * (left - start.x) / (end.x - start.x)); + if (tmp > from) { + // We increment by 2, so odd/even should match between before/after. + if ((tmp & 1) != (from & 1)) { + from = tmp - 1; + } else { + from = tmp; + } + } + } + Float right = mDirtyRect.x + mDirtyRect.width + radius + AA_MARGIN; + if (toP.x > right) { + size_t tmp = floor(count * (right - start.x) / (end.x - start.x)); + if (tmp < to) { + if ((tmp & 1) != (to & 1)) { + to = tmp + 1; + } else { + to = tmp; + } + } + } + } else if (aSide == eSideRight) { + Float top = mDirtyRect.y - radius - AA_MARGIN; + if (fromP.y < top) { + size_t tmp = ceil(count * (top - start.y) / (end.y - start.y)); + if (tmp > from) { + if ((tmp & 1) != (from & 1)) { + from = tmp - 1; + } else { + from = tmp; + } + } + } + Float bottom = mDirtyRect.y + mDirtyRect.height + radius + AA_MARGIN; + if (toP.y > bottom) { + size_t tmp = floor(count * (bottom - start.y) / (end.y - start.y)); + if (tmp < to) { + if ((tmp & 1) != (to & 1)) { + to = tmp + 1; + } else { + to = tmp; + } + } + } + } else if (aSide == eSideBottom) { + Float right = mDirtyRect.x + mDirtyRect.width + radius + AA_MARGIN; + if (fromP.x > right) { + size_t tmp = ceil(count * (right - start.x) / (end.x - start.x)); + if (tmp > from) { + if ((tmp & 1) != (from & 1)) { + from = tmp - 1; + } else { + from = tmp; + } + } + } + Float left = mDirtyRect.x - radius - AA_MARGIN; + if (toP.x < left) { + size_t tmp = floor(count * (left - start.x) / (end.x - start.x)); + if (tmp < to) { + if ((tmp & 1) != (to & 1)) { + to = tmp + 1; + } else { + to = tmp; + } + } + } + } else if (aSide == eSideLeft) { + Float bottom = mDirtyRect.y + mDirtyRect.height + radius + AA_MARGIN; + if (fromP.y > bottom) { + size_t tmp = ceil(count * (bottom - start.y) / (end.y - start.y)); + if (tmp > from) { + if ((tmp & 1) != (from & 1)) { + from = tmp - 1; + } else { + from = tmp; + } + } + } + Float top = mDirtyRect.y - radius - AA_MARGIN; + if (toP.y < top) { + size_t tmp = floor(count * (top - start.y) / (end.y - start.y)); + if (tmp < to) { + if ((tmp & 1) != (to & 1)) { + to = tmp + 1; + } else { + to = tmp; + } + } + } + } + + RefPtr<PathBuilder> builder = mDrawTarget->CreatePathBuilder(); + size_t segmentCount = 0; + for (size_t i = from; i <= to; i += 2) { + if (segmentCount > BORDER_SEGMENT_COUNT_MAX) { + RefPtr<Path> path = builder->Finish(); + mDrawTarget->Fill(path, ColorPattern(ToDeviceColor(borderColor))); + builder = mDrawTarget->CreatePathBuilder(); + segmentCount = 0; + } + + Point P = (start * (count - i) + end * i) / count; + builder->MoveTo(Point(P.x + radius, P.y)); + builder->Arc(P, radius, 0.0f, Float(2.0 * M_PI)); + segmentCount++; + } + RefPtr<Path> path = builder->Finish(); + mDrawTarget->Fill(path, ColorPattern(ToDeviceColor(borderColor))); + + if (mergeStart == MERGE_HALF || mergeEnd == MERGE_HALF) { + mDrawTarget->PopClip(); + } +} + +void nsCSSBorderRenderer::DrawDashedOrDottedCorner(mozilla::Side aSide, + Corner aCorner) { + // Draw dashed/dotted corner with following approach. + // + // dashed corner + // If both side has same border-width and border-width <= 2.0, draw dashed + // line along the corner, with appropriate dash length and gap to make the + // corner symmetric as far as possible. Dash length equals to the gap, and + // the ratio of the dash length to border-width is the maximum value in in + // [1, 3] range. + // Otherwise, draw dashed segments along the corner, keeping same dash + // length ratio to border-width at that point. + // (see DashedCornerFinder.h for more detail) + // Line ends with half segments, to joint with both side easily. + // + // dotted corner + // If both side has same border-width and border-width <= 2.0, draw 1:1 + // dashed line along the corner. + // Otherwise Draw circles along the corner, with appropriate gap that makes + // the corner symmetric as far as possible. The size of the circle may + // change along the corner, that is tangent to the outer curver and the + // inner curve. The ratio of the gap to circle diameter is the maximum + // value in [0.5, 1] range. + // (see DottedCornerFinder.h for more detail) + // Corner ends with filled dots but those dots are drawn by + // DrawDashedOrDottedSide. So this may draw no circles if there's no space + // between 2 dots at both ends. + + NS_ASSERTION(mBorderStyles[aSide] == StyleBorderStyle::Dashed || + mBorderStyles[aSide] == StyleBorderStyle::Dotted, + "Style should be dashed or dotted."); + + if (IsCornerMergeable(aCorner)) { + // DrawDashedOrDottedSide will draw corner. + return; + } + + mozilla::Side sideH(GetHorizontalSide(aCorner)); + mozilla::Side sideV(GetVerticalSide(aCorner)); + Float borderWidthH = mBorderWidths[sideH]; + Float borderWidthV = mBorderWidths[sideV]; + if (borderWidthH == 0.0f && borderWidthV == 0.0f) { + return; + } + + StyleBorderStyle styleH = mBorderStyles[sideH]; + StyleBorderStyle styleV = mBorderStyles[sideV]; + + // Corner between dotted and others with radius=0 is drawn by side. + if (IsZeroSize(mBorderRadii[aCorner]) && + (styleV == StyleBorderStyle::Dotted || + styleH == StyleBorderStyle::Dotted)) { + return; + } + + Float maxRadius = + std::max(mBorderRadii[aCorner].width, mBorderRadii[aCorner].height); + if (maxRadius > BORDER_DOTTED_CORNER_MAX_RADIUS) { + DrawFallbackSolidCorner(aSide, aCorner); + return; + } + + if (borderWidthH != borderWidthV || borderWidthH > 2.0f) { + StyleBorderStyle style = mBorderStyles[aSide]; + if (style == StyleBorderStyle::Dotted) { + DrawDottedCornerSlow(aSide, aCorner); + } else { + DrawDashedCornerSlow(aSide, aCorner); + } + return; + } + + nscolor borderColor = mBorderColors[aSide]; + Point points[4]; + bool ignored; + // Get the start and end points of the corner arc, ensuring that any dot + // origins get pushed backwards towards the edges of the corner rect to + // account for stroking. + points[0] = GetStraightBorderPoint(sideH, aCorner, &ignored, -0.5f); + points[3] = GetStraightBorderPoint(sideV, aCorner, &ignored, -0.5f); + // Round points to draw dot on each pixel. + if (borderWidthH < 2.0f) { + points[0].x = round(points[0].x); + } + if (borderWidthV < 2.0f) { + points[3].y = round(points[3].y); + } + points[1] = points[0]; + points[1].x += kKappaFactor * (points[3].x - points[0].x); + points[2] = points[3]; + points[2].y += kKappaFactor * (points[0].y - points[3].y); + + Float len = GetQuarterEllipticArcLength(fabs(points[0].x - points[3].x), + fabs(points[0].y - points[3].y)); + + Float dash[2]; + StrokeOptions strokeOptions(borderWidthH); + SetupDashedOptions(&strokeOptions, dash, aSide, len, true); + + RefPtr<PathBuilder> builder = mDrawTarget->CreatePathBuilder(); + builder->MoveTo(points[0]); + builder->BezierTo(points[1], points[2], points[3]); + RefPtr<Path> path = builder->Finish(); + mDrawTarget->Stroke(path, ColorPattern(ToDeviceColor(borderColor)), + strokeOptions); +} + +void nsCSSBorderRenderer::DrawDottedCornerSlow(mozilla::Side aSide, + Corner aCorner) { + NS_ASSERTION(mBorderStyles[aSide] == StyleBorderStyle::Dotted, + "Style should be dotted."); + + mozilla::Side sideH(GetHorizontalSide(aCorner)); + mozilla::Side sideV(GetVerticalSide(aCorner)); + Float R0 = mBorderWidths[sideH] / 2.0f; + Float Rn = mBorderWidths[sideV] / 2.0f; + if (R0 == 0.0f && Rn == 0.0f) { + return; + } + + nscolor borderColor = mBorderColors[aSide]; + Bezier outerBezier; + Bezier innerBezier; + GetOuterAndInnerBezier(&outerBezier, &innerBezier, aCorner); + + bool ignored; + Point C0 = GetStraightBorderPoint(sideH, aCorner, &ignored); + Point Cn = GetStraightBorderPoint(sideV, aCorner, &ignored); + DottedCornerFinder finder(outerBezier, innerBezier, aCorner, + mBorderRadii[aCorner].width, + mBorderRadii[aCorner].height, C0, R0, Cn, Rn, + mBorderCornerDimensions[aCorner]); + + RefPtr<PathBuilder> builder = mDrawTarget->CreatePathBuilder(); + size_t segmentCount = 0; + const Float AA_MARGIN = 2.0f; + Rect marginedDirtyRect = mDirtyRect; + marginedDirtyRect.Inflate(std::max(R0, Rn) + AA_MARGIN); + bool entered = false; + while (finder.HasMore()) { + if (segmentCount > BORDER_SEGMENT_COUNT_MAX) { + RefPtr<Path> path = builder->Finish(); + mDrawTarget->Fill(path, ColorPattern(ToDeviceColor(borderColor))); + builder = mDrawTarget->CreatePathBuilder(); + segmentCount = 0; + } + + DottedCornerFinder::Result result = finder.Next(); + + if (marginedDirtyRect.Contains(result.C) && result.r > 0) { + entered = true; + builder->MoveTo(Point(result.C.x + result.r, result.C.y)); + builder->Arc(result.C, result.r, 0, Float(2.0 * M_PI)); + segmentCount++; + } else if (entered) { + break; + } + } + RefPtr<Path> path = builder->Finish(); + mDrawTarget->Fill(path, ColorPattern(ToDeviceColor(borderColor))); +} + +static inline bool DashedPathOverlapsRect(Rect& pathRect, + const Rect& marginedDirtyRect, + DashedCornerFinder::Result& result) { + // Calculate a rect that contains all control points of the |result| path, + // and check if it intersects with |marginedDirtyRect|. + pathRect.SetRect(result.outerSectionBezier.mPoints[0].x, + result.outerSectionBezier.mPoints[0].y, 0, 0); + pathRect.ExpandToEnclose(result.outerSectionBezier.mPoints[1]); + pathRect.ExpandToEnclose(result.outerSectionBezier.mPoints[2]); + pathRect.ExpandToEnclose(result.outerSectionBezier.mPoints[3]); + pathRect.ExpandToEnclose(result.innerSectionBezier.mPoints[0]); + pathRect.ExpandToEnclose(result.innerSectionBezier.mPoints[1]); + pathRect.ExpandToEnclose(result.innerSectionBezier.mPoints[2]); + pathRect.ExpandToEnclose(result.innerSectionBezier.mPoints[3]); + + return pathRect.Intersects(marginedDirtyRect); +} + +void nsCSSBorderRenderer::DrawDashedCornerSlow(mozilla::Side aSide, + Corner aCorner) { + NS_ASSERTION(mBorderStyles[aSide] == StyleBorderStyle::Dashed, + "Style should be dashed."); + + mozilla::Side sideH(GetHorizontalSide(aCorner)); + mozilla::Side sideV(GetVerticalSide(aCorner)); + Float borderWidthH = mBorderWidths[sideH]; + Float borderWidthV = mBorderWidths[sideV]; + if (borderWidthH == 0.0f && borderWidthV == 0.0f) { + return; + } + + nscolor borderColor = mBorderColors[aSide]; + Bezier outerBezier; + Bezier innerBezier; + GetOuterAndInnerBezier(&outerBezier, &innerBezier, aCorner); + + DashedCornerFinder finder(outerBezier, innerBezier, borderWidthH, + borderWidthV, mBorderCornerDimensions[aCorner]); + + RefPtr<PathBuilder> builder = mDrawTarget->CreatePathBuilder(); + size_t segmentCount = 0; + const Float AA_MARGIN = 2.0f; + Rect marginedDirtyRect = mDirtyRect; + marginedDirtyRect.Inflate(AA_MARGIN); + Rect pathRect; + bool entered = false; + while (finder.HasMore()) { + if (segmentCount > BORDER_SEGMENT_COUNT_MAX) { + RefPtr<Path> path = builder->Finish(); + mDrawTarget->Fill(path, ColorPattern(ToDeviceColor(borderColor))); + builder = mDrawTarget->CreatePathBuilder(); + segmentCount = 0; + } + + DashedCornerFinder::Result result = finder.Next(); + + if (DashedPathOverlapsRect(pathRect, marginedDirtyRect, result)) { + entered = true; + builder->MoveTo(result.outerSectionBezier.mPoints[0]); + builder->BezierTo(result.outerSectionBezier.mPoints[1], + result.outerSectionBezier.mPoints[2], + result.outerSectionBezier.mPoints[3]); + builder->LineTo(result.innerSectionBezier.mPoints[3]); + builder->BezierTo(result.innerSectionBezier.mPoints[2], + result.innerSectionBezier.mPoints[1], + result.innerSectionBezier.mPoints[0]); + builder->LineTo(result.outerSectionBezier.mPoints[0]); + segmentCount++; + } else if (entered) { + break; + } + } + + if (outerBezier.mPoints[0].x != innerBezier.mPoints[0].x) { + // Fill gap before the first section. + // + // outnerPoint[0] + // | + // v + // _+-----------+-- + // / \##########| + // / \#########| + // + \########| + // |\ \######| + // | \ \#####| + // | \ \####| + // | \ \##| + // | \ \#| + // | \ \| + // | \ _-+-- + // +--------------+ ^ + // | | | + // | | innerPoint[0] + // | | + builder->MoveTo(outerBezier.mPoints[0]); + builder->LineTo(innerBezier.mPoints[0]); + builder->LineTo(Point(innerBezier.mPoints[0].x, outerBezier.mPoints[0].y)); + builder->LineTo(outerBezier.mPoints[0]); + } + + if (outerBezier.mPoints[3].y != innerBezier.mPoints[3].y) { + // Fill gap after the last section. + // + // outnerPoint[3] + // | + // | + // | _+-----------+-- + // | / \ | + // v/ \ | + // + \ | + // |\ \ | + // |##\ \ | + // |####\ \ | + // |######\ \ | + // |########\ \ | + // |##########\ \| + // |############\ _-+-- + // +--------------+<-- innerPoint[3] + // | | + // | | + // | | + builder->MoveTo(outerBezier.mPoints[3]); + builder->LineTo(innerBezier.mPoints[3]); + builder->LineTo(Point(outerBezier.mPoints[3].x, innerBezier.mPoints[3].y)); + builder->LineTo(outerBezier.mPoints[3]); + } + + RefPtr<Path> path = builder->Finish(); + mDrawTarget->Fill(path, ColorPattern(ToDeviceColor(borderColor))); +} + +void nsCSSBorderRenderer::DrawFallbackSolidCorner(mozilla::Side aSide, + Corner aCorner) { + // Render too large dashed or dotted corner with solid style, to avoid hangup + // inside DashedCornerFinder and DottedCornerFinder. + + NS_ASSERTION(mBorderStyles[aSide] == StyleBorderStyle::Dashed || + mBorderStyles[aSide] == StyleBorderStyle::Dotted, + "Style should be dashed or dotted."); + + nscolor borderColor = mBorderColors[aSide]; + Bezier outerBezier; + Bezier innerBezier; + GetOuterAndInnerBezier(&outerBezier, &innerBezier, aCorner); + + RefPtr<PathBuilder> builder = mDrawTarget->CreatePathBuilder(); + + builder->MoveTo(outerBezier.mPoints[0]); + builder->BezierTo(outerBezier.mPoints[1], outerBezier.mPoints[2], + outerBezier.mPoints[3]); + builder->LineTo(innerBezier.mPoints[3]); + builder->BezierTo(innerBezier.mPoints[2], innerBezier.mPoints[1], + innerBezier.mPoints[0]); + builder->LineTo(outerBezier.mPoints[0]); + + RefPtr<Path> path = builder->Finish(); + mDrawTarget->Fill(path, ColorPattern(ToDeviceColor(borderColor))); + + if (!mPresContext->HasWarnedAboutTooLargeDashedOrDottedRadius()) { + mPresContext->SetHasWarnedAboutTooLargeDashedOrDottedRadius(); + nsContentUtils::ReportToConsole( + nsIScriptError::warningFlag, "CSS"_ns, mPresContext->Document(), + nsContentUtils::eCSS_PROPERTIES, + mBorderStyles[aSide] == StyleBorderStyle::Dashed + ? "TooLargeDashedRadius" + : "TooLargeDottedRadius"); + } +} + +bool nsCSSBorderRenderer::AllBordersSameWidth() { + if (mBorderWidths[0] == mBorderWidths[1] && + mBorderWidths[0] == mBorderWidths[2] && + mBorderWidths[0] == mBorderWidths[3]) { + return true; + } + + return false; +} + +bool nsCSSBorderRenderer::AllBordersSolid() { + for (const auto i : mozilla::AllPhysicalSides()) { + if (mBorderStyles[i] == StyleBorderStyle::Solid || + mBorderStyles[i] == StyleBorderStyle::None || + mBorderStyles[i] == StyleBorderStyle::Hidden) { + continue; + } + return false; + } + + return true; +} + +static bool IsVisible(StyleBorderStyle aStyle) { + if (aStyle != StyleBorderStyle::None && aStyle != StyleBorderStyle::Hidden) { + return true; + } + return false; +} + +struct twoFloats { + Float a, b; + + twoFloats operator*(const Size& aSize) const { + return {a * aSize.width, b * aSize.height}; + } + + twoFloats operator*(Float aScale) const { return {a * aScale, b * aScale}; } + + twoFloats operator+(const Point& aPoint) const { + return {a + aPoint.x, b + aPoint.y}; + } + + operator Point() const { return Point(a, b); } +}; + +void nsCSSBorderRenderer::DrawSingleWidthSolidBorder() { + // Easy enough to deal with. + Rect rect = mOuterRect; + rect.Deflate(0.5); + + const twoFloats cornerAdjusts[4] = { + {+0.5, 0}, {0, +0.5}, {-0.5, 0}, {0, -0.5}}; + for (const auto side : mozilla::AllPhysicalSides()) { + Point firstCorner = rect.CCWCorner(side) + cornerAdjusts[side]; + Point secondCorner = rect.CWCorner(side) + cornerAdjusts[side]; + + ColorPattern color(ToDeviceColor(mBorderColors[side])); + + mDrawTarget->StrokeLine(firstCorner, secondCorner, color); + } +} + +// Intersect a ray from the inner corner to the outer corner +// with the border radius, yielding the intersection point. +static Point IntersectBorderRadius(const Point& aCenter, const Size& aRadius, + const Point& aInnerCorner, + const Point& aCornerDirection) { + Point toCorner = aCornerDirection; + // transform to-corner ray to unit-circle space + toCorner.x /= aRadius.width; + toCorner.y /= aRadius.height; + // normalize to-corner ray + Float cornerDist = toCorner.Length(); + if (cornerDist < 1.0e-6f) { + return aInnerCorner; + } + toCorner = toCorner / cornerDist; + // ray from inner corner to border radius center + Point toCenter = aCenter - aInnerCorner; + // transform to-center ray to unit-circle space + toCenter.x /= aRadius.width; + toCenter.y /= aRadius.height; + // compute offset of intersection with border radius unit circle + Float offset = toCenter.DotProduct(toCorner); + // compute discriminant to check for intersections + Float discrim = 1.0f - toCenter.DotProduct(toCenter) + offset * offset; + // choose farthest intersection + offset += sqrtf(std::max(discrim, 0.0f)); + // transform to-corner ray back out of unit-circle space + toCorner.x *= aRadius.width; + toCorner.y *= aRadius.height; + return aInnerCorner + toCorner * offset; +} + +// Calculate the split point and split angle for a border radius with +// differing sides. +static inline void SplitBorderRadius(const Point& aCenter, const Size& aRadius, + const Point& aOuterCorner, + const Point& aInnerCorner, + const twoFloats& aCornerMults, + Float aStartAngle, Point& aSplit, + Float& aSplitAngle) { + Point cornerDir = aOuterCorner - aInnerCorner; + if (cornerDir.x == cornerDir.y && aRadius.IsSquare()) { + // optimize 45-degree intersection with circle since we can assume + // the circle center lies along the intersection edge + aSplit = aCenter - aCornerMults * (aRadius * Float(1.0f / M_SQRT2)); + aSplitAngle = aStartAngle + 0.5f * M_PI / 2.0f; + } else { + aSplit = IntersectBorderRadius(aCenter, aRadius, aInnerCorner, cornerDir); + aSplitAngle = atan2f((aSplit.y - aCenter.y) / aRadius.height, + (aSplit.x - aCenter.x) / aRadius.width); + } +} + +// Compute the size of the skirt needed, given the color alphas +// of each corner side and the slope between them. +static void ComputeCornerSkirtSize(Float aAlpha1, Float aAlpha2, Float aSlopeY, + Float aSlopeX, Float& aSizeResult, + Float& aSlopeResult) { + // If either side is (almost) invisible or there is no diagonal edge, + // then don't try to render a skirt. + if (aAlpha1 < 0.01f || aAlpha2 < 0.01f) { + return; + } + aSlopeX = fabs(aSlopeX); + aSlopeY = fabs(aSlopeY); + if (aSlopeX < 1.0e-6f || aSlopeY < 1.0e-6f) { + return; + } + + // If first and second color don't match, we need to split the corner in + // half. The diagonal edges created may not have full pixel coverage given + // anti-aliasing, so we need to compute a small subpixel skirt edge. This + // assumes each half has half coverage to start with, and that coverage + // increases as the skirt is pushed over, with the end result that we want + // to roughly preserve the alpha value along this edge. + // Given slope m, alphas a and A, use quadratic formula to solve for S in: + // a*(1 - 0.5*(1-S)*(1-mS))*(1 - 0.5*A) + 0.5*A = A + // yielding: + // S = ((1+m) - sqrt((1+m)*(1+m) + 4*m*(1 - A/(a*(1-0.5*A))))) / (2*m) + // and substitute k = (1+m)/(2*m): + // S = k - sqrt(k*k + (1 - A/(a*(1-0.5*A)))/m) + Float slope = aSlopeY / aSlopeX; + Float slopeScale = (1.0f + slope) / (2.0f * slope); + Float discrim = slopeScale * slopeScale + + (1 - aAlpha2 / (aAlpha1 * (1.0f - 0.49f * aAlpha2))) / slope; + if (discrim >= 0) { + aSizeResult = slopeScale - sqrtf(discrim); + aSlopeResult = slope; + } +} + +// Draws a border radius with possibly different sides. +// A skirt is drawn underneath the corner intersection to hide possible +// seams when anti-aliased drawing is used. +static void DrawBorderRadius( + DrawTarget* aDrawTarget, Corner c, const Point& aOuterCorner, + const Point& aInnerCorner, const twoFloats& aCornerMultPrev, + const twoFloats& aCornerMultNext, const Size& aCornerDims, + const Size& aOuterRadius, const Size& aInnerRadius, + const DeviceColor& aFirstColor, const DeviceColor& aSecondColor, + Float aSkirtSize, Float aSkirtSlope) { + // Connect edge to outer arc start point + Point outerCornerStart = aOuterCorner + aCornerMultPrev * aCornerDims; + // Connect edge to outer arc end point + Point outerCornerEnd = aOuterCorner + aCornerMultNext * aCornerDims; + // Connect edge to inner arc start point + Point innerCornerStart = + outerCornerStart + aCornerMultNext * (aCornerDims - aInnerRadius); + // Connect edge to inner arc end point + Point innerCornerEnd = + outerCornerEnd + aCornerMultPrev * (aCornerDims - aInnerRadius); + + // Outer arc start point + Point outerArcStart = aOuterCorner + aCornerMultPrev * aOuterRadius; + // Outer arc end point + Point outerArcEnd = aOuterCorner + aCornerMultNext * aOuterRadius; + // Inner arc start point + Point innerArcStart = aInnerCorner + aCornerMultPrev * aInnerRadius; + // Inner arc end point + Point innerArcEnd = aInnerCorner + aCornerMultNext * aInnerRadius; + + // Outer radius center + Point outerCenter = + aOuterCorner + (aCornerMultPrev + aCornerMultNext) * aOuterRadius; + // Inner radius center + Point innerCenter = + aInnerCorner + (aCornerMultPrev + aCornerMultNext) * aInnerRadius; + + RefPtr<PathBuilder> builder; + RefPtr<Path> path; + + if (aFirstColor.a > 0) { + builder = aDrawTarget->CreatePathBuilder(); + builder->MoveTo(outerCornerStart); + } + + if (aFirstColor != aSecondColor) { + // Start and end angles of corner quadrant + constexpr float PIf = M_PI; + Float startAngle = (static_cast<float>(c) * PIf) / 2.0f - PIf; + Float endAngle = startAngle + PIf / 2.0f; + Float outerSplitAngle, innerSplitAngle; + Point outerSplit, innerSplit; + + // Outer half-way point + SplitBorderRadius(outerCenter, aOuterRadius, aOuterCorner, aInnerCorner, + aCornerMultPrev + aCornerMultNext, startAngle, outerSplit, + outerSplitAngle); + // Inner half-way point + if (aInnerRadius.IsEmpty()) { + innerSplit = aInnerCorner; + innerSplitAngle = endAngle; + } else { + SplitBorderRadius(innerCenter, aInnerRadius, aOuterCorner, aInnerCorner, + aCornerMultPrev + aCornerMultNext, startAngle, + innerSplit, innerSplitAngle); + } + + // Draw first half with first color + if (aFirstColor.a > 0) { + AcuteArcToBezier(builder.get(), outerCenter, aOuterRadius, outerArcStart, + outerSplit, startAngle, outerSplitAngle); + // Draw skirt as part of first half + if (aSkirtSize > 0) { + builder->LineTo(outerSplit + aCornerMultNext * aSkirtSize); + builder->LineTo(innerSplit - + aCornerMultPrev * (aSkirtSize * aSkirtSlope)); + } + AcuteArcToBezier(builder.get(), innerCenter, aInnerRadius, innerSplit, + innerArcStart, innerSplitAngle, startAngle); + if ((innerCornerStart - innerArcStart).DotProduct(aCornerMultPrev) > 0) { + builder->LineTo(innerCornerStart); + } + builder->Close(); + path = builder->Finish(); + aDrawTarget->Fill(path, ColorPattern(aFirstColor)); + } + + // Draw second half with second color + if (aSecondColor.a > 0) { + builder = aDrawTarget->CreatePathBuilder(); + builder->MoveTo(outerCornerEnd); + if ((innerArcEnd - innerCornerEnd).DotProduct(aCornerMultNext) < 0) { + builder->LineTo(innerCornerEnd); + } + AcuteArcToBezier(builder.get(), innerCenter, aInnerRadius, innerArcEnd, + innerSplit, endAngle, innerSplitAngle); + AcuteArcToBezier(builder.get(), outerCenter, aOuterRadius, outerSplit, + outerArcEnd, outerSplitAngle, endAngle); + builder->Close(); + path = builder->Finish(); + aDrawTarget->Fill(path, ColorPattern(aSecondColor)); + } + } else if (aFirstColor.a > 0) { + // Draw corner with single color + AcuteArcToBezier(builder.get(), outerCenter, aOuterRadius, outerArcStart, + outerArcEnd); + builder->LineTo(outerCornerEnd); + if ((innerArcEnd - innerCornerEnd).DotProduct(aCornerMultNext) < 0) { + builder->LineTo(innerCornerEnd); + } + AcuteArcToBezier(builder.get(), innerCenter, aInnerRadius, innerArcEnd, + innerArcStart, -kKappaFactor); + if ((innerCornerStart - innerArcStart).DotProduct(aCornerMultPrev) > 0) { + builder->LineTo(innerCornerStart); + } + builder->Close(); + path = builder->Finish(); + aDrawTarget->Fill(path, ColorPattern(aFirstColor)); + } +} + +// Draw a corner with possibly different sides. +// A skirt is drawn underneath the corner intersection to hide possible +// seams when anti-aliased drawing is used. +static void DrawCorner(DrawTarget* aDrawTarget, const Point& aOuterCorner, + const Point& aInnerCorner, + const twoFloats& aCornerMultPrev, + const twoFloats& aCornerMultNext, + const Size& aCornerDims, const DeviceColor& aFirstColor, + const DeviceColor& aSecondColor, Float aSkirtSize, + Float aSkirtSlope) { + // Corner box start point + Point cornerStart = aOuterCorner + aCornerMultPrev * aCornerDims; + // Corner box end point + Point cornerEnd = aOuterCorner + aCornerMultNext * aCornerDims; + + RefPtr<PathBuilder> builder; + RefPtr<Path> path; + + if (aFirstColor.a > 0) { + builder = aDrawTarget->CreatePathBuilder(); + builder->MoveTo(cornerStart); + } + + if (aFirstColor != aSecondColor) { + // Draw first half with first color + if (aFirstColor.a > 0) { + builder->LineTo(aOuterCorner); + // Draw skirt as part of first half + if (aSkirtSize > 0) { + builder->LineTo(aOuterCorner + aCornerMultNext * aSkirtSize); + builder->LineTo(aInnerCorner - + aCornerMultPrev * (aSkirtSize * aSkirtSlope)); + } + builder->LineTo(aInnerCorner); + builder->Close(); + path = builder->Finish(); + aDrawTarget->Fill(path, ColorPattern(aFirstColor)); + } + + // Draw second half with second color + if (aSecondColor.a > 0) { + builder = aDrawTarget->CreatePathBuilder(); + builder->MoveTo(cornerEnd); + builder->LineTo(aInnerCorner); + builder->LineTo(aOuterCorner); + builder->Close(); + path = builder->Finish(); + aDrawTarget->Fill(path, ColorPattern(aSecondColor)); + } + } else if (aFirstColor.a > 0) { + // Draw corner with single color + builder->LineTo(aOuterCorner); + builder->LineTo(cornerEnd); + builder->LineTo(aInnerCorner); + builder->Close(); + path = builder->Finish(); + aDrawTarget->Fill(path, ColorPattern(aFirstColor)); + } +} + +void nsCSSBorderRenderer::DrawSolidBorder() { + const twoFloats cornerMults[4] = {{-1, 0}, {0, -1}, {+1, 0}, {0, +1}}; + + const twoFloats centerAdjusts[4] = { + {0, +0.5}, {-0.5, 0}, {0, -0.5}, {+0.5, 0}}; + + RectCornerRadii innerRadii; + ComputeInnerRadii(mBorderRadii, mBorderWidths, &innerRadii); + + Rect strokeRect = mOuterRect; + strokeRect.Deflate(Margin(mBorderWidths[0] / 2.0, mBorderWidths[1] / 2.0, + mBorderWidths[2] / 2.0, mBorderWidths[3] / 2.0)); + + for (const auto i : mozilla::AllPhysicalSides()) { + // We now draw the current side and the CW corner following it. + // The CCW corner of this side was already drawn in the previous iteration. + // The side will be drawn as an explicit stroke, and the CW corner will be + // filled separately. + // If the next side does not have a matching color, then we split the + // corner into two halves, one of each side's color and draw both. + // Thus, the CCW corner of the next side will end up drawn here. + + // the corner index -- either 1 2 3 0 (cw) or 0 3 2 1 (ccw) + Corner c = Corner((i + 1) % 4); + Corner prevCorner = Corner(i); + + // i+2 and i+3 respectively. These are used to index into the corner + // multiplier table, and were deduced by calculating out the long form + // of each corner and finding a pattern in the signs and values. + int i1 = (i + 1) % 4; + int i2 = (i + 2) % 4; + int i3 = (i + 3) % 4; + + Float sideWidth = 0.0f; + DeviceColor firstColor, secondColor; + if (IsVisible(mBorderStyles[i]) && mBorderWidths[i]) { + // draw the side since it is visible + sideWidth = mBorderWidths[i]; + firstColor = ToDeviceColor(mBorderColors[i]); + // if the next side is visible, use its color for corner + secondColor = IsVisible(mBorderStyles[i1]) && mBorderWidths[i1] + ? ToDeviceColor(mBorderColors[i1]) + : firstColor; + } else if (IsVisible(mBorderStyles[i1]) && mBorderWidths[i1]) { + // assign next side's color to both corner sides + firstColor = ToDeviceColor(mBorderColors[i1]); + secondColor = firstColor; + } else { + // neither side is visible, so nothing to do + continue; + } + + Point outerCorner = mOuterRect.AtCorner(c); + Point innerCorner = mInnerRect.AtCorner(c); + + // start and end points of border side stroke between corners + Point sideStart = mOuterRect.AtCorner(prevCorner) + + cornerMults[i2] * mBorderCornerDimensions[prevCorner]; + Point sideEnd = outerCorner + cornerMults[i] * mBorderCornerDimensions[c]; + // check if the side is visible and not inverted + if (sideWidth > 0 && firstColor.a > 0 && + -(sideEnd - sideStart).DotProduct(cornerMults[i]) > 0) { + mDrawTarget->StrokeLine(sideStart + centerAdjusts[i] * sideWidth, + sideEnd + centerAdjusts[i] * sideWidth, + ColorPattern(firstColor), + StrokeOptions(sideWidth)); + } + + Float skirtSize = 0.0f, skirtSlope = 0.0f; + // the sides don't match, so compute a skirt + if (firstColor != secondColor && + mPresContext->Type() != nsPresContext::eContext_Print) { + Point cornerDir = outerCorner - innerCorner; + ComputeCornerSkirtSize( + firstColor.a, secondColor.a, cornerDir.DotProduct(cornerMults[i]), + cornerDir.DotProduct(cornerMults[i3]), skirtSize, skirtSlope); + } + + if (!mBorderRadii[c].IsEmpty()) { + // the corner has a border radius + DrawBorderRadius(mDrawTarget, c, outerCorner, innerCorner, cornerMults[i], + cornerMults[i3], mBorderCornerDimensions[c], + mBorderRadii[c], innerRadii[c], firstColor, secondColor, + skirtSize, skirtSlope); + } else if (!mBorderCornerDimensions[c].IsEmpty()) { + // a corner with no border radius + DrawCorner(mDrawTarget, outerCorner, innerCorner, cornerMults[i], + cornerMults[i3], mBorderCornerDimensions[c], firstColor, + secondColor, skirtSize, skirtSlope); + } + } +} + +void nsCSSBorderRenderer::DrawBorders() { + if (MOZ_UNLIKELY(!mDirtyRect.Intersects(mOuterRect))) { + return; + } + + if (mAllBordersSameStyle && (mBorderStyles[0] == StyleBorderStyle::None || + mBorderStyles[0] == StyleBorderStyle::Hidden || + mBorderColors[0] == NS_RGBA(0, 0, 0, 0))) { + // All borders are the same style, and the style is either none or hidden, + // or the color is transparent. + return; + } + + if (mAllBordersSameWidth && mBorderWidths[0] == 0.0) { + // Some of the mAllBordersSameWidth codepaths depend on the border + // width being greater than zero. + return; + } + + AutoRestoreTransform autoRestoreTransform; + Matrix mat = mDrawTarget->GetTransform(); + + // Clamp the CTM to be pixel-aligned; we do this only + // for translation-only matrices now, but we could do it + // if the matrix has just a scale as well. We should not + // do it if there's a rotation. + if (mat.HasNonTranslation()) { + if (!mat.HasNonAxisAlignedTransform()) { + // Scale + transform. Avoid stroke fast-paths so that we have a chance + // of snapping to pixel boundaries. + mAvoidStroke = true; + } + } else { + mat._31 = floor(mat._31 + 0.5); + mat._32 = floor(mat._32 + 0.5); + autoRestoreTransform.Init(mDrawTarget); + mDrawTarget->SetTransform(mat); + + // round mOuterRect and mInnerRect; they're already an integer + // number of pixels apart and should stay that way after + // rounding. We don't do this if there's a scale in the current transform + // since this loses information that might be relevant when we're scaling. + mOuterRect.Round(); + mInnerRect.Round(); + } + + // Initial values only used when the border colors/widths are all the same: + ColorPattern color(ToDeviceColor(mBorderColors[eSideTop])); + StrokeOptions strokeOptions(mBorderWidths[eSideTop]); // stroke width + + // First there's a couple of 'special cases' that have specifically optimized + // drawing paths, when none of these can be used we move on to the generalized + // border drawing code. + if (mAllBordersSameStyle && mAllBordersSameWidth && + mBorderStyles[0] == StyleBorderStyle::Solid && mNoBorderRadius && + !mAvoidStroke) { + // Very simple case. + Rect rect = mOuterRect; + rect.Deflate(mBorderWidths[0] / 2.0); + mDrawTarget->StrokeRect(rect, color, strokeOptions); + return; + } + + if (mAllBordersSameStyle && mBorderStyles[0] == StyleBorderStyle::Solid && + !mAvoidStroke && !mNoBorderRadius) { + // Relatively simple case. + RoundedRect borderInnerRect(mOuterRect, mBorderRadii); + borderInnerRect.Deflate(mBorderWidths[eSideTop], mBorderWidths[eSideBottom], + mBorderWidths[eSideLeft], + mBorderWidths[eSideRight]); + + // Instead of stroking we just use two paths: an inner and an outer. + // This allows us to draw borders that we couldn't when stroking. For + // example, borders with a border width >= the border radius. (i.e. when + // there are square corners on the inside) + // + // Further, this approach can be more efficient because the backend + // doesn't need to compute an offset curve to stroke the path. We know that + // the rounded parts are elipses we can offset exactly and can just compute + // a new cubic approximation. + RefPtr<PathBuilder> builder = mDrawTarget->CreatePathBuilder(); + AppendRoundedRectToPath(builder, mOuterRect, mBorderRadii, true); + AppendRoundedRectToPath(builder, borderInnerRect.rect, + borderInnerRect.corners, false); + RefPtr<Path> path = builder->Finish(); + mDrawTarget->Fill(path, color); + return; + } + + const bool allBordersSolid = AllBordersSolid(); + + // This leaves the border corners non-interpolated for single width borders. + // Doing this is slightly faster and shouldn't be a problem visually. + if (allBordersSolid && mAllBordersSameWidth && mBorderWidths[0] == 1 && + mNoBorderRadius && !mAvoidStroke) { + DrawSingleWidthSolidBorder(); + return; + } + + if (allBordersSolid && !mAvoidStroke) { + DrawSolidBorder(); + return; + } + + PrintAsString(" mOuterRect: "); + PrintAsString(mOuterRect); + PrintAsStringNewline(); + PrintAsString(" mInnerRect: "); + PrintAsString(mInnerRect); + PrintAsStringNewline(); + PrintAsFormatString(" mBorderColors: 0x%08x 0x%08x 0x%08x 0x%08x\n", + mBorderColors[0], mBorderColors[1], mBorderColors[2], + mBorderColors[3]); + + // if conditioning the outside rect failed, then bail -- the outside + // rect is supposed to enclose the entire border + { + gfxRect outerRect = ThebesRect(mOuterRect); + gfxUtils::ConditionRect(outerRect); + if (outerRect.IsEmpty()) { + return; + } + mOuterRect = ToRect(outerRect); + + if (MOZ_UNLIKELY(!mDirtyRect.Intersects(mOuterRect))) { + return; + } + + gfxRect innerRect = ThebesRect(mInnerRect); + gfxUtils::ConditionRect(innerRect); + mInnerRect = ToRect(innerRect); + } + + SideBits dashedSides = SideBits::eNone; + bool forceSeparateCorners = false; + + for (const auto i : mozilla::AllPhysicalSides()) { + StyleBorderStyle style = mBorderStyles[i]; + if (style == StyleBorderStyle::Dashed || + style == StyleBorderStyle::Dotted) { + // we need to draw things separately for dashed/dotting + forceSeparateCorners = true; + dashedSides |= static_cast<mozilla::SideBits>(1 << i); + } + } + + PrintAsFormatString(" mAllBordersSameStyle: %d dashedSides: 0x%02x\n", + mAllBordersSameStyle, + static_cast<unsigned int>(dashedSides)); + + if (mAllBordersSameStyle && !forceSeparateCorners) { + /* Draw everything in one go */ + DrawBorderSides(SideBits::eAll); + PrintAsStringNewline("---------------- (1)"); + } else { + AUTO_PROFILER_LABEL("nsCSSBorderRenderer::DrawBorders:multipass", GRAPHICS); + + /* We have more than one pass to go. Draw the corners separately from the + * sides. */ + + // The corner is going to have negligible size if its two adjacent border + // sides are only 1px wide and there is no border radius. In that case we + // skip the overhead of painting the corner by setting the width or height + // of the corner to zero, which effectively extends one of the corner's + // adjacent border sides. We extend the longer adjacent side so that + // opposite sides will be the same length, which is necessary for opposite + // dashed/dotted sides to be symmetrical. + // + // if width > height + // +--+--------------+--+ +--------------------+ + // | | | | | | + // +--+--------------+--+ +--+--------------+--+ + // | | | | | | | | + // | | | | => | | | | + // | | | | | | | | + // +--+--------------+--+ +--+--------------+--+ + // | | | | | | + // +--+--------------+--+ +--------------------+ + // + // if width <= height + // +--+--------+--+ +--+--------+--+ + // | | | | | | | | + // +--+--------+--+ | +--------+ | + // | | | | | | | | + // | | | | | | | | + // | | | | | | | | + // | | | | => | | | | + // | | | | | | | | + // | | | | | | | | + // | | | | | | | | + // +--+--------+--+ | +--------+ | + // | | | | | | | | + // +--+--------+--+ +--+--------+--+ + // + // Note that if we have different border widths we could end up with + // opposite sides of different length. For example, if the left and + // bottom borders are 2px wide instead of 1px, we will end up doing + // something like: + // + // +----+------------+--+ +----+---------------+ + // | | | | | | | + // +----+------------+--+ +----+------------+--+ + // | | | | | | | | + // | | | | => | | | | + // | | | | | | | | + // +----+------------+--+ +----+------------+--+ + // | | | | | | | | + // | | | | | | | | + // +----+------------+--+ +----+------------+--+ + // + // XXX Should we only do this optimization if |mAllBordersSameWidth| is + // true? + // + // XXX In fact is this optimization even worth the complexity it adds to + // the code? 1px wide dashed borders are not overly common, and drawing + // corners for them is not that expensive. + for (const auto corner : mozilla::AllPhysicalCorners()) { + const mozilla::Side sides[2] = {mozilla::Side(corner), PREV_SIDE(corner)}; + + if (!IsZeroSize(mBorderRadii[corner])) { + continue; + } + + if (mBorderWidths[sides[0]] == 1.0 && mBorderWidths[sides[1]] == 1.0) { + if (mOuterRect.Width() > mOuterRect.Height()) { + mBorderCornerDimensions[corner].width = 0.0; + } else { + mBorderCornerDimensions[corner].height = 0.0; + } + } + } + + // First, the corners + for (const auto corner : mozilla::AllPhysicalCorners()) { + // if there's no corner, don't do all this work for it + if (IsZeroSize(mBorderCornerDimensions[corner])) { + continue; + } + + const int sides[2] = {corner, PREV_SIDE(corner)}; + SideBits sideBits = + static_cast<SideBits>((1 << sides[0]) | (1 << sides[1])); + + bool simpleCornerStyle = AreBorderSideFinalStylesSame(sideBits); + + // If we don't have anything complex going on in this corner, + // then we can just fill the corner with a solid color, and avoid + // the potentially expensive clip. + if (simpleCornerStyle && IsZeroSize(mBorderRadii[corner]) && + IsSolidCornerStyle(mBorderStyles[sides[0]], corner)) { + sRGBColor color = MakeBorderColor( + mBorderColors[sides[0]], + BorderColorStyleForSolidCorner(mBorderStyles[sides[0]], corner)); + mDrawTarget->FillRect(GetCornerRect(corner), + ColorPattern(ToDeviceColor(color))); + continue; + } + + // clip to the corner + mDrawTarget->PushClipRect(GetCornerRect(corner)); + + if (simpleCornerStyle) { + // we don't need a group for this corner, the sides are the same, + // but we weren't able to render just a solid block for the corner. + DrawBorderSides(sideBits); + } else { + // Sides are different. We could draw using OP_ADD to + // get correct color blending behaviour at the seam. We'd need + // to do it in an offscreen surface to ensure that we're + // always compositing on transparent black. If the colors + // don't have transparency and the current destination surface + // has an alpha channel, we could just clear the region and + // avoid the temporary, but that situation doesn't happen all + // that often in practice (we double buffer to no-alpha + // surfaces). We choose just to seam though, as the performance + // advantages outway the modest easthetic improvement. + + for (int cornerSide = 0; cornerSide < 2; cornerSide++) { + mozilla::Side side = mozilla::Side(sides[cornerSide]); + StyleBorderStyle style = mBorderStyles[side]; + + PrintAsFormatString("corner: %d cornerSide: %d side: %d style: %d\n", + corner, cornerSide, side, + static_cast<int>(style)); + + RefPtr<Path> path = GetSideClipSubPath(side); + mDrawTarget->PushClip(path); + + DrawBorderSides(static_cast<mozilla::SideBits>(1 << side)); + + mDrawTarget->PopClip(); + } + } + + mDrawTarget->PopClip(); + + PrintAsStringNewline(); + } + + // in the case of a single-unit border, we already munged the + // corners up above; so we can just draw the top left and bottom + // right sides separately, if they're the same. + // + // We need to check for mNoBorderRadius, because when there is + // one, FillSolidBorder always draws the full rounded rectangle + // and expects there to be a clip in place. + SideBits alreadyDrawnSides = SideBits::eNone; + if (mOneUnitBorder && mNoBorderRadius && + (dashedSides & (SideBits::eTop | SideBits::eLeft)) == SideBits::eNone) { + bool tlBordersSameStyle = + AreBorderSideFinalStylesSame(SideBits::eTop | SideBits::eLeft); + bool brBordersSameStyle = + AreBorderSideFinalStylesSame(SideBits::eBottom | SideBits::eRight); + + if (tlBordersSameStyle) { + DrawBorderSides(SideBits::eTop | SideBits::eLeft); + alreadyDrawnSides |= (SideBits::eTop | SideBits::eLeft); + } + + if (brBordersSameStyle && + (dashedSides & (SideBits::eBottom | SideBits::eRight)) == + SideBits::eNone) { + DrawBorderSides(SideBits::eBottom | SideBits::eRight); + alreadyDrawnSides |= (SideBits::eBottom | SideBits::eRight); + } + } + + // We're done with the corners, now draw the sides. + for (const auto side : mozilla::AllPhysicalSides()) { + // if we drew it above, skip it + if (alreadyDrawnSides & static_cast<mozilla::SideBits>(1 << side)) { + continue; + } + + // If there's no border on this side, skip it + if (mBorderWidths[side] == 0.0 || + mBorderStyles[side] == StyleBorderStyle::Hidden || + mBorderStyles[side] == StyleBorderStyle::None) { + continue; + } + + if (dashedSides & static_cast<mozilla::SideBits>(1 << side)) { + // Dashed sides will always draw just the part ignoring the + // corners for the side, so no need to clip. + DrawDashedOrDottedSide(side); + + PrintAsStringNewline("---------------- (d)"); + continue; + } + + // Undashed sides will currently draw the entire side, + // including parts that would normally be covered by a corner, + // so we need to clip. + // + // XXX Optimization -- it would be good to make this work like + // DrawDashedOrDottedSide, and have a DrawOneSide function that just + // draws one side and not the corners, because then we can + // avoid the potentially expensive clip. + mDrawTarget->PushClipRect(GetSideClipWithoutCornersRect(side)); + + DrawBorderSides(static_cast<mozilla::SideBits>(1 << side)); + + mDrawTarget->PopClip(); + + PrintAsStringNewline("---------------- (*)"); + } + } +} + +void nsCSSBorderRenderer::CreateWebRenderCommands( + nsDisplayItem* aItem, wr::DisplayListBuilder& aBuilder, + wr::IpcResourceUpdateQueue& aResources, + const layers::StackingContextHelper& aSc) { + LayoutDeviceRect outerRect = LayoutDeviceRect::FromUnknownRect(mOuterRect); + wr::LayoutRect roundedRect = wr::ToLayoutRect(outerRect); + wr::LayoutRect clipRect = roundedRect; + wr::BorderSide side[4]; + for (const auto i : mozilla::AllPhysicalSides()) { + side[i] = + wr::ToBorderSide(ToDeviceColor(mBorderColors[i]), mBorderStyles[i]); + } + + wr::BorderRadius borderRadius = wr::ToBorderRadius(mBorderRadii); + + if (mLocalClip) { + LayoutDeviceRect localClip = + LayoutDeviceRect::FromUnknownRect(mLocalClip.value()); + clipRect = wr::ToLayoutRect(localClip.Intersect(outerRect)); + } + + Range<const wr::BorderSide> wrsides(side, 4); + aBuilder.PushBorder(roundedRect, clipRect, mBackfaceIsVisible, + wr::ToBorderWidths(mBorderWidths[0], mBorderWidths[1], + mBorderWidths[2], mBorderWidths[3]), + wrsides, borderRadius); +} + +/* static */ +Maybe<nsCSSBorderImageRenderer> +nsCSSBorderImageRenderer::CreateBorderImageRenderer( + nsPresContext* aPresContext, nsIFrame* aForFrame, const nsRect& aBorderArea, + const nsStyleBorder& aStyleBorder, const nsRect& aDirtyRect, + Sides aSkipSides, uint32_t aFlags, ImgDrawResult* aDrawResult) { + MOZ_ASSERT(aDrawResult); + + if (aDirtyRect.IsEmpty()) { + *aDrawResult = ImgDrawResult::SUCCESS; + return Nothing(); + } + + nsImageRenderer imgRenderer(aForFrame, &aStyleBorder.mBorderImageSource, + aFlags); + if (!imgRenderer.PrepareImage()) { + *aDrawResult = imgRenderer.PrepareResult(); + return Nothing(); + } + + // We should always get here with the frame's border, but we may construct an + // nsStyleBorder om the stack to deal with :visited and other shenaningans. + // + // We always copy the border image and such from the non-visited one, so + // there's no need to do anything with it. + MOZ_ASSERT(aStyleBorder.GetBorderImageRequest() == + aForFrame->StyleBorder()->GetBorderImageRequest()); + + nsCSSBorderImageRenderer renderer(aForFrame, aBorderArea, aStyleBorder, + aSkipSides, imgRenderer); + *aDrawResult = ImgDrawResult::SUCCESS; + return Some(renderer); +} + +ImgDrawResult nsCSSBorderImageRenderer::DrawBorderImage( + nsPresContext* aPresContext, gfxContext& aRenderingContext, + nsIFrame* aForFrame, const nsRect& aDirtyRect) { + // NOTE: no Save() yet, we do that later by calling autoSR.EnsureSaved() + // in case we need it. + gfxContextAutoSaveRestore autoSR; + + if (!mClip.IsEmpty()) { + autoSR.EnsureSaved(&aRenderingContext); + aRenderingContext.Clip(NSRectToSnappedRect( + mClip, aForFrame->PresContext()->AppUnitsPerDevPixel(), + *aRenderingContext.GetDrawTarget())); + } + + // intrinsicSize.CanComputeConcreteSize() return false means we can not + // read intrinsic size from aStyleBorder.mBorderImageSource. + // In this condition, we pass imageSize(a resolved size comes from + // default sizing algorithm) to renderer as the viewport size. + CSSSizeOrRatio intrinsicSize = mImageRenderer.ComputeIntrinsicSize(); + Maybe<nsSize> svgViewportSize = + intrinsicSize.CanComputeConcreteSize() ? Nothing() : Some(mImageSize); + bool hasIntrinsicRatio = intrinsicSize.HasRatio(); + + // These helper tables recharacterize the 'slice' and 'width' margins + // in a more convenient form: they are the x/y/width/height coords + // required for various bands of the border, and they have been transformed + // to be relative to the innerRect (for 'slice') or the page (for 'border'). + enum { LEFT, MIDDLE, RIGHT, TOP = LEFT, BOTTOM = RIGHT }; + const nscoord borderX[3] = { + mArea.x + 0, + mArea.x + mWidths.left, + mArea.x + mArea.width - mWidths.right, + }; + const nscoord borderY[3] = { + mArea.y + 0, + mArea.y + mWidths.top, + mArea.y + mArea.height - mWidths.bottom, + }; + const nscoord borderWidth[3] = { + mWidths.left, + mArea.width - mWidths.left - mWidths.right, + mWidths.right, + }; + const nscoord borderHeight[3] = { + mWidths.top, + mArea.height - mWidths.top - mWidths.bottom, + mWidths.bottom, + }; + const int32_t sliceX[3] = { + 0, + mSlice.left, + mImageSize.width - mSlice.right, + }; + const int32_t sliceY[3] = { + 0, + mSlice.top, + mImageSize.height - mSlice.bottom, + }; + const int32_t sliceWidth[3] = { + mSlice.left, + std::max(mImageSize.width - mSlice.left - mSlice.right, 0), + mSlice.right, + }; + const int32_t sliceHeight[3] = { + mSlice.top, + std::max(mImageSize.height - mSlice.top - mSlice.bottom, 0), + mSlice.bottom, + }; + + ImgDrawResult result = ImgDrawResult::SUCCESS; + + for (int i = LEFT; i <= RIGHT; i++) { + for (int j = TOP; j <= BOTTOM; j++) { + StyleBorderImageRepeat fillStyleH, fillStyleV; + nsSize unitSize; + + if (i == MIDDLE && j == MIDDLE) { + // Discard the middle portion unless set to fill. + if (!mFill) { + continue; + } + + // css-background: + // The middle image's width is scaled by the same factor as the + // top image unless that factor is zero or infinity, in which + // case the scaling factor of the bottom is substituted, and + // failing that, the width is not scaled. The height of the + // middle image is scaled by the same factor as the left image + // unless that factor is zero or infinity, in which case the + // scaling factor of the right image is substituted, and failing + // that, the height is not scaled. + gfxFloat hFactor, vFactor; + + if (0 < mWidths.left && 0 < mSlice.left) { + vFactor = gfxFloat(mWidths.left) / mSlice.left; + } else if (0 < mWidths.right && 0 < mSlice.right) { + vFactor = gfxFloat(mWidths.right) / mSlice.right; + } else { + vFactor = 1; + } + + if (0 < mWidths.top && 0 < mSlice.top) { + hFactor = gfxFloat(mWidths.top) / mSlice.top; + } else if (0 < mWidths.bottom && 0 < mSlice.bottom) { + hFactor = gfxFloat(mWidths.bottom) / mSlice.bottom; + } else { + hFactor = 1; + } + + unitSize.width = sliceWidth[i] * hFactor; + unitSize.height = sliceHeight[j] * vFactor; + fillStyleH = mRepeatModeHorizontal; + fillStyleV = mRepeatModeVertical; + + } else if (i == MIDDLE) { // top, bottom + // Sides are always stretched to the thickness of their border, + // and stretched proportionately on the other axis. + gfxFloat factor; + if (0 < borderHeight[j] && 0 < sliceHeight[j]) { + factor = gfxFloat(borderHeight[j]) / sliceHeight[j]; + } else { + factor = 1; + } + + unitSize.width = sliceWidth[i] * factor; + unitSize.height = borderHeight[j]; + fillStyleH = mRepeatModeHorizontal; + fillStyleV = StyleBorderImageRepeat::Stretch; + + } else if (j == MIDDLE) { // left, right + gfxFloat factor; + if (0 < borderWidth[i] && 0 < sliceWidth[i]) { + factor = gfxFloat(borderWidth[i]) / sliceWidth[i]; + } else { + factor = 1; + } + + unitSize.width = borderWidth[i]; + unitSize.height = sliceHeight[j] * factor; + fillStyleH = StyleBorderImageRepeat::Stretch; + fillStyleV = mRepeatModeVertical; + + } else { + // Corners are always stretched to fit the corner. + unitSize.width = borderWidth[i]; + unitSize.height = borderHeight[j]; + fillStyleH = StyleBorderImageRepeat::Stretch; + fillStyleV = StyleBorderImageRepeat::Stretch; + } + + nsRect destArea(borderX[i], borderY[j], borderWidth[i], borderHeight[j]); + nsRect subArea(sliceX[i], sliceY[j], sliceWidth[i], sliceHeight[j]); + if (subArea.IsEmpty()) continue; + + nsIntRect intSubArea = subArea.ToOutsidePixels(AppUnitsPerCSSPixel()); + result &= mImageRenderer.DrawBorderImageComponent( + aPresContext, aRenderingContext, aDirtyRect, destArea, + CSSIntRect(intSubArea.x, intSubArea.y, intSubArea.width, + intSubArea.height), + fillStyleH, fillStyleV, unitSize, j * (RIGHT + 1) + i, + svgViewportSize, hasIntrinsicRatio); + } + } + + return result; +} + +ImgDrawResult nsCSSBorderImageRenderer::CreateWebRenderCommands( + nsDisplayItem* aItem, nsIFrame* aForFrame, + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const mozilla::layers::StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + if (!mImageRenderer.IsReady()) { + return ImgDrawResult::NOT_READY; + } + + float widths[4]; + float slice[4]; + const int32_t appUnitsPerDevPixel = + aForFrame->PresContext()->AppUnitsPerDevPixel(); + for (const auto i : mozilla::AllPhysicalSides()) { + slice[i] = (float)(mSlice.Side(i)) / appUnitsPerDevPixel; + widths[i] = (float)(mWidths.Side(i)) / appUnitsPerDevPixel; + } + + LayoutDeviceRect destRect = + LayoutDeviceRect::FromAppUnits(mArea, appUnitsPerDevPixel); + destRect.Round(); + wr::LayoutRect dest = wr::ToLayoutRect(destRect); + + wr::LayoutRect clip = dest; + if (!mClip.IsEmpty()) { + LayoutDeviceRect clipRect = + LayoutDeviceRect::FromAppUnits(mClip, appUnitsPerDevPixel); + clip = wr::ToLayoutRect(clipRect); + } + + ImgDrawResult drawResult = ImgDrawResult::SUCCESS; + switch (mImageRenderer.GetType()) { + case StyleImage::Tag::Rect: + case StyleImage::Tag::Url: { + RefPtr<imgIContainer> img = mImageRenderer.GetImage(); + if (!img || img->GetType() == imgIContainer::TYPE_VECTOR) { + // Vector images will redraw each segment of the border up to 8 times. + // We draw using a restricted region derived from the segment's clip and + // scale the image accordingly (see ClippedImage::Draw). If we follow + // this convention as is for WebRender, we will need to rasterize the + // entire vector image scaled up without the restriction region, which + // means our main thread CPU and memory footprints will be much higher. + // Ideally we would be able to provide a raster image for each segment + // of the border. For now we use fallback. + return ImgDrawResult::NOT_SUPPORTED; + } + + uint32_t flags = aDisplayListBuilder->GetImageDecodeFlags(); + + LayoutDeviceRect imageRect = LayoutDeviceRect::FromAppUnits( + nsRect(nsPoint(), mImageRenderer.GetSize()), appUnitsPerDevPixel); + + SVGImageContext svgContext; + Maybe<ImageIntRegion> region; + gfx::IntSize decodeSize = + nsLayoutUtils::ComputeImageContainerDrawingParameters( + img, aForFrame, imageRect, imageRect, aSc, flags, svgContext, + region); + + RefPtr<WebRenderImageProvider> provider; + drawResult = img->GetImageProvider(aManager->LayerManager(), decodeSize, + svgContext, region, flags, + getter_AddRefs(provider)); + + Maybe<wr::ImageKey> key = + aManager->CommandBuilder().CreateImageProviderKey( + aItem, provider, drawResult, aResources); + if (key.isNothing()) { + break; + } + + auto rendering = + wr::ToImageRendering(aItem->Frame()->UsedImageRendering()); + if (mFill) { + float epsilon = 0.0001; + bool noVerticalBorders = widths[0] <= epsilon && widths[2] < epsilon; + bool noHorizontalBorders = widths[1] <= epsilon && widths[3] < epsilon; + + // Border image with no border. It's a little silly but WebRender + // currently does not handle this. We could fall back to a blob image + // but there are reftests that are sensible to the test going through a + // blob while the reference doesn't. + if (noVerticalBorders && noHorizontalBorders) { + aBuilder.PushImage(dest, clip, !aItem->BackfaceIsHidden(), false, + rendering, key.value()); + break; + } + + // Fall-back if we want to fill the middle area and opposite edges are + // both empty. + // TODO(bug 1609893): moving some of the repetition handling code out + // of the image shader will make it easier to handle these cases + // properly. + if (noHorizontalBorders || noVerticalBorders) { + return ImgDrawResult::NOT_SUPPORTED; + } + } + + wr::WrBorderImage params{ + wr::ToBorderWidths(widths[0], widths[1], widths[2], widths[3]), + key.value(), + rendering, + mImageSize.width / appUnitsPerDevPixel, + mImageSize.height / appUnitsPerDevPixel, + mFill, + wr::ToDeviceIntSideOffsets(slice[0], slice[1], slice[2], slice[3]), + wr::ToRepeatMode(mRepeatModeHorizontal), + wr::ToRepeatMode(mRepeatModeVertical)}; + + aBuilder.PushBorderImage(dest, clip, !aItem->BackfaceIsHidden(), params); + break; + } + case StyleImage::Tag::Gradient: { + const StyleGradient& gradient = *mImageRenderer.GetGradientData(); + nsCSSGradientRenderer renderer = nsCSSGradientRenderer::Create( + aForFrame->PresContext(), aForFrame->Style(), gradient, mImageSize); + + wr::ExtendMode extendMode; + nsTArray<wr::GradientStop> stops; + LayoutDevicePoint lineStart; + LayoutDevicePoint lineEnd; + LayoutDeviceSize gradientRadius; + LayoutDevicePoint gradientCenter; + float gradientAngle; + renderer.BuildWebRenderParameters(1.0, extendMode, stops, lineStart, + lineEnd, gradientRadius, gradientCenter, + gradientAngle); + + if (gradient.IsLinear()) { + LayoutDevicePoint startPoint = + LayoutDevicePoint(dest.min.x, dest.min.y) + lineStart; + LayoutDevicePoint endPoint = + LayoutDevicePoint(dest.min.x, dest.min.y) + lineEnd; + + aBuilder.PushBorderGradient( + dest, clip, !aItem->BackfaceIsHidden(), + wr::ToBorderWidths(widths[0], widths[1], widths[2], widths[3]), + (float)(mImageSize.width) / appUnitsPerDevPixel, + (float)(mImageSize.height) / appUnitsPerDevPixel, mFill, + wr::ToDeviceIntSideOffsets(slice[0], slice[1], slice[2], slice[3]), + wr::ToLayoutPoint(startPoint), wr::ToLayoutPoint(endPoint), stops, + extendMode); + } else if (gradient.IsRadial()) { + aBuilder.PushBorderRadialGradient( + dest, clip, !aItem->BackfaceIsHidden(), + wr::ToBorderWidths(widths[0], widths[1], widths[2], widths[3]), + mFill, wr::ToLayoutPoint(lineStart), + wr::ToLayoutSize(gradientRadius), stops, extendMode); + } else { + MOZ_ASSERT(gradient.IsConic()); + aBuilder.PushBorderConicGradient( + dest, clip, !aItem->BackfaceIsHidden(), + wr::ToBorderWidths(widths[0], widths[1], widths[2], widths[3]), + mFill, wr::ToLayoutPoint(gradientCenter), gradientAngle, stops, + extendMode); + } + break; + } + default: + MOZ_ASSERT_UNREACHABLE("Unsupport border image type"); + drawResult = ImgDrawResult::NOT_SUPPORTED; + } + + return drawResult; +} + +nsCSSBorderImageRenderer::nsCSSBorderImageRenderer( + const nsCSSBorderImageRenderer& aRhs) + : mImageRenderer(aRhs.mImageRenderer), + mImageSize(aRhs.mImageSize), + mSlice(aRhs.mSlice), + mWidths(aRhs.mWidths), + mImageOutset(aRhs.mImageOutset), + mArea(aRhs.mArea), + mClip(aRhs.mClip), + mRepeatModeHorizontal(aRhs.mRepeatModeHorizontal), + mRepeatModeVertical(aRhs.mRepeatModeVertical), + mFill(aRhs.mFill) { + Unused << mImageRenderer.PrepareResult(); +} + +nsCSSBorderImageRenderer& nsCSSBorderImageRenderer::operator=( + const nsCSSBorderImageRenderer& aRhs) { + mImageRenderer = aRhs.mImageRenderer; + mImageSize = aRhs.mImageSize; + mSlice = aRhs.mSlice; + mWidths = aRhs.mWidths; + mImageOutset = aRhs.mImageOutset; + mArea = aRhs.mArea; + mClip = aRhs.mClip; + mRepeatModeHorizontal = aRhs.mRepeatModeHorizontal; + mRepeatModeVertical = aRhs.mRepeatModeVertical; + mFill = aRhs.mFill; + Unused << mImageRenderer.PrepareResult(); + + return *this; +} + +nsCSSBorderImageRenderer::nsCSSBorderImageRenderer( + nsIFrame* aForFrame, const nsRect& aBorderArea, + const nsStyleBorder& aStyleBorder, Sides aSkipSides, + const nsImageRenderer& aImageRenderer) + : mImageRenderer(aImageRenderer) { + // Determine the border image area, which by default corresponds to the + // border box but can be modified by 'border-image-outset'. + // Note that 'border-radius' do not apply to 'border-image' borders per + // <http://dev.w3.org/csswg/css-backgrounds/#corner-clipping>. + nsMargin borderWidths(aStyleBorder.GetComputedBorder()); + mImageOutset = aStyleBorder.GetImageOutset(); + if (nsCSSRendering::IsBoxDecorationSlice(aStyleBorder) && + !aSkipSides.IsEmpty()) { + mArea = nsCSSRendering::BoxDecorationRectForBorder( + aForFrame, aBorderArea, aSkipSides, &aStyleBorder); + if (mArea.IsEqualEdges(aBorderArea)) { + // No need for a clip, just skip the sides we don't want. + borderWidths.ApplySkipSides(aSkipSides); + mImageOutset.ApplySkipSides(aSkipSides); + mArea.Inflate(mImageOutset); + } else { + // We're drawing borders around the joined continuation boxes so we need + // to clip that to the slice that we want for this frame. + mArea.Inflate(mImageOutset); + mImageOutset.ApplySkipSides(aSkipSides); + mClip = aBorderArea; + mClip.Inflate(mImageOutset); + } + } else { + mArea = aBorderArea; + mArea.Inflate(mImageOutset); + } + + // Calculate the image size used to compute slice points. + CSSSizeOrRatio intrinsicSize = mImageRenderer.ComputeIntrinsicSize(); + mImageSize = nsImageRenderer::ComputeConcreteSize( + CSSSizeOrRatio(), intrinsicSize, mArea.Size()); + mImageRenderer.SetPreferredSize(intrinsicSize, mImageSize); + + // Compute the used values of 'border-image-slice' and 'border-image-width'; + // we do them together because the latter can depend on the former. + nsMargin slice; + nsMargin border; + for (const auto s : mozilla::AllPhysicalSides()) { + const auto& slice = aStyleBorder.mBorderImageSlice.offsets.Get(s); + int32_t imgDimension = + SideIsVertical(s) ? mImageSize.width : mImageSize.height; + nscoord borderDimension = SideIsVertical(s) ? mArea.width : mArea.height; + double value; + if (slice.IsNumber()) { + value = nsPresContext::CSSPixelsToAppUnits(NS_lround(slice.AsNumber())); + } else { + MOZ_ASSERT(slice.IsPercentage()); + value = slice.AsPercentage()._0 * imgDimension; + } + if (value < 0) { + value = 0; + } + if (value > imgDimension && imgDimension > 0) { + value = imgDimension; + } + mSlice.Side(s) = value; + + const auto& width = aStyleBorder.mBorderImageWidth.Get(s); + switch (width.tag) { + case StyleBorderImageSideWidth::Tag::LengthPercentage: + value = + std::max(0, width.AsLengthPercentage().Resolve(borderDimension)); + break; + case StyleBorderImageSideWidth::Tag::Number: + value = width.AsNumber() * borderWidths.Side(s); + break; + case StyleBorderImageSideWidth::Tag::Auto: + value = mSlice.Side(s); + break; + default: + MOZ_ASSERT_UNREACHABLE("unexpected CSS unit for border image area"); + value = 0; + break; + } + // NSToCoordRoundWithClamp rounds towards infinity, but that's OK + // because we expect value to be non-negative. + MOZ_ASSERT(value >= 0); + mWidths.Side(s) = NSToCoordRoundWithClamp(value); + MOZ_ASSERT(mWidths.Side(s) >= 0); + } + + // "If two opposite border-image-width offsets are large enough that they + // overlap, their used values are proportionately reduced until they no + // longer overlap." + uint32_t combinedBorderWidth = + uint32_t(mWidths.left) + uint32_t(mWidths.right); + double scaleX = combinedBorderWidth > uint32_t(mArea.width) + ? mArea.width / double(combinedBorderWidth) + : 1.0; + uint32_t combinedBorderHeight = + uint32_t(mWidths.top) + uint32_t(mWidths.bottom); + double scaleY = combinedBorderHeight > uint32_t(mArea.height) + ? mArea.height / double(combinedBorderHeight) + : 1.0; + double scale = std::min(scaleX, scaleY); + if (scale < 1.0) { + mWidths.left *= scale; + mWidths.right *= scale; + mWidths.top *= scale; + mWidths.bottom *= scale; + NS_ASSERTION(mWidths.left + mWidths.right <= mArea.width && + mWidths.top + mWidths.bottom <= mArea.height, + "rounding error in width reduction???"); + } + + mRepeatModeHorizontal = aStyleBorder.mBorderImageRepeatH; + mRepeatModeVertical = aStyleBorder.mBorderImageRepeatV; + mFill = aStyleBorder.mBorderImageSlice.fill; +} diff --git a/layout/painting/nsCSSRenderingBorders.h b/layout/painting/nsCSSRenderingBorders.h new file mode 100644 index 0000000000..8817083208 --- /dev/null +++ b/layout/painting/nsCSSRenderingBorders.h @@ -0,0 +1,357 @@ +/* -*- 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/. */ + +#ifndef NS_CSS_RENDERING_BORDERS_H +#define NS_CSS_RENDERING_BORDERS_H + +#include "gfxRect.h" +#include "mozilla/Attributes.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/BezierUtils.h" +#include "mozilla/gfx/PathHelpers.h" +#include "mozilla/RefPtr.h" +#include "nsColor.h" +#include "nsCOMPtr.h" +#include "nsIFrame.h" +#include "nsImageRenderer.h" +#include "gfxUtils.h" + +struct nsBorderColors; + +namespace mozilla { +class nsDisplayItem; +class nsDisplayList; +class nsDisplayListBuilder; + +class nsDisplayBorder; +class nsDisplayButtonBorder; +class nsDisplayButtonForeground; +class nsDisplayOutline; + +enum class StyleBorderStyle : uint8_t; +enum class StyleBorderImageRepeat : uint8_t; + +namespace gfx { +class GradientStops; +} // namespace gfx +namespace layers { +class StackingContextHelper; +} // namespace layers +} // namespace mozilla + +// define this to enable a bunch of debug dump info +#undef DEBUG_NEW_BORDERS + +/* + * Helper class that handles border rendering. + * + * aDrawTarget -- the DrawTarget to which the border should be rendered + * outsideRect -- the rectangle on the outer edge of the border + * + * For any parameter where an array of side values is passed in, + * they are in top, right, bottom, left order. + * + * borderStyles -- one border style enum per side + * borderWidths -- one border width per side + * borderRadii -- a RectCornerRadii struct describing the w/h for each rounded + * corner. If the corner doesn't have a border radius, 0,0 should be given for + * it. borderColors -- one nscolor per side + * + * skipSides -- a bit mask specifying which sides, if any, to skip + * backgroundColor -- the background color of the element. + * Used in calculating colors for 2-tone borders, such as inset and outset + * gapRect - a rectangle that should be clipped out to leave a gap in a border, + * or nullptr if none. + */ + +typedef enum { + BorderColorStyleNone, + BorderColorStyleSolid, + BorderColorStyleLight, + BorderColorStyleDark +} BorderColorStyle; + +class nsPresContext; + +class nsCSSBorderRenderer final { + typedef mozilla::gfx::Bezier Bezier; + typedef mozilla::gfx::ColorPattern ColorPattern; + typedef mozilla::gfx::DrawTarget DrawTarget; + typedef mozilla::gfx::Float Float; + typedef mozilla::gfx::Path Path; + typedef mozilla::gfx::Point Point; + typedef mozilla::gfx::Rect Rect; + typedef mozilla::gfx::RectCornerRadii RectCornerRadii; + typedef mozilla::gfx::StrokeOptions StrokeOptions; + + friend class mozilla::nsDisplayOutline; + friend class mozilla::nsDisplayButtonBorder; + friend class mozilla::nsDisplayButtonForeground; + + public: + nsCSSBorderRenderer(nsPresContext* aPresContext, DrawTarget* aDrawTarget, + const Rect& aDirtyRect, Rect& aOuterRect, + const mozilla::StyleBorderStyle* aBorderStyles, + const Float* aBorderWidths, RectCornerRadii& aBorderRadii, + const nscolor* aBorderColors, bool aBackfaceIsVisible, + const mozilla::Maybe<Rect>& aClipRect); + + // draw the entire border + void DrawBorders(); + + void CreateWebRenderCommands( + mozilla::nsDisplayItem* aItem, mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const mozilla::layers::StackingContextHelper& aSc); + + // utility function used for background painting as well as borders + static void ComputeInnerRadii(const RectCornerRadii& aRadii, + const Float* aBorderSizes, + RectCornerRadii* aInnerRadiiRet); + + // Given aRadii as the border radii for a rectangle, compute the + // appropriate radii for another rectangle *outside* that rectangle + // by increasing the radii, except keeping sharp corners sharp. + // Used for spread box-shadows + static void ComputeOuterRadii(const RectCornerRadii& aRadii, + const Float* aBorderSizes, + RectCornerRadii* aOuterRadiiRet); + + static bool AllCornersZeroSize(const RectCornerRadii& corners); + + private: + RectCornerRadii mBorderCornerDimensions; + + nsPresContext* mPresContext; + + // destination DrawTarget and dirty rect + DrawTarget* mDrawTarget; + Rect mDirtyRect; + + // the rectangle of the outside and the inside of the border + Rect mOuterRect; + Rect mInnerRect; + + // the style and size of the border + mozilla::StyleBorderStyle mBorderStyles[4]; + Float mBorderWidths[4]; + RectCornerRadii mBorderRadii; + + // the colors for 'border-top-color' et. al. + nscolor mBorderColors[4]; + + // calculated values + bool mAllBordersSameStyle; + bool mAllBordersSameWidth; + bool mOneUnitBorder; + bool mNoBorderRadius; + bool mAvoidStroke; + bool mBackfaceIsVisible; + mozilla::Maybe<Rect> mLocalClip; + + // For all the sides in the bitmask, would they be rendered + // in an identical color and style? + bool AreBorderSideFinalStylesSame(mozilla::SideBits aSides); + + // For the given style, is the given corner a solid color? + bool IsSolidCornerStyle(mozilla::StyleBorderStyle aStyle, + mozilla::Corner aCorner); + + // For the given corner, is the given corner mergeable into one dot? + bool IsCornerMergeable(mozilla::Corner aCorner); + + // For the given solid corner, what color style should be used? + BorderColorStyle BorderColorStyleForSolidCorner( + mozilla::StyleBorderStyle aStyle, mozilla::Corner aCorner); + + // + // Path generation functions + // + + // Get the Rect for drawing the given corner + Rect GetCornerRect(mozilla::Corner aCorner); + // add the path for drawing the given side without any adjacent corners to the + // context + Rect GetSideClipWithoutCornersRect(mozilla::Side aSide); + + // Create a clip path for the wedge that this side of + // the border should take up. This is only called + // when we're drawing separate border sides, so we know + // that ADD compositing is taking place. + // + // This code needs to make sure that the individual pieces + // don't ever (mathematically) overlap; the pixel overlap + // is taken care of by the ADD compositing. + already_AddRefed<Path> GetSideClipSubPath(mozilla::Side aSide); + + // Return start or end point for dashed/dotted side + Point GetStraightBorderPoint(mozilla::Side aSide, mozilla::Corner aCorner, + bool* aIsUnfilled, Float aDotOffset = 0.0f); + + // Return bezier control points for the outer and the inner curve for given + // corner + void GetOuterAndInnerBezier(Bezier* aOuterBezier, Bezier* aInnerBezier, + mozilla::Corner aCorner); + + // Given a set of sides to fill and a color, do so in the fastest way. + // + // Stroke tends to be faster for smaller borders because it doesn't go + // through the tessellator, which has initialization overhead. If + // we're rendering all sides, we can use stroke at any thickness; we + // also do TL/BR pairs at 1px thickness using stroke. + // + // If we can't stroke, then if it's a TL/BR pair, we use the specific + // TL/BR paths. Otherwise, we do the full path and fill. + // + // Calling code is expected to only set up a clip as necessary; no + // clip is needed if we can render the entire border in 1 or 2 passes. + void FillSolidBorder(const Rect& aOuterRect, const Rect& aInnerRect, + const RectCornerRadii& aBorderRadii, + const Float* aBorderSizes, mozilla::SideBits aSides, + const ColorPattern& aColor); + + // + // core rendering + // + + // draw the border for the given sides, using the style of the first side + // present in the bitmask + void DrawBorderSides(mozilla::SideBits aSides); + + // Setup the stroke options for the given dashed/dotted side + void SetupDashedOptions(StrokeOptions* aStrokeOptions, Float aDash[2], + mozilla::Side aSide, Float aBorderLength, + bool isCorner); + + // Draw the given dashed/dotte side + void DrawDashedOrDottedSide(mozilla::Side aSide); + + // Draw the given dotted side, each dot separately + void DrawDottedSideSlow(mozilla::Side aSide); + + // Draw the given dashed/dotted corner + void DrawDashedOrDottedCorner(mozilla::Side aSide, mozilla::Corner aCorner); + + // Draw the given dotted corner, each segment separately + void DrawDottedCornerSlow(mozilla::Side aSide, mozilla::Corner aCorner); + + // Draw the given dashed corner, each dot separately + void DrawDashedCornerSlow(mozilla::Side aSide, mozilla::Corner aCorner); + + // Draw the given dashed/dotted corner with solid style + void DrawFallbackSolidCorner(mozilla::Side aSide, mozilla::Corner aCorner); + + // Analyze if all border sides have the same width. + bool AllBordersSameWidth(); + + // Analyze if all borders are 'solid' this also considers hidden or 'none' + // borders because they can be considered 'solid' borders of 0 width and + // with no color effect. + bool AllBordersSolid(); + + // Draw a solid color border that is uniformly the same width. + void DrawSingleWidthSolidBorder(); + + // Draw any border which is solid on all sides. + void DrawSolidBorder(); +}; + +class nsCSSBorderImageRenderer final { + typedef mozilla::nsImageRenderer nsImageRenderer; + + public: + static mozilla::Maybe<nsCSSBorderImageRenderer> CreateBorderImageRenderer( + nsPresContext* aPresContext, nsIFrame* aForFrame, + const nsRect& aBorderArea, const nsStyleBorder& aStyleBorder, + const nsRect& aDirtyRect, nsIFrame::Sides aSkipSides, uint32_t aFlags, + mozilla::image::ImgDrawResult* aDrawResult); + + mozilla::image::ImgDrawResult DrawBorderImage(nsPresContext* aPresContext, + gfxContext& aRenderingContext, + nsIFrame* aForFrame, + const nsRect& aDirtyRect); + mozilla::image::ImgDrawResult CreateWebRenderCommands( + mozilla::nsDisplayItem* aItem, nsIFrame* aForFrame, + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const mozilla::layers::StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, + mozilla::nsDisplayListBuilder* aDisplayListBuilder); + + nsCSSBorderImageRenderer(const nsCSSBorderImageRenderer& aRhs); + nsCSSBorderImageRenderer& operator=(const nsCSSBorderImageRenderer& aRhs); + + private: + nsCSSBorderImageRenderer(nsIFrame* aForFrame, const nsRect& aBorderArea, + const nsStyleBorder& aStyleBorder, + nsIFrame::Sides aSkipSides, + const nsImageRenderer& aImageRenderer); + + nsImageRenderer mImageRenderer; + nsSize mImageSize; + nsMargin mSlice; + nsMargin mWidths; + nsMargin mImageOutset; + nsRect mArea; + nsRect mClip; + mozilla::StyleBorderImageRepeat mRepeatModeHorizontal; + mozilla::StyleBorderImageRepeat mRepeatModeVertical; + bool mFill; + + friend class mozilla::nsDisplayBorder; + friend struct nsCSSRendering; +}; + +namespace mozilla { +#ifdef DEBUG_NEW_BORDERS +# include <stdarg.h> + +static inline void PrintAsString(const mozilla::gfx::Point& p) { + fprintf(stderr, "[%f,%f]", p.x, p.y); +} + +static inline void PrintAsString(const mozilla::gfx::Size& s) { + fprintf(stderr, "[%f %f]", s.width, s.height); +} + +static inline void PrintAsString(const mozilla::gfx::Rect& r) { + fprintf(stderr, "[%f %f %f %f]", r.X(), r.Y(), r.Width(), r.Height()); +} + +static inline void PrintAsString(const mozilla::gfx::Float f) { + fprintf(stderr, "%f", f); +} + +static inline void PrintAsString(const char* s) { fprintf(stderr, "%s", s); } + +static inline void PrintAsStringNewline(const char* s = nullptr) { + if (s) fprintf(stderr, "%s", s); + fprintf(stderr, "\n"); + fflush(stderr); +} + +static inline MOZ_FORMAT_PRINTF(1, 2) void PrintAsFormatString(const char* fmt, + ...) { + va_list vl; + va_start(vl, fmt); + vfprintf(stderr, fmt, vl); + va_end(vl); +} + +#else +static inline void PrintAsString(const mozilla::gfx::Point& p) {} +static inline void PrintAsString(const mozilla::gfx::Size& s) {} +static inline void PrintAsString(const mozilla::gfx::Rect& r) {} +static inline void PrintAsString(const mozilla::gfx::Float f) {} +static inline void PrintAsString(const char* s) {} +static inline void PrintAsStringNewline(const char* s = nullptr) {} +static inline MOZ_FORMAT_PRINTF(1, 2) void PrintAsFormatString(const char* fmt, + ...) {} +#endif + +} // namespace mozilla + +#endif /* NS_CSS_RENDERING_BORDERS_H */ diff --git a/layout/painting/nsCSSRenderingGradients.cpp b/layout/painting/nsCSSRenderingGradients.cpp new file mode 100644 index 0000000000..f4a859cf34 --- /dev/null +++ b/layout/painting/nsCSSRenderingGradients.cpp @@ -0,0 +1,1299 @@ +/* -*- 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/. */ + +/* utility functions for drawing borders and backgrounds */ + +#include "nsCSSRenderingGradients.h" + +#include <tuple> + +#include "gfx2DGlue.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/ComputedStyle.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/Helpers.h" +#include "mozilla/MathAlgorithms.h" +#include "mozilla/ProfilerLabels.h" + +#include "nsLayoutUtils.h" +#include "nsStyleConsts.h" +#include "nsPresContext.h" +#include "nsPoint.h" +#include "nsRect.h" +#include "nsCSSColorUtils.h" +#include "gfxContext.h" +#include "nsStyleStructInlines.h" +#include "nsCSSProps.h" +#include "gfxUtils.h" +#include "gfxGradientCache.h" + +#include "mozilla/layers/StackingContextHelper.h" +#include "mozilla/layers/WebRenderLayerManager.h" +#include "mozilla/webrender/WebRenderTypes.h" +#include "mozilla/webrender/WebRenderAPI.h" +#include "Units.h" + +using namespace mozilla; +using namespace mozilla::gfx; + +static CSSPoint ResolvePosition(const Position& aPos, const CSSSize& aSize) { + CSSCoord h = aPos.horizontal.ResolveToCSSPixels(aSize.width); + CSSCoord v = aPos.vertical.ResolveToCSSPixels(aSize.height); + return CSSPoint(h, v); +} + +// Given a box with size aBoxSize and origin (0,0), and an angle aAngle, +// and a starting point for the gradient line aStart, find the endpoint of +// the gradient line --- the intersection of the gradient line with a line +// perpendicular to aAngle that passes through the farthest corner in the +// direction aAngle. +static CSSPoint ComputeGradientLineEndFromAngle(const CSSPoint& aStart, + double aAngle, + const CSSSize& aBoxSize) { + double dx = cos(-aAngle); + double dy = sin(-aAngle); + CSSPoint farthestCorner(dx > 0 ? aBoxSize.width : 0, + dy > 0 ? aBoxSize.height : 0); + CSSPoint delta = farthestCorner - aStart; + double u = delta.x * dy - delta.y * dx; + return farthestCorner + CSSPoint(-u * dy, u * dx); +} + +// Compute the start and end points of the gradient line for a linear gradient. +static std::tuple<CSSPoint, CSSPoint> ComputeLinearGradientLine( + nsPresContext* aPresContext, const StyleGradient& aGradient, + const CSSSize& aBoxSize) { + using X = StyleHorizontalPositionKeyword; + using Y = StyleVerticalPositionKeyword; + + const StyleLineDirection& direction = aGradient.AsLinear().direction; + const bool isModern = + aGradient.AsLinear().compat_mode == StyleGradientCompatMode::Modern; + + CSSPoint center(aBoxSize.width / 2, aBoxSize.height / 2); + switch (direction.tag) { + case StyleLineDirection::Tag::Angle: { + double angle = direction.AsAngle().ToRadians(); + if (isModern) { + angle = M_PI_2 - angle; + } + CSSPoint end = ComputeGradientLineEndFromAngle(center, angle, aBoxSize); + CSSPoint start = CSSPoint(aBoxSize.width, aBoxSize.height) - end; + return {start, end}; + } + case StyleLineDirection::Tag::Vertical: { + CSSPoint start(center.x, 0); + CSSPoint end(center.x, aBoxSize.height); + if (isModern == (direction.AsVertical() == Y::Top)) { + std::swap(start.y, end.y); + } + return {start, end}; + } + case StyleLineDirection::Tag::Horizontal: { + CSSPoint start(0, center.y); + CSSPoint end(aBoxSize.width, center.y); + if (isModern == (direction.AsHorizontal() == X::Left)) { + std::swap(start.x, end.x); + } + return {start, end}; + } + case StyleLineDirection::Tag::Corner: { + const auto& corner = direction.AsCorner(); + const X& h = corner._0; + const Y& v = corner._1; + + if (isModern) { + float xSign = h == X::Right ? 1.0 : -1.0; + float ySign = v == Y::Top ? 1.0 : -1.0; + double angle = atan2(ySign * aBoxSize.width, xSign * aBoxSize.height); + CSSPoint end = ComputeGradientLineEndFromAngle(center, angle, aBoxSize); + CSSPoint start = CSSPoint(aBoxSize.width, aBoxSize.height) - end; + return {start, end}; + } + + CSSCoord startX = h == X::Left ? 0.0 : aBoxSize.width; + CSSCoord startY = v == Y::Top ? 0.0 : aBoxSize.height; + + CSSPoint start(startX, startY); + CSSPoint end = CSSPoint(aBoxSize.width, aBoxSize.height) - start; + return {start, end}; + } + default: + break; + } + MOZ_ASSERT_UNREACHABLE("Unknown line direction"); + return {CSSPoint(), CSSPoint()}; +} + +using EndingShape = StyleGenericEndingShape<Length, LengthPercentage>; +using RadialGradientRadii = + Variant<StyleShapeExtent, std::pair<CSSCoord, CSSCoord>>; + +static RadialGradientRadii ComputeRadialGradientRadii(const EndingShape& aShape, + const CSSSize& aSize) { + if (aShape.IsCircle()) { + auto& circle = aShape.AsCircle(); + if (circle.IsExtent()) { + return RadialGradientRadii(circle.AsExtent()); + } + CSSCoord radius = circle.AsRadius().ToCSSPixels(); + return RadialGradientRadii(std::make_pair(radius, radius)); + } + auto& ellipse = aShape.AsEllipse(); + if (ellipse.IsExtent()) { + return RadialGradientRadii(ellipse.AsExtent()); + } + + auto& radii = ellipse.AsRadii(); + return RadialGradientRadii( + std::make_pair(radii._0.ResolveToCSSPixels(aSize.width), + radii._1.ResolveToCSSPixels(aSize.height))); +} + +// Compute the start and end points of the gradient line for a radial gradient. +// Also returns the horizontal and vertical radii defining the circle or +// ellipse to use. +static std::tuple<CSSPoint, CSSPoint, CSSCoord, CSSCoord> +ComputeRadialGradientLine(const StyleGradient& aGradient, + const CSSSize& aBoxSize) { + const auto& radial = aGradient.AsRadial(); + const EndingShape& endingShape = radial.shape; + const Position& position = radial.position; + CSSPoint start = ResolvePosition(position, aBoxSize); + + // Compute gradient shape: the x and y radii of an ellipse. + CSSCoord radiusX, radiusY; + CSSCoord leftDistance = Abs(start.x); + CSSCoord rightDistance = Abs(aBoxSize.width - start.x); + CSSCoord topDistance = Abs(start.y); + CSSCoord bottomDistance = Abs(aBoxSize.height - start.y); + + auto radii = ComputeRadialGradientRadii(endingShape, aBoxSize); + if (radii.is<StyleShapeExtent>()) { + switch (radii.as<StyleShapeExtent>()) { + case StyleShapeExtent::ClosestSide: + radiusX = std::min(leftDistance, rightDistance); + radiusY = std::min(topDistance, bottomDistance); + if (endingShape.IsCircle()) { + radiusX = radiusY = std::min(radiusX, radiusY); + } + break; + case StyleShapeExtent::ClosestCorner: { + // Compute x and y distances to nearest corner + CSSCoord offsetX = std::min(leftDistance, rightDistance); + CSSCoord offsetY = std::min(topDistance, bottomDistance); + if (endingShape.IsCircle()) { + radiusX = radiusY = NS_hypot(offsetX, offsetY); + } else { + // maintain aspect ratio + radiusX = offsetX * M_SQRT2; + radiusY = offsetY * M_SQRT2; + } + break; + } + case StyleShapeExtent::FarthestSide: + radiusX = std::max(leftDistance, rightDistance); + radiusY = std::max(topDistance, bottomDistance); + if (endingShape.IsCircle()) { + radiusX = radiusY = std::max(radiusX, radiusY); + } + break; + case StyleShapeExtent::FarthestCorner: { + // Compute x and y distances to nearest corner + CSSCoord offsetX = std::max(leftDistance, rightDistance); + CSSCoord offsetY = std::max(topDistance, bottomDistance); + if (endingShape.IsCircle()) { + radiusX = radiusY = NS_hypot(offsetX, offsetY); + } else { + // maintain aspect ratio + radiusX = offsetX * M_SQRT2; + radiusY = offsetY * M_SQRT2; + } + break; + } + default: + MOZ_ASSERT_UNREACHABLE("Unknown shape extent keyword?"); + radiusX = radiusY = 0; + } + } else { + auto pair = radii.as<std::pair<CSSCoord, CSSCoord>>(); + radiusX = pair.first; + radiusY = pair.second; + } + + // The gradient line end point is where the gradient line intersects + // the ellipse. + CSSPoint end = start + CSSPoint(radiusX, 0); + return {start, end, radiusX, radiusY}; +} + +// Compute the center and the start angle of the conic gradient. +static std::tuple<CSSPoint, float> ComputeConicGradientProperties( + const StyleGradient& aGradient, const CSSSize& aBoxSize) { + const auto& conic = aGradient.AsConic(); + const Position& position = conic.position; + float angle = static_cast<float>(conic.angle.ToRadians()); + CSSPoint center = ResolvePosition(position, aBoxSize); + + return {center, angle}; +} + +static float Interpolate(float aF1, float aF2, float aFrac) { + return aF1 + aFrac * (aF2 - aF1); +} + +static StyleAbsoluteColor Interpolate(const StyleAbsoluteColor& aLeft, + const StyleAbsoluteColor& aRight, + float aFrac) { + // NOTE: This has to match the interpolation method that WebRender uses which + // right now is sRGB. In the future we should implement interpolation in more + // gradient color-spaces. + static constexpr auto kMethod = StyleColorInterpolationMethod{ + StyleColorSpace::Srgb, + StyleHueInterpolationMethod::Shorter, + }; + return Servo_InterpolateColor(kMethod, &aRight, &aLeft, aFrac); +} + +static nscoord FindTileStart(nscoord aDirtyCoord, nscoord aTilePos, + nscoord aTileDim) { + NS_ASSERTION(aTileDim > 0, "Non-positive tile dimension"); + double multiples = floor(double(aDirtyCoord - aTilePos) / aTileDim); + return NSToCoordRound(multiples * aTileDim + aTilePos); +} + +static gfxFloat LinearGradientStopPositionForPoint( + const gfxPoint& aGradientStart, const gfxPoint& aGradientEnd, + const gfxPoint& aPoint) { + gfxPoint d = aGradientEnd - aGradientStart; + gfxPoint p = aPoint - aGradientStart; + /** + * Compute a parameter t such that a line perpendicular to the + * d vector, passing through aGradientStart + d*t, also + * passes through aPoint. + * + * t is given by + * (p.x - d.x*t)*d.x + (p.y - d.y*t)*d.y = 0 + * + * Solving for t we get + * numerator = d.x*p.x + d.y*p.y + * denominator = d.x^2 + d.y^2 + * t = numerator/denominator + * + * In nsCSSRendering::PaintGradient we know the length of d + * is not zero. + */ + double numerator = d.x.value * p.x.value + d.y.value * p.y.value; + double denominator = d.x.value * d.x.value + d.y.value * d.y.value; + return numerator / denominator; +} + +static bool RectIsBeyondLinearGradientEdge(const gfxRect& aRect, + const gfxMatrix& aPatternMatrix, + const nsTArray<ColorStop>& aStops, + const gfxPoint& aGradientStart, + const gfxPoint& aGradientEnd, + StyleAbsoluteColor* aOutEdgeColor) { + gfxFloat topLeft = LinearGradientStopPositionForPoint( + aGradientStart, aGradientEnd, + aPatternMatrix.TransformPoint(aRect.TopLeft())); + gfxFloat topRight = LinearGradientStopPositionForPoint( + aGradientStart, aGradientEnd, + aPatternMatrix.TransformPoint(aRect.TopRight())); + gfxFloat bottomLeft = LinearGradientStopPositionForPoint( + aGradientStart, aGradientEnd, + aPatternMatrix.TransformPoint(aRect.BottomLeft())); + gfxFloat bottomRight = LinearGradientStopPositionForPoint( + aGradientStart, aGradientEnd, + aPatternMatrix.TransformPoint(aRect.BottomRight())); + + const ColorStop& firstStop = aStops[0]; + if (topLeft < firstStop.mPosition && topRight < firstStop.mPosition && + bottomLeft < firstStop.mPosition && bottomRight < firstStop.mPosition) { + *aOutEdgeColor = firstStop.mColor; + return true; + } + + const ColorStop& lastStop = aStops.LastElement(); + if (topLeft >= lastStop.mPosition && topRight >= lastStop.mPosition && + bottomLeft >= lastStop.mPosition && bottomRight >= lastStop.mPosition) { + *aOutEdgeColor = lastStop.mColor; + return true; + } + + return false; +} + +static void ResolveMidpoints(nsTArray<ColorStop>& stops) { + for (size_t x = 1; x < stops.Length() - 1;) { + if (!stops[x].mIsMidpoint) { + x++; + continue; + } + + const auto& color1 = stops[x - 1].mColor; + const auto& color2 = stops[x + 1].mColor; + float offset1 = stops[x - 1].mPosition; + float offset2 = stops[x + 1].mPosition; + float offset = stops[x].mPosition; + // check if everything coincides. If so, ignore the midpoint. + if (offset - offset1 == offset2 - offset) { + stops.RemoveElementAt(x); + continue; + } + + // Check if we coincide with the left colorstop. + if (offset1 == offset) { + // Morph the midpoint to a regular stop with the color of the next + // color stop. + stops[x].mColor = color2; + stops[x].mIsMidpoint = false; + continue; + } + + // Check if we coincide with the right colorstop. + if (offset2 == offset) { + // Morph the midpoint to a regular stop with the color of the previous + // color stop. + stops[x].mColor = color1; + stops[x].mIsMidpoint = false; + continue; + } + + float midpoint = (offset - offset1) / (offset2 - offset1); + ColorStop newStops[9]; + if (midpoint > .5f) { + for (size_t y = 0; y < 7; y++) { + newStops[y].mPosition = offset1 + (offset - offset1) * (7 + y) / 13; + } + + newStops[7].mPosition = offset + (offset2 - offset) / 3; + newStops[8].mPosition = offset + (offset2 - offset) * 2 / 3; + } else { + newStops[0].mPosition = offset1 + (offset - offset1) / 3; + newStops[1].mPosition = offset1 + (offset - offset1) * 2 / 3; + + for (size_t y = 0; y < 7; y++) { + newStops[y + 2].mPosition = offset + (offset2 - offset) * y / 13; + } + } + // calculate colors + + for (auto& newStop : newStops) { + // Calculate the intermediate color stops per the formula of the CSS + // images spec. http://dev.w3.org/csswg/css-images/#color-stop-syntax 9 + // points were chosen since it is the minimum number of stops that always + // give the smoothest appearace regardless of midpoint position and + // difference in luminance of the end points. + const float relativeOffset = + (newStop.mPosition - offset1) / (offset2 - offset1); + const float multiplier = powf(relativeOffset, logf(.5f) / logf(midpoint)); + + auto srgb1 = color1.ToColorSpace(StyleColorSpace::Srgb); + auto srgb2 = color2.ToColorSpace(StyleColorSpace::Srgb); + + const float red = + srgb1.components._0 + + multiplier * (srgb2.components._0 - srgb1.components._0); + const float green = + srgb1.components._1 + + multiplier * (srgb2.components._1 - srgb1.components._1); + const float blue = + srgb1.components._2 + + multiplier * (srgb2.components._2 - srgb1.components._2); + const float alpha = + srgb1.alpha + multiplier * (srgb2.alpha - srgb1.alpha); + + newStop.mColor = StyleAbsoluteColor::Srgb(red, green, blue, alpha); + } + + stops.ReplaceElementsAt(x, 1, newStops, 9); + x += 9; + } +} + +static StyleAbsoluteColor TransparentColor(const StyleAbsoluteColor& aColor) { + auto color = aColor; + color.alpha = 0.0f; + return color; +} + +// Adjusts and adds color stops in such a way that drawing the gradient with +// unpremultiplied interpolation looks nearly the same as if it were drawn with +// premultiplied interpolation. +static const float kAlphaIncrementPerGradientStep = 0.1f; +static void ResolvePremultipliedAlpha(nsTArray<ColorStop>& aStops) { + for (size_t x = 1; x < aStops.Length(); x++) { + const ColorStop leftStop = aStops[x - 1]; + const ColorStop rightStop = aStops[x]; + + // if the left and right stop have the same alpha value, we don't need + // to do anything. Hardstops should be instant, and also should never + // require dealing with interpolation. + if (leftStop.mColor.alpha == rightStop.mColor.alpha || + leftStop.mPosition == rightStop.mPosition) { + continue; + } + + // Is the stop on the left 100% transparent? If so, have it adopt the color + // of the right stop + if (leftStop.mColor.alpha == 0) { + aStops[x - 1].mColor = TransparentColor(rightStop.mColor); + continue; + } + + // Is the stop on the right completely transparent? + // If so, duplicate it and assign it the color on the left. + if (rightStop.mColor.alpha == 0) { + ColorStop newStop = rightStop; + newStop.mColor = TransparentColor(leftStop.mColor); + aStops.InsertElementAt(x, newStop); + x++; + continue; + } + + // Now handle cases where one or both of the stops are partially + // transparent. + if (leftStop.mColor.alpha != 1.0f || rightStop.mColor.alpha != 1.0f) { + // Calculate how many extra steps. We do a step per 10% transparency. + size_t stepCount = + NSToIntFloor(fabsf(leftStop.mColor.alpha - rightStop.mColor.alpha) / + kAlphaIncrementPerGradientStep); + for (size_t y = 1; y < stepCount; y++) { + float frac = static_cast<float>(y) / stepCount; + ColorStop newStop( + Interpolate(leftStop.mPosition, rightStop.mPosition, frac), false, + Interpolate(leftStop.mColor, rightStop.mColor, frac)); + aStops.InsertElementAt(x, newStop); + x++; + } + } + } +} + +static ColorStop InterpolateColorStop(const ColorStop& aFirst, + const ColorStop& aSecond, + double aPosition, + const StyleAbsoluteColor& aDefault) { + MOZ_ASSERT(aFirst.mPosition <= aPosition); + MOZ_ASSERT(aPosition <= aSecond.mPosition); + + double delta = aSecond.mPosition - aFirst.mPosition; + if (delta < 1e-6) { + return ColorStop(aPosition, false, aDefault); + } + + return ColorStop(aPosition, false, + Interpolate(aFirst.mColor, aSecond.mColor, + (aPosition - aFirst.mPosition) / delta)); +} + +// Clamp and extend the given ColorStop array in-place to fit exactly into the +// range [0, 1]. +static void ClampColorStops(nsTArray<ColorStop>& aStops) { + MOZ_ASSERT(aStops.Length() > 0); + + // If all stops are outside the range, then get rid of everything and replace + // with a single colour. + if (aStops.Length() < 2 || aStops[0].mPosition > 1 || + aStops.LastElement().mPosition < 0) { + const auto c = aStops[0].mPosition > 1 ? aStops[0].mColor + : aStops.LastElement().mColor; + aStops.Clear(); + aStops.AppendElement(ColorStop(0, false, c)); + return; + } + + // Create the 0 and 1 points if they fall in the range of |aStops|, and + // discard all stops outside the range [0, 1]. + // XXX: If we have stops positioned at 0 or 1, we only keep the innermost of + // those stops. This should be fine for the current user(s) of this function. + for (size_t i = aStops.Length() - 1; i > 0; i--) { + if (aStops[i - 1].mPosition < 1 && aStops[i].mPosition >= 1) { + // Add a point to position 1. + aStops[i] = + InterpolateColorStop(aStops[i - 1], aStops[i], + /* aPosition = */ 1, aStops[i - 1].mColor); + // Remove all the elements whose position is greater than 1. + aStops.RemoveLastElements(aStops.Length() - (i + 1)); + } + if (aStops[i - 1].mPosition <= 0 && aStops[i].mPosition > 0) { + // Add a point to position 0. + aStops[i - 1] = + InterpolateColorStop(aStops[i - 1], aStops[i], + /* aPosition = */ 0, aStops[i].mColor); + // Remove all of the preceding stops -- they are all negative. + aStops.RemoveElementsAt(0, i - 1); + break; + } + } + + MOZ_ASSERT(aStops[0].mPosition >= -1e6); + MOZ_ASSERT(aStops.LastElement().mPosition - 1 <= 1e6); + + // The end points won't exist yet if they don't fall in the original range of + // |aStops|. Create them if needed. + if (aStops[0].mPosition > 0) { + aStops.InsertElementAt(0, ColorStop(0, false, aStops[0].mColor)); + } + if (aStops.LastElement().mPosition < 1) { + aStops.AppendElement(ColorStop(1, false, aStops.LastElement().mColor)); + } +} + +namespace mozilla { + +template <typename T> +static StyleAbsoluteColor GetSpecifiedColor( + const StyleGenericGradientItem<StyleColor, T>& aItem, + const ComputedStyle& aStyle) { + if (aItem.IsInterpolationHint()) { + return StyleAbsoluteColor::Transparent(); + } + const StyleColor& c = aItem.IsSimpleColorStop() + ? aItem.AsSimpleColorStop() + : aItem.AsComplexColorStop().color; + + return c.ResolveColor(aStyle.StyleText()->mColor); +} + +static Maybe<double> GetSpecifiedGradientPosition( + const StyleGenericGradientItem<StyleColor, StyleLengthPercentage>& aItem, + CSSCoord aLineLength) { + if (aItem.IsSimpleColorStop()) { + return Nothing(); + } + + const LengthPercentage& pos = aItem.IsComplexColorStop() + ? aItem.AsComplexColorStop().position + : aItem.AsInterpolationHint(); + + if (pos.ConvertsToPercentage()) { + return Some(pos.ToPercentage()); + } + + if (aLineLength < 1e-6) { + return Some(0.0); + } + return Some(pos.ResolveToCSSPixels(aLineLength) / aLineLength); +} + +// aLineLength argument is unused for conic-gradients. +static Maybe<double> GetSpecifiedGradientPosition( + const StyleGenericGradientItem<StyleColor, StyleAngleOrPercentage>& aItem, + CSSCoord aLineLength) { + if (aItem.IsSimpleColorStop()) { + return Nothing(); + } + + const StyleAngleOrPercentage& pos = aItem.IsComplexColorStop() + ? aItem.AsComplexColorStop().position + : aItem.AsInterpolationHint(); + + if (pos.IsPercentage()) { + return Some(pos.AsPercentage()._0); + } + + return Some(pos.AsAngle().ToRadians() / (2 * M_PI)); +} + +template <typename T> +static nsTArray<ColorStop> ComputeColorStopsForItems( + ComputedStyle* aComputedStyle, + Span<const StyleGenericGradientItem<StyleColor, T>> aItems, + CSSCoord aLineLength) { + MOZ_ASSERT(aItems.Length() >= 2, + "The parser should reject gradients with less than two stops"); + + nsTArray<ColorStop> stops(aItems.Length()); + + // If there is a run of stops before stop i that did not have specified + // positions, then this is the index of the first stop in that run. + Maybe<size_t> firstUnsetPosition; + for (size_t i = 0; i < aItems.Length(); ++i) { + const auto& stop = aItems[i]; + double position; + + Maybe<double> specifiedPosition = + GetSpecifiedGradientPosition(stop, aLineLength); + + if (specifiedPosition) { + position = *specifiedPosition; + } else if (i == 0) { + // First stop defaults to position 0.0 + position = 0.0; + } else if (i == aItems.Length() - 1) { + // Last stop defaults to position 1.0 + position = 1.0; + } else { + // Other stops with no specified position get their position assigned + // later by interpolation, see below. + // Remember where the run of stops with no specified position starts, + // if it starts here. + if (firstUnsetPosition.isNothing()) { + firstUnsetPosition.emplace(i); + } + MOZ_ASSERT(!stop.IsInterpolationHint(), + "Interpolation hints always specify position"); + auto color = GetSpecifiedColor(stop, *aComputedStyle); + stops.AppendElement(ColorStop(0, false, color)); + continue; + } + + if (i > 0) { + // Prevent decreasing stop positions by advancing this position + // to the previous stop position, if necessary + double previousPosition = firstUnsetPosition + ? stops[*firstUnsetPosition - 1].mPosition + : stops[i - 1].mPosition; + position = std::max(position, previousPosition); + } + auto stopColor = GetSpecifiedColor(stop, *aComputedStyle); + stops.AppendElement( + ColorStop(position, stop.IsInterpolationHint(), stopColor)); + if (firstUnsetPosition) { + // Interpolate positions for all stops that didn't have a specified + // position + double p = stops[*firstUnsetPosition - 1].mPosition; + double d = (stops[i].mPosition - p) / (i - *firstUnsetPosition + 1); + for (size_t j = *firstUnsetPosition; j < i; ++j) { + p += d; + stops[j].mPosition = p; + } + firstUnsetPosition.reset(); + } + } + + return stops; +} + +static nsTArray<ColorStop> ComputeColorStops(ComputedStyle* aComputedStyle, + const StyleGradient& aGradient, + CSSCoord aLineLength) { + if (aGradient.IsLinear()) { + return ComputeColorStopsForItems( + aComputedStyle, aGradient.AsLinear().items.AsSpan(), aLineLength); + } + if (aGradient.IsRadial()) { + return ComputeColorStopsForItems( + aComputedStyle, aGradient.AsRadial().items.AsSpan(), aLineLength); + } + return ComputeColorStopsForItems( + aComputedStyle, aGradient.AsConic().items.AsSpan(), aLineLength); +} + +nsCSSGradientRenderer nsCSSGradientRenderer::Create( + nsPresContext* aPresContext, ComputedStyle* aComputedStyle, + const StyleGradient& aGradient, const nsSize& aIntrinsicSize) { + auto srcSize = CSSSize::FromAppUnits(aIntrinsicSize); + + // Compute "gradient line" start and end relative to the intrinsic size of + // the gradient. + CSSPoint lineStart, lineEnd, center; // center is for conic gradients only + CSSCoord radiusX = 0, radiusY = 0; // for radial gradients only + float angle = 0.0; // for conic gradients only + if (aGradient.IsLinear()) { + std::tie(lineStart, lineEnd) = + ComputeLinearGradientLine(aPresContext, aGradient, srcSize); + } else if (aGradient.IsRadial()) { + std::tie(lineStart, lineEnd, radiusX, radiusY) = + ComputeRadialGradientLine(aGradient, srcSize); + } else { + MOZ_ASSERT(aGradient.IsConic()); + std::tie(center, angle) = + ComputeConicGradientProperties(aGradient, srcSize); + } + // Avoid sending Infs or Nans to downwind draw targets. + if (!lineStart.IsFinite() || !lineEnd.IsFinite()) { + lineStart = lineEnd = CSSPoint(0, 0); + } + if (!center.IsFinite()) { + center = CSSPoint(0, 0); + } + CSSCoord lineLength = + NS_hypot(lineEnd.x - lineStart.x, lineEnd.y - lineStart.y); + + // Build color stop array and compute stop positions + nsTArray<ColorStop> stops = + ComputeColorStops(aComputedStyle, aGradient, lineLength); + + ResolveMidpoints(stops); + + nsCSSGradientRenderer renderer; + renderer.mPresContext = aPresContext; + renderer.mGradient = &aGradient; + renderer.mStops = std::move(stops); + renderer.mLineStart = { + aPresContext->CSSPixelsToDevPixels(lineStart.x), + aPresContext->CSSPixelsToDevPixels(lineStart.y), + }; + renderer.mLineEnd = { + aPresContext->CSSPixelsToDevPixels(lineEnd.x), + aPresContext->CSSPixelsToDevPixels(lineEnd.y), + }; + renderer.mRadiusX = aPresContext->CSSPixelsToDevPixels(radiusX); + renderer.mRadiusY = aPresContext->CSSPixelsToDevPixels(radiusY); + renderer.mCenter = { + aPresContext->CSSPixelsToDevPixels(center.x), + aPresContext->CSSPixelsToDevPixels(center.y), + }; + renderer.mAngle = angle; + return renderer; +} + +void nsCSSGradientRenderer::Paint(gfxContext& aContext, const nsRect& aDest, + const nsRect& aFillArea, + const nsSize& aRepeatSize, + const CSSIntRect& aSrc, + const nsRect& aDirtyRect, float aOpacity) { + AUTO_PROFILER_LABEL("nsCSSGradientRenderer::Paint", GRAPHICS); + + if (aDest.IsEmpty() || aFillArea.IsEmpty()) { + return; + } + + nscoord appUnitsPerDevPixel = mPresContext->AppUnitsPerDevPixel(); + + gfxFloat lineLength = + NS_hypot(mLineEnd.x - mLineStart.x, mLineEnd.y - mLineStart.y); + bool cellContainsFill = aDest.Contains(aFillArea); + + // If a non-repeating linear gradient is axis-aligned and there are no gaps + // between tiles, we can optimise away most of the work by converting to a + // repeating linear gradient and filling the whole destination rect at once. + bool forceRepeatToCoverTiles = + mGradient->IsLinear() && + (mLineStart.x == mLineEnd.x) != (mLineStart.y == mLineEnd.y) && + aRepeatSize.width == aDest.width && aRepeatSize.height == aDest.height && + !mGradient->AsLinear().repeating && !aSrc.IsEmpty() && !cellContainsFill; + + gfxMatrix matrix; + if (forceRepeatToCoverTiles) { + // Length of the source rectangle along the gradient axis. + double rectLen; + // The position of the start of the rectangle along the gradient. + double offset; + + // The gradient line is "backwards". Flip the line upside down to make + // things easier, and then rotate the matrix to turn everything back the + // right way up. + if (mLineStart.x > mLineEnd.x || mLineStart.y > mLineEnd.y) { + std::swap(mLineStart, mLineEnd); + matrix.PreScale(-1, -1); + } + + // Fit the gradient line exactly into the source rect. + // aSrc is relative to aIntrinsincSize. + // srcRectDev will be relative to srcSize, so in the same coordinate space + // as lineStart / lineEnd. + gfxRect srcRectDev = nsLayoutUtils::RectToGfxRect( + CSSPixel::ToAppUnits(aSrc), appUnitsPerDevPixel); + if (mLineStart.x != mLineEnd.x) { + rectLen = srcRectDev.width; + offset = (srcRectDev.x - mLineStart.x) / lineLength; + mLineStart.x = srcRectDev.x; + mLineEnd.x = srcRectDev.XMost(); + } else { + rectLen = srcRectDev.height; + offset = (srcRectDev.y - mLineStart.y) / lineLength; + mLineStart.y = srcRectDev.y; + mLineEnd.y = srcRectDev.YMost(); + } + + // Adjust gradient stop positions for the new gradient line. + double scale = lineLength / rectLen; + for (size_t i = 0; i < mStops.Length(); i++) { + mStops[i].mPosition = (mStops[i].mPosition - offset) * fabs(scale); + } + + // Clamp or extrapolate gradient stops to exactly [0, 1]. + ClampColorStops(mStops); + + lineLength = rectLen; + } + + // Eliminate negative-position stops if the gradient is radial. + double firstStop = mStops[0].mPosition; + if (mGradient->IsRadial() && firstStop < 0.0) { + if (mGradient->AsRadial().repeating) { + // Choose an instance of the repeated pattern that gives us all positive + // stop-offsets. + double lastStop = mStops[mStops.Length() - 1].mPosition; + double stopDelta = lastStop - firstStop; + // If all the stops are in approximately the same place then logic below + // will kick in that makes us draw just the last stop color, so don't + // try to do anything in that case. We certainly need to avoid + // dividing by zero. + if (stopDelta >= 1e-6) { + double instanceCount = ceil(-firstStop / stopDelta); + // Advance stops by instanceCount multiples of the period of the + // repeating gradient. + double offset = instanceCount * stopDelta; + for (uint32_t i = 0; i < mStops.Length(); i++) { + mStops[i].mPosition += offset; + } + } + } else { + // Move negative-position stops to position 0.0. We may also need + // to set the color of the stop to the color the gradient should have + // at the center of the ellipse. + for (uint32_t i = 0; i < mStops.Length(); i++) { + double pos = mStops[i].mPosition; + if (pos < 0.0) { + mStops[i].mPosition = 0.0; + // If this is the last stop, we don't need to adjust the color, + // it will fill the entire area. + if (i < mStops.Length() - 1) { + double nextPos = mStops[i + 1].mPosition; + // If nextPos is approximately equal to pos, then we don't + // need to adjust the color of this stop because it's + // not going to be displayed. + // If nextPos is negative, we don't need to adjust the color of + // this stop since it's not going to be displayed because + // nextPos will also be moved to 0.0. + if (nextPos >= 0.0 && nextPos - pos >= 1e-6) { + // Compute how far the new position 0.0 is along the interval + // between pos and nextPos. + // XXX Color interpolation (in cairo, too) should use the + // CSS 'color-interpolation' property! + float frac = float((0.0 - pos) / (nextPos - pos)); + mStops[i].mColor = + Interpolate(mStops[i].mColor, mStops[i + 1].mColor, frac); + } + } + } + } + } + firstStop = mStops[0].mPosition; + MOZ_ASSERT(firstStop >= 0.0, "Failed to fix stop offsets"); + } + + if (mGradient->IsRadial() && !mGradient->AsRadial().repeating) { + // Direct2D can only handle a particular class of radial gradients because + // of the way the it specifies gradients. Setting firstStop to 0, when we + // can, will help us stay on the fast path. Currently we don't do this + // for repeating gradients but we could by adjusting the stop collection + // to start at 0 + firstStop = 0; + } + + double lastStop = mStops[mStops.Length() - 1].mPosition; + // Cairo gradients must have stop positions in the range [0, 1]. So, + // stop positions will be normalized below by subtracting firstStop and then + // multiplying by stopScale. + double stopScale; + double stopOrigin = firstStop; + double stopEnd = lastStop; + double stopDelta = lastStop - firstStop; + bool zeroRadius = + mGradient->IsRadial() && (mRadiusX < 1e-6 || mRadiusY < 1e-6); + if (stopDelta < 1e-6 || (!mGradient->IsConic() && lineLength < 1e-6) || + zeroRadius) { + // Stops are all at the same place. Map all stops to 0.0. + // For repeating radial gradients, or for any radial gradients with + // a zero radius, we need to fill with the last stop color, so just set + // both radii to 0. + if (mGradient->Repeating() || zeroRadius) { + mRadiusX = mRadiusY = 0.0; + } + stopDelta = 0.0; + } + + // Don't normalize non-repeating or degenerate gradients below 0..1 + // This keeps the gradient line as large as the box and doesn't + // lets us avoiding having to get padding correct for stops + // at 0 and 1 + if (!mGradient->Repeating() || stopDelta == 0.0) { + stopOrigin = std::min(stopOrigin, 0.0); + stopEnd = std::max(stopEnd, 1.0); + } + stopScale = 1.0 / (stopEnd - stopOrigin); + + // Create the gradient pattern. + RefPtr<gfxPattern> gradientPattern; + gfxPoint gradientStart; + gfxPoint gradientEnd; + if (mGradient->IsLinear()) { + // Compute the actual gradient line ends we need to pass to cairo after + // stops have been normalized. + gradientStart = mLineStart + (mLineEnd - mLineStart) * stopOrigin; + gradientEnd = mLineStart + (mLineEnd - mLineStart) * stopEnd; + + if (stopDelta == 0.0) { + // Stops are all at the same place. For repeating gradients, this will + // just paint the last stop color. We don't need to do anything. + // For non-repeating gradients, this should render as two colors, one + // on each "side" of the gradient line segment, which is a point. All + // our stops will be at 0.0; we just need to set the direction vector + // correctly. + gradientEnd = gradientStart + (mLineEnd - mLineStart); + } + + gradientPattern = new gfxPattern(gradientStart.x, gradientStart.y, + gradientEnd.x, gradientEnd.y); + } else if (mGradient->IsRadial()) { + NS_ASSERTION(firstStop >= 0.0, + "Negative stops not allowed for radial gradients"); + + // To form an ellipse, we'll stretch a circle vertically, if necessary. + // So our radii are based on radiusX. + double innerRadius = mRadiusX * stopOrigin; + double outerRadius = mRadiusX * stopEnd; + if (stopDelta == 0.0) { + // Stops are all at the same place. See above (except we now have + // the inside vs. outside of an ellipse). + outerRadius = innerRadius + 1; + } + gradientPattern = new gfxPattern(mLineStart.x, mLineStart.y, innerRadius, + mLineStart.x, mLineStart.y, outerRadius); + if (mRadiusX != mRadiusY) { + // Stretch the circles into ellipses vertically by setting a transform + // in the pattern. + // Recall that this is the transform from user space to pattern space. + // So to stretch the ellipse by factor of P vertically, we scale + // user coordinates by 1/P. + matrix.PreTranslate(mLineStart); + matrix.PreScale(1.0, mRadiusX / mRadiusY); + matrix.PreTranslate(-mLineStart); + } + } else { + gradientPattern = + new gfxPattern(mCenter.x, mCenter.y, mAngle, stopOrigin, stopEnd); + } + // Use a pattern transform to take account of source and dest rects + matrix.PreTranslate(gfxPoint(mPresContext->CSSPixelsToDevPixels(aSrc.x), + mPresContext->CSSPixelsToDevPixels(aSrc.y))); + matrix.PreScale( + gfxFloat(nsPresContext::CSSPixelsToAppUnits(aSrc.width)) / aDest.width, + gfxFloat(nsPresContext::CSSPixelsToAppUnits(aSrc.height)) / aDest.height); + gradientPattern->SetMatrix(matrix); + + if (stopDelta == 0.0) { + // Non-repeating gradient with all stops in same place -> just add + // first stop and last stop, both at position 0. + // Repeating gradient with all stops in the same place, or radial + // gradient with radius of 0 -> just paint the last stop color. + // We use firstStop offset to keep |stops| with same units (will later + // normalize to 0). + auto firstColor(mStops[0].mColor); + auto lastColor(mStops.LastElement().mColor); + mStops.Clear(); + + if (!mGradient->Repeating() && !zeroRadius) { + mStops.AppendElement(ColorStop(firstStop, false, firstColor)); + } + mStops.AppendElement(ColorStop(firstStop, false, lastColor)); + } + + ResolvePremultipliedAlpha(mStops); + + bool isRepeat = mGradient->Repeating() || forceRepeatToCoverTiles; + + // Now set normalized color stops in pattern. + // Offscreen gradient surface cache (not a tile): + // On some backends (e.g. D2D), the GradientStops object holds an offscreen + // surface which is a lookup table used to evaluate the gradient. This surface + // can use much memory (ram and/or GPU ram) and can be expensive to create. So + // we cache it. The cache key correlates 1:1 with the arguments for + // CreateGradientStops (also the implied backend type) Note that GradientStop + // is a simple struct with a stop value (while GradientStops has the surface). + nsTArray<gfx::GradientStop> rawStops(mStops.Length()); + rawStops.SetLength(mStops.Length()); + for (uint32_t i = 0; i < mStops.Length(); i++) { + rawStops[i].color = ToDeviceColor(mStops[i].mColor); + rawStops[i].color.a *= aOpacity; + rawStops[i].offset = stopScale * (mStops[i].mPosition - stopOrigin); + } + RefPtr<mozilla::gfx::GradientStops> gs = + gfxGradientCache::GetOrCreateGradientStops( + aContext.GetDrawTarget(), rawStops, + isRepeat ? gfx::ExtendMode::REPEAT : gfx::ExtendMode::CLAMP); + gradientPattern->SetColorStops(gs); + + // Paint gradient tiles. This isn't terribly efficient, but doing it this + // way is simple and sure to get pixel-snapping right. We could speed things + // up by drawing tiles into temporary surfaces and copying those to the + // destination, but after pixel-snapping tiles may not all be the same size. + nsRect dirty; + if (!dirty.IntersectRect(aDirtyRect, aFillArea)) return; + + gfxRect areaToFill = + nsLayoutUtils::RectToGfxRect(aFillArea, appUnitsPerDevPixel); + gfxRect dirtyAreaToFill = + nsLayoutUtils::RectToGfxRect(dirty, appUnitsPerDevPixel); + dirtyAreaToFill.RoundOut(); + + Matrix ctm = aContext.CurrentMatrix(); + bool isCTMPreservingAxisAlignedRectangles = + ctm.PreservesAxisAlignedRectangles(); + + // xStart/yStart are the top-left corner of the top-left tile. + nscoord xStart = FindTileStart(dirty.x, aDest.x, aRepeatSize.width); + nscoord yStart = FindTileStart(dirty.y, aDest.y, aRepeatSize.height); + nscoord xEnd = forceRepeatToCoverTiles ? xStart + aDest.width : dirty.XMost(); + nscoord yEnd = + forceRepeatToCoverTiles ? yStart + aDest.height : dirty.YMost(); + + if (TryPaintTilesWithExtendMode(aContext, gradientPattern, xStart, yStart, + dirtyAreaToFill, aDest, aRepeatSize, + forceRepeatToCoverTiles)) { + return; + } + + // x and y are the top-left corner of the tile to draw + for (nscoord y = yStart; y < yEnd; y += aRepeatSize.height) { + for (nscoord x = xStart; x < xEnd; x += aRepeatSize.width) { + // The coordinates of the tile + gfxRect tileRect = nsLayoutUtils::RectToGfxRect( + nsRect(x, y, aDest.width, aDest.height), appUnitsPerDevPixel); + // The actual area to fill with this tile is the intersection of this + // tile with the overall area we're supposed to be filling + gfxRect fillRect = + forceRepeatToCoverTiles ? areaToFill : tileRect.Intersect(areaToFill); + // Try snapping the fill rect. Snap its top-left and bottom-right + // independently to preserve the orientation. + gfxPoint snappedFillRectTopLeft = fillRect.TopLeft(); + gfxPoint snappedFillRectTopRight = fillRect.TopRight(); + gfxPoint snappedFillRectBottomRight = fillRect.BottomRight(); + // Snap three points instead of just two to ensure we choose the + // correct orientation if there's a reflection. + if (isCTMPreservingAxisAlignedRectangles && + aContext.UserToDevicePixelSnapped(snappedFillRectTopLeft, true) && + aContext.UserToDevicePixelSnapped(snappedFillRectBottomRight, true) && + aContext.UserToDevicePixelSnapped(snappedFillRectTopRight, true)) { + if (snappedFillRectTopLeft.x == snappedFillRectBottomRight.x || + snappedFillRectTopLeft.y == snappedFillRectBottomRight.y) { + // Nothing to draw; avoid scaling by zero and other weirdness that + // could put the context in an error state. + continue; + } + // Set the context's transform to the transform that maps fillRect to + // snappedFillRect. The part of the gradient that was going to + // exactly fill fillRect will fill snappedFillRect instead. + gfxMatrix transform = gfxUtils::TransformRectToRect( + fillRect, snappedFillRectTopLeft, snappedFillRectTopRight, + snappedFillRectBottomRight); + aContext.SetMatrixDouble(transform); + } + aContext.NewPath(); + aContext.Rectangle(fillRect); + + gfxRect dirtyFillRect = fillRect.Intersect(dirtyAreaToFill); + gfxRect fillRectRelativeToTile = dirtyFillRect - tileRect.TopLeft(); + auto edgeColor = StyleAbsoluteColor::Transparent(); + if (mGradient->IsLinear() && !isRepeat && + RectIsBeyondLinearGradientEdge(fillRectRelativeToTile, matrix, mStops, + gradientStart, gradientEnd, + &edgeColor)) { + edgeColor.alpha *= aOpacity; + aContext.SetColor(ToSRGBColor(edgeColor)); + } else { + aContext.SetMatrixDouble( + aContext.CurrentMatrixDouble().Copy().PreTranslate( + tileRect.TopLeft())); + aContext.SetPattern(gradientPattern); + } + aContext.Fill(); + aContext.SetMatrix(ctm); + } + } +} + +bool nsCSSGradientRenderer::TryPaintTilesWithExtendMode( + gfxContext& aContext, gfxPattern* aGradientPattern, nscoord aXStart, + nscoord aYStart, const gfxRect& aDirtyAreaToFill, const nsRect& aDest, + const nsSize& aRepeatSize, bool aForceRepeatToCoverTiles) { + // If we have forced a non-repeating gradient to repeat to cover tiles, + // then it will be faster to just paint it once using that optimization + if (aForceRepeatToCoverTiles) { + return false; + } + + nscoord appUnitsPerDevPixel = mPresContext->AppUnitsPerDevPixel(); + + // We can only use this fast path if we don't have to worry about pixel + // snapping, and there is no spacing between tiles. We could handle spacing + // by increasing the size of tileSurface and leaving it transparent, but I'm + // not sure it's worth it + bool canUseExtendModeForTiling = (aXStart % appUnitsPerDevPixel == 0) && + (aYStart % appUnitsPerDevPixel == 0) && + (aDest.width % appUnitsPerDevPixel == 0) && + (aDest.height % appUnitsPerDevPixel == 0) && + (aRepeatSize.width == aDest.width) && + (aRepeatSize.height == aDest.height); + + if (!canUseExtendModeForTiling) { + return false; + } + + IntSize tileSize{ + NSAppUnitsToIntPixels(aDest.width, appUnitsPerDevPixel), + NSAppUnitsToIntPixels(aDest.height, appUnitsPerDevPixel), + }; + + // Check whether this is a reasonable surface size and doesn't overflow + // before doing calculations with the tile size + if (!Factory::ReasonableSurfaceSize(tileSize)) { + return false; + } + + // We only want to do this when there are enough tiles to justify the + // overhead of painting to an offscreen surface. The heuristic here + // is when we will be painting at least 16 tiles or more, this is kind + // of arbitrary + bool shouldUseExtendModeForTiling = + aDirtyAreaToFill.Area() > (tileSize.width * tileSize.height) * 16.0; + + if (!shouldUseExtendModeForTiling) { + return false; + } + + // Draw the gradient pattern into a surface for our single tile + RefPtr<gfx::SourceSurface> tileSurface; + { + RefPtr<gfx::DrawTarget> tileTarget = + aContext.GetDrawTarget()->CreateSimilarDrawTarget( + tileSize, gfx::SurfaceFormat::B8G8R8A8); + if (!tileTarget || !tileTarget->IsValid()) { + return false; + } + + { + gfxContext tileContext(tileTarget); + + tileContext.SetPattern(aGradientPattern); + tileContext.Paint(); + } + + tileSurface = tileTarget->Snapshot(); + tileTarget = nullptr; + } + + // Draw the gradient using tileSurface as a repeating pattern masked by + // the dirtyRect + Matrix tileTransform = Matrix::Translation( + NSAppUnitsToFloatPixels(aXStart, appUnitsPerDevPixel), + NSAppUnitsToFloatPixels(aYStart, appUnitsPerDevPixel)); + + aContext.NewPath(); + aContext.Rectangle(aDirtyAreaToFill); + aContext.Fill(SurfacePattern(tileSurface, ExtendMode::REPEAT, tileTransform)); + + return true; +} + +void nsCSSGradientRenderer::BuildWebRenderParameters( + float aOpacity, wr::ExtendMode& aMode, nsTArray<wr::GradientStop>& aStops, + LayoutDevicePoint& aLineStart, LayoutDevicePoint& aLineEnd, + LayoutDeviceSize& aGradientRadius, LayoutDevicePoint& aGradientCenter, + float& aGradientAngle) { + aMode = + mGradient->Repeating() ? wr::ExtendMode::Repeat : wr::ExtendMode::Clamp; + + aStops.SetLength(mStops.Length()); + for (uint32_t i = 0; i < mStops.Length(); i++) { + aStops[i].color = wr::ToColorF(ToDeviceColor(mStops[i].mColor)); + aStops[i].color.a *= aOpacity; + aStops[i].offset = mStops[i].mPosition; + } + + aLineStart = LayoutDevicePoint(mLineStart.x, mLineStart.y); + aLineEnd = LayoutDevicePoint(mLineEnd.x, mLineEnd.y); + aGradientRadius = LayoutDeviceSize(mRadiusX, mRadiusY); + aGradientCenter = LayoutDevicePoint(mCenter.x, mCenter.y); + aGradientAngle = mAngle; +} + +void nsCSSGradientRenderer::BuildWebRenderDisplayItems( + wr::DisplayListBuilder& aBuilder, const layers::StackingContextHelper& aSc, + const nsRect& aDest, const nsRect& aFillArea, const nsSize& aRepeatSize, + const CSSIntRect& aSrc, bool aIsBackfaceVisible, float aOpacity) { + if (aDest.IsEmpty() || aFillArea.IsEmpty()) { + return; + } + + wr::ExtendMode extendMode; + nsTArray<wr::GradientStop> stops; + LayoutDevicePoint lineStart; + LayoutDevicePoint lineEnd; + LayoutDeviceSize gradientRadius; + LayoutDevicePoint gradientCenter; + float gradientAngle; + BuildWebRenderParameters(aOpacity, extendMode, stops, lineStart, lineEnd, + gradientRadius, gradientCenter, gradientAngle); + + nscoord appUnitsPerDevPixel = mPresContext->AppUnitsPerDevPixel(); + + nsPoint firstTile = + nsPoint(FindTileStart(aFillArea.x, aDest.x, aRepeatSize.width), + FindTileStart(aFillArea.y, aDest.y, aRepeatSize.height)); + + // Translate the parameters into device coordinates + LayoutDeviceRect clipBounds = + LayoutDevicePixel::FromAppUnits(aFillArea, appUnitsPerDevPixel); + LayoutDeviceRect firstTileBounds = LayoutDevicePixel::FromAppUnits( + nsRect(firstTile, aDest.Size()), appUnitsPerDevPixel); + LayoutDeviceSize tileRepeat = + LayoutDevicePixel::FromAppUnits(aRepeatSize, appUnitsPerDevPixel); + + // Calculate the bounds of the gradient display item, which starts at the + // first tile and extends to the end of clip bounds + LayoutDevicePoint tileToClip = + clipBounds.BottomRight() - firstTileBounds.TopLeft(); + LayoutDeviceRect gradientBounds = LayoutDeviceRect( + firstTileBounds.TopLeft(), LayoutDeviceSize(tileToClip.x, tileToClip.y)); + + // Calculate the tile spacing, which is the repeat size minus the tile size + LayoutDeviceSize tileSpacing = tileRepeat - firstTileBounds.Size(); + + // srcTransform is used for scaling the gradient to match aSrc + LayoutDeviceRect srcTransform = LayoutDeviceRect( + nsPresContext::CSSPixelsToAppUnits(aSrc.x), + nsPresContext::CSSPixelsToAppUnits(aSrc.y), + aDest.width / ((float)nsPresContext::CSSPixelsToAppUnits(aSrc.width)), + aDest.height / ((float)nsPresContext::CSSPixelsToAppUnits(aSrc.height))); + + lineStart.x = (lineStart.x - srcTransform.x) * srcTransform.width; + lineStart.y = (lineStart.y - srcTransform.y) * srcTransform.height; + + gradientCenter.x = (gradientCenter.x - srcTransform.x) * srcTransform.width; + gradientCenter.y = (gradientCenter.y - srcTransform.y) * srcTransform.height; + + if (mGradient->IsLinear()) { + lineEnd.x = (lineEnd.x - srcTransform.x) * srcTransform.width; + lineEnd.y = (lineEnd.y - srcTransform.y) * srcTransform.height; + + aBuilder.PushLinearGradient( + mozilla::wr::ToLayoutRect(gradientBounds), + mozilla::wr::ToLayoutRect(clipBounds), aIsBackfaceVisible, + mozilla::wr::ToLayoutPoint(lineStart), + mozilla::wr::ToLayoutPoint(lineEnd), stops, extendMode, + mozilla::wr::ToLayoutSize(firstTileBounds.Size()), + mozilla::wr::ToLayoutSize(tileSpacing)); + } else if (mGradient->IsRadial()) { + gradientRadius.width *= srcTransform.width; + gradientRadius.height *= srcTransform.height; + + aBuilder.PushRadialGradient( + mozilla::wr::ToLayoutRect(gradientBounds), + mozilla::wr::ToLayoutRect(clipBounds), aIsBackfaceVisible, + mozilla::wr::ToLayoutPoint(lineStart), + mozilla::wr::ToLayoutSize(gradientRadius), stops, extendMode, + mozilla::wr::ToLayoutSize(firstTileBounds.Size()), + mozilla::wr::ToLayoutSize(tileSpacing)); + } else { + MOZ_ASSERT(mGradient->IsConic()); + aBuilder.PushConicGradient( + mozilla::wr::ToLayoutRect(gradientBounds), + mozilla::wr::ToLayoutRect(clipBounds), aIsBackfaceVisible, + mozilla::wr::ToLayoutPoint(gradientCenter), gradientAngle, stops, + extendMode, mozilla::wr::ToLayoutSize(firstTileBounds.Size()), + mozilla::wr::ToLayoutSize(tileSpacing)); + } +} + +} // namespace mozilla diff --git a/layout/painting/nsCSSRenderingGradients.h b/layout/painting/nsCSSRenderingGradients.h new file mode 100644 index 0000000000..5cf00fb4f8 --- /dev/null +++ b/layout/painting/nsCSSRenderingGradients.h @@ -0,0 +1,125 @@ +/* -*- 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/. */ + +#ifndef nsCSSRenderingGradients_h__ +#define nsCSSRenderingGradients_h__ + +#include "gfxRect.h" +#include "nsStyleStruct.h" +#include "Units.h" +#include "mozilla/Maybe.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/webrender/webrender_ffi.h" + +class gfxPattern; + +namespace mozilla { + +namespace layers { +class StackingContextHelper; +} // namespace layers + +namespace wr { +class DisplayListBuilder; +} // namespace wr + +// A resolved color stop, with a specific position along the gradient line and +// a color. +struct ColorStop { + ColorStop() : mPosition(0), mIsMidpoint(false) {} + ColorStop(double aPosition, bool aIsMidPoint, + const StyleAbsoluteColor& aColor) + : mPosition(aPosition), mIsMidpoint(aIsMidPoint), mColor(aColor) {} + double mPosition; // along the gradient line; 0=start, 1=end + bool mIsMidpoint; + StyleAbsoluteColor mColor; +}; + +class nsCSSGradientRenderer final { + public: + /** + * Prepare a nsCSSGradientRenderer for a gradient for an element. + * aIntrinsicSize - the size of the source gradient. + */ + static nsCSSGradientRenderer Create(nsPresContext* aPresContext, + ComputedStyle* aComputedStyle, + const StyleGradient& aGradient, + const nsSize& aIntrinsiceSize); + + /** + * Draw the gradient to aContext + * aDest - where the first tile of gradient is + * aFill - the area to be filled with tiles of aDest + * aSrc - the area of the gradient that will fill aDest + * aRepeatSize - the distance from the origin of a tile + * to the next origin of a tile + * aDirtyRect - pixels outside of this area may be skipped + */ + void Paint(gfxContext& aContext, const nsRect& aDest, const nsRect& aFill, + const nsSize& aRepeatSize, const mozilla::CSSIntRect& aSrc, + const nsRect& aDirtyRect, float aOpacity = 1.0); + + /** + * Collect the gradient parameters + */ + void BuildWebRenderParameters(float aOpacity, wr::ExtendMode& aMode, + nsTArray<wr::GradientStop>& aStops, + LayoutDevicePoint& aLineStart, + LayoutDevicePoint& aLineEnd, + LayoutDeviceSize& aGradientRadius, + LayoutDevicePoint& aGradientCenter, + float& aGradientAngle); + + /** + * Build display items for the gradient + * aLayer - the layer to make this display item relative to + * aDest - where the first tile of gradient is + * aFill - the area to be filled with tiles of aDest + * aRepeatSize - the distance from the origin of a tile + * to the next origin of a tile + * aSrc - the area of the gradient that will fill aDest + */ + void BuildWebRenderDisplayItems(wr::DisplayListBuilder& aBuilder, + const layers::StackingContextHelper& aSc, + const nsRect& aDest, const nsRect& aFill, + const nsSize& aRepeatSize, + const mozilla::CSSIntRect& aSrc, + bool aIsBackfaceVisible, + float aOpacity = 1.0); + + private: + nsCSSGradientRenderer() + : mPresContext(nullptr), + mGradient(nullptr), + mRadiusX(0.0), + mRadiusY(0.0), + mAngle(0.0) {} + + /** + * Attempts to paint the tiles for a gradient by painting it once to an + * offscreen surface and then painting that offscreen surface with + * ExtendMode::Repeat to cover all tiles. + * + * Returns false if the optimization wasn't able to be used, in which case + * a fallback should be used. + */ + bool TryPaintTilesWithExtendMode( + gfxContext& aContext, gfxPattern* aGradientPattern, nscoord aXStart, + nscoord aYStart, const gfxRect& aDirtyAreaToFill, const nsRect& aDest, + const nsSize& aRepeatSize, bool aForceRepeatToCoverTiles); + + nsPresContext* mPresContext; + const StyleGradient* mGradient; + nsTArray<ColorStop> mStops; + gfxPoint mLineStart, mLineEnd; // only for linear/radial gradients + double mRadiusX, mRadiusY; // only for radial gradients + gfxPoint mCenter; // only for conic gradients + float mAngle; // only for conic gradients +}; + +} // namespace mozilla + +#endif /* nsCSSRenderingGradients_h__ */ diff --git a/layout/painting/nsDisplayItemTypes.h b/layout/painting/nsDisplayItemTypes.h new file mode 100644 index 0000000000..f0c7c333e0 --- /dev/null +++ b/layout/painting/nsDisplayItemTypes.h @@ -0,0 +1,73 @@ +/* -*- 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/. + */ +// IWYU pragma: private, include "nsDisplayList.h" + +/** + * It's useful to be able to dynamically check the type of certain items. + * Every subclass of nsDisplayItem must have a new type added here for the + * purposes of easy comparison and matching of items in different display lists. + */ + +#ifndef NSDISPLAYITEMTYPES_H_ +#define NSDISPLAYITEMTYPES_H_ + +enum class DisplayItemType : uint8_t { + TYPE_ZERO = 0, /** Spacer so that the first item starts at 1 */ + +#define DECLARE_DISPLAY_ITEM_TYPE(name, flags) TYPE_##name, +#include "nsDisplayItemTypesList.h" +#undef DECLARE_DISPLAY_ITEM_TYPE + + TYPE_MAX +}; + +enum { + // Number of bits needed to represent all types + TYPE_BITS = 8 +}; + +enum DisplayItemFlags { + TYPE_RENDERS_NO_IMAGES = 1 << 0, + TYPE_IS_CONTENTFUL = 1 << 1, + TYPE_IS_CONTAINER = 1 << 2 +}; + +inline const char* DisplayItemTypeName(DisplayItemType aType) { + switch (aType) { +#define DECLARE_DISPLAY_ITEM_TYPE(name, flags) \ + case DisplayItemType::TYPE_##name: \ + return #name; +#include "nsDisplayItemTypesList.h" +#undef DECLARE_DISPLAY_ITEM_TYPE + + default: + return "TYPE_UNKNOWN"; + } +} + +inline uint8_t GetDisplayItemFlagsForType(DisplayItemType aType) { + static const uint8_t flags[static_cast<uint32_t>(DisplayItemType::TYPE_MAX)] = + {0 +#define DECLARE_DISPLAY_ITEM_TYPE(name, flags) , flags +#include "nsDisplayItemTypesList.h" +#undef DECLARE_DISPLAY_ITEM_TYPE + }; + + return flags[static_cast<uint32_t>(aType)]; +} + +inline DisplayItemType GetDisplayItemTypeFromKey(uint32_t aDisplayItemKey) { + static const uint32_t typeMask = (1 << TYPE_BITS) - 1; + DisplayItemType type = + static_cast<DisplayItemType>(aDisplayItemKey & typeMask); + NS_ASSERTION( + type >= DisplayItemType::TYPE_ZERO && type < DisplayItemType::TYPE_MAX, + "Invalid display item type!"); + return type; +} + +#endif /*NSDISPLAYITEMTYPES_H_*/ diff --git a/layout/painting/nsDisplayItemTypesList.h b/layout/painting/nsDisplayItemTypesList.h new file mode 100644 index 0000000000..b3d4f14c4d --- /dev/null +++ b/layout/painting/nsDisplayItemTypesList.h @@ -0,0 +1,120 @@ +/* -*- 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/. */ +// IWYU pragma: private, include "nsDisplayList.h" +DECLARE_DISPLAY_ITEM_TYPE(ALT_FEEDBACK, 0) +DECLARE_DISPLAY_ITEM_TYPE(ASYNC_ZOOM, + TYPE_RENDERS_NO_IMAGES | TYPE_IS_CONTAINER) +DECLARE_DISPLAY_ITEM_TYPE(BACKDROP_FILTER, TYPE_IS_CONTAINER) +DECLARE_DISPLAY_ITEM_TYPE(BACKDROP_ROOT_CONTAINER, TYPE_IS_CONTAINER) +DECLARE_DISPLAY_ITEM_TYPE(BACKGROUND, TYPE_IS_CONTENTFUL) +DECLARE_DISPLAY_ITEM_TYPE(BACKGROUND_COLOR, TYPE_RENDERS_NO_IMAGES) +DECLARE_DISPLAY_ITEM_TYPE(BLEND_CONTAINER, + TYPE_RENDERS_NO_IMAGES | TYPE_IS_CONTAINER) +DECLARE_DISPLAY_ITEM_TYPE(BLEND_MODE, + TYPE_RENDERS_NO_IMAGES | TYPE_IS_CONTAINER) +DECLARE_DISPLAY_ITEM_TYPE(BORDER, 0) +DECLARE_DISPLAY_ITEM_TYPE(BOX_SHADOW_INNER, TYPE_RENDERS_NO_IMAGES) +DECLARE_DISPLAY_ITEM_TYPE(BOX_SHADOW_OUTER, TYPE_RENDERS_NO_IMAGES) +DECLARE_DISPLAY_ITEM_TYPE(BUTTON_BORDER_BACKGROUND, TYPE_IS_CONTENTFUL) +DECLARE_DISPLAY_ITEM_TYPE(BUTTON_BOX_SHADOW_OUTER, + TYPE_RENDERS_NO_IMAGES | TYPE_IS_CONTENTFUL) +DECLARE_DISPLAY_ITEM_TYPE(BUTTON_FOREGROUND, TYPE_IS_CONTENTFUL) +DECLARE_DISPLAY_ITEM_TYPE(CANVAS, TYPE_RENDERS_NO_IMAGES) +DECLARE_DISPLAY_ITEM_TYPE(CANVAS_BACKGROUND_COLOR, TYPE_RENDERS_NO_IMAGES) +DECLARE_DISPLAY_ITEM_TYPE(CANVAS_BACKGROUND_IMAGE, TYPE_IS_CONTENTFUL) +DECLARE_DISPLAY_ITEM_TYPE(CANVAS_FOCUS, TYPE_RENDERS_NO_IMAGES) +DECLARE_DISPLAY_ITEM_TYPE(CANVAS_THEMED_BACKGROUND, + TYPE_RENDERS_NO_IMAGES | TYPE_IS_CONTENTFUL) +DECLARE_DISPLAY_ITEM_TYPE(CARET, TYPE_RENDERS_NO_IMAGES | TYPE_IS_CONTENTFUL) +DECLARE_DISPLAY_ITEM_TYPE(CHECKED_CHECKBOX, + TYPE_RENDERS_NO_IMAGES | TYPE_IS_CONTENTFUL) +DECLARE_DISPLAY_ITEM_TYPE(CHECKED_RADIOBUTTON, + TYPE_RENDERS_NO_IMAGES | TYPE_IS_CONTENTFUL) +DECLARE_DISPLAY_ITEM_TYPE(COLUMN_RULE, TYPE_RENDERS_NO_IMAGES) +DECLARE_DISPLAY_ITEM_TYPE(COMBOBOX_FOCUS, TYPE_RENDERS_NO_IMAGES) +DECLARE_DISPLAY_ITEM_TYPE(COMPOSITOR_HITTEST_INFO, TYPE_RENDERS_NO_IMAGES) +DECLARE_DISPLAY_ITEM_TYPE(CONTAINER, TYPE_RENDERS_NO_IMAGES | TYPE_IS_CONTAINER) +DECLARE_DISPLAY_ITEM_TYPE(DESTINATION, TYPE_RENDERS_NO_IMAGES) +DECLARE_DISPLAY_ITEM_TYPE(EVENT_RECEIVER, TYPE_RENDERS_NO_IMAGES) +DECLARE_DISPLAY_ITEM_TYPE(FIELDSET_BORDER_BACKGROUND, 0) +DECLARE_DISPLAY_ITEM_TYPE(FILTER, TYPE_RENDERS_NO_IMAGES | TYPE_IS_CONTAINER) +DECLARE_DISPLAY_ITEM_TYPE(FIXED_POSITION, + TYPE_RENDERS_NO_IMAGES | TYPE_IS_CONTAINER) +DECLARE_DISPLAY_ITEM_TYPE(FOREIGN_OBJECT, + TYPE_IS_CONTENTFUL | TYPE_IS_CONTAINER) +DECLARE_DISPLAY_ITEM_TYPE(FRAMESET_BLANK, TYPE_RENDERS_NO_IMAGES) +DECLARE_DISPLAY_ITEM_TYPE(FRAMESET_BORDER, TYPE_RENDERS_NO_IMAGES) +DECLARE_DISPLAY_ITEM_TYPE(GENERIC, TYPE_RENDERS_NO_IMAGES) +DECLARE_DISPLAY_ITEM_TYPE(GRADIENT, TYPE_IS_CONTENTFUL) +DECLARE_DISPLAY_ITEM_TYPE(HEADER_FOOTER, TYPE_RENDERS_NO_IMAGES) +DECLARE_DISPLAY_ITEM_TYPE(IMAGE, TYPE_IS_CONTENTFUL) +DECLARE_DISPLAY_ITEM_TYPE(LINK, TYPE_RENDERS_NO_IMAGES) +DECLARE_DISPLAY_ITEM_TYPE(LIST_FOCUS, TYPE_RENDERS_NO_IMAGES) +DECLARE_DISPLAY_ITEM_TYPE(MARGIN_GUIDES, TYPE_RENDERS_NO_IMAGES) +DECLARE_DISPLAY_ITEM_TYPE(MASK, TYPE_IS_CONTAINER) +DECLARE_DISPLAY_ITEM_TYPE(OPACITY, TYPE_RENDERS_NO_IMAGES | TYPE_IS_CONTAINER) +DECLARE_DISPLAY_ITEM_TYPE(OPTION_EVENT_GRABBER, + TYPE_RENDERS_NO_IMAGES | TYPE_IS_CONTAINER) +DECLARE_DISPLAY_ITEM_TYPE(OUTLINE, TYPE_RENDERS_NO_IMAGES) +DECLARE_DISPLAY_ITEM_TYPE(OWN_LAYER, TYPE_RENDERS_NO_IMAGES | TYPE_IS_CONTAINER) +DECLARE_DISPLAY_ITEM_TYPE(PERSPECTIVE, + TYPE_RENDERS_NO_IMAGES | TYPE_IS_CONTAINER) +DECLARE_DISPLAY_ITEM_TYPE(RANGE_FOCUS_RING, 0) +DECLARE_DISPLAY_ITEM_TYPE(REMOTE, TYPE_RENDERS_NO_IMAGES) +DECLARE_DISPLAY_ITEM_TYPE(SCROLL_INFO_LAYER, + TYPE_RENDERS_NO_IMAGES | TYPE_IS_CONTAINER) +DECLARE_DISPLAY_ITEM_TYPE(SLIDER_MARKS, TYPE_RENDERS_NO_IMAGES) +DECLARE_DISPLAY_ITEM_TYPE(SELECTION_OVERLAY, TYPE_RENDERS_NO_IMAGES) +DECLARE_DISPLAY_ITEM_TYPE(SOLID_COLOR, TYPE_RENDERS_NO_IMAGES) +DECLARE_DISPLAY_ITEM_TYPE(SOLID_COLOR_REGION, TYPE_RENDERS_NO_IMAGES) +DECLARE_DISPLAY_ITEM_TYPE(SUBDOCUMENT, + TYPE_RENDERS_NO_IMAGES | TYPE_IS_CONTAINER) +DECLARE_DISPLAY_ITEM_TYPE(STICKY_POSITION, + TYPE_RENDERS_NO_IMAGES | TYPE_IS_CONTAINER) +DECLARE_DISPLAY_ITEM_TYPE(SVG_GEOMETRY, TYPE_IS_CONTENTFUL) +DECLARE_DISPLAY_ITEM_TYPE(SVG_IMAGE, TYPE_IS_CONTENTFUL) +DECLARE_DISPLAY_ITEM_TYPE(SVG_TEXT, TYPE_IS_CONTENTFUL) +DECLARE_DISPLAY_ITEM_TYPE(SVG_WRAPPER, TYPE_IS_CONTAINER) +DECLARE_DISPLAY_ITEM_TYPE(TABLE_BACKGROUND_COLOR, TYPE_RENDERS_NO_IMAGES) +DECLARE_DISPLAY_ITEM_TYPE(TABLE_BACKGROUND_IMAGE, TYPE_IS_CONTENTFUL) +DECLARE_DISPLAY_ITEM_TYPE(TABLE_BLEND_CONTAINER, + TYPE_RENDERS_NO_IMAGES | TYPE_IS_CONTAINER) +DECLARE_DISPLAY_ITEM_TYPE(TABLE_BLEND_MODE, + TYPE_RENDERS_NO_IMAGES | TYPE_IS_CONTAINER) +DECLARE_DISPLAY_ITEM_TYPE(TABLE_BORDER_COLLAPSE, 0) +DECLARE_DISPLAY_ITEM_TYPE(TABLE_CELL_BACKGROUND, 0) +DECLARE_DISPLAY_ITEM_TYPE(TABLE_CELL_SELECTION, TYPE_RENDERS_NO_IMAGES) +DECLARE_DISPLAY_ITEM_TYPE(TABLE_THEMED_BACKGROUND_IMAGE, TYPE_IS_CONTENTFUL) +DECLARE_DISPLAY_ITEM_TYPE(TABLE_FIXED_POSITION, + TYPE_RENDERS_NO_IMAGES | TYPE_IS_CONTAINER) +DECLARE_DISPLAY_ITEM_TYPE(TEXT, TYPE_RENDERS_NO_IMAGES | TYPE_IS_CONTENTFUL) +DECLARE_DISPLAY_ITEM_TYPE(TEXT_OVERFLOW, + TYPE_RENDERS_NO_IMAGES | TYPE_IS_CONTENTFUL) +DECLARE_DISPLAY_ITEM_TYPE(THEMED_BACKGROUND, TYPE_RENDERS_NO_IMAGES) +DECLARE_DISPLAY_ITEM_TYPE(TRANSFORM, TYPE_RENDERS_NO_IMAGES | TYPE_IS_CONTAINER) +DECLARE_DISPLAY_ITEM_TYPE(VIDEO, TYPE_RENDERS_NO_IMAGES | TYPE_IS_CONTENTFUL) +DECLARE_DISPLAY_ITEM_TYPE(WRAP_LIST, TYPE_RENDERS_NO_IMAGES | TYPE_IS_CONTAINER) +DECLARE_DISPLAY_ITEM_TYPE(ZOOM, TYPE_RENDERS_NO_IMAGES | TYPE_IS_CONTAINER) + +#if defined(MOZ_REFLOW_PERF_DSP) && defined(MOZ_REFLOW_PERF) +DECLARE_DISPLAY_ITEM_TYPE(REFLOW_COUNT, TYPE_RENDERS_NO_IMAGES) +#endif + +DECLARE_DISPLAY_ITEM_TYPE(XUL_GROUP_BACKGROUND, 0) +DECLARE_DISPLAY_ITEM_TYPE(XUL_TEXT_BOX, TYPE_RENDERS_NO_IMAGES) +DECLARE_DISPLAY_ITEM_TYPE(XUL_TREE_BODY, 0) +DECLARE_DISPLAY_ITEM_TYPE(XUL_TREE_COL_SPLITTER_TARGET, TYPE_RENDERS_NO_IMAGES) + +DECLARE_DISPLAY_ITEM_TYPE(MATHML_BAR, TYPE_RENDERS_NO_IMAGES) +DECLARE_DISPLAY_ITEM_TYPE(MATHML_CHAR_FOREGROUND, TYPE_RENDERS_NO_IMAGES) +DECLARE_DISPLAY_ITEM_TYPE(MATHML_ERROR, TYPE_RENDERS_NO_IMAGES) +DECLARE_DISPLAY_ITEM_TYPE(MATHML_MENCLOSE_NOTATION, TYPE_RENDERS_NO_IMAGES) +DECLARE_DISPLAY_ITEM_TYPE(MATHML_SELECTION_RECT, TYPE_RENDERS_NO_IMAGES) +DECLARE_DISPLAY_ITEM_TYPE(MATHML_SLASH, TYPE_RENDERS_NO_IMAGES) +#ifdef DEBUG +DECLARE_DISPLAY_ITEM_TYPE(MATHML_BOUNDING_METRICS, TYPE_RENDERS_NO_IMAGES) +DECLARE_DISPLAY_ITEM_TYPE(MATHML_CHAR_DEBUG, TYPE_RENDERS_NO_IMAGES) +#endif diff --git a/layout/painting/nsDisplayList.cpp b/layout/painting/nsDisplayList.cpp new file mode 100644 index 0000000000..da806037d1 --- /dev/null +++ b/layout/painting/nsDisplayList.cpp @@ -0,0 +1,8708 @@ +/* -*- 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/. + */ + +/* + * structures that represent things to be painted (ordered in z-order), + * used during painting and hit testing + */ + +#include "nsDisplayList.h" + +#include <stdint.h> +#include <algorithm> +#include <limits> + +#include "gfxContext.h" +#include "gfxUtils.h" +#include "mozilla/DisplayPortUtils.h" +#include "mozilla/Likely.h" +#include "mozilla/dom/BrowserChild.h" +#include "mozilla/dom/HTMLCanvasElement.h" +#include "mozilla/dom/RemoteBrowser.h" +#include "mozilla/dom/Selection.h" +#include "mozilla/dom/ServiceWorkerRegistrar.h" +#include "mozilla/dom/ServiceWorkerRegistration.h" +#include "mozilla/dom/SVGElement.h" +#include "mozilla/dom/TouchEvent.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/PresShell.h" +#include "mozilla/ShapeUtils.h" +#include "mozilla/StaticPrefs_apz.h" +#include "mozilla/StaticPrefs_gfx.h" +#include "mozilla/StaticPrefs_layers.h" +#include "mozilla/StaticPrefs_layout.h" +#include "mozilla/StaticPrefs_print.h" +#include "mozilla/SVGIntegrationUtils.h" +#include "mozilla/SVGUtils.h" +#include "mozilla/ViewportUtils.h" +#include "nsCSSRendering.h" +#include "nsCSSRenderingGradients.h" +#include "nsRefreshDriver.h" +#include "nsRegion.h" +#include "nsStyleStructInlines.h" +#include "nsStyleTransformMatrix.h" +#include "nsTransitionManager.h" +#include "gfxMatrix.h" +#include "nsLayoutUtils.h" +#include "nsIScrollableFrame.h" +#include "nsIFrameInlines.h" +#include "nsStyleConsts.h" +#include "BorderConsts.h" +#include "mozilla/MathAlgorithms.h" + +#include "imgIContainer.h" +#include "nsImageFrame.h" +#include "nsSubDocumentFrame.h" +#include "nsViewManager.h" +#include "ImageContainer.h" +#include "nsCanvasFrame.h" +#include "nsSubDocumentFrame.h" +#include "StickyScrollContainer.h" +#include "mozilla/AnimationPerformanceWarning.h" +#include "mozilla/AnimationUtils.h" +#include "mozilla/AutoRestore.h" +#include "mozilla/EffectCompositor.h" +#include "mozilla/EffectSet.h" +#include "mozilla/HashTable.h" +#include "mozilla/LookAndFeel.h" +#include "mozilla/OperatorNewExtensions.h" +#include "mozilla/PendingAnimationTracker.h" +#include "mozilla/Preferences.h" +#include "mozilla/ProfilerLabels.h" +#include "mozilla/ProfilerMarkers.h" +#include "mozilla/StyleAnimationValue.h" +#include "mozilla/ServoBindings.h" +#include "mozilla/SVGClipPathFrame.h" +#include "mozilla/SVGMaskFrame.h" +#include "mozilla/SVGObserverUtils.h" +#include "mozilla/Telemetry.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/Unused.h" +#include "mozilla/ViewportFrame.h" +#include "mozilla/gfx/gfxVars.h" +#include "ActiveLayerTracker.h" +#include "nsEscape.h" +#include "nsPrintfCString.h" +#include "UnitTransforms.h" +#include "LayerAnimationInfo.h" +#include "mozilla/EventStateManager.h" +#include "nsCaret.h" +#include "nsDOMTokenList.h" +#include "nsCSSProps.h" +#include "nsTableCellFrame.h" +#include "nsTableColFrame.h" +#include "nsTextFrame.h" +#include "nsTextPaintStyle.h" +#include "nsSliderFrame.h" +#include "nsFocusManager.h" +#include "TextDrawTarget.h" +#include "mozilla/layers/AnimationHelper.h" +#include "mozilla/layers/CompositorThread.h" +#include "mozilla/layers/InputAPZContext.h" +#include "mozilla/layers/RenderRootStateManager.h" +#include "mozilla/layers/StackingContextHelper.h" +#include "mozilla/layers/TreeTraversal.h" +#include "mozilla/layers/WebRenderBridgeChild.h" +#include "mozilla/layers/WebRenderLayerManager.h" +#include "mozilla/layers/WebRenderMessages.h" +#include "mozilla/layers/WebRenderScrollData.h" + +namespace mozilla { + +using namespace dom; +using namespace gfx; +using namespace layout; +using namespace layers; +using namespace image; + +LazyLogModule sContentDisplayListLog("dl.content"); +LazyLogModule sParentDisplayListLog("dl.parent"); + +LazyLogModule& GetLoggerByProcess() { + return XRE_IsContentProcess() ? sContentDisplayListLog + : sParentDisplayListLog; +} + +#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED +void AssertUniqueItem(nsDisplayItem* aItem) { + for (nsDisplayItem* i : aItem->Frame()->DisplayItems()) { + if (i != aItem && !i->HasDeletedFrame() && i->Frame() == aItem->Frame() && + i->GetPerFrameKey() == aItem->GetPerFrameKey()) { + if (i->IsPreProcessedItem() || i->IsPreProcessed()) { + continue; + } + MOZ_DIAGNOSTIC_ASSERT(false, "Duplicate display item!"); + } + } +} +#endif + +bool ShouldBuildItemForEvents(const DisplayItemType aType) { + return aType == DisplayItemType::TYPE_COMPOSITOR_HITTEST_INFO || + (GetDisplayItemFlagsForType(aType) & TYPE_IS_CONTAINER); +} + +static bool ItemTypeSupportsHitTesting(const DisplayItemType aType) { + switch (aType) { + case DisplayItemType::TYPE_BACKGROUND: + case DisplayItemType::TYPE_BACKGROUND_COLOR: + case DisplayItemType::TYPE_THEMED_BACKGROUND: + return true; + default: + return false; + } +} + +void InitializeHitTestInfo(nsDisplayListBuilder* aBuilder, + nsPaintedDisplayItem* aItem, + const DisplayItemType aType) { + if (ItemTypeSupportsHitTesting(aType)) { + aItem->InitializeHitTestInfo(aBuilder); + } +} + +/* static */ +already_AddRefed<ActiveScrolledRoot> ActiveScrolledRoot::CreateASRForFrame( + const ActiveScrolledRoot* aParent, nsIScrollableFrame* aScrollableFrame, + bool aIsRetained) { + nsIFrame* f = do_QueryFrame(aScrollableFrame); + + RefPtr<ActiveScrolledRoot> asr; + if (aIsRetained) { + asr = f->GetProperty(ActiveScrolledRootCache()); + } + + if (!asr) { + asr = new ActiveScrolledRoot(); + + if (aIsRetained) { + RefPtr<ActiveScrolledRoot> ref = asr; + f->SetProperty(ActiveScrolledRootCache(), ref.forget().take()); + } + } + asr->mParent = aParent; + asr->mScrollableFrame = aScrollableFrame; + asr->mViewId = Nothing(); + asr->mDepth = aParent ? aParent->mDepth + 1 : 1; + asr->mRetained = aIsRetained; + + return asr.forget(); +} + +/* static */ +bool ActiveScrolledRoot::IsAncestor(const ActiveScrolledRoot* aAncestor, + const ActiveScrolledRoot* aDescendant) { + if (!aAncestor) { + // nullptr is the root + return true; + } + if (Depth(aAncestor) > Depth(aDescendant)) { + return false; + } + const ActiveScrolledRoot* asr = aDescendant; + while (asr) { + if (asr == aAncestor) { + return true; + } + asr = asr->mParent; + } + return false; +} + +/* static */ +bool ActiveScrolledRoot::IsProperAncestor( + const ActiveScrolledRoot* aAncestor, + const ActiveScrolledRoot* aDescendant) { + return aAncestor != aDescendant && IsAncestor(aAncestor, aDescendant); +} + +/* static */ +nsCString ActiveScrolledRoot::ToString( + const ActiveScrolledRoot* aActiveScrolledRoot) { + nsAutoCString str; + for (const auto* asr = aActiveScrolledRoot; asr; asr = asr->mParent) { + str.AppendPrintf("<0x%p>", asr->mScrollableFrame); + if (asr->mParent) { + str.AppendLiteral(", "); + } + } + return std::move(str); +} + +ScrollableLayerGuid::ViewID ActiveScrolledRoot::ComputeViewId() const { + nsIContent* content = mScrollableFrame->GetScrolledFrame()->GetContent(); + return nsLayoutUtils::FindOrCreateIDFor(content); +} + +ActiveScrolledRoot::~ActiveScrolledRoot() { + if (mScrollableFrame && mRetained) { + nsIFrame* f = do_QueryFrame(mScrollableFrame); + f->RemoveProperty(ActiveScrolledRootCache()); + } +} + +static uint64_t AddAnimationsForWebRender( + nsDisplayItem* aItem, RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder, + const Maybe<LayoutDevicePoint>& aPosition = Nothing()) { + auto* effects = EffectSet::GetForFrame(aItem->Frame(), aItem->GetType()); + if (!effects || effects->IsEmpty()) { + // If there is no animation on the nsIFrame, that means + // 1) we've never created any animations on this frame or + // 2) the frame was reconstruced or + // 3) all animations on the frame have finished + // in such cases we don't need do anything here. + // + // Even if there is a WebRenderAnimationData for the display item type on + // this frame, it's going to be discarded since it's not marked as being + // used. + return 0; + } + + RefPtr<WebRenderAnimationData> animationData = + aManager->CommandBuilder() + .CreateOrRecycleWebRenderUserData<WebRenderAnimationData>(aItem); + AnimationInfo& animationInfo = animationData->GetAnimationInfo(); + animationInfo.AddAnimationsForDisplayItem( + aItem->Frame(), aDisplayListBuilder, aItem, aItem->GetType(), + aManager->LayerManager(), aPosition); + animationInfo.StartPendingAnimations( + aManager->LayerManager()->GetAnimationReadyTime()); + + // Note that animationsId can be 0 (uninitialized in AnimationInfo) if there + // are no active animations. + uint64_t animationsId = animationInfo.GetCompositorAnimationsId(); + if (!animationInfo.GetAnimations().IsEmpty()) { + OpAddCompositorAnimations anim( + CompositorAnimations(animationInfo.GetAnimations(), animationsId)); + aManager->WrBridge()->AddWebRenderParentCommand(anim); + aManager->AddActiveCompositorAnimationId(animationsId); + } else if (animationsId) { + aManager->AddCompositorAnimationsIdForDiscard(animationsId); + animationsId = 0; + } + + return animationsId; +} + +static bool GenerateAndPushTextMask(nsIFrame* aFrame, gfxContext* aContext, + const nsRect& aFillRect, + nsDisplayListBuilder* aBuilder) { + if (aBuilder->IsForGenerateGlyphMask()) { + return false; + } + + SVGObserverUtils::GetAndObserveBackgroundClip(aFrame); + + // The main function of enabling background-clip:text property value. + // When a nsDisplayBackgroundImage detects "text" bg-clip style, it will call + // this function to + // 1. Generate a mask by all descendant text frames + // 2. Push the generated mask into aContext. + + gfxContext* sourceCtx = aContext; + LayoutDeviceRect bounds = LayoutDeviceRect::FromAppUnits( + aFillRect, aFrame->PresContext()->AppUnitsPerDevPixel()); + + // Create a mask surface. + RefPtr<DrawTarget> sourceTarget = sourceCtx->GetDrawTarget(); + RefPtr<DrawTarget> maskDT = sourceTarget->CreateClippedDrawTarget( + bounds.ToUnknownRect(), SurfaceFormat::A8); + if (!maskDT || !maskDT->IsValid()) { + return false; + } + gfxContext maskCtx(maskDT, /* aPreserveTransform */ true); + maskCtx.Multiply(Matrix::Translation(bounds.TopLeft().ToUnknownPoint())); + + // Shade text shape into mask A8 surface. + nsLayoutUtils::PaintFrame( + &maskCtx, aFrame, nsRect(nsPoint(0, 0), aFrame->GetSize()), + NS_RGB(255, 255, 255), nsDisplayListBuilderMode::GenerateGlyph); + + // Push the generated mask into aContext, so that the caller can pop and + // blend with it. + + Matrix currentMatrix = sourceCtx->CurrentMatrix(); + Matrix invCurrentMatrix = currentMatrix; + invCurrentMatrix.Invert(); + + RefPtr<SourceSurface> maskSurface = maskDT->Snapshot(); + sourceCtx->PushGroupForBlendBack(gfxContentType::COLOR_ALPHA, 1.0, + maskSurface, invCurrentMatrix); + + return true; +} + +nsDisplayWrapper* nsDisplayWrapList::CreateShallowCopy( + nsDisplayListBuilder* aBuilder) { + const nsDisplayWrapList* wrappedItem = AsDisplayWrapList(); + MOZ_ASSERT(wrappedItem); + + // Create a new nsDisplayWrapList using a copy-constructor. This is done + // to preserve the information about bounds. + nsDisplayWrapper* wrapper = + new (aBuilder) nsDisplayWrapper(aBuilder, *wrappedItem); + wrapper->SetType(nsDisplayWrapper::ItemType()); + MOZ_ASSERT(wrapper); + + // Set the display list pointer of the new wrapper item to the display list + // of the wrapped item. + wrapper->mListPtr = wrappedItem->mListPtr; + return wrapper; +} + +nsDisplayWrapList* nsDisplayListBuilder::MergeItems( + nsTArray<nsDisplayItem*>& aItems) { + // For merging, we create a temporary item by cloning the last item of the + // mergeable items list. This ensures that the temporary item will have the + // correct frame and bounds. + nsDisplayWrapList* last = aItems.PopLastElement()->AsDisplayWrapList(); + MOZ_ASSERT(last); + nsDisplayWrapList* merged = last->Clone(this); + MOZ_ASSERT(merged); + AddTemporaryItem(merged); + + // Create nsDisplayWrappers that point to the internal display lists of the + // items we are merging. These nsDisplayWrappers are added to the display list + // of the temporary item. + for (nsDisplayItem* item : aItems) { + MOZ_ASSERT(item); + MOZ_ASSERT(merged->CanMerge(item)); + merged->Merge(item); + MOZ_ASSERT(item->AsDisplayWrapList()); + merged->GetChildren()->AppendToTop( + static_cast<nsDisplayWrapList*>(item)->CreateShallowCopy(this)); + } + + merged->GetChildren()->AppendToTop(last->CreateShallowCopy(this)); + + return merged; +} + +void nsDisplayListBuilder::AutoCurrentActiveScrolledRootSetter:: + SetCurrentActiveScrolledRoot( + const ActiveScrolledRoot* aActiveScrolledRoot) { + MOZ_ASSERT(!mUsed); + + // Set the builder's mCurrentActiveScrolledRoot. + mBuilder->mCurrentActiveScrolledRoot = aActiveScrolledRoot; + + // We also need to adjust the builder's mCurrentContainerASR. + // mCurrentContainerASR needs to be an ASR that all the container's + // contents have finite bounds with respect to. If aActiveScrolledRoot + // is an ancestor ASR of mCurrentContainerASR, that means we need to + // set mCurrentContainerASR to aActiveScrolledRoot, because otherwise + // the items that will be created with aActiveScrolledRoot wouldn't + // have finite bounds with respect to mCurrentContainerASR. There's one + // exception, in the case where there's a content clip on the builder + // that is scrolled by a descendant ASR of aActiveScrolledRoot. This + // content clip will clip all items that are created while this + // AutoCurrentActiveScrolledRootSetter exists. This means that the items + // created during our lifetime will have finite bounds with respect to + // the content clip's ASR, even if the items' actual ASR is an ancestor + // of that. And it also means that mCurrentContainerASR only needs to be + // set to the content clip's ASR and not all the way to aActiveScrolledRoot. + // This case is tested by fixed-pos-scrolled-clip-opacity-layerize.html + // and fixed-pos-scrolled-clip-opacity-inside-layerize.html. + + // finiteBoundsASR is the leafmost ASR that all items created during + // object's lifetime have finite bounds with respect to. + const ActiveScrolledRoot* finiteBoundsASR = + ActiveScrolledRoot::PickDescendant(mContentClipASR, aActiveScrolledRoot); + + // mCurrentContainerASR is adjusted so that it's still an ancestor of + // finiteBoundsASR. + mBuilder->mCurrentContainerASR = ActiveScrolledRoot::PickAncestor( + mBuilder->mCurrentContainerASR, finiteBoundsASR); + + // If we are entering out-of-flow content inside a CSS filter, mark + // scroll frames wrt. which the content is fixed as containing such content. + if (mBuilder->mFilterASR && ActiveScrolledRoot::IsAncestor( + aActiveScrolledRoot, mBuilder->mFilterASR)) { + for (const ActiveScrolledRoot* asr = mBuilder->mFilterASR; + asr && asr != aActiveScrolledRoot; asr = asr->mParent) { + asr->mScrollableFrame->SetHasOutOfFlowContentInsideFilter(); + } + } + + mUsed = true; +} + +void nsDisplayListBuilder::AutoCurrentActiveScrolledRootSetter:: + InsertScrollFrame(nsIScrollableFrame* aScrollableFrame) { + MOZ_ASSERT(!mUsed); + size_t descendantsEndIndex = mBuilder->mActiveScrolledRoots.Length(); + const ActiveScrolledRoot* parentASR = mBuilder->mCurrentActiveScrolledRoot; + const ActiveScrolledRoot* asr = + mBuilder->AllocateActiveScrolledRoot(parentASR, aScrollableFrame); + mBuilder->mCurrentActiveScrolledRoot = asr; + + // All child ASRs of parentASR that were created while this + // AutoCurrentActiveScrolledRootSetter object was on the stack belong to us + // now. Reparent them to asr. + for (size_t i = mDescendantsStartIndex; i < descendantsEndIndex; i++) { + ActiveScrolledRoot* descendantASR = mBuilder->mActiveScrolledRoots[i]; + if (ActiveScrolledRoot::IsAncestor(parentASR, descendantASR)) { + descendantASR->IncrementDepth(); + if (descendantASR->mParent == parentASR) { + descendantASR->mParent = asr; + } + } + } + + mUsed = true; +} + +nsDisplayListBuilder::AutoContainerASRTracker::AutoContainerASRTracker( + nsDisplayListBuilder* aBuilder) + : mBuilder(aBuilder), mSavedContainerASR(aBuilder->mCurrentContainerASR) { + mBuilder->mCurrentContainerASR = mBuilder->mCurrentActiveScrolledRoot; +} + +nsPresContext* nsDisplayListBuilder::CurrentPresContext() { + return CurrentPresShellState()->mPresShell->GetPresContext(); +} + +/* static */ +nsRect nsDisplayListBuilder::OutOfFlowDisplayData::ComputeVisibleRectForFrame( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + const nsRect& aVisibleRect, const nsRect& aDirtyRect, + nsRect* aOutDirtyRect) { + nsRect visible = aVisibleRect; + nsRect dirtyRectRelativeToDirtyFrame = aDirtyRect; + + bool inPartialUpdate = + aBuilder->IsRetainingDisplayList() && aBuilder->IsPartialUpdate(); + if (StaticPrefs::apz_allow_zooming() && + DisplayPortUtils::IsFixedPosFrameInDisplayPort(aFrame) && + aBuilder->IsPaintingToWindow() && !inPartialUpdate) { + dirtyRectRelativeToDirtyFrame = + nsRect(nsPoint(0, 0), aFrame->GetParent()->GetSize()); + + // If there's a visual viewport size set, restrict the amount of the + // fixed-position element we paint to the visual viewport. (In general + // the fixed-position element can be as large as the layout viewport, + // which at a high zoom level can cause us to paint too large of an + // area.) + PresShell* presShell = aFrame->PresShell(); + if (presShell->IsVisualViewportSizeSet()) { + dirtyRectRelativeToDirtyFrame = + nsRect(presShell->GetVisualViewportOffsetRelativeToLayoutViewport(), + presShell->GetVisualViewportSize()); + // But if we have a displayport, expand it to the displayport, so + // that async-scrolling the visual viewport within the layout viewport + // will not checkerboard. + if (nsIFrame* rootScrollFrame = presShell->GetRootScrollFrame()) { + nsRect displayport; + // Note that the displayport here is already in the right coordinate + // space: it's relative to the scroll port (= layout viewport), but + // covers the visual viewport with some margins around it, which is + // exactly what we want. + if (DisplayPortUtils::GetDisplayPort( + rootScrollFrame->GetContent(), &displayport, + DisplayPortOptions().With(ContentGeometryType::Fixed))) { + dirtyRectRelativeToDirtyFrame = displayport; + } + } + } + visible = dirtyRectRelativeToDirtyFrame; + if (StaticPrefs::apz_test_logging_enabled() && + presShell->GetDocument()->IsContentDocument()) { + nsLayoutUtils::LogAdditionalTestData( + aBuilder, "fixedPosDisplayport", + ToString(CSSSize::FromAppUnits(visible))); + } + } + + *aOutDirtyRect = dirtyRectRelativeToDirtyFrame - aFrame->GetPosition(); + visible -= aFrame->GetPosition(); + + nsRect overflowRect = aFrame->InkOverflowRect(); + + if (aFrame->IsTransformed() && EffectCompositor::HasAnimationsForCompositor( + aFrame, DisplayItemType::TYPE_TRANSFORM)) { + /** + * Add a fuzz factor to the overflow rectangle so that elements only + * just out of view are pulled into the display list, so they can be + * prerendered if necessary. + */ + overflowRect.Inflate(nsPresContext::CSSPixelsToAppUnits(32)); + } + + visible.IntersectRect(visible, overflowRect); + aOutDirtyRect->IntersectRect(*aOutDirtyRect, overflowRect); + + return visible; +} + +nsDisplayListBuilder::Linkifier::Linkifier(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame, + nsDisplayList* aList) + : mList(aList) { + // Find the element that we need to check for link-ness, bailing out if + // we can't find one. + Element* elem = Element::FromNodeOrNull(aFrame->GetContent()); + if (!elem) { + return; + } + + // If the element has an id and/or name attribute, generate a destination + // for possible internal linking. + auto maybeGenerateDest = [&](const nsAtom* aAttr) { + nsAutoString attrValue; + elem->GetAttr(aAttr, attrValue); + if (!attrValue.IsEmpty()) { + NS_ConvertUTF16toUTF8 dest(attrValue); + // Ensure that we only emit a given destination once, although there may + // be multiple frames associated with a given element; we'll simply use + // the first of them as the target of any links to it. + // XXX(jfkthame) This prevents emitting duplicate destinations *on the + // same page*, but does not prevent duplicates on subsequent pages, as + // each new page is handled by a new temporary DisplayListBuilder. This + // seems to be harmless in practice, though a bit wasteful of space. To + // fix, we need to maintain the set of already-seen destinations globally + // for the print job, rather than attached to the (per-page) builder. + if (aBuilder->mDestinations.EnsureInserted(dest)) { + auto* destination = MakeDisplayItem<nsDisplayDestination>( + aBuilder, aFrame, dest.get(), aFrame->GetRect().TopLeft()); + mList->AppendToTop(destination); + } + } + }; + + if (StaticPrefs::print_save_as_pdf_internal_destinations_enabled()) { + if (elem->HasID()) { + maybeGenerateDest(nsGkAtoms::id); + } + if (elem->HasName()) { + maybeGenerateDest(nsGkAtoms::name); + } + } + + // Links don't nest, so if the builder already has a destination, no need to + // check for a link element here. + if (!aBuilder->mLinkSpec.IsEmpty()) { + return; + } + + // Check if we have actually found a link. + if (!elem->IsLink()) { + return; + } + + nsCOMPtr<nsIURI> uri = elem->GetHrefURI(); + if (!uri) { + return; + } + + // Is it a local (in-page) destination? + bool hasRef, eqExRef; + nsIURI* docURI; + if (StaticPrefs::print_save_as_pdf_internal_destinations_enabled() && + NS_SUCCEEDED(uri->GetHasRef(&hasRef)) && hasRef && + (docURI = aFrame->PresContext()->Document()->GetDocumentURI()) && + NS_SUCCEEDED(uri->EqualsExceptRef(docURI, &eqExRef)) && eqExRef) { + if (NS_FAILED(uri->GetRef(aBuilder->mLinkSpec)) || + aBuilder->mLinkSpec.IsEmpty()) { + return; + } + // The destination name is simply a string; we don't want URL-escaping + // applied to it. + NS_UnescapeURL(aBuilder->mLinkSpec); + // Mark the link spec as being an internal destination + aBuilder->mLinkSpec.Insert('#', 0); + } else { + if (NS_FAILED(uri->GetSpec(aBuilder->mLinkSpec)) || + aBuilder->mLinkSpec.IsEmpty()) { + return; + } + } + + // Record that we need to reset the builder's state on destruction. + mBuilderToReset = aBuilder; +} + +void nsDisplayListBuilder::Linkifier::MaybeAppendLink( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame) { + // Note that we may generate a link here even if the constructor bailed out + // without updating aBuilder->LinkSpec(), because it may have been set by + // an ancestor that was associated with a link element. + if (!aBuilder->mLinkSpec.IsEmpty()) { + auto* link = MakeDisplayItem<nsDisplayLink>( + aBuilder, aFrame, aBuilder->mLinkSpec.get(), aFrame->GetRect()); + mList->AppendToTop(link); + } +} + +uint32_t nsDisplayListBuilder::sPaintSequenceNumber(1); + +nsDisplayListBuilder::nsDisplayListBuilder(nsIFrame* aReferenceFrame, + nsDisplayListBuilderMode aMode, + bool aBuildCaret, + bool aRetainingDisplayList) + : mReferenceFrame(aReferenceFrame), + mIgnoreScrollFrame(nullptr), + mCurrentActiveScrolledRoot(nullptr), + mCurrentContainerASR(nullptr), + mCurrentFrame(aReferenceFrame), + mCurrentReferenceFrame(aReferenceFrame), + mGlassDisplayItem(nullptr), + mCaretFrame(nullptr), + mScrollInfoItemsForHoisting(nullptr), + mFirstClipChainToDestroy(nullptr), + mTableBackgroundSet(nullptr), + mCurrentScrollParentId(ScrollableLayerGuid::NULL_SCROLL_ID), + mCurrentScrollbarTarget(ScrollableLayerGuid::NULL_SCROLL_ID), + mFilterASR(nullptr), + mDirtyRect(-1, -1, -1, -1), + mBuildingExtraPagesForPageNum(0), + mHasGlassItemDuringPartial(false), + mMode(aMode), + mContainsBlendMode(false), + mIsBuildingScrollbar(false), + mCurrentScrollbarWillHaveLayer(false), + mBuildCaret(aBuildCaret), + mRetainingDisplayList(aRetainingDisplayList), + mPartialUpdate(false), + mIgnoreSuppression(false), + mIncludeAllOutOfFlows(false), + mDescendIntoSubdocuments(true), + mSelectedFramesOnly(false), + mAllowMergingAndFlattening(true), + mInTransform(false), + mInEventsOnly(false), + mInFilter(false), + mInPageSequence(false), + mIsInChromePresContext(false), + mSyncDecodeImages(false), + mIsPaintingToWindow(false), + mUseHighQualityScaling(false), + mIsPaintingForWebRender(false), + mIsCompositingCheap(false), + mAncestorHasApzAwareEventHandler(false), + mHaveScrollableDisplayPort(false), + mWindowDraggingAllowed(false), + mIsBuildingForPopup(nsLayoutUtils::IsPopup(aReferenceFrame)), + mForceLayerForScrollParent(false), + mContainsNonMinimalDisplayPort(false), + mAsyncPanZoomEnabled(nsLayoutUtils::AsyncPanZoomEnabled(aReferenceFrame)), + mBuildingInvisibleItems(false), + mIsBuilding(false), + mInInvalidSubtree(false), + mDisablePartialUpdates(false), + mPartialBuildFailed(false), + mIsInActiveDocShell(false), + mBuildAsyncZoomContainer(false), + mIsRelativeToLayoutViewport(false), + mUseOverlayScrollbars(false), + mAlwaysLayerizeScrollbars(false) { + MOZ_COUNT_CTOR(nsDisplayListBuilder); + + mBuildCompositorHitTestInfo = mAsyncPanZoomEnabled && IsForPainting(); + + ShouldRebuildDisplayListDueToPrefChange(); + + mUseOverlayScrollbars = + !!LookAndFeel::GetInt(LookAndFeel::IntID::UseOverlayScrollbars); + + mAlwaysLayerizeScrollbars = + StaticPrefs::layout_scrollbars_always_layerize_track(); + + static_assert( + static_cast<uint32_t>(DisplayItemType::TYPE_MAX) < (1 << TYPE_BITS), + "Check TYPE_MAX should not overflow"); + + mIsReusingStackingContextItems = + mRetainingDisplayList && StaticPrefs::layout_display_list_retain_sc(); +} + +static PresShell* GetFocusedPresShell() { + nsPIDOMWindowOuter* focusedWnd = + nsFocusManager::GetFocusManager()->GetFocusedWindow(); + if (!focusedWnd) { + return nullptr; + } + + nsCOMPtr<nsIDocShell> focusedDocShell = focusedWnd->GetDocShell(); + if (!focusedDocShell) { + return nullptr; + } + + return focusedDocShell->GetPresShell(); +} + +void nsDisplayListBuilder::BeginFrame() { + nsCSSRendering::BeginFrameTreesLocked(); + + mIsPaintingToWindow = false; + mUseHighQualityScaling = false; + mIgnoreSuppression = false; + mInTransform = false; + mInFilter = false; + mSyncDecodeImages = false; + + if (!mBuildCaret) { + return; + } + + RefPtr<PresShell> presShell = GetFocusedPresShell(); + if (presShell) { + RefPtr<nsCaret> caret = presShell->GetCaret(); + mCaretFrame = caret->GetPaintGeometry(&mCaretRect); + + // The focused pres shell may not be in the document that we're + // painting, or be in a popup. Check if the display root for + // the caret matches the display root that we're painting, and + // only use it if it matches. + if (mCaretFrame && + nsLayoutUtils::GetDisplayRootFrame(mCaretFrame) != + nsLayoutUtils::GetDisplayRootFrame(mReferenceFrame)) { + mCaretFrame = nullptr; + } + } +} + +void nsDisplayListBuilder::AddEffectUpdate(dom::RemoteBrowser* aBrowser, + const dom::EffectsInfo& aUpdate) { + dom::EffectsInfo update = aUpdate; + // For printing we create one display item for each page that an iframe + // appears on, the proper visible rect is the union of all the visible rects + // we get from each display item. + nsPresContext* pc = + mReferenceFrame ? mReferenceFrame->PresContext() : nullptr; + if (pc && (pc->Type() != nsPresContext::eContext_Galley)) { + Maybe<dom::EffectsInfo> existing = mEffectsUpdates.MaybeGet(aBrowser); + if (existing.isSome()) { + // Only the visible rect should differ, the scales should match. + MOZ_ASSERT(existing->mRasterScale == aUpdate.mRasterScale && + existing->mTransformToAncestorScale == + aUpdate.mTransformToAncestorScale); + update.mVisibleRect = update.mVisibleRect.Union(existing->mVisibleRect); + } + } + mEffectsUpdates.InsertOrUpdate(aBrowser, update); +} + +void nsDisplayListBuilder::EndFrame() { + NS_ASSERTION(!mInInvalidSubtree, + "Someone forgot to cleanup mInInvalidSubtree!"); + mCurrentContainerASR = nullptr; + mActiveScrolledRoots.Clear(); + mEffectsUpdates.Clear(); + FreeClipChains(); + FreeTemporaryItems(); + nsCSSRendering::EndFrameTreesLocked(); + mCaretFrame = nullptr; +} + +void nsDisplayListBuilder::MarkFrameForDisplay(nsIFrame* aFrame, + const nsIFrame* aStopAtFrame) { + mFramesMarkedForDisplay.AppendElement(aFrame); + for (nsIFrame* f = aFrame; f; + f = nsLayoutUtils::GetParentOrPlaceholderForCrossDoc(f)) { + if (f->HasAnyStateBits(NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO)) { + return; + } + f->AddStateBits(NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO); + if (f == aStopAtFrame) { + // we've reached a frame that we know will be painted, so we can stop. + break; + } + } +} + +void nsDisplayListBuilder::AddFrameMarkedForDisplayIfVisible(nsIFrame* aFrame) { + mFramesMarkedForDisplayIfVisible.AppendElement(aFrame); +} + +static void MarkFrameForDisplayIfVisibleInternal(nsIFrame* aFrame, + const nsIFrame* aStopAtFrame) { + for (nsIFrame* f = aFrame; f; f = nsLayoutUtils::GetDisplayListParent(f)) { + if (f->ForceDescendIntoIfVisible()) { + return; + } + f->SetForceDescendIntoIfVisible(true); + + // This condition must match the condition in + // nsLayoutUtils::GetParentOrPlaceholderFor which is used by + // nsLayoutUtils::GetDisplayListParent + if (f->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW) && !f->GetPrevInFlow()) { + nsIFrame* parent = f->GetParent(); + if (parent && !parent->ForceDescendIntoIfVisible()) { + // If the GetDisplayListParent call is going to walk to a placeholder, + // in rare cases the placeholder might be contained in a different + // continuation from the oof. So we have to make sure to mark the oofs + // parent. In the common case this doesn't make us do any extra work, + // just changes the order in which we visit the frames since walking + // through placeholders will walk through the parent, and we stop when + // we find a ForceDescendIntoIfVisible bit set. + MarkFrameForDisplayIfVisibleInternal(parent, aStopAtFrame); + } + } + + if (f == aStopAtFrame) { + // we've reached a frame that we know will be painted, so we can stop. + break; + } + } +} + +void nsDisplayListBuilder::MarkFrameForDisplayIfVisible( + nsIFrame* aFrame, const nsIFrame* aStopAtFrame) { + AddFrameMarkedForDisplayIfVisible(aFrame); + + MarkFrameForDisplayIfVisibleInternal(aFrame, aStopAtFrame); +} + +void nsDisplayListBuilder::SetGlassDisplayItem(nsDisplayItem* aItem) { + // Web pages or extensions could trigger the "Multiple glass backgrounds + // found?" warning by using -moz-appearance:win-borderless-glass etc on their + // own elements (as long as they are root frames, which is rare as each doc + // only gets one near the root). We only care about the first one, since that + // will be the background of the root window. + + if (IsPartialUpdate()) { + if (aItem->Frame()->Style()->IsRootElementStyle()) { +#ifdef DEBUG + if (mHasGlassItemDuringPartial) { + NS_WARNING("Multiple glass backgrounds found?"); + } else +#endif + if (!mHasGlassItemDuringPartial) { + mHasGlassItemDuringPartial = true; + aItem->SetIsGlassItem(); + } + } + return; + } + + if (aItem->Frame()->Style()->IsRootElementStyle()) { +#ifdef DEBUG + if (mGlassDisplayItem) { + NS_WARNING("Multiple glass backgrounds found?"); + } else +#endif + if (!mGlassDisplayItem) { + mGlassDisplayItem = aItem; + mGlassDisplayItem->SetIsGlassItem(); + } + } +} + +bool nsDisplayListBuilder::NeedToForceTransparentSurfaceForItem( + nsDisplayItem* aItem) { + return aItem == mGlassDisplayItem; +} + +void nsDisplayListBuilder::SetIsRelativeToLayoutViewport() { + mIsRelativeToLayoutViewport = true; + UpdateShouldBuildAsyncZoomContainer(); +} + +void nsDisplayListBuilder::UpdateShouldBuildAsyncZoomContainer() { + const Document* document = mReferenceFrame->PresContext()->Document(); + mBuildAsyncZoomContainer = !mIsRelativeToLayoutViewport && + !document->Fullscreen() && + nsLayoutUtils::AllowZoomingForDocument(document); +} + +// Certain prefs may cause display list items to be added or removed when they +// are toggled. In those cases, we need to fully rebuild the display list. +bool nsDisplayListBuilder::ShouldRebuildDisplayListDueToPrefChange() { + // If we transition between wrapping the RCD-RSF contents into an async + // zoom container vs. not, we need to rebuild the display list. This only + // happens when the zooming or container scrolling prefs are toggled + // (manually by the user, or during test setup). + bool didBuildAsyncZoomContainer = mBuildAsyncZoomContainer; + UpdateShouldBuildAsyncZoomContainer(); + + bool hadOverlayScrollbarsLastTime = mUseOverlayScrollbars; + mUseOverlayScrollbars = + !!LookAndFeel::GetInt(LookAndFeel::IntID::UseOverlayScrollbars); + + bool alwaysLayerizedScrollbarsLastTime = mAlwaysLayerizeScrollbars; + mAlwaysLayerizeScrollbars = + StaticPrefs::layout_scrollbars_always_layerize_track(); + + if (didBuildAsyncZoomContainer != mBuildAsyncZoomContainer) { + return true; + } + + if (hadOverlayScrollbarsLastTime != mUseOverlayScrollbars) { + return true; + } + + if (alwaysLayerizedScrollbarsLastTime != mAlwaysLayerizeScrollbars) { + return true; + } + + return false; +} + +void nsDisplayListBuilder::AddScrollFrameToNotify( + nsIScrollableFrame* aScrollFrame) { + mScrollFramesToNotify.insert(aScrollFrame); +} + +void nsDisplayListBuilder::NotifyAndClearScrollFrames() { + for (const auto& it : mScrollFramesToNotify) { + it->NotifyApzTransaction(); + } + mScrollFramesToNotify.clear(); +} + +bool nsDisplayListBuilder::MarkOutOfFlowFrameForDisplay( + nsIFrame* aDirtyFrame, nsIFrame* aFrame, const nsRect& aVisibleRect, + const nsRect& aDirtyRect) { + MOZ_ASSERT(aFrame->GetParent() == aDirtyFrame); + nsRect dirty; + nsRect visible = OutOfFlowDisplayData::ComputeVisibleRectForFrame( + this, aFrame, aVisibleRect, aDirtyRect, &dirty); + if (!aFrame->HasAnyStateBits(NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO) && + visible.IsEmpty()) { + return false; + } + + // Only MarkFrameForDisplay if we're dirty. If this is a nested out-of-flow + // frame, then it will also mark any outer frames to ensure that building + // reaches the dirty feame. + if (!dirty.IsEmpty() || aFrame->ForceDescendIntoIfVisible()) { + MarkFrameForDisplay(aFrame, aDirtyFrame); + } + + return true; +} + +static void UnmarkFrameForDisplay(nsIFrame* aFrame, + const nsIFrame* aStopAtFrame) { + for (nsIFrame* f = aFrame; f; + f = nsLayoutUtils::GetParentOrPlaceholderForCrossDoc(f)) { + if (!f->HasAnyStateBits(NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO)) { + return; + } + f->RemoveStateBits(NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO); + if (f == aStopAtFrame) { + // we've reached a frame that we know will be painted, so we can stop. + break; + } + } +} + +static void UnmarkFrameForDisplayIfVisible(nsIFrame* aFrame) { + for (nsIFrame* f = aFrame; f; f = nsLayoutUtils::GetDisplayListParent(f)) { + if (!f->ForceDescendIntoIfVisible()) { + return; + } + f->SetForceDescendIntoIfVisible(false); + + // This condition must match the condition in + // nsLayoutUtils::GetParentOrPlaceholderFor which is used by + // nsLayoutUtils::GetDisplayListParent + if (f->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW) && !f->GetPrevInFlow()) { + nsIFrame* parent = f->GetParent(); + if (parent && parent->ForceDescendIntoIfVisible()) { + // If the GetDisplayListParent call is going to walk to a placeholder, + // in rare cases the placeholder might be contained in a different + // continuation from the oof. So we have to make sure to mark the oofs + // parent. In the common case this doesn't make us do any extra work, + // just changes the order in which we visit the frames since walking + // through placeholders will walk through the parent, and we stop when + // we find a ForceDescendIntoIfVisible bit set. + UnmarkFrameForDisplayIfVisible(f); + } + } + } +} + +nsDisplayListBuilder::~nsDisplayListBuilder() { + NS_ASSERTION(mFramesMarkedForDisplay.Length() == 0, + "All frames should have been unmarked"); + NS_ASSERTION(mFramesWithOOFData.Length() == 0, + "All OOF data should have been removed"); + NS_ASSERTION(mPresShellStates.Length() == 0, + "All presshells should have been exited"); + + DisplayItemClipChain* c = mFirstClipChainToDestroy; + while (c) { + DisplayItemClipChain* next = c->mNextClipChainToDestroy; + c->DisplayItemClipChain::~DisplayItemClipChain(); + c = next; + } + + MOZ_COUNT_DTOR(nsDisplayListBuilder); +} + +uint32_t nsDisplayListBuilder::GetBackgroundPaintFlags() { + uint32_t flags = 0; + if (mSyncDecodeImages) { + flags |= nsCSSRendering::PAINTBG_SYNC_DECODE_IMAGES; + } + if (mIsPaintingToWindow) { + flags |= nsCSSRendering::PAINTBG_TO_WINDOW; + } + if (mUseHighQualityScaling) { + flags |= nsCSSRendering::PAINTBG_HIGH_QUALITY_SCALING; + } + return flags; +} + +// TODO(emilio): Maybe unify BackgroundPaintFlags and IamgeRendererFlags. +uint32_t nsDisplayListBuilder::GetImageRendererFlags() const { + uint32_t flags = 0; + if (mSyncDecodeImages) { + flags |= nsImageRenderer::FLAG_SYNC_DECODE_IMAGES; + } + if (mIsPaintingToWindow) { + flags |= nsImageRenderer::FLAG_PAINTING_TO_WINDOW; + } + if (mUseHighQualityScaling) { + flags |= nsImageRenderer::FLAG_HIGH_QUALITY_SCALING; + } + return flags; +} + +uint32_t nsDisplayListBuilder::GetImageDecodeFlags() const { + uint32_t flags = imgIContainer::FLAG_ASYNC_NOTIFY; + if (mSyncDecodeImages) { + flags |= imgIContainer::FLAG_SYNC_DECODE; + } else { + flags |= imgIContainer::FLAG_SYNC_DECODE_IF_FAST; + } + if (mIsPaintingToWindow || mUseHighQualityScaling) { + flags |= imgIContainer::FLAG_HIGH_QUALITY_SCALING; + } + return flags; +} + +void nsDisplayListBuilder::SubtractFromVisibleRegion(nsRegion* aVisibleRegion, + const nsRegion& aRegion) { + if (aRegion.IsEmpty()) { + return; + } + + nsRegion tmp; + tmp.Sub(*aVisibleRegion, aRegion); + // Don't let *aVisibleRegion get too complex, but don't let it fluff out + // to its bounds either, which can be very bad (see bug 516740). + // Do let aVisibleRegion get more complex if by doing so we reduce its + // area by at least half. + if (tmp.GetNumRects() <= 15 || tmp.Area() <= aVisibleRegion->Area() / 2) { + *aVisibleRegion = tmp; + } +} + +nsCaret* nsDisplayListBuilder::GetCaret() { + RefPtr<nsCaret> caret = CurrentPresShellState()->mPresShell->GetCaret(); + return caret; +} + +void nsDisplayListBuilder::IncrementPresShellPaintCount(PresShell* aPresShell) { + if (mIsPaintingToWindow) { + aPresShell->IncrementPaintCount(); + } +} + +void nsDisplayListBuilder::EnterPresShell(const nsIFrame* aReferenceFrame, + bool aPointerEventsNoneDoc) { + PresShellState* state = mPresShellStates.AppendElement(); + state->mPresShell = aReferenceFrame->PresShell(); + state->mFirstFrameMarkedForDisplay = mFramesMarkedForDisplay.Length(); + state->mFirstFrameWithOOFData = mFramesWithOOFData.Length(); + + nsIScrollableFrame* sf = state->mPresShell->GetRootScrollFrameAsScrollable(); + if (sf && IsInSubdocument()) { + // We are forcing a rebuild of nsDisplayCanvasBackgroundColor to make sure + // that the canvas background color will be set correctly, and that only one + // unscrollable item will be created. + // This is done to avoid, for example, a case where only scrollbar frames + // are invalidated - we would skip creating nsDisplayCanvasBackgroundColor + // and possibly end up with an extra nsDisplaySolidColor item. + // We skip this for the root document, since we don't want to use + // MarkFrameForDisplayIfVisible before ComputeRebuildRegion. We'll + // do it manually there. + nsCanvasFrame* canvasFrame = do_QueryFrame(sf->GetScrolledFrame()); + if (canvasFrame) { + MarkFrameForDisplayIfVisible(canvasFrame, aReferenceFrame); + } + } + +#ifdef DEBUG + state->mAutoLayoutPhase.emplace(aReferenceFrame->PresContext(), + nsLayoutPhase::DisplayListBuilding); +#endif + + state->mPresShell->UpdateCanvasBackground(); + + bool buildCaret = mBuildCaret; + if (mIgnoreSuppression || !state->mPresShell->IsPaintingSuppressed()) { + state->mIsBackgroundOnly = false; + } else { + state->mIsBackgroundOnly = true; + buildCaret = false; + } + + bool pointerEventsNone = aPointerEventsNoneDoc; + if (IsInSubdocument()) { + pointerEventsNone |= mPresShellStates[mPresShellStates.Length() - 2] + .mInsidePointerEventsNoneDoc; + } + state->mInsidePointerEventsNoneDoc = pointerEventsNone; + + state->mPresShellIgnoreScrollFrame = + state->mPresShell->IgnoringViewportScrolling() + ? state->mPresShell->GetRootScrollFrame() + : nullptr; + + nsPresContext* pc = aReferenceFrame->PresContext(); + mIsInChromePresContext = pc->IsChrome(); + nsIDocShell* docShell = pc->GetDocShell(); + + if (docShell) { + docShell->GetWindowDraggingAllowed(&mWindowDraggingAllowed); + } + + state->mTouchEventPrefEnabledDoc = dom::TouchEvent::PrefEnabled(docShell); + + if (!buildCaret) { + return; + } + + // Caret frames add visual area to their frame, but we don't update the + // overflow area. Use flags to make sure we build display items for that frame + // instead. + if (mCaretFrame && mCaretFrame->PresShell() == state->mPresShell) { + MarkFrameForDisplay(mCaretFrame, aReferenceFrame); + } +} + +// A non-blank paint is a paint that does not just contain the canvas +// background. +static bool DisplayListIsNonBlank(nsDisplayList* aList) { + for (nsDisplayItem* i : *aList) { + switch (i->GetType()) { + case DisplayItemType::TYPE_COMPOSITOR_HITTEST_INFO: + case DisplayItemType::TYPE_CANVAS_BACKGROUND_COLOR: + case DisplayItemType::TYPE_CANVAS_BACKGROUND_IMAGE: + continue; + case DisplayItemType::TYPE_SOLID_COLOR: + case DisplayItemType::TYPE_BACKGROUND: + case DisplayItemType::TYPE_BACKGROUND_COLOR: + if (i->Frame()->IsCanvasFrame()) { + continue; + } + return true; + default: + return true; + } + } + return false; +} + +// A contentful paint is a paint that does contains DOM content (text, +// images, non-blank canvases, SVG): "First Contentful Paint entry +// contains a DOMHighResTimeStamp reporting the time when the browser +// first rendered any text, image (including background images), +// non-white canvas or SVG. This excludes any content of iframes, but +// includes text with pending webfonts. This is the first time users +// could start consuming page content." +static bool DisplayListIsContentful(nsDisplayListBuilder* aBuilder, + nsDisplayList* aList) { + for (nsDisplayItem* i : *aList) { + DisplayItemType type = i->GetType(); + nsDisplayList* children = i->GetChildren(); + + switch (type) { + case DisplayItemType::TYPE_SUBDOCUMENT: // iframes are ignored + break; + // CANVASes check if they may have been modified (as a stand-in + // actually tracking all modifications) + default: + if (i->IsContentful()) { + bool dummy; + nsRect bound = i->GetBounds(aBuilder, &dummy); + if (!bound.IsEmpty()) { + return true; + } + } + if (children) { + if (DisplayListIsContentful(aBuilder, children)) { + return true; + } + } + break; + } + } + return false; +} + +void nsDisplayListBuilder::LeavePresShell(const nsIFrame* aReferenceFrame, + nsDisplayList* aPaintedContents) { + NS_ASSERTION( + CurrentPresShellState()->mPresShell == aReferenceFrame->PresShell(), + "Presshell mismatch"); + + if (mIsPaintingToWindow && aPaintedContents) { + nsPresContext* pc = aReferenceFrame->PresContext(); + if (!pc->HadNonBlankPaint()) { + if (!CurrentPresShellState()->mIsBackgroundOnly && + DisplayListIsNonBlank(aPaintedContents)) { + pc->NotifyNonBlankPaint(); + } + } + nsRootPresContext* rootPresContext = pc->GetRootPresContext(); + if (!pc->HadFirstContentfulPaint() && rootPresContext) { + if (!CurrentPresShellState()->mIsBackgroundOnly) { + if (pc->HasEverBuiltInvisibleText() || + DisplayListIsContentful(this, aPaintedContents)) { + pc->NotifyContentfulPaint(); + } + } + } + } + + ResetMarkedFramesForDisplayList(aReferenceFrame); + mPresShellStates.RemoveLastElement(); + + if (!mPresShellStates.IsEmpty()) { + nsPresContext* pc = CurrentPresContext(); + nsIDocShell* docShell = pc->GetDocShell(); + if (docShell) { + docShell->GetWindowDraggingAllowed(&mWindowDraggingAllowed); + } + mIsInChromePresContext = pc->IsChrome(); + } else { + for (uint32_t i = 0; i < mFramesMarkedForDisplayIfVisible.Length(); ++i) { + UnmarkFrameForDisplayIfVisible(mFramesMarkedForDisplayIfVisible[i]); + } + mFramesMarkedForDisplayIfVisible.SetLength(0); + } +} + +void nsDisplayListBuilder::FreeClipChains() { + // Iterate the clip chains from newest to oldest (forward + // iteration), so that we destroy descendants first which + // will drop the ref count on their ancestors. + DisplayItemClipChain** indirect = &mFirstClipChainToDestroy; + + while (*indirect) { + if (!(*indirect)->mRefCount) { + DisplayItemClipChain* next = (*indirect)->mNextClipChainToDestroy; + + mClipDeduplicator.erase(*indirect); + (*indirect)->DisplayItemClipChain::~DisplayItemClipChain(); + Destroy(DisplayListArenaObjectId::CLIPCHAIN, *indirect); + + *indirect = next; + } else { + indirect = &(*indirect)->mNextClipChainToDestroy; + } + } +} + +void nsDisplayListBuilder::FreeTemporaryItems() { + for (nsDisplayItem* i : mTemporaryItems) { + // Temporary display items are not added to the frames. + MOZ_ASSERT(i->Frame()); + i->RemoveFrame(i->Frame()); + i->Destroy(this); + } + + mTemporaryItems.Clear(); +} + +void nsDisplayListBuilder::ResetMarkedFramesForDisplayList( + const nsIFrame* aReferenceFrame) { + // Unmark and pop off the frames marked for display in this pres shell. + uint32_t firstFrameForShell = + CurrentPresShellState()->mFirstFrameMarkedForDisplay; + for (uint32_t i = firstFrameForShell; i < mFramesMarkedForDisplay.Length(); + ++i) { + UnmarkFrameForDisplay(mFramesMarkedForDisplay[i], aReferenceFrame); + } + mFramesMarkedForDisplay.SetLength(firstFrameForShell); + + firstFrameForShell = CurrentPresShellState()->mFirstFrameWithOOFData; + for (uint32_t i = firstFrameForShell; i < mFramesWithOOFData.Length(); ++i) { + mFramesWithOOFData[i]->RemoveProperty(OutOfFlowDisplayDataProperty()); + } + mFramesWithOOFData.SetLength(firstFrameForShell); +} + +void nsDisplayListBuilder::ClearFixedBackgroundDisplayData() { + CurrentPresShellState()->mFixedBackgroundDisplayData = Nothing(); +} + +void nsDisplayListBuilder::MarkFramesForDisplayList( + nsIFrame* aDirtyFrame, const nsFrameList& aFrames) { + nsRect visibleRect = GetVisibleRect(); + nsRect dirtyRect = GetDirtyRect(); + + // If we are entering content that is fixed to the RCD-RSF, we are + // crossing the async zoom container boundary, and need to convert from + // visual to layout coordinates. + if (ViewportFrame* viewportFrame = do_QueryFrame(aDirtyFrame)) { + if (IsForEventDelivery() && ShouldBuildAsyncZoomContainer() && + viewportFrame->PresContext()->IsRootContentDocumentCrossProcess()) { + if (viewportFrame->PresShell()->GetRootScrollFrame()) { +#ifdef DEBUG + for (nsIFrame* f : aFrames) { + MOZ_ASSERT(ViewportUtils::IsZoomedContentRoot(f)); + } +#endif + visibleRect = ViewportUtils::VisualToLayout(visibleRect, + viewportFrame->PresShell()); + dirtyRect = ViewportUtils::VisualToLayout(dirtyRect, + viewportFrame->PresShell()); + } +#ifdef DEBUG + else { + // This is an edge case that should only happen if we are in a + // document with a XUL root element so that it does not have a root + // scroll frame but it has fixed pos content and all of the frames in + // aFrames are that fixed pos content. + for (nsIFrame* f : aFrames) { + MOZ_ASSERT(!ViewportUtils::IsZoomedContentRoot(f) && + f->GetParent() == aDirtyFrame && + f->StyleDisplay()->mPosition == + StylePositionProperty::Fixed); + } + // There's no root scroll frame so there can't be any zooming or async + // panning so we don't need to adjust the visible and dirty rects. + } +#endif + } + } + + bool markedFrames = false; + for (nsIFrame* e : aFrames) { + // Skip the AccessibleCaret frame when building no caret. + if (!IsBuildingCaret()) { + nsIContent* content = e->GetContent(); + if (content && content->IsInNativeAnonymousSubtree() && + content->IsElement()) { + auto* classList = content->AsElement()->ClassList(); + if (classList->Contains(u"moz-accessiblecaret"_ns)) { + continue; + } + } + } + if (MarkOutOfFlowFrameForDisplay(aDirtyFrame, e, visibleRect, dirtyRect)) { + markedFrames = true; + } + } + + if (markedFrames) { + // mClipState.GetClipChainForContainingBlockDescendants can return pointers + // to objects on the stack, so we need to clone the chain. + const DisplayItemClipChain* clipChain = + CopyWholeChain(mClipState.GetClipChainForContainingBlockDescendants()); + const DisplayItemClipChain* combinedClipChain = + mClipState.GetCurrentCombinedClipChain(this); + const ActiveScrolledRoot* asr = mCurrentActiveScrolledRoot; + + OutOfFlowDisplayData* data = new OutOfFlowDisplayData( + clipChain, combinedClipChain, asr, this->mCurrentScrollParentId, + visibleRect, dirtyRect); + aDirtyFrame->SetProperty( + nsDisplayListBuilder::OutOfFlowDisplayDataProperty(), data); + mFramesWithOOFData.AppendElement(aDirtyFrame); + } + + if (!aDirtyFrame->GetParent()) { + // This is the viewport frame of aDirtyFrame's presshell. + // Store the current display data so that it can be used for fixed + // background images. + NS_ASSERTION( + CurrentPresShellState()->mPresShell == aDirtyFrame->PresShell(), + "Presshell mismatch"); + MOZ_ASSERT(!CurrentPresShellState()->mFixedBackgroundDisplayData, + "already traversed this presshell's root frame?"); + + const DisplayItemClipChain* clipChain = + CopyWholeChain(mClipState.GetClipChainForContainingBlockDescendants()); + const DisplayItemClipChain* combinedClipChain = + mClipState.GetCurrentCombinedClipChain(this); + const ActiveScrolledRoot* asr = mCurrentActiveScrolledRoot; + CurrentPresShellState()->mFixedBackgroundDisplayData.emplace( + clipChain, combinedClipChain, asr, this->mCurrentScrollParentId, + GetVisibleRect(), GetDirtyRect()); + } +} + +/** + * Mark all preserve-3d children with + * NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO to make sure + * nsIFrame::BuildDisplayListForChild() would visit them. Also compute + * dirty rect for preserve-3d children. + * + * @param aDirtyFrame is the frame to mark children extending context. + */ +void nsDisplayListBuilder::MarkPreserve3DFramesForDisplayList( + nsIFrame* aDirtyFrame) { + for (const auto& childList : aDirtyFrame->ChildLists()) { + for (nsIFrame* child : childList.mList) { + if (child->Combines3DTransformWithAncestors()) { + MarkFrameForDisplay(child, aDirtyFrame); + } + + if (child->IsBlockWrapper()) { + // Mark preserve-3d frames inside the block wrapper. + MarkPreserve3DFramesForDisplayList(child); + } + } + } +} + +ActiveScrolledRoot* nsDisplayListBuilder::AllocateActiveScrolledRoot( + const ActiveScrolledRoot* aParent, nsIScrollableFrame* aScrollableFrame) { + RefPtr<ActiveScrolledRoot> asr = ActiveScrolledRoot::CreateASRForFrame( + aParent, aScrollableFrame, IsRetainingDisplayList()); + mActiveScrolledRoots.AppendElement(asr); + return asr; +} + +const DisplayItemClipChain* nsDisplayListBuilder::AllocateDisplayItemClipChain( + const DisplayItemClip& aClip, const ActiveScrolledRoot* aASR, + const DisplayItemClipChain* aParent) { + MOZ_ASSERT(!(aParent && aParent->mOnStack)); + void* p = Allocate(sizeof(DisplayItemClipChain), + DisplayListArenaObjectId::CLIPCHAIN); + DisplayItemClipChain* c = new (KnownNotNull, p) + DisplayItemClipChain(aClip, aASR, aParent, mFirstClipChainToDestroy); +#ifdef DEBUG + c->mOnStack = false; +#endif + auto result = mClipDeduplicator.insert(c); + if (!result.second) { + // An equivalent clip chain item was already created, so let's return that + // instead. Destroy the one we just created. + // Note that this can cause clip chains from different coordinate systems to + // collapse into the same clip chain object, because clip chains do not keep + // track of the reference frame that they were created in. + c->DisplayItemClipChain::~DisplayItemClipChain(); + Destroy(DisplayListArenaObjectId::CLIPCHAIN, c); + return *(result.first); + } + mFirstClipChainToDestroy = c; + return c; +} + +struct ClipChainItem { + DisplayItemClip clip; + const ActiveScrolledRoot* asr; +}; + +static const DisplayItemClipChain* FindCommonAncestorClipForIntersection( + const DisplayItemClipChain* aOne, const DisplayItemClipChain* aTwo) { + for (const ActiveScrolledRoot* asr = + ActiveScrolledRoot::PickDescendant(aOne->mASR, aTwo->mASR); + asr; asr = asr->mParent) { + if (aOne == aTwo) { + return aOne; + } + if (aOne->mASR == asr) { + aOne = aOne->mParent; + } + if (aTwo->mASR == asr) { + aTwo = aTwo->mParent; + } + if (!aOne) { + return aTwo; + } + if (!aTwo) { + return aOne; + } + } + return nullptr; +} + +const DisplayItemClipChain* nsDisplayListBuilder::CreateClipChainIntersection( + const DisplayItemClipChain* aAncestor, + const DisplayItemClipChain* aLeafClip1, + const DisplayItemClipChain* aLeafClip2) { + AutoTArray<ClipChainItem, 8> intersectedClips; + + const DisplayItemClipChain* clip1 = aLeafClip1; + const DisplayItemClipChain* clip2 = aLeafClip2; + + const ActiveScrolledRoot* asr = ActiveScrolledRoot::PickDescendant( + clip1 ? clip1->mASR : nullptr, clip2 ? clip2->mASR : nullptr); + + // Build up the intersection from the leaf to the root and put it into + // intersectedClips. The loop below will convert intersectedClips into an + // actual DisplayItemClipChain. + // (We need to do this in two passes because we need the parent clip in order + // to create the DisplayItemClipChain object, but the parent clip has not + // been created at that point.) + while (!aAncestor || asr != aAncestor->mASR) { + if (clip1 && clip1->mASR == asr) { + if (clip2 && clip2->mASR == asr) { + DisplayItemClip intersection = clip1->mClip; + intersection.IntersectWith(clip2->mClip); + intersectedClips.AppendElement(ClipChainItem{intersection, asr}); + clip2 = clip2->mParent; + } else { + intersectedClips.AppendElement(ClipChainItem{clip1->mClip, asr}); + } + clip1 = clip1->mParent; + } else if (clip2 && clip2->mASR == asr) { + intersectedClips.AppendElement(ClipChainItem{clip2->mClip, asr}); + clip2 = clip2->mParent; + } + if (!asr) { + MOZ_ASSERT(!aAncestor, "We should have exited this loop earlier"); + break; + } + asr = asr->mParent; + } + + // Convert intersectedClips into a DisplayItemClipChain. + const DisplayItemClipChain* parentSC = aAncestor; + for (auto& sc : Reversed(intersectedClips)) { + parentSC = AllocateDisplayItemClipChain(sc.clip, sc.asr, parentSC); + } + return parentSC; +} + +const DisplayItemClipChain* nsDisplayListBuilder::CreateClipChainIntersection( + const DisplayItemClipChain* aLeafClip1, + const DisplayItemClipChain* aLeafClip2) { + // aLeafClip2 might be a reference to a clip on the stack. We need to make + // sure that CreateClipChainIntersection will allocate the actual intersected + // clip in the builder's arena, so for the aLeafClip1 == nullptr case, + // we supply nullptr as the common ancestor so that + // CreateClipChainIntersection clones the whole chain. + const DisplayItemClipChain* ancestorClip = + aLeafClip1 ? FindCommonAncestorClipForIntersection(aLeafClip1, aLeafClip2) + : nullptr; + + return CreateClipChainIntersection(ancestorClip, aLeafClip1, aLeafClip2); +} + +const DisplayItemClipChain* nsDisplayListBuilder::CopyWholeChain( + const DisplayItemClipChain* aClipChain) { + return CreateClipChainIntersection(nullptr, aClipChain, nullptr); +} + +const nsIFrame* nsDisplayListBuilder::FindReferenceFrameFor( + const nsIFrame* aFrame, nsPoint* aOffset) const { + auto MaybeApplyAdditionalOffset = [&]() { + if (auto offset = AdditionalOffset()) { + *aOffset += *offset; + } + }; + + if (aFrame == mCurrentFrame) { + if (aOffset) { + *aOffset = mCurrentOffsetToReferenceFrame; + } + return mCurrentReferenceFrame; + } + + for (const nsIFrame* f = aFrame; f; + f = nsLayoutUtils::GetCrossDocParentFrameInProcess(f)) { + if (f == mReferenceFrame || f->IsTransformed()) { + if (aOffset) { + *aOffset = aFrame->GetOffsetToCrossDoc(f); + MaybeApplyAdditionalOffset(); + } + return f; + } + } + + if (aOffset) { + *aOffset = aFrame->GetOffsetToCrossDoc(mReferenceFrame); + MaybeApplyAdditionalOffset(); + } + + return mReferenceFrame; +} + +// Sticky frames are active if their nearest scrollable frame is also active. +static bool IsStickyFrameActive(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame) { + MOZ_ASSERT(aFrame->StyleDisplay()->mPosition == + StylePositionProperty::Sticky); + + StickyScrollContainer* stickyScrollContainer = + StickyScrollContainer::GetStickyScrollContainerForFrame(aFrame); + return stickyScrollContainer && + stickyScrollContainer->ScrollFrame()->IsMaybeAsynchronouslyScrolled(); +} + +bool nsDisplayListBuilder::IsAnimatedGeometryRoot(nsIFrame* aFrame, + nsIFrame** aParent) { + if (aFrame == mReferenceFrame) { + return true; + } + + if (!IsPaintingToWindow()) { + if (aParent) { + *aParent = nsLayoutUtils::GetCrossDocParentFrameInProcess(aFrame); + } + return false; + } + + nsIFrame* parent = nsLayoutUtils::GetCrossDocParentFrameInProcess(aFrame); + if (!parent) { + return true; + } + *aParent = parent; + + if (aFrame->StyleDisplay()->mPosition == StylePositionProperty::Sticky && + IsStickyFrameActive(this, aFrame)) { + return true; + } + + if (aFrame->IsTransformed()) { + if (EffectCompositor::HasAnimationsForCompositor( + aFrame, DisplayItemType::TYPE_TRANSFORM)) { + return true; + } + } + + LayoutFrameType parentType = parent->Type(); + if (parentType == LayoutFrameType::Scroll || + parentType == LayoutFrameType::ListControl) { + nsIScrollableFrame* sf = do_QueryFrame(parent); + if (sf->GetScrolledFrame() == aFrame) { + MOZ_ASSERT(!aFrame->IsTransformed()); + return sf->IsMaybeAsynchronouslyScrolled(); + } + } + + return false; +} + +nsIFrame* nsDisplayListBuilder::FindAnimatedGeometryRootFrameFor( + nsIFrame* aFrame) { + MOZ_ASSERT(nsLayoutUtils::IsAncestorFrameCrossDocInProcess( + RootReferenceFrame(), aFrame)); + nsIFrame* cursor = aFrame; + while (cursor != RootReferenceFrame()) { + nsIFrame* next; + if (IsAnimatedGeometryRoot(cursor, &next)) { + return cursor; + } + cursor = next; + } + return cursor; +} + +static nsRect ApplyAllClipNonRoundedIntersection( + const DisplayItemClipChain* aClipChain, const nsRect& aRect) { + nsRect result = aRect; + while (aClipChain) { + result = aClipChain->mClip.ApplyNonRoundedIntersection(result); + aClipChain = aClipChain->mParent; + } + return result; +} + +void nsDisplayListBuilder::AdjustWindowDraggingRegion(nsIFrame* aFrame) { + if (!mWindowDraggingAllowed || !IsForPainting()) { + return; + } + + const nsStyleUIReset* styleUI = aFrame->StyleUIReset(); + if (styleUI->mWindowDragging == StyleWindowDragging::Default) { + // This frame has the default value and doesn't influence the window + // dragging region. + return; + } + + LayoutDeviceToLayoutDeviceMatrix4x4 referenceFrameToRootReferenceFrame; + + // The const_cast is for nsLayoutUtils::GetTransformToAncestor. + nsIFrame* referenceFrame = + const_cast<nsIFrame*>(FindReferenceFrameFor(aFrame)); + + if (IsInTransform()) { + // Only support 2d rectilinear transforms. Transform support is needed for + // the horizontal flip transform that's applied to the urlbar textbox in + // RTL mode - it should be able to exclude itself from the draggable region. + referenceFrameToRootReferenceFrame = + ViewAs<LayoutDeviceToLayoutDeviceMatrix4x4>( + nsLayoutUtils::GetTransformToAncestor(RelativeTo{referenceFrame}, + RelativeTo{mReferenceFrame}) + .GetMatrix()); + Matrix referenceFrameToRootReferenceFrame2d; + if (!referenceFrameToRootReferenceFrame.Is2D( + &referenceFrameToRootReferenceFrame2d) || + !referenceFrameToRootReferenceFrame2d.IsRectilinear()) { + return; + } + } else { + MOZ_ASSERT(referenceFrame == mReferenceFrame, + "referenceFrameToRootReferenceFrame needs to be adjusted"); + } + + // We do some basic visibility checking on the frame's border box here. + // We intersect it both with the current dirty rect and with the current + // clip. Either one is just a conservative approximation on its own, but + // their intersection luckily works well enough for our purposes, so that + // we don't have to do full-blown visibility computations. + // The most important case we need to handle is the scrolled-off tab: + // If the tab bar overflows, tab parts that are clipped by the scrollbox + // should not be allowed to interfere with the window dragging region. Using + // just the current DisplayItemClip is not enough to cover this case + // completely because clips are reset while building stacking context + // contents, so for example we'd fail to clip frames that have a clip path + // applied to them. But the current dirty rect doesn't get reset in that + // case, so we use it to make this case work. + nsRect borderBox = aFrame->GetRectRelativeToSelf().Intersect(mVisibleRect); + borderBox += ToReferenceFrame(aFrame); + const DisplayItemClipChain* clip = + ClipState().GetCurrentCombinedClipChain(this); + borderBox = ApplyAllClipNonRoundedIntersection(clip, borderBox); + if (borderBox.IsEmpty()) { + return; + } + + LayoutDeviceRect devPixelBorderBox = LayoutDevicePixel::FromAppUnits( + borderBox, aFrame->PresContext()->AppUnitsPerDevPixel()); + + LayoutDeviceRect transformedDevPixelBorderBox = + TransformBy(referenceFrameToRootReferenceFrame, devPixelBorderBox); + transformedDevPixelBorderBox.Round(); + LayoutDeviceIntRect transformedDevPixelBorderBoxInt; + + if (!transformedDevPixelBorderBox.ToIntRect( + &transformedDevPixelBorderBoxInt)) { + return; + } + + LayoutDeviceIntRegion& region = + styleUI->mWindowDragging == StyleWindowDragging::Drag + ? mWindowDraggingRegion + : mWindowNoDraggingRegion; + + if (!IsRetainingDisplayList()) { + region.OrWith(transformedDevPixelBorderBoxInt); + return; + } + + gfx::IntRect rect(transformedDevPixelBorderBoxInt.ToUnknownRect()); + if (styleUI->mWindowDragging == StyleWindowDragging::Drag) { + mRetainedWindowDraggingRegion.Add(aFrame, rect); + } else { + mRetainedWindowNoDraggingRegion.Add(aFrame, rect); + } +} + +LayoutDeviceIntRegion nsDisplayListBuilder::GetWindowDraggingRegion() const { + LayoutDeviceIntRegion result; + if (!IsRetainingDisplayList()) { + result.Sub(mWindowDraggingRegion, mWindowNoDraggingRegion); + return result; + } + + LayoutDeviceIntRegion dragRegion = + mRetainedWindowDraggingRegion.ToLayoutDeviceIntRegion(); + + LayoutDeviceIntRegion noDragRegion = + mRetainedWindowNoDraggingRegion.ToLayoutDeviceIntRegion(); + + result.Sub(dragRegion, noDragRegion); + return result; +} + +void nsDisplayTransform::AddSizeOfExcludingThis(nsWindowSizes& aSizes) const { + nsPaintedDisplayItem::AddSizeOfExcludingThis(aSizes); + aSizes.mLayoutRetainedDisplayListSize += + aSizes.mState.mMallocSizeOf(mTransformPreserves3D.get()); +} + +void nsDisplayListBuilder::AddSizeOfExcludingThis(nsWindowSizes& aSizes) const { + mPool.AddSizeOfExcludingThis(aSizes, Arena::ArenaKind::DisplayList); + + size_t n = 0; + MallocSizeOf mallocSizeOf = aSizes.mState.mMallocSizeOf; + n += mDocumentWillChangeBudgets.ShallowSizeOfExcludingThis(mallocSizeOf); + n += mFrameWillChangeBudgets.ShallowSizeOfExcludingThis(mallocSizeOf); + n += mEffectsUpdates.ShallowSizeOfExcludingThis(mallocSizeOf); + n += mWindowExcludeGlassRegion.SizeOfExcludingThis(mallocSizeOf); + n += mRetainedWindowDraggingRegion.SizeOfExcludingThis(mallocSizeOf); + n += mRetainedWindowNoDraggingRegion.SizeOfExcludingThis(mallocSizeOf); + n += mRetainedWindowOpaqueRegion.SizeOfExcludingThis(mallocSizeOf); + // XXX can't measure mClipDeduplicator since it uses std::unordered_set. + + aSizes.mLayoutRetainedDisplayListSize += n; +} + +void RetainedDisplayList::AddSizeOfExcludingThis(nsWindowSizes& aSizes) const { + for (nsDisplayItem* item : *this) { + item->AddSizeOfExcludingThis(aSizes); + if (RetainedDisplayList* children = item->GetChildren()) { + children->AddSizeOfExcludingThis(aSizes); + } + } + + size_t n = 0; + + n += mDAG.mDirectPredecessorList.ShallowSizeOfExcludingThis( + aSizes.mState.mMallocSizeOf); + n += mDAG.mNodesInfo.ShallowSizeOfExcludingThis(aSizes.mState.mMallocSizeOf); + n += mOldItems.ShallowSizeOfExcludingThis(aSizes.mState.mMallocSizeOf); + + aSizes.mLayoutRetainedDisplayListSize += n; +} + +size_t nsDisplayListBuilder::WeakFrameRegion::SizeOfExcludingThis( + MallocSizeOf aMallocSizeOf) const { + size_t n = 0; + n += mFrames.ShallowSizeOfExcludingThis(aMallocSizeOf); + for (const auto& frame : mFrames) { + const UniquePtr<WeakFrame>& weakFrame = frame.mWeakFrame; + n += aMallocSizeOf(weakFrame.get()); + } + n += mRects.ShallowSizeOfExcludingThis(aMallocSizeOf); + return n; +} + +/** + * Removes modified frames and rects from this WeakFrameRegion. + */ +void nsDisplayListBuilder::WeakFrameRegion::RemoveModifiedFramesAndRects() { + MOZ_ASSERT(mFrames.Length() == mRects.Length()); + + uint32_t i = 0; + uint32_t length = mFrames.Length(); + + while (i < length) { + auto& wrapper = mFrames[i]; + + if (!wrapper.mWeakFrame->IsAlive() || + AnyContentAncestorModified(wrapper.mWeakFrame->GetFrame())) { + // To avoid multiple O(n) shifts in the array, move the last element of + // the array to the current position and decrease the array length. + mFrameSet.Remove(wrapper.mFrame); + mFrames[i] = std::move(mFrames[length - 1]); + mRects[i] = std::move(mRects[length - 1]); + length--; + } else { + i++; + } + } + + mFrames.TruncateLength(length); + mRects.TruncateLength(length); +} + +void nsDisplayListBuilder::RemoveModifiedWindowRegions() { + mRetainedWindowDraggingRegion.RemoveModifiedFramesAndRects(); + mRetainedWindowNoDraggingRegion.RemoveModifiedFramesAndRects(); + mWindowExcludeGlassRegion.RemoveModifiedFramesAndRects(); + mRetainedWindowOpaqueRegion.RemoveModifiedFramesAndRects(); + + mHasGlassItemDuringPartial = false; +} + +void nsDisplayListBuilder::ClearRetainedWindowRegions() { + mRetainedWindowDraggingRegion.Clear(); + mRetainedWindowNoDraggingRegion.Clear(); + mWindowExcludeGlassRegion.Clear(); + mRetainedWindowOpaqueRegion.Clear(); + + mGlassDisplayItem = nullptr; +} + +const uint32_t gWillChangeAreaMultiplier = 3; +static uint32_t GetLayerizationCost(const nsSize& aSize) { + // There's significant overhead for each layer created from Gecko + // (IPC+Shared Objects) and from the backend (like an OpenGL texture). + // Therefore we set a minimum cost threshold of a 64x64 area. + const int minBudgetCost = 64 * 64; + + const uint32_t budgetCost = std::max( + minBudgetCost, nsPresContext::AppUnitsToIntCSSPixels(aSize.width) * + nsPresContext::AppUnitsToIntCSSPixels(aSize.height)); + + return budgetCost; +} + +bool nsDisplayListBuilder::AddToWillChangeBudget(nsIFrame* aFrame, + const nsSize& aSize) { + MOZ_ASSERT(IsForPainting()); + + if (aFrame->MayHaveWillChangeBudget()) { + // The frame is already in the will-change budget. + return true; + } + + const nsPresContext* presContext = aFrame->PresContext(); + const nsRect area = presContext->GetVisibleArea(); + const uint32_t budgetLimit = + nsPresContext::AppUnitsToIntCSSPixels(area.width) * + nsPresContext::AppUnitsToIntCSSPixels(area.height); + const uint32_t cost = GetLayerizationCost(aSize); + + DocumentWillChangeBudget& documentBudget = + mDocumentWillChangeBudgets.LookupOrInsert(presContext); + + const bool onBudget = + (documentBudget + cost) / gWillChangeAreaMultiplier < budgetLimit; + + if (onBudget) { + documentBudget += cost; + mFrameWillChangeBudgets.InsertOrUpdate( + aFrame, FrameWillChangeBudget(presContext, cost)); + aFrame->SetMayHaveWillChangeBudget(true); + } + + return onBudget; +} + +bool nsDisplayListBuilder::IsInWillChangeBudget(nsIFrame* aFrame, + const nsSize& aSize) { + if (!IsForPainting()) { + // If this nsDisplayListBuilder is not for painting, the layerization should + // not matter. Do the simple thing and return false. + return false; + } + + const bool onBudget = AddToWillChangeBudget(aFrame, aSize); + if (onBudget) { + return true; + } + + auto* pc = aFrame->PresContext(); + auto* doc = pc->Document(); + if (!doc->HasWarnedAbout(Document::eIgnoringWillChangeOverBudget)) { + AutoTArray<nsString, 2> params; + params.AppendElement()->AppendInt(gWillChangeAreaMultiplier); + + nsRect area = pc->GetVisibleArea(); + uint32_t budgetLimit = nsPresContext::AppUnitsToIntCSSPixels(area.width) * + nsPresContext::AppUnitsToIntCSSPixels(area.height); + params.AppendElement()->AppendInt(budgetLimit); + + doc->WarnOnceAbout(Document::eIgnoringWillChangeOverBudget, false, params); + } + + return false; +} + +void nsDisplayListBuilder::ClearWillChangeBudgetStatus(nsIFrame* aFrame) { + MOZ_ASSERT(IsForPainting()); + + if (!aFrame->MayHaveWillChangeBudget()) { + return; + } + + aFrame->SetMayHaveWillChangeBudget(false); + RemoveFromWillChangeBudgets(aFrame); +} + +void nsDisplayListBuilder::RemoveFromWillChangeBudgets(const nsIFrame* aFrame) { + if (auto entry = mFrameWillChangeBudgets.Lookup(aFrame)) { + const FrameWillChangeBudget& frameBudget = entry.Data(); + + auto documentBudget = + mDocumentWillChangeBudgets.Lookup(frameBudget.mPresContext); + + if (documentBudget) { + *documentBudget -= frameBudget.mUsage; + } + + entry.Remove(); + } +} + +void nsDisplayListBuilder::ClearWillChangeBudgets() { + mFrameWillChangeBudgets.Clear(); + mDocumentWillChangeBudgets.Clear(); +} + +void nsDisplayListBuilder::EnterSVGEffectsContents( + nsIFrame* aEffectsFrame, nsDisplayList* aHoistedItemsStorage) { + MOZ_ASSERT(aHoistedItemsStorage); + if (mSVGEffectsFrames.IsEmpty()) { + MOZ_ASSERT(!mScrollInfoItemsForHoisting); + mScrollInfoItemsForHoisting = aHoistedItemsStorage; + } + mSVGEffectsFrames.AppendElement(aEffectsFrame); +} + +void nsDisplayListBuilder::ExitSVGEffectsContents() { + MOZ_ASSERT(!mSVGEffectsFrames.IsEmpty()); + mSVGEffectsFrames.RemoveLastElement(); + MOZ_ASSERT(mScrollInfoItemsForHoisting); + if (mSVGEffectsFrames.IsEmpty()) { + mScrollInfoItemsForHoisting = nullptr; + } +} + +bool nsDisplayListBuilder::ShouldBuildScrollInfoItemsForHoisting() const { + /* + * Note: if changing the conditions under which scroll info layers + * are created, make a corresponding change to + * ScrollFrameWillBuildScrollInfoLayer() in nsSliderFrame.cpp. + */ + for (nsIFrame* frame : mSVGEffectsFrames) { + if (SVGIntegrationUtils::UsesSVGEffectsNotSupportedInCompositor(frame)) { + return true; + } + } + return false; +} + +void nsDisplayListBuilder::AppendNewScrollInfoItemForHoisting( + nsDisplayScrollInfoLayer* aScrollInfoItem) { + MOZ_ASSERT(ShouldBuildScrollInfoItemsForHoisting()); + MOZ_ASSERT(mScrollInfoItemsForHoisting); + mScrollInfoItemsForHoisting->AppendToTop(aScrollInfoItem); +} + +void nsDisplayListBuilder::BuildCompositorHitTestInfoIfNeeded( + nsIFrame* aFrame, nsDisplayList* aList) { + MOZ_ASSERT(aFrame); + MOZ_ASSERT(aList); + + if (!BuildCompositorHitTestInfo()) { + return; + } + + const CompositorHitTestInfo info = aFrame->GetCompositorHitTestInfo(this); + if (info != CompositorHitTestInvisibleToHit) { + aList->AppendNewToTop<nsDisplayCompositorHitTestInfo>(this, aFrame); + } +} + +void nsDisplayListBuilder::AddReusableDisplayItem(nsDisplayItem* aItem) { + mReuseableItems.Insert(aItem); +} + +void nsDisplayListBuilder::RemoveReusedDisplayItem(nsDisplayItem* aItem) { + MOZ_ASSERT(aItem->IsReusedItem()); + mReuseableItems.Remove(aItem); +} + +void nsDisplayListBuilder::ClearReuseableDisplayItems() { + const size_t total = mReuseableItems.Count(); + + size_t reused = 0; + for (auto* item : mReuseableItems) { + if (item->IsReusedItem()) { + reused++; + item->SetReusable(); + } else { + item->Destroy(this); + } + } + + DL_LOGI("RDL - Reused %zu of %zu SC display items", reused, total); + mReuseableItems.Clear(); +} + +void nsDisplayListBuilder::ReuseDisplayItem(nsDisplayItem* aItem) { + const auto* previous = mCurrentContainerASR; + const auto* asr = aItem->GetActiveScrolledRoot(); + mCurrentContainerASR = + ActiveScrolledRoot::PickAncestor(asr, mCurrentContainerASR); + + if (previous != mCurrentContainerASR) { + DL_LOGV("RDL - Changed mCurrentContainerASR from %p to %p", previous, + mCurrentContainerASR); + } + + aItem->SetReusedItem(); +} + +void nsDisplayListSet::CopyTo(const nsDisplayListSet& aDestination) const { + for (size_t i = 0; i < mLists.size(); ++i) { + auto* from = mLists[i]; + auto* to = aDestination.mLists[i]; + + from->CopyTo(to); + } +} + +void nsDisplayListSet::MoveTo(const nsDisplayListSet& aDestination) const { + aDestination.BorderBackground()->AppendToTop(BorderBackground()); + aDestination.BlockBorderBackgrounds()->AppendToTop(BlockBorderBackgrounds()); + aDestination.Floats()->AppendToTop(Floats()); + aDestination.Content()->AppendToTop(Content()); + aDestination.PositionedDescendants()->AppendToTop(PositionedDescendants()); + aDestination.Outlines()->AppendToTop(Outlines()); +} + +nsRect nsDisplayList::GetClippedBounds(nsDisplayListBuilder* aBuilder) const { + nsRect bounds; + for (nsDisplayItem* i : *this) { + bounds.UnionRect(bounds, i->GetClippedBounds(aBuilder)); + } + return bounds; +} + +nsRect nsDisplayList::GetClippedBoundsWithRespectToASR( + nsDisplayListBuilder* aBuilder, const ActiveScrolledRoot* aASR, + nsRect* aBuildingRect) const { + nsRect bounds; + for (nsDisplayItem* i : *this) { + nsRect r = i->GetClippedBounds(aBuilder); + if (aASR != i->GetActiveScrolledRoot() && !r.IsEmpty()) { + if (Maybe<nsRect> clip = i->GetClipWithRespectToASR(aBuilder, aASR)) { + r = clip.ref(); + } + } + if (aBuildingRect) { + aBuildingRect->UnionRect(*aBuildingRect, i->GetBuildingRect()); + } + bounds.UnionRect(bounds, r); + } + return bounds; +} + +nsRect nsDisplayList::GetBuildingRect() const { + nsRect result; + for (nsDisplayItem* i : *this) { + result.UnionRect(result, i->GetBuildingRect()); + } + return result; +} + +static void TriggerPendingAnimations(Document& aDoc, + const TimeStamp& aReadyTime) { + MOZ_ASSERT(!aReadyTime.IsNull(), + "Animation ready time is not set. Perhaps we're using a layer" + " manager that doesn't update it"); + if (PendingAnimationTracker* tracker = aDoc.GetPendingAnimationTracker()) { + PresShell* presShell = aDoc.GetPresShell(); + // If paint-suppression is in effect then we haven't finished painting + // this document yet so we shouldn't start animations + if (!presShell || !presShell->IsPaintingSuppressed()) { + tracker->TriggerPendingAnimationsOnNextTick(aReadyTime); + } + } + auto recurse = [&aReadyTime](Document& aDoc) { + TriggerPendingAnimations(aDoc, aReadyTime); + return CallState::Continue; + }; + aDoc.EnumerateSubDocuments(recurse); +} + +WindowRenderer* nsDisplayListBuilder::GetWidgetWindowRenderer(nsView** aView) { + if (aView) { + *aView = RootReferenceFrame()->GetView(); + } + if (RootReferenceFrame() != + nsLayoutUtils::GetDisplayRootFrame(RootReferenceFrame())) { + return nullptr; + } + nsIWidget* window = RootReferenceFrame()->GetNearestWidget(); + if (window) { + return window->GetWindowRenderer(); + } + return nullptr; +} + +WebRenderLayerManager* nsDisplayListBuilder::GetWidgetLayerManager( + nsView** aView) { + WindowRenderer* renderer = GetWidgetWindowRenderer(); + if (renderer) { + return renderer->AsWebRender(); + } + return nullptr; +} + +void nsDisplayList::Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx, + int32_t aAppUnitsPerDevPixel) { + FlattenedDisplayListIterator iter(aBuilder, this); + while (iter.HasNext()) { + nsPaintedDisplayItem* item = iter.GetNextItem()->AsPaintedDisplayItem(); + if (!item) { + continue; + } + + nsRect visible = item->GetClippedBounds(aBuilder); + visible = visible.Intersect(item->GetPaintRect(aBuilder, aCtx)); + if (visible.IsEmpty()) { + continue; + } + + DisplayItemClip currentClip = item->GetClip(); + if (currentClip.HasClip()) { + aCtx->Save(); + if (currentClip.IsRectClippedByRoundedCorner(visible)) { + currentClip.ApplyTo(aCtx, aAppUnitsPerDevPixel); + } else { + currentClip.ApplyRectTo(aCtx, aAppUnitsPerDevPixel); + } + } + aCtx->NewPath(); + + item->Paint(aBuilder, aCtx); + + if (currentClip.HasClip()) { + aCtx->Restore(); + } + } +} + +/** + * We paint by executing a layer manager transaction, constructing a + * single layer representing the display list, and then making it the + * root of the layer manager, drawing into the PaintedLayers. + */ +void nsDisplayList::PaintRoot(nsDisplayListBuilder* aBuilder, gfxContext* aCtx, + uint32_t aFlags, + Maybe<double> aDisplayListBuildTime) { + AUTO_PROFILER_LABEL("nsDisplayList::PaintRoot", GRAPHICS); + + RefPtr<WebRenderLayerManager> layerManager; + WindowRenderer* renderer = nullptr; + bool widgetTransaction = false; + bool doBeginTransaction = true; + nsView* view = nullptr; + if (aFlags & PAINT_USE_WIDGET_LAYERS) { + renderer = aBuilder->GetWidgetWindowRenderer(&view); + if (renderer) { + // The fallback renderer doesn't retain any content, so it's + // not meaningful to use it when drawing to an external context. + if (aCtx && renderer->AsFallback()) { + MOZ_ASSERT(!(aFlags & PAINT_EXISTING_TRANSACTION)); + renderer = nullptr; + } else { + layerManager = renderer->AsWebRender(); + doBeginTransaction = !(aFlags & PAINT_EXISTING_TRANSACTION); + widgetTransaction = true; + } + } + } + + nsIFrame* frame = aBuilder->RootReferenceFrame(); + nsPresContext* presContext = frame->PresContext(); + PresShell* presShell = presContext->PresShell(); + Document* document = presShell->GetDocument(); + + ScopeExit g([&]() { +#ifdef DEBUG + MOZ_ASSERT(!layerManager || !layerManager->GetTarget()); +#endif + + // For layers-free mode, we check the invalidation state bits in the + // EndTransaction. So we clear the invalidation state bits after + // EndTransaction. + if (widgetTransaction || + // SVG-as-an-image docs don't paint as part of the retained layer tree, + // but they still need the invalidation state bits cleared in order for + // invalidation for CSS/SMIL animation to work properly. + (document && document->IsBeingUsedAsImage())) { + DL_LOGD("Clearing invalidation state bits"); + frame->ClearInvalidationStateBits(); + } + }); + + if (!renderer) { + if (!aCtx) { + NS_WARNING("Nowhere to paint into"); + return; + } + bool prevIsCompositingCheap = aBuilder->SetIsCompositingCheap(false); + Paint(aBuilder, aCtx, presContext->AppUnitsPerDevPixel()); + + aBuilder->SetIsCompositingCheap(prevIsCompositingCheap); + return; + } + + if (renderer->GetBackendType() == LayersBackend::LAYERS_WR) { + MOZ_ASSERT(layerManager); + if (doBeginTransaction) { + if (aCtx) { + if (!layerManager->BeginTransactionWithTarget(aCtx, nsCString())) { + return; + } + } else { + if (!layerManager->BeginTransaction(nsCString())) { + return; + } + } + } + + bool prevIsCompositingCheap = aBuilder->SetIsCompositingCheap(true); + layerManager->SetTransactionIdAllocator(presContext->RefreshDriver()); + + bool sent = false; + if (aFlags & PAINT_IDENTICAL_DISPLAY_LIST) { + MOZ_ASSERT(!aCtx); + sent = layerManager->EndEmptyTransaction(); + } + + if (!sent) { + auto* wrManager = static_cast<WebRenderLayerManager*>(layerManager.get()); + + nsIDocShell* docShell = presContext->GetDocShell(); + WrFiltersHolder wrFilters; + gfx::Matrix5x4* colorMatrix = + nsDocShell::Cast(docShell)->GetColorMatrix(); + if (colorMatrix) { + wrFilters.filters.AppendElement( + wr::FilterOp::ColorMatrix(colorMatrix->components)); + } + + wrManager->EndTransactionWithoutLayer(this, aBuilder, + std::move(wrFilters), nullptr, + aDisplayListBuildTime.valueOr(0.0)); + } + + aBuilder->SetIsCompositingCheap(prevIsCompositingCheap); + if (document && widgetTransaction) { + TriggerPendingAnimations(*document, + layerManager->GetAnimationReadyTime()); + } + + if (presContext->RefreshDriver()->HasScheduleFlush()) { + presContext->NotifyInvalidation(layerManager->GetLastTransactionId(), + frame->GetRect()); + } + + return; + } + + FallbackRenderer* fallback = renderer->AsFallback(); + MOZ_ASSERT(fallback); + + if (doBeginTransaction) { + MOZ_ASSERT(!aCtx); + if (!fallback->BeginTransaction()) { + return; + } + } + + bool temp = aBuilder->SetIsCompositingCheap(renderer->IsCompositingCheap()); + fallback->EndTransactionWithList(aBuilder, this, + presContext->AppUnitsPerDevPixel(), + WindowRenderer::END_DEFAULT); + + aBuilder->SetIsCompositingCheap(temp); + + if (document && widgetTransaction) { + TriggerPendingAnimations(*document, renderer->GetAnimationReadyTime()); + } +} + +void nsDisplayList::DeleteAll(nsDisplayListBuilder* aBuilder) { + for (auto* item : TakeItems()) { + item->Destroy(aBuilder); + } +} + +static bool IsFrameReceivingPointerEvents(nsIFrame* aFrame) { + return aFrame->Style()->PointerEvents() != StylePointerEvents::None; +} + +// A list of frames, and their z depth. Used for sorting +// the results of hit testing. +struct FramesWithDepth { + explicit FramesWithDepth(float aDepth) : mDepth(aDepth) {} + + bool operator<(const FramesWithDepth& aOther) const { + if (!FuzzyEqual(mDepth, aOther.mDepth, 0.1f)) { + // We want to sort so that the shallowest item (highest depth value) is + // first + return mDepth > aOther.mDepth; + } + return this < &aOther; + } + bool operator==(const FramesWithDepth& aOther) const { + return this == &aOther; + } + + float mDepth; + nsTArray<nsIFrame*> mFrames; +}; + +// Sort the frames by depth and then moves all the contained frames to the +// destination +static void FlushFramesArray(nsTArray<FramesWithDepth>& aSource, + nsTArray<nsIFrame*>* aDest) { + if (aSource.IsEmpty()) { + return; + } + aSource.Sort(); + uint32_t length = aSource.Length(); + for (uint32_t i = 0; i < length; i++) { + aDest->AppendElements(std::move(aSource[i].mFrames)); + } + aSource.Clear(); +} + +void nsDisplayList::HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect, + nsDisplayItem::HitTestState* aState, + nsTArray<nsIFrame*>* aOutFrames) const { + nsDisplayItem* item; + + if (aState->mInPreserves3D) { + // Collect leaves of the current 3D rendering context. + for (nsDisplayItem* item : *this) { + auto itemType = item->GetType(); + if (itemType != DisplayItemType::TYPE_TRANSFORM || + !static_cast<nsDisplayTransform*>(item)->IsLeafOf3DContext()) { + item->HitTest(aBuilder, aRect, aState, aOutFrames); + } else { + // One of leaves in the current 3D rendering context. + aState->mItemBuffer.AppendElement(item); + } + } + return; + } + + int32_t itemBufferStart = aState->mItemBuffer.Length(); + for (nsDisplayItem* item : *this) { + aState->mItemBuffer.AppendElement(item); + } + + AutoTArray<FramesWithDepth, 16> temp; + for (int32_t i = aState->mItemBuffer.Length() - 1; i >= itemBufferStart; + --i) { + // Pop element off the end of the buffer. We want to shorten the buffer + // so that recursive calls to HitTest have more buffer space. + item = aState->mItemBuffer[i]; + aState->mItemBuffer.SetLength(i); + + bool snap; + nsRect r = item->GetBounds(aBuilder, &snap).Intersect(aRect); + auto itemType = item->GetType(); + bool same3DContext = + (itemType == DisplayItemType::TYPE_TRANSFORM && + static_cast<nsDisplayTransform*>(item)->IsParticipating3DContext()) || + (itemType == DisplayItemType::TYPE_PERSPECTIVE && + item->Frame()->Extend3DContext()); + if (same3DContext && + (itemType != DisplayItemType::TYPE_TRANSFORM || + !static_cast<nsDisplayTransform*>(item)->IsLeafOf3DContext())) { + if (!item->GetClip().MayIntersect(aRect)) { + continue; + } + AutoTArray<nsIFrame*, 1> neverUsed; + // Start gethering leaves of the 3D rendering context, and + // append leaves at the end of mItemBuffer. Leaves are + // processed at following iterations. + aState->mInPreserves3D = true; + item->HitTest(aBuilder, aRect, aState, &neverUsed); + aState->mInPreserves3D = false; + i = aState->mItemBuffer.Length(); + continue; + } + if (same3DContext || item->GetClip().MayIntersect(r)) { + AutoTArray<nsIFrame*, 16> outFrames; + item->HitTest(aBuilder, aRect, aState, &outFrames); + + // For 3d transforms with preserve-3d we add hit frames into the temp list + // so we can sort them later, otherwise we add them directly to the output + // list. + nsTArray<nsIFrame*>* writeFrames = aOutFrames; + if (item->GetType() == DisplayItemType::TYPE_TRANSFORM && + static_cast<nsDisplayTransform*>(item)->IsLeafOf3DContext()) { + if (outFrames.Length()) { + nsDisplayTransform* transform = + static_cast<nsDisplayTransform*>(item); + nsPoint point = aRect.TopLeft(); + // A 1x1 rect means a point, otherwise use the center of the rect + if (aRect.width != 1 || aRect.height != 1) { + point = aRect.Center(); + } + temp.AppendElement( + FramesWithDepth(transform->GetHitDepthAtPoint(aBuilder, point))); + writeFrames = &temp[temp.Length() - 1].mFrames; + } + } else { + // We may have just finished a run of consecutive preserve-3d + // transforms, so flush these into the destination array before + // processing our frame list. + FlushFramesArray(temp, aOutFrames); + } + + for (uint32_t j = 0; j < outFrames.Length(); j++) { + nsIFrame* f = outFrames.ElementAt(j); + // Filter out some frames depending on the type of hittest + // we are doing. For visibility tests, pass through all frames. + // For pointer tests, only pass through frames that are styled + // to receive pointer events. + if (aBuilder->HitTestIsForVisibility() || + IsFrameReceivingPointerEvents(f)) { + writeFrames->AppendElement(f); + } + } + + if (aBuilder->HitTestIsForVisibility()) { + aState->mHitOccludingItem = [&] { + if (aState->mHitOccludingItem) { + // We already hit something before. + return true; + } + if (aState->mCurrentOpacity == 1.0f && + item->GetOpaqueRegion(aBuilder, &snap).Contains(aRect)) { + // An opaque item always occludes everything. Note that we need to + // check wrapping opacity and such as well. + return true; + } + float threshold = aBuilder->VisibilityThreshold(); + if (threshold == 1.0f) { + return false; + } + float itemOpacity = [&] { + switch (item->GetType()) { + case DisplayItemType::TYPE_OPACITY: + return static_cast<nsDisplayOpacity*>(item)->GetOpacity(); + case DisplayItemType::TYPE_BACKGROUND_COLOR: + return static_cast<nsDisplayBackgroundColor*>(item) + ->GetOpacity(); + default: + // Be conservative and assume it won't occlude other items. + return 0.0f; + } + }(); + return itemOpacity * aState->mCurrentOpacity >= threshold; + }(); + + if (aState->mHitOccludingItem) { + // We're exiting early, so pop the remaining items off the buffer. + aState->mItemBuffer.TruncateLength(itemBufferStart); + break; + } + } + } + } + // Clear any remaining preserve-3d transforms. + FlushFramesArray(temp, aOutFrames); + NS_ASSERTION(aState->mItemBuffer.Length() == uint32_t(itemBufferStart), + "How did we forget to pop some elements?"); +} + +static nsIContent* FindContentInDocument(nsDisplayItem* aItem, Document* aDoc) { + nsIFrame* f = aItem->Frame(); + while (f) { + nsPresContext* pc = f->PresContext(); + if (pc->Document() == aDoc) { + return f->GetContent(); + } + f = nsLayoutUtils::GetCrossDocParentFrameInProcess( + pc->PresShell()->GetRootFrame()); + } + return nullptr; +} + +struct ZSortItem { + nsDisplayItem* item; + int32_t zIndex; + + explicit ZSortItem(nsDisplayItem* aItem) + : item(aItem), zIndex(aItem->ZIndex()) {} + + operator nsDisplayItem*() { return item; } +}; + +struct ZOrderComparator { + bool operator()(const ZSortItem& aLeft, const ZSortItem& aRight) const { + // Note that we can't just take the difference of the two + // z-indices here, because that might overflow a 32-bit int. + return aLeft.zIndex < aRight.zIndex; + } +}; + +void nsDisplayList::SortByZOrder() { Sort<ZSortItem>(ZOrderComparator()); } + +struct ContentComparator { + nsIContent* mCommonAncestor; + + explicit ContentComparator(nsIContent* aCommonAncestor) + : mCommonAncestor(aCommonAncestor) {} + + bool operator()(nsDisplayItem* aLeft, nsDisplayItem* aRight) const { + // It's possible that the nsIContent for aItem1 or aItem2 is in a + // subdocument of commonAncestor, because display items for subdocuments + // have been mixed into the same list. Ensure that we're looking at content + // in commonAncestor's document. + Document* commonAncestorDoc = mCommonAncestor->OwnerDoc(); + nsIContent* content1 = FindContentInDocument(aLeft, commonAncestorDoc); + nsIContent* content2 = FindContentInDocument(aRight, commonAncestorDoc); + if (!content1 || !content2) { + NS_ERROR("Document trees are mixed up!"); + // Something weird going on + return true; + } + return nsLayoutUtils::CompareTreePosition(content1, content2, + mCommonAncestor) < 0; + } +}; + +void nsDisplayList::SortByContentOrder(nsIContent* aCommonAncestor) { + Sort<nsDisplayItem*>(ContentComparator(aCommonAncestor)); +} + +#if !defined(DEBUG) && !defined(MOZ_DIAGNOSTIC_ASSERT_ENABLED) +static_assert(sizeof(nsDisplayItem) <= 176, "nsDisplayItem has grown"); +#endif + +nsDisplayItem::nsDisplayItem(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame) + : nsDisplayItem(aBuilder, aFrame, aBuilder->CurrentActiveScrolledRoot()) {} + +nsDisplayItem::nsDisplayItem(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + const ActiveScrolledRoot* aActiveScrolledRoot) + : mFrame(aFrame), mActiveScrolledRoot(aActiveScrolledRoot) { + MOZ_COUNT_CTOR(nsDisplayItem); + MOZ_ASSERT(mFrame); + if (aBuilder->IsRetainingDisplayList()) { + mFrame->AddDisplayItem(this); + } + + aBuilder->FindReferenceFrameFor(aFrame, &mToReferenceFrame); + NS_ASSERTION( + aBuilder->GetVisibleRect().width >= 0 || !aBuilder->IsForPainting(), + "visible rect not set"); + + mClipChain = aBuilder->ClipState().GetCurrentCombinedClipChain(aBuilder); + + // The visible rect is for mCurrentFrame, so we have to use + // mCurrentOffsetToReferenceFrame + nsRect visible = aBuilder->GetVisibleRect() + + aBuilder->GetCurrentFrameOffsetToReferenceFrame(); + SetBuildingRect(visible); + + const nsStyleDisplay* disp = mFrame->StyleDisplay(); + if (mFrame->BackfaceIsHidden(disp)) { + mItemFlags += ItemFlag::BackfaceHidden; + } + if (mFrame->Combines3DTransformWithAncestors()) { + mItemFlags += ItemFlag::Combines3DTransformWithAncestors; + } +} + +void nsDisplayItem::SetDeletedFrame() { mItemFlags += ItemFlag::DeletedFrame; } + +bool nsDisplayItem::HasDeletedFrame() const { + bool retval = mItemFlags.contains(ItemFlag::DeletedFrame) || + (GetType() == DisplayItemType::TYPE_REMOTE && + !static_cast<const nsDisplayRemote*>(this)->GetFrameLoader()); + MOZ_ASSERT(retval || mFrame); + return retval; +} + +/* static */ +bool nsDisplayItem::ForceActiveLayers() { + return StaticPrefs::layers_force_active(); +} + +int32_t nsDisplayItem::ZIndex() const { return mFrame->ZIndex().valueOr(0); } + +void nsDisplayItem::SetClipChain(const DisplayItemClipChain* aClipChain, + bool aStore) { + mClipChain = aClipChain; +} + +Maybe<nsRect> nsDisplayItem::GetClipWithRespectToASR( + nsDisplayListBuilder* aBuilder, const ActiveScrolledRoot* aASR) const { + if (const DisplayItemClip* clip = + DisplayItemClipChain::ClipForASR(GetClipChain(), aASR)) { + return Some(clip->GetClipRect()); + } +#ifdef DEBUG + NS_ASSERTION(false, "item should have finite clip with respect to aASR"); +#endif + return Nothing(); +} + +const DisplayItemClip& nsDisplayItem::GetClip() const { + const DisplayItemClip* clip = + DisplayItemClipChain::ClipForASR(mClipChain, mActiveScrolledRoot); + return clip ? *clip : DisplayItemClip::NoClip(); +} + +void nsDisplayItem::IntersectClip(nsDisplayListBuilder* aBuilder, + const DisplayItemClipChain* aOther, + bool aStore) { + if (!aOther || mClipChain == aOther) { + return; + } + + // aOther might be a reference to a clip on the stack. We need to make sure + // that CreateClipChainIntersection will allocate the actual intersected + // clip in the builder's arena, so for the mClipChain == nullptr case, + // we supply nullptr as the common ancestor so that + // CreateClipChainIntersection clones the whole chain. + const DisplayItemClipChain* ancestorClip = + mClipChain ? FindCommonAncestorClipForIntersection(mClipChain, aOther) + : nullptr; + + SetClipChain( + aBuilder->CreateClipChainIntersection(ancestorClip, mClipChain, aOther), + aStore); +} + +nsRect nsDisplayItem::GetClippedBounds(nsDisplayListBuilder* aBuilder) const { + bool snap; + nsRect r = GetBounds(aBuilder, &snap); + return GetClip().ApplyNonRoundedIntersection(r); +} + +nsDisplayContainer::nsDisplayContainer( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + const ActiveScrolledRoot* aActiveScrolledRoot, nsDisplayList* aList) + : nsDisplayItem(aBuilder, aFrame, aActiveScrolledRoot), + mChildren(aBuilder) { + MOZ_COUNT_CTOR(nsDisplayContainer); + mChildren.AppendToTop(aList); + UpdateBounds(aBuilder); + + // Clear and store the clip chain set by nsDisplayItem constructor. + nsDisplayItem::SetClipChain(nullptr, true); +} + +nsRect nsDisplayItem::GetPaintRect(nsDisplayListBuilder* aBuilder, + gfxContext* aCtx) { + bool dummy; + nsRect result = GetBounds(aBuilder, &dummy); + if (aCtx) { + result.IntersectRect(result, + nsLayoutUtils::RoundGfxRectToAppRect( + aCtx->GetClipExtents(), + mFrame->PresContext()->AppUnitsPerDevPixel())); + } + return result; +} + +bool nsDisplayContainer::CreateWebRenderCommands( + wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + aManager->CommandBuilder().CreateWebRenderCommandsFromDisplayList( + GetChildren(), this, aDisplayListBuilder, aSc, aBuilder, aResources, + false); + return true; +} + +nsRect nsDisplayContainer::GetBounds(nsDisplayListBuilder* aBuilder, + bool* aSnap) const { + *aSnap = false; + return mBounds; +} + +nsRect nsDisplayContainer::GetComponentAlphaBounds( + nsDisplayListBuilder* aBuilder) const { + return mChildren.GetComponentAlphaBounds(aBuilder); +} + +static nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder, + nsDisplayList* aList, + const nsRect& aListBounds) { + return aList->GetOpaqueRegion(aBuilder); +} + +nsRegion nsDisplayContainer::GetOpaqueRegion(nsDisplayListBuilder* aBuilder, + bool* aSnap) const { + return ::mozilla::GetOpaqueRegion(aBuilder, GetChildren(), + GetBounds(aBuilder, aSnap)); +} + +Maybe<nsRect> nsDisplayContainer::GetClipWithRespectToASR( + nsDisplayListBuilder* aBuilder, const ActiveScrolledRoot* aASR) const { + // Our children should have finite bounds with respect to |aASR|. + if (aASR == mActiveScrolledRoot) { + return Some(mBounds); + } + + return Some(mChildren.GetClippedBoundsWithRespectToASR(aBuilder, aASR)); +} + +void nsDisplayContainer::HitTest(nsDisplayListBuilder* aBuilder, + const nsRect& aRect, HitTestState* aState, + nsTArray<nsIFrame*>* aOutFrames) { + mChildren.HitTest(aBuilder, aRect, aState, aOutFrames); +} + +void nsDisplayContainer::UpdateBounds(nsDisplayListBuilder* aBuilder) { + // Container item bounds are expected to be clipped. + mBounds = + mChildren.GetClippedBoundsWithRespectToASR(aBuilder, mActiveScrolledRoot); +} + +nsRect nsDisplaySolidColor::GetBounds(nsDisplayListBuilder* aBuilder, + bool* aSnap) const { + *aSnap = true; + return mBounds; +} + +void nsDisplaySolidColor::Paint(nsDisplayListBuilder* aBuilder, + gfxContext* aCtx) { + int32_t appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel(); + DrawTarget* drawTarget = aCtx->GetDrawTarget(); + Rect rect = NSRectToSnappedRect(GetPaintRect(aBuilder, aCtx), + appUnitsPerDevPixel, *drawTarget); + drawTarget->FillRect(rect, ColorPattern(ToDeviceColor(mColor))); +} + +void nsDisplaySolidColor::WriteDebugInfo(std::stringstream& aStream) { + aStream << " (rgba " << (int)NS_GET_R(mColor) << "," << (int)NS_GET_G(mColor) + << "," << (int)NS_GET_B(mColor) << "," << (int)NS_GET_A(mColor) + << ")"; +} + +bool nsDisplaySolidColor::CreateWebRenderCommands( + wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + LayoutDeviceRect bounds = LayoutDeviceRect::FromAppUnits( + mBounds, mFrame->PresContext()->AppUnitsPerDevPixel()); + wr::LayoutRect r = wr::ToLayoutRect(bounds); + aBuilder.PushRect(r, r, !BackfaceIsHidden(), false, mIsCheckerboardBackground, + wr::ToColorF(ToDeviceColor(mColor))); + + return true; +} + +nsRect nsDisplaySolidColorRegion::GetBounds(nsDisplayListBuilder* aBuilder, + bool* aSnap) const { + *aSnap = true; + return mRegion.GetBounds(); +} + +void nsDisplaySolidColorRegion::Paint(nsDisplayListBuilder* aBuilder, + gfxContext* aCtx) { + int32_t appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel(); + DrawTarget* drawTarget = aCtx->GetDrawTarget(); + ColorPattern color(ToDeviceColor(mColor)); + for (auto iter = mRegion.RectIter(); !iter.Done(); iter.Next()) { + Rect rect = + NSRectToSnappedRect(iter.Get(), appUnitsPerDevPixel, *drawTarget); + drawTarget->FillRect(rect, color); + } +} + +void nsDisplaySolidColorRegion::WriteDebugInfo(std::stringstream& aStream) { + aStream << " (rgba " << int(mColor.r * 255) << "," << int(mColor.g * 255) + << "," << int(mColor.b * 255) << "," << mColor.a << ")"; +} + +bool nsDisplaySolidColorRegion::CreateWebRenderCommands( + wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + for (auto iter = mRegion.RectIter(); !iter.Done(); iter.Next()) { + nsRect rect = iter.Get(); + LayoutDeviceRect layerRects = LayoutDeviceRect::FromAppUnits( + rect, mFrame->PresContext()->AppUnitsPerDevPixel()); + wr::LayoutRect r = wr::ToLayoutRect(layerRects); + aBuilder.PushRect(r, r, !BackfaceIsHidden(), false, false, + wr::ToColorF(ToDeviceColor(mColor))); + } + + return true; +} + +static void RegisterThemeGeometry(nsDisplayListBuilder* aBuilder, + nsDisplayItem* aItem, nsIFrame* aFrame, + nsITheme::ThemeGeometryType aType) { + if (aBuilder->IsInChromeDocumentOrPopup()) { + nsIFrame* displayRoot = nsLayoutUtils::GetDisplayRootFrame(aFrame); + bool preservesAxisAlignedRectangles = false; + nsRect borderBox = nsLayoutUtils::TransformFrameRectToAncestor( + aFrame, aFrame->GetRectRelativeToSelf(), displayRoot, + &preservesAxisAlignedRectangles); + if (preservesAxisAlignedRectangles) { + aBuilder->RegisterThemeGeometry( + aType, aItem, + LayoutDeviceIntRect::FromUnknownRect(borderBox.ToNearestPixels( + aFrame->PresContext()->AppUnitsPerDevPixel()))); + } + } +} + +// Return the bounds of the viewport relative to |aFrame|'s reference frame. +// Returns Nothing() if transforming into |aFrame|'s coordinate space fails. +static Maybe<nsRect> GetViewportRectRelativeToReferenceFrame( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame) { + nsIFrame* rootFrame = aFrame->PresShell()->GetRootFrame(); + nsRect rootRect = rootFrame->GetRectRelativeToSelf(); + if (nsLayoutUtils::TransformRect(rootFrame, aFrame, rootRect) == + nsLayoutUtils::TRANSFORM_SUCCEEDED) { + return Some(rootRect + aBuilder->ToReferenceFrame(aFrame)); + } + return Nothing(); +} + +/* static */ nsDisplayBackgroundImage::InitData +nsDisplayBackgroundImage::GetInitData(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame, uint16_t aLayer, + const nsRect& aBackgroundRect, + const ComputedStyle* aBackgroundStyle) { + nsPresContext* presContext = aFrame->PresContext(); + uint32_t flags = aBuilder->GetBackgroundPaintFlags(); + const nsStyleImageLayers::Layer& layer = + aBackgroundStyle->StyleBackground()->mImage.mLayers[aLayer]; + + bool isTransformedFixed; + nsBackgroundLayerState state = nsCSSRendering::PrepareImageLayer( + presContext, aFrame, flags, aBackgroundRect, aBackgroundRect, layer, + &isTransformedFixed); + + // background-attachment:fixed is treated as background-attachment:scroll + // if it's affected by a transform. + // See https://www.w3.org/Bugs/Public/show_bug.cgi?id=17521. + bool shouldTreatAsFixed = + layer.mAttachment == StyleImageLayerAttachment::Fixed && + !isTransformedFixed; + + bool shouldFixToViewport = shouldTreatAsFixed && !layer.mImage.IsNone(); + bool isRasterImage = state.mImageRenderer.IsRasterImage(); + nsCOMPtr<imgIContainer> image; + if (isRasterImage) { + image = state.mImageRenderer.GetImage(); + } + return InitData{aBuilder, aBackgroundStyle, image, + aBackgroundRect, state.mFillArea, state.mDestArea, + aLayer, isRasterImage, shouldFixToViewport}; +} + +nsDisplayBackgroundImage::nsDisplayBackgroundImage( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, const InitData& aInitData, + nsIFrame* aFrameForBounds) + : nsPaintedDisplayItem(aBuilder, aFrame), + mBackgroundStyle(aInitData.backgroundStyle), + mImage(aInitData.image), + mDependentFrame(nullptr), + mBackgroundRect(aInitData.backgroundRect), + mFillRect(aInitData.fillArea), + mDestRect(aInitData.destArea), + mLayer(aInitData.layer), + mIsRasterImage(aInitData.isRasterImage), + mShouldFixToViewport(aInitData.shouldFixToViewport) { + MOZ_COUNT_CTOR(nsDisplayBackgroundImage); +#ifdef DEBUG + if (mBackgroundStyle && mBackgroundStyle != mFrame->Style()) { + // If this changes, then you also need to adjust css::ImageLoader to + // invalidate mFrame as needed. + MOZ_ASSERT(mFrame->IsCanvasFrame() || + mFrame->IsFrameOfType(nsIFrame::eTablePart)); + } +#endif + + mBounds = GetBoundsInternal(aInitData.builder, aFrameForBounds); + if (mShouldFixToViewport) { + // Expand the item's visible rect to cover the entire bounds, limited to the + // viewport rect. This is necessary because the background's clip can move + // asynchronously. + if (Maybe<nsRect> viewportRect = GetViewportRectRelativeToReferenceFrame( + aInitData.builder, mFrame)) { + SetBuildingRect(mBounds.Intersect(*viewportRect)); + } + } +} + +nsDisplayBackgroundImage::~nsDisplayBackgroundImage() { + MOZ_COUNT_DTOR(nsDisplayBackgroundImage); + if (mDependentFrame) { + mDependentFrame->RemoveDisplayItem(this); + } +} + +static void SetBackgroundClipRegion( + DisplayListClipState::AutoSaveRestore& aClipState, nsIFrame* aFrame, + const nsStyleImageLayers::Layer& aLayer, const nsRect& aBackgroundRect, + bool aWillPaintBorder) { + nsCSSRendering::ImageLayerClipState clip; + nsCSSRendering::GetImageLayerClip( + aLayer, aFrame, *aFrame->StyleBorder(), aBackgroundRect, aBackgroundRect, + aWillPaintBorder, aFrame->PresContext()->AppUnitsPerDevPixel(), &clip); + + if (clip.mHasAdditionalBGClipArea) { + aClipState.ClipContentDescendants( + clip.mAdditionalBGClipArea, clip.mBGClipArea, + clip.mHasRoundedCorners ? clip.mRadii : nullptr); + } else { + aClipState.ClipContentDescendants( + clip.mBGClipArea, clip.mHasRoundedCorners ? clip.mRadii : nullptr); + } +} + +/** + * This is used for the find bar highlighter overlay. It's only accessible + * through the AnonymousContent API, so it's not exposed to general web pages. + */ +static bool SpecialCutoutRegionCase(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame, + const nsRect& aBackgroundRect, + nsDisplayList* aList, nscolor aColor) { + nsIContent* content = aFrame->GetContent(); + if (!content) { + return false; + } + + void* cutoutRegion = content->GetProperty(nsGkAtoms::cutoutregion); + if (!cutoutRegion) { + return false; + } + + if (NS_GET_A(aColor) == 0) { + return true; + } + + nsRegion region; + region.Sub(aBackgroundRect, *static_cast<nsRegion*>(cutoutRegion)); + region.MoveBy(aBuilder->ToReferenceFrame(aFrame)); + aList->AppendNewToTop<nsDisplaySolidColorRegion>(aBuilder, aFrame, region, + aColor); + + return true; +} + +enum class TableType : uint8_t { + Table, + TableCol, + TableColGroup, + TableRow, + TableRowGroup, + TableCell, + + MAX, +}; + +enum class TableTypeBits : uint8_t { Count = 3 }; + +static_assert(static_cast<uint8_t>(TableType::MAX) < + (1 << (static_cast<uint8_t>(TableTypeBits::Count) + 1)), + "TableType cannot fit with TableTypeBits::Count"); +TableType GetTableTypeFromFrame(nsIFrame* aFrame); + +static uint16_t CalculateTablePerFrameKey(const uint16_t aIndex, + const TableType aType) { + const uint32_t key = (aIndex << static_cast<uint8_t>(TableTypeBits::Count)) | + static_cast<uint8_t>(aType); + + return static_cast<uint16_t>(key); +} + +static nsDisplayBackgroundImage* CreateBackgroundImage( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsIFrame* aSecondaryFrame, + const nsDisplayBackgroundImage::InitData& aBgData) { + const auto index = aBgData.layer; + + if (aSecondaryFrame) { + const auto tableType = GetTableTypeFromFrame(aFrame); + const uint16_t tableItemIndex = CalculateTablePerFrameKey(index, tableType); + + return MakeDisplayItemWithIndex<nsDisplayTableBackgroundImage>( + aBuilder, aSecondaryFrame, tableItemIndex, aBgData, aFrame); + } + + return MakeDisplayItemWithIndex<nsDisplayBackgroundImage>(aBuilder, aFrame, + index, aBgData); +} + +static nsDisplayThemedBackground* CreateThemedBackground( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsIFrame* aSecondaryFrame, + const nsRect& aBgRect) { + if (aSecondaryFrame) { + const uint16_t index = static_cast<uint16_t>(GetTableTypeFromFrame(aFrame)); + return MakeDisplayItemWithIndex<nsDisplayTableThemedBackground>( + aBuilder, aSecondaryFrame, index, aBgRect, aFrame); + } + + return MakeDisplayItem<nsDisplayThemedBackground>(aBuilder, aFrame, aBgRect); +} + +static nsDisplayBackgroundColor* CreateBackgroundColor( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsIFrame* aSecondaryFrame, + nsRect& aBgRect, const ComputedStyle* aBgSC, nscolor aColor) { + if (aSecondaryFrame) { + const uint16_t index = static_cast<uint16_t>(GetTableTypeFromFrame(aFrame)); + return MakeDisplayItemWithIndex<nsDisplayTableBackgroundColor>( + aBuilder, aSecondaryFrame, index, aBgRect, aBgSC, aColor, aFrame); + } + + return MakeDisplayItem<nsDisplayBackgroundColor>(aBuilder, aFrame, aBgRect, + aBgSC, aColor); +} + +static void DealWithWindowsAppearanceHacks(nsIFrame* aFrame, + nsDisplayListBuilder* aBuilder) { + const auto& disp = *aFrame->StyleDisplay(); + + // We use default appearance rather than effective appearance because we want + // to handle when titlebar buttons that have appearance: none. + const auto defaultAppearance = disp.mDefaultAppearance; + if (MOZ_LIKELY(defaultAppearance == StyleAppearance::None)) { + return; + } + + if (defaultAppearance == StyleAppearance::MozWinExcludeGlass) { + // Check for frames that are marked as a part of the region used in + // calculating glass margins on Windows. + aBuilder->AddWindowExcludeGlassRegion( + aFrame, nsRect(aBuilder->ToReferenceFrame(aFrame), aFrame->GetSize())); + } + + if (auto type = disp.GetWindowButtonType()) { + if (auto* widget = aFrame->GetNearestWidget()) { + auto rect = LayoutDevicePixel::FromAppUnitsToNearest( + nsRect(aBuilder->ToReferenceFrame(aFrame), aFrame->GetSize()), + aFrame->PresContext()->AppUnitsPerDevPixel()); + widget->SetWindowButtonRect(*type, rect); + } + } +} + +/*static*/ +AppendedBackgroundType nsDisplayBackgroundImage::AppendBackgroundItemsToTop( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + const nsRect& aBackgroundRect, nsDisplayList* aList, + bool aAllowWillPaintBorderOptimization, const nsRect& aBackgroundOriginRect, + nsIFrame* aSecondaryReferenceFrame, + Maybe<nsDisplayListBuilder::AutoBuildingDisplayList>* + aAutoBuildingDisplayList) { + MOZ_ASSERT(!aFrame->IsCanvasFrame(), + "We don't expect propagated canvas backgrounds here"); +#ifdef DEBUG + { + nsIFrame* bgFrame = nsCSSRendering::FindBackgroundFrame(aFrame); + MOZ_ASSERT( + !bgFrame || bgFrame == aFrame, + "Should only suppress backgrounds, never propagate to another frame"); + } +#endif + + DealWithWindowsAppearanceHacks(aFrame, aBuilder); + + const bool isThemed = aFrame->IsThemed(); + + const ComputedStyle* bgSC = aFrame->Style(); + const nsStyleBackground* bg = bgSC->StyleBackground(); + const bool needsBackgroundColor = + aBuilder->IsForEventDelivery() || + (EffectCompositor::HasAnimationsForCompositor( + aFrame, DisplayItemType::TYPE_BACKGROUND_COLOR) && + !isThemed); + if (!needsBackgroundColor && !isThemed && bg->IsTransparent(bgSC)) { + return AppendedBackgroundType::None; + } + + bool drawBackgroundColor = false; + bool drawBackgroundImage = false; + nscolor color = NS_RGBA(0, 0, 0, 0); + // Don't get background color / images if we propagated our background to the + // canvas (that is, if FindBackgroundFrame is null). But don't early return + // yet, since we might still need a background-color item for hit-testing. + if (!isThemed && nsCSSRendering::FindBackgroundFrame(aFrame)) { + color = nsCSSRendering::DetermineBackgroundColor( + aFrame->PresContext(), bgSC, aFrame, drawBackgroundImage, + drawBackgroundColor); + } + + if (SpecialCutoutRegionCase(aBuilder, aFrame, aBackgroundRect, aList, + color)) { + return AppendedBackgroundType::None; + } + + const nsStyleBorder& border = *aFrame->StyleBorder(); + const bool willPaintBorder = + aAllowWillPaintBorderOptimization && !isThemed && + !aFrame->StyleEffects()->HasBoxShadowWithInset(true) && + border.HasBorder(); + + auto EnsureBuildingDisplayList = [&] { + if (!aAutoBuildingDisplayList || *aAutoBuildingDisplayList) { + return; + } + nsPoint offset = aBuilder->GetCurrentFrame()->GetOffsetTo(aFrame); + aAutoBuildingDisplayList->emplace(aBuilder, aFrame, + aBuilder->GetVisibleRect() + offset, + aBuilder->GetDirtyRect() + offset); + }; + + // An auxiliary list is necessary in case we have background blending; if that + // is the case, background items need to be wrapped by a blend container to + // isolate blending to the background + nsDisplayList bgItemList(aBuilder); + // Even if we don't actually have a background color to paint, we may still + // need to create an item for hit testing and we still need to create an item + // for background-color animations. + if ((drawBackgroundColor && color != NS_RGBA(0, 0, 0, 0)) || + needsBackgroundColor) { + EnsureBuildingDisplayList(); + Maybe<DisplayListClipState::AutoSaveRestore> clipState; + nsRect bgColorRect = aBackgroundRect; + if (!isThemed && !aBuilder->IsForEventDelivery()) { + // Disable the will-paint-border optimization for background + // colors with no border-radius. Enabling it for background colors + // doesn't help much (there are no tiling issues) and clipping the + // background breaks detection of the element's border-box being + // opaque. For nonzero border-radius we still need it because we + // want to inset the background if possible to avoid antialiasing + // artifacts along the rounded corners. + const bool useWillPaintBorderOptimization = + willPaintBorder && + nsLayoutUtils::HasNonZeroCorner(border.mBorderRadius); + + nsCSSRendering::ImageLayerClipState clip; + nsCSSRendering::GetImageLayerClip( + bg->BottomLayer(), aFrame, border, aBackgroundRect, aBackgroundRect, + useWillPaintBorderOptimization, + aFrame->PresContext()->AppUnitsPerDevPixel(), &clip); + + bgColorRect = bgColorRect.Intersect(clip.mBGClipArea); + if (clip.mHasAdditionalBGClipArea) { + bgColorRect = bgColorRect.Intersect(clip.mAdditionalBGClipArea); + } + if (clip.mHasRoundedCorners) { + clipState.emplace(aBuilder); + clipState->ClipContentDescendants(clip.mBGClipArea, clip.mRadii); + } + } + + nsDisplayBackgroundColor* bgItem = CreateBackgroundColor( + aBuilder, aFrame, aSecondaryReferenceFrame, bgColorRect, bgSC, + drawBackgroundColor ? color : NS_RGBA(0, 0, 0, 0)); + + if (bgItem) { + bgItemList.AppendToTop(bgItem); + } + } + + if (isThemed) { + nsDisplayThemedBackground* bgItem = CreateThemedBackground( + aBuilder, aFrame, aSecondaryReferenceFrame, aBackgroundRect); + + if (bgItem) { + bgItem->Init(aBuilder); + bgItemList.AppendToTop(bgItem); + } + + if (!bgItemList.IsEmpty()) { + aList->AppendToTop(&bgItemList); + return AppendedBackgroundType::ThemedBackground; + } + + return AppendedBackgroundType::None; + } + + if (!drawBackgroundImage) { + if (!bgItemList.IsEmpty()) { + aList->AppendToTop(&bgItemList); + return AppendedBackgroundType::Background; + } + + return AppendedBackgroundType::None; + } + + const ActiveScrolledRoot* asr = aBuilder->CurrentActiveScrolledRoot(); + + bool needBlendContainer = false; + const nsRect& bgOriginRect = + aBackgroundOriginRect.IsEmpty() ? aBackgroundRect : aBackgroundOriginRect; + + // Passing bg == nullptr in this macro will result in one iteration with + // i = 0. + NS_FOR_VISIBLE_IMAGE_LAYERS_BACK_TO_FRONT(i, bg->mImage) { + if (bg->mImage.mLayers[i].mImage.IsNone()) { + continue; + } + + EnsureBuildingDisplayList(); + + if (bg->mImage.mLayers[i].mBlendMode != StyleBlend::Normal) { + needBlendContainer = true; + } + + DisplayListClipState::AutoSaveRestore clipState(aBuilder); + if (!aBuilder->IsForEventDelivery()) { + const nsStyleImageLayers::Layer& layer = bg->mImage.mLayers[i]; + SetBackgroundClipRegion(clipState, aFrame, layer, aBackgroundRect, + willPaintBorder); + } + + nsDisplayList thisItemList(aBuilder); + nsDisplayBackgroundImage::InitData bgData = + nsDisplayBackgroundImage::GetInitData(aBuilder, aFrame, i, bgOriginRect, + bgSC); + + if (bgData.shouldFixToViewport) { + auto* displayData = aBuilder->GetCurrentFixedBackgroundDisplayData(); + nsDisplayListBuilder::AutoBuildingDisplayList buildingDisplayList( + aBuilder, aFrame, aBuilder->GetVisibleRect(), + aBuilder->GetDirtyRect()); + + nsDisplayListBuilder::AutoCurrentActiveScrolledRootSetter asrSetter( + aBuilder); + if (displayData) { + asrSetter.SetCurrentActiveScrolledRoot( + displayData->mContainingBlockActiveScrolledRoot); + asrSetter.SetCurrentScrollParentId(displayData->mScrollParentId); + if (nsLayoutUtils::UsesAsyncScrolling(aFrame)) { + // Override the dirty rect on the builder to be the dirty rect of + // the viewport. + // displayData->mDirtyRect is relative to the presshell's viewport + // frame (the root frame), and we need it to be relative to aFrame. + nsIFrame* rootFrame = + aBuilder->CurrentPresShellState()->mPresShell->GetRootFrame(); + // There cannot be any transforms between aFrame and rootFrame + // because then bgData.shouldFixToViewport would have been false. + nsRect visibleRect = + displayData->mVisibleRect + aFrame->GetOffsetTo(rootFrame); + aBuilder->SetVisibleRect(visibleRect); + nsRect dirtyRect = + displayData->mDirtyRect + aFrame->GetOffsetTo(rootFrame); + aBuilder->SetDirtyRect(dirtyRect); + } + } + + nsDisplayBackgroundImage* bgItem = nullptr; + { + // The clip is captured by the nsDisplayFixedPosition, so clear the + // clip for the nsDisplayBackgroundImage inside. + DisplayListClipState::AutoSaveRestore bgImageClip(aBuilder); + bgImageClip.Clear(); + bgItem = CreateBackgroundImage(aBuilder, aFrame, + aSecondaryReferenceFrame, bgData); + } + if (bgItem) { + thisItemList.AppendToTop( + nsDisplayFixedPosition::CreateForFixedBackground( + aBuilder, aFrame, aSecondaryReferenceFrame, bgItem, i, asr)); + } + } else { // bgData.shouldFixToViewport == false + nsDisplayBackgroundImage* bgItem = CreateBackgroundImage( + aBuilder, aFrame, aSecondaryReferenceFrame, bgData); + if (bgItem) { + thisItemList.AppendToTop(bgItem); + } + } + + if (bg->mImage.mLayers[i].mBlendMode != StyleBlend::Normal) { + // asr is scrolled. Even if we wrap a fixed background layer, that's + // fine, because the item will have a scrolled clip that limits the + // item with respect to asr. + if (aSecondaryReferenceFrame) { + const auto tableType = GetTableTypeFromFrame(aFrame); + const uint16_t index = CalculateTablePerFrameKey(i + 1, tableType); + + thisItemList.AppendNewToTopWithIndex<nsDisplayTableBlendMode>( + aBuilder, aSecondaryReferenceFrame, index, &thisItemList, + bg->mImage.mLayers[i].mBlendMode, asr, aFrame, true); + } else { + thisItemList.AppendNewToTopWithIndex<nsDisplayBlendMode>( + aBuilder, aFrame, i + 1, &thisItemList, + bg->mImage.mLayers[i].mBlendMode, asr, true); + } + } + bgItemList.AppendToTop(&thisItemList); + } + + if (needBlendContainer) { + bgItemList.AppendToTop( + nsDisplayBlendContainer::CreateForBackgroundBlendMode( + aBuilder, aFrame, aSecondaryReferenceFrame, &bgItemList, asr)); + } + + if (!bgItemList.IsEmpty()) { + aList->AppendToTop(&bgItemList); + return AppendedBackgroundType::Background; + } + + return AppendedBackgroundType::None; +} + +// Check that the rounded border of aFrame, added to aToReferenceFrame, +// intersects aRect. Assumes that the unrounded border has already +// been checked for intersection. +static bool RoundedBorderIntersectsRect(nsIFrame* aFrame, + const nsPoint& aFrameToReferenceFrame, + const nsRect& aTestRect) { + if (!nsRect(aFrameToReferenceFrame, aFrame->GetSize()) + .Intersects(aTestRect)) { + return false; + } + + nscoord radii[8]; + return !aFrame->GetBorderRadii(radii) || + nsLayoutUtils::RoundedRectIntersectsRect( + nsRect(aFrameToReferenceFrame, aFrame->GetSize()), radii, + aTestRect); +} + +// Returns TRUE if aContainedRect is guaranteed to be contained in +// the rounded rect defined by aRoundedRect and aRadii. Complex cases are +// handled conservatively by returning FALSE in some situations where +// a more thorough analysis could return TRUE. +// +// See also RoundedRectIntersectsRect. +static bool RoundedRectContainsRect(const nsRect& aRoundedRect, + const nscoord aRadii[8], + const nsRect& aContainedRect) { + nsRegion rgn = nsLayoutUtils::RoundedRectIntersectRect(aRoundedRect, aRadii, + aContainedRect); + return rgn.Contains(aContainedRect); +} + +bool nsDisplayBackgroundImage::CanApplyOpacity( + WebRenderLayerManager* aManager, nsDisplayListBuilder* aBuilder) const { + return CanBuildWebRenderDisplayItems(aManager, aBuilder); +} + +bool nsDisplayBackgroundImage::CanBuildWebRenderDisplayItems( + WebRenderLayerManager* aManager, nsDisplayListBuilder* aBuilder) const { + return mBackgroundStyle->StyleBackground()->mImage.mLayers[mLayer].mClip != + StyleGeometryBox::Text && + nsCSSRendering::CanBuildWebRenderDisplayItemsForStyleImageLayer( + aManager, *StyleFrame()->PresContext(), StyleFrame(), + mBackgroundStyle->StyleBackground(), mLayer, + aBuilder->GetBackgroundPaintFlags()); +} + +bool nsDisplayBackgroundImage::CreateWebRenderCommands( + wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + if (!CanBuildWebRenderDisplayItems(aManager->LayerManager(), + aDisplayListBuilder)) { + return false; + } + + uint32_t paintFlags = aDisplayListBuilder->GetBackgroundPaintFlags(); + bool dummy; + nsCSSRendering::PaintBGParams params = + nsCSSRendering::PaintBGParams::ForSingleLayer( + *StyleFrame()->PresContext(), GetBounds(aDisplayListBuilder, &dummy), + mBackgroundRect, StyleFrame(), paintFlags, mLayer, + CompositionOp::OP_OVER, aBuilder.GetInheritedOpacity()); + params.bgClipRect = &mBounds; + ImgDrawResult result = + nsCSSRendering::BuildWebRenderDisplayItemsForStyleImageLayer( + params, aBuilder, aResources, aSc, aManager, this); + if (result == ImgDrawResult::NOT_SUPPORTED) { + return false; + } + return true; +} + +void nsDisplayBackgroundImage::HitTest(nsDisplayListBuilder* aBuilder, + const nsRect& aRect, + HitTestState* aState, + nsTArray<nsIFrame*>* aOutFrames) { + if (RoundedBorderIntersectsRect(mFrame, ToReferenceFrame(), aRect)) { + aOutFrames->AppendElement(mFrame); + } +} + +static nsRect GetInsideClipRect(const nsDisplayItem* aItem, + StyleGeometryBox aClip, const nsRect& aRect, + const nsRect& aBackgroundRect) { + if (aRect.IsEmpty()) { + return {}; + } + + nsIFrame* frame = aItem->Frame(); + + nsRect clipRect = aBackgroundRect; + if (frame->IsCanvasFrame()) { + nsCanvasFrame* canvasFrame = static_cast<nsCanvasFrame*>(frame); + clipRect = canvasFrame->CanvasArea() + aItem->ToReferenceFrame(); + } else if (aClip == StyleGeometryBox::PaddingBox || + aClip == StyleGeometryBox::ContentBox) { + nsMargin border = frame->GetUsedBorder(); + if (aClip == StyleGeometryBox::ContentBox) { + border += frame->GetUsedPadding(); + } + border.ApplySkipSides(frame->GetSkipSides()); + clipRect.Deflate(border); + } + + return clipRect.Intersect(aRect); +} + +nsRegion nsDisplayBackgroundImage::GetOpaqueRegion( + nsDisplayListBuilder* aBuilder, bool* aSnap) const { + nsRegion result; + *aSnap = false; + + if (!mBackgroundStyle) { + return result; + } + + *aSnap = true; + + // For StyleBoxDecorationBreak::Slice, don't try to optimize here, since + // this could easily lead to O(N^2) behavior inside InlineBackgroundData, + // which expects frames to be sent to it in content order, not reverse + // content order which we'll produce here. + // Of course, if there's only one frame in the flow, it doesn't matter. + if (mFrame->StyleBorder()->mBoxDecorationBreak == + StyleBoxDecorationBreak::Clone || + (!mFrame->GetPrevContinuation() && !mFrame->GetNextContinuation())) { + const nsStyleImageLayers::Layer& layer = + mBackgroundStyle->StyleBackground()->mImage.mLayers[mLayer]; + if (layer.mImage.IsOpaque() && layer.mBlendMode == StyleBlend::Normal && + layer.mRepeat.mXRepeat != StyleImageLayerRepeat::Space && + layer.mRepeat.mYRepeat != StyleImageLayerRepeat::Space && + layer.mClip != StyleGeometryBox::Text) { + result = GetInsideClipRect(this, layer.mClip, mBounds, mBackgroundRect); + } + } + + return result; +} + +Maybe<nscolor> nsDisplayBackgroundImage::IsUniform( + nsDisplayListBuilder* aBuilder) const { + if (!mBackgroundStyle) { + return Some(NS_RGBA(0, 0, 0, 0)); + } + return Nothing(); +} + +nsRect nsDisplayBackgroundImage::GetPositioningArea() const { + if (!mBackgroundStyle) { + return nsRect(); + } + nsIFrame* attachedToFrame; + bool transformedFixed; + return nsCSSRendering::ComputeImageLayerPositioningArea( + mFrame->PresContext(), mFrame, mBackgroundRect, + mBackgroundStyle->StyleBackground()->mImage.mLayers[mLayer], + &attachedToFrame, &transformedFixed) + + ToReferenceFrame(); +} + +bool nsDisplayBackgroundImage::RenderingMightDependOnPositioningAreaSizeChange() + const { + if (!mBackgroundStyle) { + return false; + } + + nscoord radii[8]; + if (mFrame->GetBorderRadii(radii)) { + // A change in the size of the positioning area might change the position + // of the rounded corners. + return true; + } + + const nsStyleImageLayers::Layer& layer = + mBackgroundStyle->StyleBackground()->mImage.mLayers[mLayer]; + return layer.RenderingMightDependOnPositioningAreaSizeChange(); +} + +void nsDisplayBackgroundImage::Paint(nsDisplayListBuilder* aBuilder, + gfxContext* aCtx) { + PaintInternal(aBuilder, aCtx, GetPaintRect(aBuilder, aCtx), &mBounds); +} + +void nsDisplayBackgroundImage::PaintInternal(nsDisplayListBuilder* aBuilder, + gfxContext* aCtx, + const nsRect& aBounds, + nsRect* aClipRect) { + gfxContext* ctx = aCtx; + StyleGeometryBox clip = + mBackgroundStyle->StyleBackground()->mImage.mLayers[mLayer].mClip; + + if (clip == StyleGeometryBox::Text) { + if (!GenerateAndPushTextMask(StyleFrame(), aCtx, mBackgroundRect, + aBuilder)) { + return; + } + } + + nsCSSRendering::PaintBGParams params = + nsCSSRendering::PaintBGParams::ForSingleLayer( + *StyleFrame()->PresContext(), aBounds, mBackgroundRect, StyleFrame(), + aBuilder->GetBackgroundPaintFlags(), mLayer, CompositionOp::OP_OVER, + 1.0f); + params.bgClipRect = aClipRect; + Unused << nsCSSRendering::PaintStyleImageLayer(params, *aCtx); + + if (clip == StyleGeometryBox::Text) { + ctx->PopGroupAndBlend(); + } +} + +void nsDisplayBackgroundImage::ComputeInvalidationRegion( + nsDisplayListBuilder* aBuilder, const nsDisplayItemGeometry* aGeometry, + nsRegion* aInvalidRegion) const { + if (!mBackgroundStyle) { + return; + } + + const auto* geometry = + static_cast<const nsDisplayBackgroundGeometry*>(aGeometry); + + bool snap; + nsRect bounds = GetBounds(aBuilder, &snap); + nsRect positioningArea = GetPositioningArea(); + if (positioningArea.TopLeft() != geometry->mPositioningArea.TopLeft() || + (positioningArea.Size() != geometry->mPositioningArea.Size() && + RenderingMightDependOnPositioningAreaSizeChange())) { + // Positioning area changed in a way that could cause everything to change, + // so invalidate everything (both old and new painting areas). + aInvalidRegion->Or(bounds, geometry->mBounds); + return; + } + if (!mDestRect.IsEqualInterior(geometry->mDestRect)) { + // Dest area changed in a way that could cause everything to change, + // so invalidate everything (both old and new painting areas). + aInvalidRegion->Or(bounds, geometry->mBounds); + return; + } + if (!bounds.IsEqualInterior(geometry->mBounds)) { + // Positioning area is unchanged, so invalidate just the change in the + // painting area. + aInvalidRegion->Xor(bounds, geometry->mBounds); + } +} + +nsRect nsDisplayBackgroundImage::GetBounds(nsDisplayListBuilder* aBuilder, + bool* aSnap) const { + *aSnap = true; + return mBounds; +} + +nsRect nsDisplayBackgroundImage::GetBoundsInternal( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrameForBounds) { + // This allows nsDisplayTableBackgroundImage to change the frame used for + // bounds calculation. + nsIFrame* frame = aFrameForBounds ? aFrameForBounds : mFrame; + + nsPresContext* presContext = frame->PresContext(); + + if (!mBackgroundStyle) { + return nsRect(); + } + + nsRect clipRect = mBackgroundRect; + if (frame->IsCanvasFrame()) { + nsCanvasFrame* canvasFrame = static_cast<nsCanvasFrame*>(frame); + clipRect = canvasFrame->CanvasArea() + ToReferenceFrame(); + } + const nsStyleImageLayers::Layer& layer = + mBackgroundStyle->StyleBackground()->mImage.mLayers[mLayer]; + return nsCSSRendering::GetBackgroundLayerRect( + presContext, frame, mBackgroundRect, clipRect, layer, + aBuilder->GetBackgroundPaintFlags()); +} + +nsDisplayTableBackgroundImage::nsDisplayTableBackgroundImage( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, const InitData& aData, + nsIFrame* aCellFrame) + : nsDisplayBackgroundImage(aBuilder, aFrame, aData, aCellFrame), + mStyleFrame(aCellFrame) { + if (aBuilder->IsRetainingDisplayList()) { + mStyleFrame->AddDisplayItem(this); + } +} + +nsDisplayTableBackgroundImage::~nsDisplayTableBackgroundImage() { + if (mStyleFrame) { + mStyleFrame->RemoveDisplayItem(this); + } +} + +bool nsDisplayTableBackgroundImage::IsInvalid(nsRect& aRect) const { + bool result = mStyleFrame ? mStyleFrame->IsInvalid(aRect) : false; + aRect += ToReferenceFrame(); + return result; +} + +nsDisplayThemedBackground::nsDisplayThemedBackground( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + const nsRect& aBackgroundRect) + : nsPaintedDisplayItem(aBuilder, aFrame), mBackgroundRect(aBackgroundRect) { + MOZ_COUNT_CTOR(nsDisplayThemedBackground); +} + +void nsDisplayThemedBackground::Init(nsDisplayListBuilder* aBuilder) { + const nsStyleDisplay* disp = StyleFrame()->StyleDisplay(); + mAppearance = disp->EffectiveAppearance(); + StyleFrame()->IsThemed(disp, &mThemeTransparency); + + // Perform necessary RegisterThemeGeometry + nsITheme* theme = StyleFrame()->PresContext()->Theme(); + nsITheme::ThemeGeometryType type = + theme->ThemeGeometryTypeForWidget(StyleFrame(), mAppearance); + if (type != nsITheme::eThemeGeometryTypeUnknown) { + RegisterThemeGeometry(aBuilder, this, StyleFrame(), type); + } + + if (mAppearance == StyleAppearance::MozWinBorderlessGlass) { + aBuilder->SetGlassDisplayItem(this); + } + + mBounds = GetBoundsInternal(); +} + +void nsDisplayThemedBackground::WriteDebugInfo(std::stringstream& aStream) { + aStream << " (themed, appearance:" << (int)mAppearance << ")"; +} + +void nsDisplayThemedBackground::HitTest(nsDisplayListBuilder* aBuilder, + const nsRect& aRect, + HitTestState* aState, + nsTArray<nsIFrame*>* aOutFrames) { + // Assume that any point in our background rect is a hit. + if (mBackgroundRect.Intersects(aRect)) { + aOutFrames->AppendElement(mFrame); + } +} + +nsRegion nsDisplayThemedBackground::GetOpaqueRegion( + nsDisplayListBuilder* aBuilder, bool* aSnap) const { + nsRegion result; + *aSnap = false; + + if (mThemeTransparency == nsITheme::eOpaque) { + *aSnap = true; + result = mBackgroundRect; + } + return result; +} + +Maybe<nscolor> nsDisplayThemedBackground::IsUniform( + nsDisplayListBuilder* aBuilder) const { + if (mAppearance == StyleAppearance::MozWinBorderlessGlass) { + return Some(NS_RGBA(0, 0, 0, 0)); + } + return Nothing(); +} + +nsRect nsDisplayThemedBackground::GetPositioningArea() const { + return mBackgroundRect; +} + +void nsDisplayThemedBackground::Paint(nsDisplayListBuilder* aBuilder, + gfxContext* aCtx) { + PaintInternal(aBuilder, aCtx, GetPaintRect(aBuilder, aCtx), nullptr); +} + +void nsDisplayThemedBackground::PaintInternal(nsDisplayListBuilder* aBuilder, + gfxContext* aCtx, + const nsRect& aBounds, + nsRect* aClipRect) { + // XXXzw this ignores aClipRect. + nsPresContext* presContext = StyleFrame()->PresContext(); + nsITheme* theme = presContext->Theme(); + nsRect drawing(mBackgroundRect); + theme->GetWidgetOverflow(presContext->DeviceContext(), StyleFrame(), + mAppearance, &drawing); + drawing.IntersectRect(drawing, aBounds); + theme->DrawWidgetBackground(aCtx, StyleFrame(), mAppearance, mBackgroundRect, + drawing); +} + +bool nsDisplayThemedBackground::CreateWebRenderCommands( + wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + nsITheme* theme = StyleFrame()->PresContext()->Theme(); + return theme->CreateWebRenderCommandsForWidget(aBuilder, aResources, aSc, + aManager, StyleFrame(), + mAppearance, mBackgroundRect); +} + +bool nsDisplayThemedBackground::IsWindowActive() const { + DocumentState docState = mFrame->GetContent()->OwnerDoc()->GetDocumentState(); + return !docState.HasState(DocumentState::WINDOW_INACTIVE); +} + +void nsDisplayThemedBackground::ComputeInvalidationRegion( + nsDisplayListBuilder* aBuilder, const nsDisplayItemGeometry* aGeometry, + nsRegion* aInvalidRegion) const { + const auto* geometry = + static_cast<const nsDisplayThemedBackgroundGeometry*>(aGeometry); + + bool snap; + nsRect bounds = GetBounds(aBuilder, &snap); + nsRect positioningArea = GetPositioningArea(); + if (!positioningArea.IsEqualInterior(geometry->mPositioningArea)) { + // Invalidate everything (both old and new painting areas). + aInvalidRegion->Or(bounds, geometry->mBounds); + return; + } + if (!bounds.IsEqualInterior(geometry->mBounds)) { + // Positioning area is unchanged, so invalidate just the change in the + // painting area. + aInvalidRegion->Xor(bounds, geometry->mBounds); + } + nsITheme* theme = StyleFrame()->PresContext()->Theme(); + if (theme->WidgetAppearanceDependsOnWindowFocus(mAppearance) && + IsWindowActive() != geometry->mWindowIsActive) { + aInvalidRegion->Or(*aInvalidRegion, bounds); + } +} + +nsRect nsDisplayThemedBackground::GetBounds(nsDisplayListBuilder* aBuilder, + bool* aSnap) const { + *aSnap = true; + return mBounds; +} + +nsRect nsDisplayThemedBackground::GetBoundsInternal() { + nsPresContext* presContext = mFrame->PresContext(); + + nsRect r = mBackgroundRect - ToReferenceFrame(); + presContext->Theme()->GetWidgetOverflow( + presContext->DeviceContext(), mFrame, + mFrame->StyleDisplay()->EffectiveAppearance(), &r); + return r + ToReferenceFrame(); +} + +#if defined(MOZ_REFLOW_PERF_DSP) && defined(MOZ_REFLOW_PERF) +void nsDisplayReflowCount::Paint(nsDisplayListBuilder* aBuilder, + gfxContext* aCtx) { + mFrame->PresShell()->PaintCount(mFrameName, aCtx, mFrame->PresContext(), + mFrame, ToReferenceFrame(), mColor); +} +#endif + +bool nsDisplayBackgroundColor::CanApplyOpacity( + WebRenderLayerManager* aManager, nsDisplayListBuilder* aBuilder) const { + // Don't apply opacity if the background color is animated since the color is + // going to be changed on the compositor. + return !EffectCompositor::HasAnimationsForCompositor( + mFrame, DisplayItemType::TYPE_BACKGROUND_COLOR); +} + +bool nsDisplayBackgroundColor::CreateWebRenderCommands( + wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + gfx::sRGBColor color = mColor; + color.a *= aBuilder.GetInheritedOpacity(); + + if (color == sRGBColor() && + !EffectCompositor::HasAnimationsForCompositor( + mFrame, DisplayItemType::TYPE_BACKGROUND_COLOR)) { + return true; + } + + if (HasBackgroundClipText()) { + return false; + } + + uint64_t animationsId = 0; + // We don't support background-color animations on table elements yet. + if (GetType() == DisplayItemType::TYPE_BACKGROUND_COLOR) { + animationsId = + AddAnimationsForWebRender(this, aManager, aDisplayListBuilder); + } + + LayoutDeviceRect bounds = LayoutDeviceRect::FromAppUnits( + mBackgroundRect, mFrame->PresContext()->AppUnitsPerDevPixel()); + wr::LayoutRect r = wr::ToLayoutRect(bounds); + + if (animationsId) { + wr::WrAnimationProperty prop{ + wr::WrAnimationType::BackgroundColor, + animationsId, + }; + aBuilder.PushRectWithAnimation(r, r, !BackfaceIsHidden(), + wr::ToColorF(ToDeviceColor(color)), &prop); + } else { + aBuilder.StartGroup(this); + aBuilder.PushRect(r, r, !BackfaceIsHidden(), false, false, + wr::ToColorF(ToDeviceColor(color))); + aBuilder.FinishGroup(); + } + + return true; +} + +void nsDisplayBackgroundColor::PaintWithClip(nsDisplayListBuilder* aBuilder, + gfxContext* aCtx, + const DisplayItemClip& aClip) { + MOZ_ASSERT(!HasBackgroundClipText()); + + if (mColor == sRGBColor()) { + return; + } + + nsRect fillRect = mBackgroundRect; + if (aClip.HasClip()) { + fillRect.IntersectRect(fillRect, aClip.GetClipRect()); + } + + DrawTarget* dt = aCtx->GetDrawTarget(); + int32_t A2D = mFrame->PresContext()->AppUnitsPerDevPixel(); + Rect bounds = ToRect(nsLayoutUtils::RectToGfxRect(fillRect, A2D)); + MaybeSnapToDevicePixels(bounds, *dt); + ColorPattern fill(ToDeviceColor(mColor)); + + if (aClip.GetRoundedRectCount()) { + MOZ_ASSERT(aClip.GetRoundedRectCount() == 1); + + AutoTArray<DisplayItemClip::RoundedRect, 1> roundedRect; + aClip.AppendRoundedRects(&roundedRect); + + bool pushedClip = false; + if (!fillRect.Contains(roundedRect[0].mRect)) { + dt->PushClipRect(bounds); + pushedClip = true; + } + + RectCornerRadii pixelRadii; + nsCSSRendering::ComputePixelRadii(roundedRect[0].mRadii, A2D, &pixelRadii); + dt->FillRoundedRect( + RoundedRect(NSRectToSnappedRect(roundedRect[0].mRect, A2D, *dt), + pixelRadii), + fill); + if (pushedClip) { + dt->PopClip(); + } + } else { + dt->FillRect(bounds, fill); + } +} + +void nsDisplayBackgroundColor::Paint(nsDisplayListBuilder* aBuilder, + gfxContext* aCtx) { + if (mColor == sRGBColor()) { + return; + } + +#if 0 + // See https://bugzilla.mozilla.org/show_bug.cgi?id=1148418#c21 for why this + // results in a precision induced rounding issue that makes the rect one + // pixel shorter in rare cases. Disabled in favor of the old code for now. + // Note that the pref layout.css.devPixelsPerPx needs to be set to 1 to + // reproduce the bug. + // + // TODO: + // This new path does not include support for background-clip:text; need to + // be fixed if/when we switch to this new code path. + + DrawTarget& aDrawTarget = *aCtx->GetDrawTarget(); + + Rect rect = NSRectToSnappedRect(mBackgroundRect, + mFrame->PresContext()->AppUnitsPerDevPixel(), + aDrawTarget); + ColorPattern color(ToDeviceColor(mColor)); + aDrawTarget.FillRect(rect, color); +#else + gfxContext* ctx = aCtx; + gfxRect bounds = nsLayoutUtils::RectToGfxRect( + mBackgroundRect, mFrame->PresContext()->AppUnitsPerDevPixel()); + + if (HasBackgroundClipText()) { + if (!GenerateAndPushTextMask(mFrame, aCtx, mBackgroundRect, aBuilder)) { + return; + } + + ctx->SetColor(mColor); + ctx->NewPath(); + ctx->SnappedRectangle(bounds); + ctx->Fill(); + ctx->PopGroupAndBlend(); + return; + } + + ctx->SetColor(mColor); + ctx->NewPath(); + ctx->SnappedRectangle(bounds); + ctx->Fill(); +#endif +} + +nsRegion nsDisplayBackgroundColor::GetOpaqueRegion( + nsDisplayListBuilder* aBuilder, bool* aSnap) const { + *aSnap = false; + + if (mColor.a != 1 || + // Even if the current alpha channel is 1, we treat this item as if it's + // non-opaque if there is a background-color animation since the animation + // might change the alpha channel. + EffectCompositor::HasAnimationsForCompositor( + mFrame, DisplayItemType::TYPE_BACKGROUND_COLOR)) { + return nsRegion(); + } + + if (!mHasStyle || HasBackgroundClipText()) { + return nsRegion(); + } + + *aSnap = true; + return GetInsideClipRect(this, mBottomLayerClip, mBackgroundRect, + mBackgroundRect); +} + +Maybe<nscolor> nsDisplayBackgroundColor::IsUniform( + nsDisplayListBuilder* aBuilder) const { + return Some(mColor.ToABGR()); +} + +void nsDisplayBackgroundColor::HitTest(nsDisplayListBuilder* aBuilder, + const nsRect& aRect, + HitTestState* aState, + nsTArray<nsIFrame*>* aOutFrames) { + if (!RoundedBorderIntersectsRect(mFrame, ToReferenceFrame(), aRect)) { + // aRect doesn't intersect our border-radius curve. + return; + } + + aOutFrames->AppendElement(mFrame); +} + +void nsDisplayBackgroundColor::WriteDebugInfo(std::stringstream& aStream) { + aStream << " (rgba " << mColor.r << "," << mColor.g << "," << mColor.b << "," + << mColor.a << ")"; + aStream << " backgroundRect" << mBackgroundRect; +} + +nsRect nsDisplayOutline::GetBounds(nsDisplayListBuilder* aBuilder, + bool* aSnap) const { + *aSnap = false; + return mFrame->InkOverflowRectRelativeToSelf() + ToReferenceFrame(); +} + +nsRect nsDisplayOutline::GetInnerRect() const { + if (nsRect* savedOutlineInnerRect = + mFrame->GetProperty(nsIFrame::OutlineInnerRectProperty())) { + return *savedOutlineInnerRect; + } + return mFrame->GetRectRelativeToSelf(); +} + +void nsDisplayOutline::Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) { + // TODO join outlines together + MOZ_ASSERT(mFrame->StyleOutline()->ShouldPaintOutline(), + "Should have not created a nsDisplayOutline!"); + + nsRect rect = GetInnerRect() + ToReferenceFrame(); + nsPresContext* pc = mFrame->PresContext(); + if (IsThemedOutline()) { + rect.Inflate(mFrame->StyleOutline()->mOutlineOffset.ToAppUnits()); + pc->Theme()->DrawWidgetBackground(aCtx, mFrame, + StyleAppearance::FocusOutline, rect, + GetPaintRect(aBuilder, aCtx)); + return; + } + + nsCSSRendering::PaintNonThemedOutline( + pc, *aCtx, mFrame, GetPaintRect(aBuilder, aCtx), rect, mFrame->Style()); +} + +bool nsDisplayOutline::IsThemedOutline() const { +#ifdef DEBUG + nsPresContext* pc = mFrame->PresContext(); + MOZ_ASSERT( + pc->Theme()->ThemeSupportsWidget(pc, mFrame, + StyleAppearance::FocusOutline), + "All of our supported platforms have support for themed focus-outlines"); +#endif + return mFrame->StyleOutline()->mOutlineStyle.IsAuto(); +} + +bool nsDisplayOutline::CreateWebRenderCommands( + wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + nsPresContext* pc = mFrame->PresContext(); + nsRect rect = GetInnerRect() + ToReferenceFrame(); + if (IsThemedOutline()) { + rect.Inflate(mFrame->StyleOutline()->mOutlineOffset.ToAppUnits()); + return pc->Theme()->CreateWebRenderCommandsForWidget( + aBuilder, aResources, aSc, aManager, mFrame, + StyleAppearance::FocusOutline, rect); + } + + bool dummy; + Maybe<nsCSSBorderRenderer> borderRenderer = + nsCSSRendering::CreateBorderRendererForNonThemedOutline( + pc, /* aDrawTarget = */ nullptr, mFrame, + GetBounds(aDisplayListBuilder, &dummy), rect, mFrame->Style()); + + if (!borderRenderer) { + // No border renderer means "there is no outline". + // Paint nothing and return success. + return true; + } + + borderRenderer->CreateWebRenderCommands(this, aBuilder, aResources, aSc); + return true; +} + +bool nsDisplayOutline::HasRadius() const { + const auto& radius = mFrame->StyleBorder()->mBorderRadius; + return !nsLayoutUtils::HasNonZeroCorner(radius); +} + +bool nsDisplayOutline::IsInvisibleInRect(const nsRect& aRect) const { + const nsStyleOutline* outline = mFrame->StyleOutline(); + nsRect borderBox(ToReferenceFrame(), mFrame->GetSize()); + if (borderBox.Contains(aRect) && !HasRadius() && + outline->mOutlineOffset.ToCSSPixels() >= 0.0f) { + // aRect is entirely inside the border-rect, and the outline isn't rendered + // inside the border-rect, so the outline is not visible. + return true; + } + return false; +} + +void nsDisplayEventReceiver::HitTest(nsDisplayListBuilder* aBuilder, + const nsRect& aRect, HitTestState* aState, + nsTArray<nsIFrame*>* aOutFrames) { + if (!RoundedBorderIntersectsRect(mFrame, ToReferenceFrame(), aRect)) { + // aRect doesn't intersect our border-radius curve. + return; + } + + aOutFrames->AppendElement(mFrame); +} + +bool nsDisplayCompositorHitTestInfo::CreateWebRenderCommands( + wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + return true; +} + +int32_t nsDisplayCompositorHitTestInfo::ZIndex() const { + return mOverrideZIndex ? *mOverrideZIndex : nsDisplayItem::ZIndex(); +} + +void nsDisplayCompositorHitTestInfo::SetOverrideZIndex(int32_t aZIndex) { + mOverrideZIndex = Some(aZIndex); +} + +nsDisplayCaret::nsDisplayCaret(nsDisplayListBuilder* aBuilder, + nsIFrame* aCaretFrame) + : nsPaintedDisplayItem(aBuilder, aCaretFrame), + mCaret(aBuilder->GetCaret()), + mBounds(aBuilder->GetCaretRect() + ToReferenceFrame()) { + MOZ_COUNT_CTOR(nsDisplayCaret); + // The presence of a caret doesn't change the overflow rect + // of the owning frame, so the normal building rect might not + // include the caret at all. We use MarkFrameForDisplay to ensure + // we build this item, and here we override the building rect + // to cover the pixels we're going to draw. + SetBuildingRect(mBounds); +} + +#ifdef NS_BUILD_REFCNT_LOGGING +nsDisplayCaret::~nsDisplayCaret() { MOZ_COUNT_DTOR(nsDisplayCaret); } +#endif + +nsRect nsDisplayCaret::GetBounds(nsDisplayListBuilder* aBuilder, + bool* aSnap) const { + *aSnap = true; + // The caret returns a rect in the coordinates of mFrame. + return mBounds; +} + +void nsDisplayCaret::Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) { + // Note: Because we exist, we know that the caret is visible, so we don't + // need to check for the caret's visibility. + mCaret->PaintCaret(*aCtx->GetDrawTarget(), mFrame, ToReferenceFrame()); +} + +bool nsDisplayCaret::CreateWebRenderCommands( + wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + using namespace layers; + nsRect caretRect; + nsRect hookRect; + nscolor caretColor; + nsIFrame* frame = + mCaret->GetPaintGeometry(&caretRect, &hookRect, &caretColor); + MOZ_ASSERT(frame == mFrame, "We're referring different frame"); + if (!frame) { + return true; + } + + int32_t appUnitsPerDevPixel = frame->PresContext()->AppUnitsPerDevPixel(); + gfx::DeviceColor color = ToDeviceColor(caretColor); + LayoutDeviceRect devCaretRect = LayoutDeviceRect::FromAppUnits( + caretRect + ToReferenceFrame(), appUnitsPerDevPixel); + LayoutDeviceRect devHookRect = LayoutDeviceRect::FromAppUnits( + hookRect + ToReferenceFrame(), appUnitsPerDevPixel); + + wr::LayoutRect caret = wr::ToLayoutRect(devCaretRect); + wr::LayoutRect hook = wr::ToLayoutRect(devHookRect); + + // Note, WR will pixel snap anything that is layout aligned. + aBuilder.PushRect(caret, caret, !BackfaceIsHidden(), false, false, + wr::ToColorF(color)); + + if (!devHookRect.IsEmpty()) { + aBuilder.PushRect(hook, hook, !BackfaceIsHidden(), false, false, + wr::ToColorF(color)); + } + return true; +} + +nsDisplayBorder::nsDisplayBorder(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame) + : nsPaintedDisplayItem(aBuilder, aFrame) { + MOZ_COUNT_CTOR(nsDisplayBorder); + + mBounds = CalculateBounds<nsRect>(*mFrame->StyleBorder()); +} + +bool nsDisplayBorder::IsInvisibleInRect(const nsRect& aRect) const { + nsRect paddingRect = GetPaddingRect(); + const nsStyleBorder* styleBorder; + if (paddingRect.Contains(aRect) && + !(styleBorder = mFrame->StyleBorder())->IsBorderImageSizeAvailable() && + !nsLayoutUtils::HasNonZeroCorner(styleBorder->mBorderRadius)) { + // aRect is entirely inside the content rect, and no part + // of the border is rendered inside the content rect, so we are not + // visible + // Skip this if there's a border-image (which draws a background + // too) or if there is a border-radius (which makes the border draw + // further in). + return true; + } + + return false; +} + +nsDisplayItemGeometry* nsDisplayBorder::AllocateGeometry( + nsDisplayListBuilder* aBuilder) { + return new nsDisplayBorderGeometry(this, aBuilder); +} + +void nsDisplayBorder::ComputeInvalidationRegion( + nsDisplayListBuilder* aBuilder, const nsDisplayItemGeometry* aGeometry, + nsRegion* aInvalidRegion) const { + const auto* geometry = static_cast<const nsDisplayBorderGeometry*>(aGeometry); + bool snap; + + if (!geometry->mBounds.IsEqualInterior(GetBounds(aBuilder, &snap))) { + // We can probably get away with only invalidating the difference + // between the border and padding rects, but the XUL ui at least + // is apparently painting a background with this? + aInvalidRegion->Or(GetBounds(aBuilder, &snap), geometry->mBounds); + } +} + +bool nsDisplayBorder::CreateWebRenderCommands( + wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + nsRect rect = nsRect(ToReferenceFrame(), mFrame->GetSize()); + + ImgDrawResult drawResult = nsCSSRendering::CreateWebRenderCommandsForBorder( + this, mFrame, rect, aBuilder, aResources, aSc, aManager, + aDisplayListBuilder); + + if (drawResult == ImgDrawResult::NOT_SUPPORTED) { + return false; + } + return true; +}; + +void nsDisplayBorder::Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) { + nsPoint offset = ToReferenceFrame(); + + PaintBorderFlags flags = aBuilder->ShouldSyncDecodeImages() + ? PaintBorderFlags::SyncDecodeImages + : PaintBorderFlags(); + + Unused << nsCSSRendering::PaintBorder( + mFrame->PresContext(), *aCtx, mFrame, GetPaintRect(aBuilder, aCtx), + nsRect(offset, mFrame->GetSize()), mFrame->Style(), flags, + mFrame->GetSkipSides()); +} + +nsRect nsDisplayBorder::GetBounds(nsDisplayListBuilder* aBuilder, + bool* aSnap) const { + *aSnap = true; + return mBounds; +} + +void nsDisplayBoxShadowOuter::Paint(nsDisplayListBuilder* aBuilder, + gfxContext* aCtx) { + nsPoint offset = ToReferenceFrame(); + nsRect borderRect = mFrame->VisualBorderRectRelativeToSelf() + offset; + nsPresContext* presContext = mFrame->PresContext(); + + AUTO_PROFILER_LABEL("nsDisplayBoxShadowOuter::Paint", GRAPHICS); + + nsCSSRendering::PaintBoxShadowOuter(presContext, *aCtx, mFrame, borderRect, + GetPaintRect(aBuilder, aCtx), 1.0f); +} + +nsRect nsDisplayBoxShadowOuter::GetBounds(nsDisplayListBuilder* aBuilder, + bool* aSnap) const { + *aSnap = false; + return mBounds; +} + +nsRect nsDisplayBoxShadowOuter::GetBoundsInternal() { + return nsLayoutUtils::GetBoxShadowRectForFrame(mFrame, mFrame->GetSize()) + + ToReferenceFrame(); +} + +bool nsDisplayBoxShadowOuter::IsInvisibleInRect(const nsRect& aRect) const { + nsPoint origin = ToReferenceFrame(); + nsRect frameRect(origin, mFrame->GetSize()); + if (!frameRect.Contains(aRect)) { + return false; + } + + // the visible region is entirely inside the border-rect, and box shadows + // never render within the border-rect (unless there's a border radius). + nscoord twipsRadii[8]; + bool hasBorderRadii = mFrame->GetBorderRadii(twipsRadii); + if (!hasBorderRadii) { + return true; + } + + return RoundedRectContainsRect(frameRect, twipsRadii, aRect); +} + +bool nsDisplayBoxShadowOuter::CanBuildWebRenderDisplayItems() const { + auto shadows = mFrame->StyleEffects()->mBoxShadow.AsSpan(); + if (shadows.IsEmpty()) { + return false; + } + + bool hasBorderRadius; + // We don't support native themed things yet like box shadows around + // input buttons. + // + // TODO(emilio): The non-native theme could provide the right rect+radius + // instead relatively painlessly, if we find this causes performance issues or + // what not. + return !nsCSSRendering::HasBoxShadowNativeTheme(mFrame, hasBorderRadius); +} + +bool nsDisplayBoxShadowOuter::CreateWebRenderCommands( + wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + if (!CanBuildWebRenderDisplayItems()) { + return false; + } + + int32_t appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel(); + nsPoint offset = ToReferenceFrame(); + nsRect borderRect = mFrame->VisualBorderRectRelativeToSelf() + offset; + bool snap; + nsRect bounds = GetBounds(aDisplayListBuilder, &snap); + + bool hasBorderRadius; + bool nativeTheme = + nsCSSRendering::HasBoxShadowNativeTheme(mFrame, hasBorderRadius); + + // Don't need the full size of the shadow rect like we do in + // nsCSSRendering since WR takes care of calculations for blur + // and spread radius. + nsRect frameRect = + nsCSSRendering::GetShadowRect(borderRect, nativeTheme, mFrame); + + RectCornerRadii borderRadii; + if (hasBorderRadius) { + hasBorderRadius = nsCSSRendering::GetBorderRadii(frameRect, borderRect, + mFrame, borderRadii); + } + + // Everything here is in app units, change to device units. + LayoutDeviceRect clipRect = + LayoutDeviceRect::FromAppUnits(bounds, appUnitsPerDevPixel); + auto shadows = mFrame->StyleEffects()->mBoxShadow.AsSpan(); + MOZ_ASSERT(!shadows.IsEmpty()); + + for (const auto& shadow : Reversed(shadows)) { + if (shadow.inset) { + continue; + } + + float blurRadius = + float(shadow.base.blur.ToAppUnits()) / float(appUnitsPerDevPixel); + gfx::sRGBColor shadowColor = nsCSSRendering::GetShadowColor( + shadow.base, mFrame, aBuilder.GetInheritedOpacity()); + + // We don't move the shadow rect here since WR does it for us + // Now translate everything to device pixels. + const nsRect& shadowRect = frameRect; + LayoutDevicePoint shadowOffset = LayoutDevicePoint::FromAppUnits( + nsPoint(shadow.base.horizontal.ToAppUnits(), + shadow.base.vertical.ToAppUnits()), + appUnitsPerDevPixel); + + LayoutDeviceRect deviceBox = + LayoutDeviceRect::FromAppUnits(shadowRect, appUnitsPerDevPixel); + wr::LayoutRect deviceBoxRect = wr::ToLayoutRect(deviceBox); + wr::LayoutRect deviceClipRect = wr::ToLayoutRect(clipRect); + + LayoutDeviceSize zeroSize; + wr::BorderRadius borderRadius = + wr::ToBorderRadius(zeroSize, zeroSize, zeroSize, zeroSize); + if (hasBorderRadius) { + borderRadius = wr::ToBorderRadius( + LayoutDeviceSize::FromUnknownSize(borderRadii.TopLeft()), + LayoutDeviceSize::FromUnknownSize(borderRadii.TopRight()), + LayoutDeviceSize::FromUnknownSize(borderRadii.BottomLeft()), + LayoutDeviceSize::FromUnknownSize(borderRadii.BottomRight())); + } + + float spreadRadius = + float(shadow.spread.ToAppUnits()) / float(appUnitsPerDevPixel); + + aBuilder.PushBoxShadow(deviceBoxRect, deviceClipRect, !BackfaceIsHidden(), + deviceBoxRect, wr::ToLayoutVector2D(shadowOffset), + wr::ToColorF(ToDeviceColor(shadowColor)), blurRadius, + spreadRadius, borderRadius, + wr::BoxShadowClipMode::Outset); + } + + return true; +} + +void nsDisplayBoxShadowOuter::ComputeInvalidationRegion( + nsDisplayListBuilder* aBuilder, const nsDisplayItemGeometry* aGeometry, + nsRegion* aInvalidRegion) const { + const auto* geometry = + static_cast<const nsDisplayItemGenericGeometry*>(aGeometry); + bool snap; + if (!geometry->mBounds.IsEqualInterior(GetBounds(aBuilder, &snap)) || + !geometry->mBorderRect.IsEqualInterior(GetBorderRect())) { + nsRegion oldShadow, newShadow; + nscoord dontCare[8]; + bool hasBorderRadius = mFrame->GetBorderRadii(dontCare); + if (hasBorderRadius) { + // If we have rounded corners then we need to invalidate the frame area + // too since we paint into it. + oldShadow = geometry->mBounds; + newShadow = GetBounds(aBuilder, &snap); + } else { + oldShadow.Sub(geometry->mBounds, geometry->mBorderRect); + newShadow.Sub(GetBounds(aBuilder, &snap), GetBorderRect()); + } + aInvalidRegion->Or(oldShadow, newShadow); + } +} + +void nsDisplayBoxShadowInner::Paint(nsDisplayListBuilder* aBuilder, + gfxContext* aCtx) { + nsPoint offset = ToReferenceFrame(); + nsRect borderRect = nsRect(offset, mFrame->GetSize()); + nsPresContext* presContext = mFrame->PresContext(); + + AUTO_PROFILER_LABEL("nsDisplayBoxShadowInner::Paint", GRAPHICS); + + nsCSSRendering::PaintBoxShadowInner(presContext, *aCtx, mFrame, borderRect); +} + +bool nsDisplayBoxShadowInner::CanCreateWebRenderCommands( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + const nsPoint& aReferenceOffset) { + auto shadows = aFrame->StyleEffects()->mBoxShadow.AsSpan(); + if (shadows.IsEmpty()) { + // Means we don't have to paint anything + return true; + } + + bool hasBorderRadius; + bool nativeTheme = + nsCSSRendering::HasBoxShadowNativeTheme(aFrame, hasBorderRadius); + + // We don't support native themed things yet like box shadows around + // input buttons. + return !nativeTheme; +} + +/* static */ +void nsDisplayBoxShadowInner::CreateInsetBoxShadowWebRenderCommands( + wr::DisplayListBuilder& aBuilder, const StackingContextHelper& aSc, + nsRect& aVisibleRect, nsIFrame* aFrame, const nsRect& aBorderRect) { + if (!nsCSSRendering::ShouldPaintBoxShadowInner(aFrame)) { + return; + } + + int32_t appUnitsPerDevPixel = aFrame->PresContext()->AppUnitsPerDevPixel(); + + auto shadows = aFrame->StyleEffects()->mBoxShadow.AsSpan(); + + LayoutDeviceRect clipRect = + LayoutDeviceRect::FromAppUnits(aVisibleRect, appUnitsPerDevPixel); + + for (const auto& shadow : Reversed(shadows)) { + if (!shadow.inset) { + continue; + } + + nsRect shadowRect = + nsCSSRendering::GetBoxShadowInnerPaddingRect(aFrame, aBorderRect); + RectCornerRadii innerRadii; + nsCSSRendering::GetShadowInnerRadii(aFrame, aBorderRect, innerRadii); + + // Now translate everything to device pixels. + LayoutDeviceRect deviceBoxRect = + LayoutDeviceRect::FromAppUnits(shadowRect, appUnitsPerDevPixel); + wr::LayoutRect deviceClipRect = wr::ToLayoutRect(clipRect); + sRGBColor shadowColor = + nsCSSRendering::GetShadowColor(shadow.base, aFrame, 1.0); + + LayoutDevicePoint shadowOffset = LayoutDevicePoint::FromAppUnits( + nsPoint(shadow.base.horizontal.ToAppUnits(), + shadow.base.vertical.ToAppUnits()), + appUnitsPerDevPixel); + + float blurRadius = + float(shadow.base.blur.ToAppUnits()) / float(appUnitsPerDevPixel); + + wr::BorderRadius borderRadius = wr::ToBorderRadius( + LayoutDeviceSize::FromUnknownSize(innerRadii.TopLeft()), + LayoutDeviceSize::FromUnknownSize(innerRadii.TopRight()), + LayoutDeviceSize::FromUnknownSize(innerRadii.BottomLeft()), + LayoutDeviceSize::FromUnknownSize(innerRadii.BottomRight())); + // NOTE: Any spread radius > 0 will render nothing. WR Bug. + float spreadRadius = + float(shadow.spread.ToAppUnits()) / float(appUnitsPerDevPixel); + + aBuilder.PushBoxShadow( + wr::ToLayoutRect(deviceBoxRect), deviceClipRect, + !aFrame->BackfaceIsHidden(), wr::ToLayoutRect(deviceBoxRect), + wr::ToLayoutVector2D(shadowOffset), + wr::ToColorF(ToDeviceColor(shadowColor)), blurRadius, spreadRadius, + borderRadius, wr::BoxShadowClipMode::Inset); + } +} + +bool nsDisplayBoxShadowInner::CreateWebRenderCommands( + wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + if (!CanCreateWebRenderCommands(aDisplayListBuilder, mFrame, + ToReferenceFrame())) { + return false; + } + + bool snap; + nsRect visible = GetBounds(aDisplayListBuilder, &snap); + nsPoint offset = ToReferenceFrame(); + nsRect borderRect = nsRect(offset, mFrame->GetSize()); + nsDisplayBoxShadowInner::CreateInsetBoxShadowWebRenderCommands( + aBuilder, aSc, visible, mFrame, borderRect); + + return true; +} + +nsDisplayWrapList::nsDisplayWrapList(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame, nsDisplayList* aList) + : nsDisplayWrapList(aBuilder, aFrame, aList, + aBuilder->CurrentActiveScrolledRoot(), false) {} + +nsDisplayWrapList::nsDisplayWrapList( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayList* aList, + const ActiveScrolledRoot* aActiveScrolledRoot, bool aClearClipChain) + : nsPaintedDisplayItem(aBuilder, aFrame, aActiveScrolledRoot), + mList(aBuilder), + mFrameActiveScrolledRoot(aBuilder->CurrentActiveScrolledRoot()), + mOverrideZIndex(0), + mHasZIndexOverride(false), + mClearingClipChain(aClearClipChain) { + MOZ_COUNT_CTOR(nsDisplayWrapList); + + mBaseBuildingRect = GetBuildingRect(); + + mListPtr = &mList; + mListPtr->AppendToTop(aList); + mOriginalClipChain = mClipChain; + nsDisplayWrapList::UpdateBounds(aBuilder); +} + +nsDisplayWrapList::nsDisplayWrapList(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame, nsDisplayItem* aItem) + : nsPaintedDisplayItem(aBuilder, aFrame, + aBuilder->CurrentActiveScrolledRoot()), + mList(aBuilder), + mOverrideZIndex(0), + mHasZIndexOverride(false) { + MOZ_COUNT_CTOR(nsDisplayWrapList); + + mBaseBuildingRect = GetBuildingRect(); + + mListPtr = &mList; + mListPtr->AppendToTop(aItem); + mOriginalClipChain = mClipChain; + nsDisplayWrapList::UpdateBounds(aBuilder); + + if (!aFrame || !aFrame->IsTransformed()) { + return; + } + + // See the previous nsDisplayWrapList constructor + if (aItem->Frame() == aFrame) { + mToReferenceFrame = aItem->ToReferenceFrame(); + } + + nsRect visible = aBuilder->GetVisibleRect() + + aBuilder->GetCurrentFrameOffsetToReferenceFrame(); + + SetBuildingRect(visible); +} + +nsDisplayWrapList::~nsDisplayWrapList() { MOZ_COUNT_DTOR(nsDisplayWrapList); } + +void nsDisplayWrapList::HitTest(nsDisplayListBuilder* aBuilder, + const nsRect& aRect, HitTestState* aState, + nsTArray<nsIFrame*>* aOutFrames) { + mListPtr->HitTest(aBuilder, aRect, aState, aOutFrames); +} + +nsRect nsDisplayWrapList::GetBounds(nsDisplayListBuilder* aBuilder, + bool* aSnap) const { + *aSnap = false; + return mBounds; +} + +nsRegion nsDisplayWrapList::GetOpaqueRegion(nsDisplayListBuilder* aBuilder, + bool* aSnap) const { + *aSnap = false; + bool snap; + return ::mozilla::GetOpaqueRegion(aBuilder, GetChildren(), + GetBounds(aBuilder, &snap)); +} + +Maybe<nscolor> nsDisplayWrapList::IsUniform( + nsDisplayListBuilder* aBuilder) const { + // We could try to do something but let's conservatively just return Nothing. + return Nothing(); +} + +void nsDisplayWrapper::Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) { + NS_ERROR("nsDisplayWrapper should have been flattened away for painting"); +} + +nsRect nsDisplayWrapList::GetComponentAlphaBounds( + nsDisplayListBuilder* aBuilder) const { + return mListPtr->GetComponentAlphaBounds(aBuilder); +} + +bool nsDisplayWrapList::CreateWebRenderCommandsNewClipListOption( + wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder, bool aNewClipList) { + aManager->CommandBuilder().CreateWebRenderCommandsFromDisplayList( + GetChildren(), this, aDisplayListBuilder, aSc, aBuilder, aResources, + aNewClipList); + return true; +} + +static nsresult WrapDisplayList(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame, nsDisplayList* aList, + nsDisplayItemWrapper* aWrapper) { + if (!aList->GetTop()) { + return NS_OK; + } + nsDisplayItem* item = aWrapper->WrapList(aBuilder, aFrame, aList); + if (!item) { + return NS_ERROR_OUT_OF_MEMORY; + } + // aList was emptied + aList->AppendToTop(item); + return NS_OK; +} + +static nsresult WrapEachDisplayItem(nsDisplayListBuilder* aBuilder, + nsDisplayList* aList, + nsDisplayItemWrapper* aWrapper) { + for (nsDisplayItem* item : aList->TakeItems()) { + item = aWrapper->WrapItem(aBuilder, item); + if (!item) { + return NS_ERROR_OUT_OF_MEMORY; + } + aList->AppendToTop(item); + } + // aList was emptied + return NS_OK; +} + +nsresult nsDisplayItemWrapper::WrapLists(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame, + const nsDisplayListSet& aIn, + const nsDisplayListSet& aOut) { + nsresult rv = WrapListsInPlace(aBuilder, aFrame, aIn); + NS_ENSURE_SUCCESS(rv, rv); + + if (&aOut == &aIn) { + return NS_OK; + } + aOut.BorderBackground()->AppendToTop(aIn.BorderBackground()); + aOut.BlockBorderBackgrounds()->AppendToTop(aIn.BlockBorderBackgrounds()); + aOut.Floats()->AppendToTop(aIn.Floats()); + aOut.Content()->AppendToTop(aIn.Content()); + aOut.PositionedDescendants()->AppendToTop(aIn.PositionedDescendants()); + aOut.Outlines()->AppendToTop(aIn.Outlines()); + return NS_OK; +} + +nsresult nsDisplayItemWrapper::WrapListsInPlace( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + const nsDisplayListSet& aLists) { + nsresult rv; + if (WrapBorderBackground()) { + // Our border-backgrounds are in-flow + rv = WrapDisplayList(aBuilder, aFrame, aLists.BorderBackground(), this); + NS_ENSURE_SUCCESS(rv, rv); + } + // Our block border-backgrounds are in-flow + rv = WrapDisplayList(aBuilder, aFrame, aLists.BlockBorderBackgrounds(), this); + NS_ENSURE_SUCCESS(rv, rv); + // The floats are not in flow + rv = WrapEachDisplayItem(aBuilder, aLists.Floats(), this); + NS_ENSURE_SUCCESS(rv, rv); + // Our child content is in flow + rv = WrapDisplayList(aBuilder, aFrame, aLists.Content(), this); + NS_ENSURE_SUCCESS(rv, rv); + // The positioned descendants may not be in-flow + rv = WrapEachDisplayItem(aBuilder, aLists.PositionedDescendants(), this); + NS_ENSURE_SUCCESS(rv, rv); + // The outlines may not be in-flow + return WrapEachDisplayItem(aBuilder, aLists.Outlines(), this); +} + +nsDisplayOpacity::nsDisplayOpacity( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayList* aList, + const ActiveScrolledRoot* aActiveScrolledRoot, bool aForEventsOnly, + bool aNeedsActiveLayer, bool aWrapsBackdropFilter) + : nsDisplayWrapList(aBuilder, aFrame, aList, aActiveScrolledRoot, true), + mOpacity(aFrame->StyleEffects()->mOpacity), + mForEventsOnly(aForEventsOnly), + mNeedsActiveLayer(aNeedsActiveLayer), + mChildOpacityState(ChildOpacityState::Unknown), + mWrapsBackdropFilter(aWrapsBackdropFilter) { + MOZ_COUNT_CTOR(nsDisplayOpacity); +} + +void nsDisplayOpacity::HitTest(nsDisplayListBuilder* aBuilder, + const nsRect& aRect, + nsDisplayItem::HitTestState* aState, + nsTArray<nsIFrame*>* aOutFrames) { + AutoRestore<float> opacity(aState->mCurrentOpacity); + aState->mCurrentOpacity *= mOpacity; + + // TODO(emilio): special-casing zero is a bit arbitrary... Maybe we should + // only consider fully opaque items? Or make this configurable somehow? + if (aBuilder->HitTestIsForVisibility() && mOpacity == 0.0f) { + return; + } + nsDisplayWrapList::HitTest(aBuilder, aRect, aState, aOutFrames); +} + +nsRegion nsDisplayOpacity::GetOpaqueRegion(nsDisplayListBuilder* aBuilder, + bool* aSnap) const { + *aSnap = false; + // The only time where mOpacity == 1.0 should be when we have will-change. + // We could report this as opaque then but when the will-change value starts + // animating the element would become non opaque and could cause repaints. + return nsRegion(); +} + +void nsDisplayOpacity::Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) { + if (GetOpacity() == 0.0f) { + return; + } + + if (GetOpacity() == 1.0f) { + GetChildren()->Paint(aBuilder, aCtx, + mFrame->PresContext()->AppUnitsPerDevPixel()); + return; + } + + // TODO: Compute a bounds rect to pass to PushLayer for a smaller + // allocation. + aCtx->GetDrawTarget()->PushLayer(false, GetOpacity(), nullptr, gfx::Matrix()); + GetChildren()->Paint(aBuilder, aCtx, + mFrame->PresContext()->AppUnitsPerDevPixel()); + aCtx->GetDrawTarget()->PopLayer(); +} + +/* static */ +bool nsDisplayOpacity::NeedsActiveLayer(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame) { + return EffectCompositor::HasAnimationsForCompositor( + aFrame, DisplayItemType::TYPE_OPACITY) || + (ActiveLayerTracker::IsStyleAnimated( + aBuilder, aFrame, nsCSSPropertyIDSet::OpacityProperties())); +} + +bool nsDisplayOpacity::CanApplyOpacity(WebRenderLayerManager* aManager, + nsDisplayListBuilder* aBuilder) const { + return !EffectCompositor::HasAnimationsForCompositor( + mFrame, DisplayItemType::TYPE_OPACITY); +} + +// Only try folding our opacity down if we have at most |kOpacityMaxChildCount| +// children that don't overlap and can all apply the opacity to themselves. +static const size_t kOpacityMaxChildCount = 3; + +// |kOpacityMaxListSize| defines an early exit condition for opacity items that +// are likely have more child items than |kOpacityMaxChildCount|. +static const size_t kOpacityMaxListSize = kOpacityMaxChildCount * 2; + +/** + * Recursively iterates through |aList| and collects at most + * |kOpacityMaxChildCount| display item pointers to items that return true for + * CanApplyOpacity(). The item pointers are added to |aArray|. + * + * LayerEventRegions and WrapList items are ignored. + * + * We need to do this recursively, because the child display items might contain + * nested nsDisplayWrapLists. + * + * Returns false if there are more than |kOpacityMaxChildCount| items, or if an + * item that returns false for CanApplyOpacity() is encountered. + * Otherwise returns true. + */ +static bool CollectItemsWithOpacity(WebRenderLayerManager* aManager, + nsDisplayListBuilder* aBuilder, + nsDisplayList* aList, + nsTArray<nsPaintedDisplayItem*>& aArray) { + if (aList->Length() > kOpacityMaxListSize) { + // Exit early, since |aList| will likely contain more than + // |kOpacityMaxChildCount| items. + return false; + } + + for (nsDisplayItem* i : *aList) { + const DisplayItemType type = i->GetType(); + + if (type == DisplayItemType::TYPE_COMPOSITOR_HITTEST_INFO) { + continue; + } + + // Descend only into wraplists. + if (type == DisplayItemType::TYPE_WRAP_LIST || + type == DisplayItemType::TYPE_CONTAINER) { + // The current display item has children, process them first. + if (!CollectItemsWithOpacity(aManager, aBuilder, i->GetChildren(), + aArray)) { + return false; + } + + continue; + } + + if (aArray.Length() == kOpacityMaxChildCount) { + return false; + } + + auto* item = i->AsPaintedDisplayItem(); + if (!item || !item->CanApplyOpacity(aManager, aBuilder)) { + return false; + } + + aArray.AppendElement(item); + } + + return true; +} + +bool nsDisplayOpacity::CanApplyToChildren(WebRenderLayerManager* aManager, + nsDisplayListBuilder* aBuilder) { + if (mChildOpacityState == ChildOpacityState::Deferred) { + return false; + } + + // Iterate through the child display list and copy at most + // |kOpacityMaxChildCount| child display item pointers to a temporary list. + AutoTArray<nsPaintedDisplayItem*, kOpacityMaxChildCount> items; + if (!CollectItemsWithOpacity(aManager, aBuilder, &mList, items)) { + mChildOpacityState = ChildOpacityState::Deferred; + return false; + } + + struct { + nsPaintedDisplayItem* item{}; + nsRect bounds; + } children[kOpacityMaxChildCount]; + + bool snap; + size_t childCount = 0; + for (nsPaintedDisplayItem* item : items) { + children[childCount].item = item; + children[childCount].bounds = item->GetBounds(aBuilder, &snap); + childCount++; + } + + for (size_t i = 0; i < childCount; i++) { + for (size_t j = i + 1; j < childCount; j++) { + if (children[i].bounds.Intersects(children[j].bounds)) { + mChildOpacityState = ChildOpacityState::Deferred; + return false; + } + } + } + + mChildOpacityState = ChildOpacityState::Applied; + return true; +} + +/** + * Returns true if this nsDisplayOpacity contains only a filter or a mask item + * that has the same frame as the opacity item, and that supports painting with + * opacity. In this case the opacity item can be optimized away. + */ +bool nsDisplayOpacity::ApplyToMask() { + if (mList.Length() != 1) { + return false; + } + + nsDisplayItem* item = mList.GetBottom(); + if (item->Frame() != mFrame) { + // The effect item needs to have the same frame as the opacity item. + return false; + } + + const DisplayItemType type = item->GetType(); + if (type == DisplayItemType::TYPE_MASK) { + return true; + } + + return false; +} + +bool nsDisplayOpacity::CanApplyOpacityToChildren( + WebRenderLayerManager* aManager, nsDisplayListBuilder* aBuilder, + float aInheritedOpacity) { + if (mFrame->GetPrevContinuation() || mFrame->GetNextContinuation() || + mFrame->HasAnyStateBits(NS_FRAME_PART_OF_IBSPLIT)) { + // If we've been split, then we might need to merge, so + // don't flatten us away. + return false; + } + + if (mNeedsActiveLayer || mOpacity == 0.0) { + // If our opacity is zero then we'll discard all descendant display items + // except for layer event regions, so there's no point in doing this + // optimization (and if we do do it, then invalidations of those descendants + // might trigger repainting). + return false; + } + + if (mList.IsEmpty()) { + return false; + } + + // We can only flatten opacity items into a mask if we haven't + // already flattened an earlier ancestor, since the SVG code pulls the opacity + // from style directly, and won't know about the outer opacity value. + if (aInheritedOpacity == 1.0f && ApplyToMask()) { + MOZ_ASSERT(SVGIntegrationUtils::UsingEffectsForFrame(mFrame)); + mChildOpacityState = ChildOpacityState::Applied; + return true; + } + + // Return true if we successfully applied opacity to child items. + return CanApplyToChildren(aManager, aBuilder); +} + +void nsDisplayOpacity::ComputeInvalidationRegion( + nsDisplayListBuilder* aBuilder, const nsDisplayItemGeometry* aGeometry, + nsRegion* aInvalidRegion) const { + const auto* geometry = + static_cast<const nsDisplayOpacityGeometry*>(aGeometry); + + bool snap; + if (mOpacity != geometry->mOpacity) { + aInvalidRegion->Or(GetBounds(aBuilder, &snap), geometry->mBounds); + } +} + +void nsDisplayOpacity::WriteDebugInfo(std::stringstream& aStream) { + aStream << " (opacity " << mOpacity << ", mChildOpacityState: "; + switch (mChildOpacityState) { + case ChildOpacityState::Unknown: + aStream << "Unknown"; + break; + case ChildOpacityState::Applied: + aStream << "Applied"; + break; + case ChildOpacityState::Deferred: + aStream << "Deferred"; + break; + default: + break; + } + + aStream << ")"; +} + +bool nsDisplayOpacity::CreateWebRenderCommands( + wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + MOZ_ASSERT(mChildOpacityState != ChildOpacityState::Applied); + float oldOpacity = aBuilder.GetInheritedOpacity(); + const DisplayItemClipChain* oldClipChain = aBuilder.GetInheritedClipChain(); + aBuilder.SetInheritedOpacity(1.0f); + aBuilder.SetInheritedClipChain(nullptr); + float opacity = mOpacity * oldOpacity; + float* opacityForSC = &opacity; + + uint64_t animationsId = + AddAnimationsForWebRender(this, aManager, aDisplayListBuilder); + wr::WrAnimationProperty prop{ + wr::WrAnimationType::Opacity, + animationsId, + }; + + wr::StackingContextParams params; + params.animation = animationsId ? &prop : nullptr; + params.opacity = opacityForSC; + params.clip = + wr::WrStackingContextClip::ClipChain(aBuilder.CurrentClipChainId()); + if (mWrapsBackdropFilter) { + params.flags |= wr::StackingContextFlags::WRAPS_BACKDROP_FILTER; + } + StackingContextHelper sc(aSc, GetActiveScrolledRoot(), mFrame, this, aBuilder, + params); + + aManager->CommandBuilder().CreateWebRenderCommandsFromDisplayList( + &mList, this, aDisplayListBuilder, sc, aBuilder, aResources); + aBuilder.SetInheritedOpacity(oldOpacity); + aBuilder.SetInheritedClipChain(oldClipChain); + return true; +} + +nsDisplayBlendMode::nsDisplayBlendMode( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayList* aList, + StyleBlend aBlendMode, const ActiveScrolledRoot* aActiveScrolledRoot, + const bool aIsForBackground) + : nsDisplayWrapList(aBuilder, aFrame, aList, aActiveScrolledRoot, true), + mBlendMode(aBlendMode), + mIsForBackground(aIsForBackground) { + MOZ_COUNT_CTOR(nsDisplayBlendMode); +} + +nsRegion nsDisplayBlendMode::GetOpaqueRegion(nsDisplayListBuilder* aBuilder, + bool* aSnap) const { + *aSnap = false; + // We are never considered opaque + return nsRegion(); +} + +bool nsDisplayBlendMode::CreateWebRenderCommands( + wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + wr::StackingContextParams params; + params.mix_blend_mode = + wr::ToMixBlendMode(nsCSSRendering::GetGFXBlendMode(mBlendMode)); + params.clip = + wr::WrStackingContextClip::ClipChain(aBuilder.CurrentClipChainId()); + StackingContextHelper sc(aSc, GetActiveScrolledRoot(), mFrame, this, aBuilder, + params); + + return nsDisplayWrapList::CreateWebRenderCommands( + aBuilder, aResources, sc, aManager, aDisplayListBuilder); +} + +void nsDisplayBlendMode::Paint(nsDisplayListBuilder* aBuilder, + gfxContext* aCtx) { + // This should be switched to use PushLayerWithBlend, once it's + // been implemented for all DrawTarget backends. + DrawTarget* dt = aCtx->GetDrawTarget(); + int32_t appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel(); + Rect rect = NSRectToRect(GetPaintRect(aBuilder, aCtx), appUnitsPerDevPixel); + rect.RoundOut(); + + // Create a temporary DrawTarget that is clipped to the area that + // we're going to draw to. This will include the same transform as + // is currently on |dt|. + RefPtr<DrawTarget> temp = + dt->CreateClippedDrawTarget(rect, SurfaceFormat::B8G8R8A8); + if (!temp) { + return; + } + + gfxContext ctx(temp, /* aPreserveTransform */ true); + + GetChildren()->Paint(aBuilder, &ctx, + mFrame->PresContext()->AppUnitsPerDevPixel()); + + // Draw the temporary DT to the real destination, applying the blend mode, but + // no transform. + temp->Flush(); + RefPtr<SourceSurface> surface = temp->Snapshot(); + gfxContextMatrixAutoSaveRestore saveMatrix(aCtx); + dt->SetTransform(Matrix()); + dt->DrawSurface( + surface, Rect(surface->GetRect()), Rect(surface->GetRect()), + DrawSurfaceOptions(), + DrawOptions(1.0f, nsCSSRendering::GetGFXBlendMode(mBlendMode))); +} + +gfx::CompositionOp nsDisplayBlendMode::BlendMode() { + return nsCSSRendering::GetGFXBlendMode(mBlendMode); +} + +bool nsDisplayBlendMode::CanMerge(const nsDisplayItem* aItem) const { + // Items for the same content element should be merged into a single + // compositing group. + if (!HasDifferentFrame(aItem) || !HasSameTypeAndClip(aItem) || + !HasSameContent(aItem)) { + return false; + } + + const auto* item = static_cast<const nsDisplayBlendMode*>(aItem); + if (mIsForBackground || item->mIsForBackground) { + // Don't merge background-blend-mode items + return false; + } + + return true; +} + +/* static */ +nsDisplayBlendContainer* nsDisplayBlendContainer::CreateForMixBlendMode( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayList* aList, + const ActiveScrolledRoot* aActiveScrolledRoot) { + return MakeDisplayItem<nsDisplayBlendContainer>(aBuilder, aFrame, aList, + aActiveScrolledRoot, false); +} + +/* static */ +nsDisplayBlendContainer* nsDisplayBlendContainer::CreateForBackgroundBlendMode( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsIFrame* aSecondaryFrame, + nsDisplayList* aList, const ActiveScrolledRoot* aActiveScrolledRoot) { + if (aSecondaryFrame) { + auto type = GetTableTypeFromFrame(aFrame); + auto index = static_cast<uint16_t>(type); + + return MakeDisplayItemWithIndex<nsDisplayTableBlendContainer>( + aBuilder, aSecondaryFrame, index, aList, aActiveScrolledRoot, true, + aFrame); + } + + return MakeDisplayItemWithIndex<nsDisplayBlendContainer>( + aBuilder, aFrame, 1, aList, aActiveScrolledRoot, true); +} + +nsDisplayBlendContainer::nsDisplayBlendContainer( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayList* aList, + const ActiveScrolledRoot* aActiveScrolledRoot, bool aIsForBackground) + : nsDisplayWrapList(aBuilder, aFrame, aList, aActiveScrolledRoot, true), + mIsForBackground(aIsForBackground) { + MOZ_COUNT_CTOR(nsDisplayBlendContainer); +} + +void nsDisplayBlendContainer::Paint(nsDisplayListBuilder* aBuilder, + gfxContext* aCtx) { + aCtx->GetDrawTarget()->PushLayer(false, 1.0, nullptr, gfx::Matrix()); + GetChildren()->Paint(aBuilder, aCtx, + mFrame->PresContext()->AppUnitsPerDevPixel()); + aCtx->GetDrawTarget()->PopLayer(); +} + +bool nsDisplayBlendContainer::CreateWebRenderCommands( + wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + wr::StackingContextParams params; + params.flags |= wr::StackingContextFlags::IS_BLEND_CONTAINER; + params.clip = + wr::WrStackingContextClip::ClipChain(aBuilder.CurrentClipChainId()); + StackingContextHelper sc(aSc, GetActiveScrolledRoot(), mFrame, this, aBuilder, + params); + + return nsDisplayWrapList::CreateWebRenderCommands( + aBuilder, aResources, sc, aManager, aDisplayListBuilder); +} + +nsDisplayOwnLayer::nsDisplayOwnLayer( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayList* aList, + const ActiveScrolledRoot* aActiveScrolledRoot, + nsDisplayOwnLayerFlags aFlags, const ScrollbarData& aScrollbarData, + bool aForceActive, bool aClearClipChain) + : nsDisplayWrapList(aBuilder, aFrame, aList, aActiveScrolledRoot, + aClearClipChain), + mFlags(aFlags), + mScrollbarData(aScrollbarData), + mForceActive(aForceActive), + mWrAnimationId(0) { + MOZ_COUNT_CTOR(nsDisplayOwnLayer); +} + +bool nsDisplayOwnLayer::IsScrollThumbLayer() const { + return mScrollbarData.mScrollbarLayerType == ScrollbarLayerType::Thumb; +} + +bool nsDisplayOwnLayer::IsScrollbarContainer() const { + return mScrollbarData.mScrollbarLayerType == ScrollbarLayerType::Container; +} + +bool nsDisplayOwnLayer::IsRootScrollbarContainer() const { + if (!IsScrollbarContainer()) { + return false; + } + + return mFrame->PresContext()->IsRootContentDocumentCrossProcess() && + mScrollbarData.mTargetViewId == + nsLayoutUtils::ScrollIdForRootScrollFrame(mFrame->PresContext()); +} + +bool nsDisplayOwnLayer::IsZoomingLayer() const { + return GetType() == DisplayItemType::TYPE_ASYNC_ZOOM; +} + +bool nsDisplayOwnLayer::IsFixedPositionLayer() const { + return GetType() == DisplayItemType::TYPE_FIXED_POSITION; +} + +bool nsDisplayOwnLayer::IsStickyPositionLayer() const { + return GetType() == DisplayItemType::TYPE_STICKY_POSITION; +} + +bool nsDisplayOwnLayer::HasDynamicToolbar() const { + if (!mFrame->PresContext()->IsRootContentDocumentCrossProcess()) { + return false; + } + return mFrame->PresContext()->HasDynamicToolbar() || + // For tests on Android, this pref is set to simulate the dynamic + // toolbar + StaticPrefs::apz_fixed_margin_override_enabled(); +} + +bool nsDisplayOwnLayer::ShouldFixedAndStickyContentGetAnimationIds() const { +#if defined(MOZ_WIDGET_ANDROID) + return mFrame->PresContext()->IsRootContentDocumentCrossProcess(); +#else + return false; +#endif +} + +bool nsDisplayOwnLayer::CreateWebRenderCommands( + wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + Maybe<wr::WrAnimationProperty> prop; + bool needsProp = aManager->LayerManager()->AsyncPanZoomEnabled() && + (IsScrollThumbLayer() || IsZoomingLayer() || + (IsFixedPositionLayer() && + ShouldFixedAndStickyContentGetAnimationIds()) || + (IsStickyPositionLayer() && + ShouldFixedAndStickyContentGetAnimationIds()) || + (IsRootScrollbarContainer() && HasDynamicToolbar())); + + if (needsProp) { + // APZ is enabled and this is a scroll thumb or zooming layer, so we need + // to create and set an animation id. That way APZ can adjust the position/ + // zoom of this content asynchronously as needed. + RefPtr<WebRenderAPZAnimationData> animationData = + aManager->CommandBuilder() + .CreateOrRecycleWebRenderUserData<WebRenderAPZAnimationData>(this); + mWrAnimationId = animationData->GetAnimationId(); + + prop.emplace(); + prop->id = mWrAnimationId; + prop->key = wr::SpatialKey(uint64_t(mFrame), GetPerFrameKey(), + wr::SpatialKeyKind::APZ); + prop->effect_type = wr::WrAnimationType::Transform; + } + + wr::StackingContextParams params; + params.animation = prop.ptrOr(nullptr); + params.clip = + wr::WrStackingContextClip::ClipChain(aBuilder.CurrentClipChainId()); + if (IsScrollbarContainer() && IsRootScrollbarContainer()) { + params.prim_flags |= wr::PrimitiveFlags::IS_SCROLLBAR_CONTAINER; + } + if (IsZoomingLayer() || + ((IsFixedPositionLayer() && + ShouldFixedAndStickyContentGetAnimationIds()) || + (IsStickyPositionLayer() && + ShouldFixedAndStickyContentGetAnimationIds()) || + (IsRootScrollbarContainer() && HasDynamicToolbar()))) { + params.is_2d_scale_translation = true; + params.should_snap = true; + } + + StackingContextHelper sc(aSc, GetActiveScrolledRoot(), mFrame, this, aBuilder, + params); + + nsDisplayWrapList::CreateWebRenderCommands(aBuilder, aResources, sc, aManager, + aDisplayListBuilder); + return true; +} + +bool nsDisplayOwnLayer::UpdateScrollData(WebRenderScrollData* aData, + WebRenderLayerScrollData* aLayerData) { + bool isRelevantToApz = + (IsScrollThumbLayer() || IsScrollbarContainer() || IsZoomingLayer() || + (IsFixedPositionLayer() && + ShouldFixedAndStickyContentGetAnimationIds()) || + (IsStickyPositionLayer() && + ShouldFixedAndStickyContentGetAnimationIds())); + + if (!isRelevantToApz) { + return false; + } + + if (!aLayerData) { + return true; + } + + if (IsZoomingLayer()) { + aLayerData->SetZoomAnimationId(mWrAnimationId); + return true; + } + + if (IsFixedPositionLayer() && ShouldFixedAndStickyContentGetAnimationIds()) { + aLayerData->SetFixedPositionAnimationId(mWrAnimationId); + return true; + } + + if (IsStickyPositionLayer() && ShouldFixedAndStickyContentGetAnimationIds()) { + aLayerData->SetStickyPositionAnimationId(mWrAnimationId); + return true; + } + + MOZ_ASSERT(IsScrollbarContainer() || IsScrollThumbLayer()); + + aLayerData->SetScrollbarData(mScrollbarData); + + if (IsRootScrollbarContainer() && HasDynamicToolbar()) { + aLayerData->SetScrollbarAnimationId(mWrAnimationId); + return true; + } + + if (IsScrollThumbLayer()) { + aLayerData->SetScrollbarAnimationId(mWrAnimationId); + LayoutDeviceRect bounds = LayoutDeviceIntRect::FromAppUnits( + mBounds, mFrame->PresContext()->AppUnitsPerDevPixel()); + // We use a resolution of 1.0 because this is a WebRender codepath which + // always uses containerless scrolling, and so resolution doesn't apply to + // scrollbars. + LayerIntRect layerBounds = + RoundedOut(bounds * LayoutDeviceToLayerScale(1.0f)); + aLayerData->SetVisibleRegion(LayerIntRegion(layerBounds)); + } + return true; +} + +void nsDisplayOwnLayer::WriteDebugInfo(std::stringstream& aStream) { + aStream << nsPrintfCString(" (flags 0x%x) (scrolltarget %" PRIu64 ")", + (int)mFlags, mScrollbarData.mTargetViewId) + .get(); +} + +nsDisplaySubDocument::nsDisplaySubDocument(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame, + nsSubDocumentFrame* aSubDocFrame, + nsDisplayList* aList, + nsDisplayOwnLayerFlags aFlags) + : nsDisplayOwnLayer(aBuilder, aFrame, aList, + aBuilder->CurrentActiveScrolledRoot(), aFlags), + mScrollParentId(aBuilder->GetCurrentScrollParentId()), + mShouldFlatten(false), + mSubDocFrame(aSubDocFrame) { + MOZ_COUNT_CTOR(nsDisplaySubDocument); + + if (mSubDocFrame && mSubDocFrame != mFrame) { + mSubDocFrame->AddDisplayItem(this); + } +} + +nsDisplaySubDocument::~nsDisplaySubDocument() { + MOZ_COUNT_DTOR(nsDisplaySubDocument); + if (mSubDocFrame) { + mSubDocFrame->RemoveDisplayItem(this); + } +} + +nsIFrame* nsDisplaySubDocument::FrameForInvalidation() const { + return mSubDocFrame ? mSubDocFrame : mFrame; +} + +void nsDisplaySubDocument::RemoveFrame(nsIFrame* aFrame) { + if (aFrame == mSubDocFrame) { + mSubDocFrame = nullptr; + SetDeletedFrame(); + } + nsDisplayOwnLayer::RemoveFrame(aFrame); +} + +static bool UseDisplayPortForViewport(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame) { + return aBuilder->IsPaintingToWindow() && + DisplayPortUtils::ViewportHasDisplayPort(aFrame->PresContext()); +} + +nsRect nsDisplaySubDocument::GetBounds(nsDisplayListBuilder* aBuilder, + bool* aSnap) const { + bool usingDisplayPort = UseDisplayPortForViewport(aBuilder, mFrame); + + if ((mFlags & nsDisplayOwnLayerFlags::GenerateScrollableLayer) && + usingDisplayPort) { + *aSnap = false; + return mFrame->GetRect() + aBuilder->ToReferenceFrame(mFrame); + } + + return nsDisplayOwnLayer::GetBounds(aBuilder, aSnap); +} + +nsRegion nsDisplaySubDocument::GetOpaqueRegion(nsDisplayListBuilder* aBuilder, + bool* aSnap) const { + bool usingDisplayPort = UseDisplayPortForViewport(aBuilder, mFrame); + + if ((mFlags & nsDisplayOwnLayerFlags::GenerateScrollableLayer) && + usingDisplayPort) { + *aSnap = false; + return nsRegion(); + } + + return nsDisplayOwnLayer::GetOpaqueRegion(aBuilder, aSnap); +} + +/* static */ +nsDisplayFixedPosition* nsDisplayFixedPosition::CreateForFixedBackground( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsIFrame* aSecondaryFrame, + nsDisplayBackgroundImage* aImage, const uint16_t aIndex, + const ActiveScrolledRoot* aScrollTargetASR) { + nsDisplayList temp(aBuilder); + temp.AppendToTop(aImage); + + if (aSecondaryFrame) { + auto tableType = GetTableTypeFromFrame(aFrame); + const uint16_t index = CalculateTablePerFrameKey(aIndex + 1, tableType); + return MakeDisplayItemWithIndex<nsDisplayTableFixedPosition>( + aBuilder, aSecondaryFrame, index, &temp, aFrame, aScrollTargetASR); + } + + return MakeDisplayItemWithIndex<nsDisplayFixedPosition>( + aBuilder, aFrame, aIndex + 1, &temp, aScrollTargetASR); +} + +nsDisplayFixedPosition::nsDisplayFixedPosition( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayList* aList, + const ActiveScrolledRoot* aActiveScrolledRoot, + const ActiveScrolledRoot* aScrollTargetASR) + : nsDisplayOwnLayer(aBuilder, aFrame, aList, aActiveScrolledRoot), + mScrollTargetASR(aScrollTargetASR), + mIsFixedBackground(false) { + MOZ_COUNT_CTOR(nsDisplayFixedPosition); +} + +nsDisplayFixedPosition::nsDisplayFixedPosition( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayList* aList, + const ActiveScrolledRoot* aScrollTargetASR) + : nsDisplayOwnLayer(aBuilder, aFrame, aList, + aBuilder->CurrentActiveScrolledRoot()), + // For fixed backgrounds, this is the ASR for the nearest scroll frame. + mScrollTargetASR(aScrollTargetASR), + mIsFixedBackground(true) { + MOZ_COUNT_CTOR(nsDisplayFixedPosition); +} + +ScrollableLayerGuid::ViewID nsDisplayFixedPosition::GetScrollTargetId() { + if (mScrollTargetASR && + (mIsFixedBackground || !nsLayoutUtils::IsReallyFixedPos(mFrame))) { + return mScrollTargetASR->GetViewId(); + } + return nsLayoutUtils::ScrollIdForRootScrollFrame(mFrame->PresContext()); +} + +bool nsDisplayFixedPosition::CreateWebRenderCommands( + wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + SideBits sides = SideBits::eNone; + if (!mIsFixedBackground) { + sides = nsLayoutUtils::GetSideBitsForFixedPositionContent(mFrame); + } + + // We install this RAII scrolltarget tracker so that any + // nsDisplayCompositorHitTestInfo items inside this fixed-pos item (and that + // share the same ASR as this item) use the correct scroll target. That way + // attempts to scroll on those items will scroll the root scroll frame. + wr::DisplayListBuilder::FixedPosScrollTargetTracker tracker( + aBuilder, GetActiveScrolledRoot(), GetScrollTargetId(), sides); + return nsDisplayOwnLayer::CreateWebRenderCommands( + aBuilder, aResources, aSc, aManager, aDisplayListBuilder); +} + +bool nsDisplayFixedPosition::UpdateScrollData( + WebRenderScrollData* aData, WebRenderLayerScrollData* aLayerData) { + if (aLayerData) { + if (!mIsFixedBackground) { + aLayerData->SetFixedPositionSides( + nsLayoutUtils::GetSideBitsForFixedPositionContent(mFrame)); + } + aLayerData->SetFixedPositionScrollContainerId(GetScrollTargetId()); + } + nsDisplayOwnLayer::UpdateScrollData(aData, aLayerData); + return true; +} + +void nsDisplayFixedPosition::WriteDebugInfo(std::stringstream& aStream) { + aStream << nsPrintfCString( + " (containerASR %s) (scrolltarget %" PRIu64 ")", + ActiveScrolledRoot::ToString(mScrollTargetASR).get(), + GetScrollTargetId()) + .get(); +} + +TableType GetTableTypeFromFrame(nsIFrame* aFrame) { + if (aFrame->IsTableFrame()) { + return TableType::Table; + } + + if (aFrame->IsTableColFrame()) { + return TableType::TableCol; + } + + if (aFrame->IsTableColGroupFrame()) { + return TableType::TableColGroup; + } + + if (aFrame->IsTableRowFrame()) { + return TableType::TableRow; + } + + if (aFrame->IsTableRowGroupFrame()) { + return TableType::TableRowGroup; + } + + if (aFrame->IsTableCellFrame()) { + return TableType::TableCell; + } + + MOZ_ASSERT_UNREACHABLE("Invalid frame."); + return TableType::Table; +} + +nsDisplayTableFixedPosition::nsDisplayTableFixedPosition( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayList* aList, + nsIFrame* aAncestorFrame, const ActiveScrolledRoot* aScrollTargetASR) + : nsDisplayFixedPosition(aBuilder, aFrame, aList, aScrollTargetASR), + mAncestorFrame(aAncestorFrame) { + if (aBuilder->IsRetainingDisplayList()) { + mAncestorFrame->AddDisplayItem(this); + } +} + +nsDisplayStickyPosition::nsDisplayStickyPosition( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayList* aList, + const ActiveScrolledRoot* aActiveScrolledRoot, + const ActiveScrolledRoot* aContainerASR, bool aClippedToDisplayPort) + : nsDisplayOwnLayer(aBuilder, aFrame, aList, aActiveScrolledRoot), + mContainerASR(aContainerASR), + mClippedToDisplayPort(aClippedToDisplayPort), + mShouldFlatten(false) { + MOZ_COUNT_CTOR(nsDisplayStickyPosition); +} + +// Returns the smallest distance from "0" to the range [min, max] where +// min <= max. Despite the name, the return value is actually a 1-D vector, +// and so may be negative if max < 0. +static nscoord DistanceToRange(nscoord min, nscoord max) { + MOZ_ASSERT(min <= max); + if (max < 0) { + return max; + } + if (min > 0) { + return min; + } + MOZ_ASSERT(min <= 0 && max >= 0); + return 0; +} + +// Returns the magnitude of the part of the range [min, max] that is greater +// than zero. The return value is always non-negative. +static nscoord PositivePart(nscoord min, nscoord max) { + MOZ_ASSERT(min <= max); + if (min >= 0) { + return max - min; + } + if (max > 0) { + return max; + } + return 0; +} + +// Returns the magnitude of the part of the range [min, max] that is less +// than zero. The return value is always non-negative. +static nscoord NegativePart(nscoord min, nscoord max) { + MOZ_ASSERT(min <= max); + if (max <= 0) { + return max - min; + } + if (min < 0) { + return 0 - min; + } + return 0; +} + +StickyScrollContainer* nsDisplayStickyPosition::GetStickyScrollContainer() { + StickyScrollContainer* stickyScrollContainer = + StickyScrollContainer::GetStickyScrollContainerForFrame(mFrame); + if (stickyScrollContainer) { + // If there's no ASR for the scrollframe that this sticky item is attached + // to, then don't create a WR sticky item for it either. Trying to do so + // will end in sadness because WR will interpret some coordinates as + // relative to the nearest enclosing scrollframe, which will correspond + // to the nearest ancestor ASR on the gecko side. That ASR will not be the + // same as the scrollframe this sticky item is actually supposed to be + // attached to, thus the sadness. + // Not sending WR the sticky item is ok, because the enclosing scrollframe + // will never be asynchronously scrolled. Instead we will always position + // the sticky items correctly on the gecko side and WR will never need to + // adjust their position itself. + MOZ_ASSERT( + stickyScrollContainer->ScrollFrame()->IsMaybeAsynchronouslyScrolled()); + if (!stickyScrollContainer->ScrollFrame() + ->IsMaybeAsynchronouslyScrolled()) { + stickyScrollContainer = nullptr; + } + } + return stickyScrollContainer; +} + +bool nsDisplayStickyPosition::CreateWebRenderCommands( + wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + StickyScrollContainer* stickyScrollContainer = GetStickyScrollContainer(); + + Maybe<wr::SpaceAndClipChainHelper> saccHelper; + + if (stickyScrollContainer) { + float auPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel(); + + bool snap; + nsRect itemBounds = GetBounds(aDisplayListBuilder, &snap); + + Maybe<float> topMargin; + Maybe<float> rightMargin; + Maybe<float> bottomMargin; + Maybe<float> leftMargin; + wr::StickyOffsetBounds vBounds = {0.0, 0.0}; + wr::StickyOffsetBounds hBounds = {0.0, 0.0}; + nsPoint appliedOffset; + + nsRectAbsolute outer; + nsRectAbsolute inner; + stickyScrollContainer->GetScrollRanges(mFrame, &outer, &inner); + + nsIFrame* scrollFrame = do_QueryFrame(stickyScrollContainer->ScrollFrame()); + nsPoint offset = + scrollFrame->GetOffsetToCrossDoc(Frame()) + ToReferenceFrame(); + + // Adjust the scrollPort coordinates to be relative to the reference frame, + // so that it is in the same space as everything else. + nsRect scrollPort = + stickyScrollContainer->ScrollFrame()->GetScrollPortRect(); + scrollPort += offset; + + // The following computations make more sense upon understanding the + // semantics of "inner" and "outer", which is explained in the comment on + // SetStickyPositionData in Layers.h. + + if (outer.YMost() != inner.YMost()) { + // Question: How far will itemBounds.y be from the top of the scrollport + // when we have scrolled from the current scroll position of "0" to + // reach the range [inner.YMost(), outer.YMost()] where the item gets + // stuck? + // Answer: the current distance is "itemBounds.y - scrollPort.y". That + // needs to be adjusted by the distance to the range, less any other + // sticky ranges that fall between 0 and the range. If the distance is + // negative (i.e. inner.YMost() <= outer.YMost() < 0) then we would be + // scrolling upwards (decreasing scroll offset) to reach that range, + // which would increase itemBounds.y and make it farther away from the + // top of the scrollport. So in that case the adjustment is -distance. + // If the distance is positive (0 < inner.YMost() <= outer.YMost()) then + // we would be scrolling downwards, itemBounds.y would decrease, and we + // again need to adjust by -distance. If we are already in the range + // then no adjustment is needed and distance is 0 so again using + // -distance works. If the distance is positive, and the item has both + // top and bottom sticky ranges, then the bottom sticky range may fall + // (entirely[1] or partly[2]) between the current scroll position. + // [1]: 0 <= outer.Y() <= inner.Y() < inner.YMost() <= outer.YMost() + // [2]: outer.Y() < 0 <= inner.Y() < inner.YMost() <= outer.YMost() + // In these cases, the item doesn't actually move for that part of the + // distance, so we need to subtract out that bit, which can be computed + // as the positive portion of the range [outer.Y(), inner.Y()]. + nscoord distance = DistanceToRange(inner.YMost(), outer.YMost()); + if (distance > 0) { + distance -= PositivePart(outer.Y(), inner.Y()); + } + topMargin = Some(NSAppUnitsToFloatPixels( + itemBounds.y - scrollPort.y - distance, auPerDevPixel)); + // Question: What is the maximum positive ("downward") offset that WR + // will have to apply to this item in order to prevent the item from + // visually moving? + // Answer: Since the item is "sticky" in the range [inner.YMost(), + // outer.YMost()], the maximum offset will be the size of the range, which + // is outer.YMost() - inner.YMost(). + vBounds.max = + NSAppUnitsToFloatPixels(outer.YMost() - inner.YMost(), auPerDevPixel); + // Question: how much of an offset has layout already applied to the item? + // Answer: if we are + // (a) inside the sticky range (inner.YMost() < 0 <= outer.YMost()), or + // (b) past the sticky range (inner.YMost() < outer.YMost() < 0) + // then layout has already applied some offset to the position of the + // item. The amount of the adjustment is |0 - inner.YMost()| in case (a) + // and |outer.YMost() - inner.YMost()| in case (b). + if (inner.YMost() < 0) { + appliedOffset.y = std::min(0, outer.YMost()) - inner.YMost(); + MOZ_ASSERT(appliedOffset.y > 0); + } + } + if (outer.Y() != inner.Y()) { + // Similar logic as in the previous section, but this time we care about + // the distance from itemBounds.YMost() to scrollPort.YMost(). + nscoord distance = DistanceToRange(outer.Y(), inner.Y()); + if (distance < 0) { + distance += NegativePart(inner.YMost(), outer.YMost()); + } + bottomMargin = Some(NSAppUnitsToFloatPixels( + scrollPort.YMost() - itemBounds.YMost() + distance, auPerDevPixel)); + // And here WR will be moving the item upwards rather than downwards so + // again things are inverted from the previous block. + vBounds.min = + NSAppUnitsToFloatPixels(outer.Y() - inner.Y(), auPerDevPixel); + // We can't have appliedOffset be both positive and negative, and the top + // adjustment takes priority. So here we only update appliedOffset.y if + // it wasn't set by the top-sticky case above. + if (appliedOffset.y == 0 && inner.Y() > 0) { + appliedOffset.y = std::max(0, outer.Y()) - inner.Y(); + MOZ_ASSERT(appliedOffset.y < 0); + } + } + // Same as above, but for the x-axis + if (outer.XMost() != inner.XMost()) { + nscoord distance = DistanceToRange(inner.XMost(), outer.XMost()); + if (distance > 0) { + distance -= PositivePart(outer.X(), inner.X()); + } + leftMargin = Some(NSAppUnitsToFloatPixels( + itemBounds.x - scrollPort.x - distance, auPerDevPixel)); + hBounds.max = + NSAppUnitsToFloatPixels(outer.XMost() - inner.XMost(), auPerDevPixel); + if (inner.XMost() < 0) { + appliedOffset.x = std::min(0, outer.XMost()) - inner.XMost(); + MOZ_ASSERT(appliedOffset.x > 0); + } + } + if (outer.X() != inner.X()) { + nscoord distance = DistanceToRange(outer.X(), inner.X()); + if (distance < 0) { + distance += NegativePart(inner.XMost(), outer.XMost()); + } + rightMargin = Some(NSAppUnitsToFloatPixels( + scrollPort.XMost() - itemBounds.XMost() + distance, auPerDevPixel)); + hBounds.min = + NSAppUnitsToFloatPixels(outer.X() - inner.X(), auPerDevPixel); + if (appliedOffset.x == 0 && inner.X() > 0) { + appliedOffset.x = std::max(0, outer.X()) - inner.X(); + MOZ_ASSERT(appliedOffset.x < 0); + } + } + + LayoutDeviceRect bounds = + LayoutDeviceRect::FromAppUnits(itemBounds, auPerDevPixel); + wr::LayoutVector2D applied = { + NSAppUnitsToFloatPixels(appliedOffset.x, auPerDevPixel), + NSAppUnitsToFloatPixels(appliedOffset.y, auPerDevPixel)}; + wr::WrSpatialId spatialId = aBuilder.DefineStickyFrame( + wr::ToLayoutRect(bounds), topMargin.ptrOr(nullptr), + rightMargin.ptrOr(nullptr), bottomMargin.ptrOr(nullptr), + leftMargin.ptrOr(nullptr), vBounds, hBounds, applied, + wr::SpatialKey(uint64_t(mFrame), GetPerFrameKey(), + wr::SpatialKeyKind::Sticky)); + + saccHelper.emplace(aBuilder, spatialId); + aManager->CommandBuilder().PushOverrideForASR(mContainerASR, spatialId); + } + + { + wr::StackingContextParams params; + params.clip = + wr::WrStackingContextClip::ClipChain(aBuilder.CurrentClipChainId()); + StackingContextHelper sc(aSc, GetActiveScrolledRoot(), mFrame, this, + aBuilder, params); + nsDisplayOwnLayer::CreateWebRenderCommands(aBuilder, aResources, sc, + aManager, aDisplayListBuilder); + } + + if (stickyScrollContainer) { + aManager->CommandBuilder().PopOverrideForASR(mContainerASR); + } + + return true; +} + +void nsDisplayStickyPosition::CalculateLayerScrollRanges( + StickyScrollContainer* aStickyScrollContainer, float aAppUnitsPerDevPixel, + float aScaleX, float aScaleY, LayerRectAbsolute& aStickyOuter, + LayerRectAbsolute& aStickyInner) { + nsRectAbsolute outer; + nsRectAbsolute inner; + aStickyScrollContainer->GetScrollRanges(mFrame, &outer, &inner); + aStickyOuter.SetBox( + NSAppUnitsToFloatPixels(outer.X(), aAppUnitsPerDevPixel) * aScaleX, + NSAppUnitsToFloatPixels(outer.Y(), aAppUnitsPerDevPixel) * aScaleY, + NSAppUnitsToFloatPixels(outer.XMost(), aAppUnitsPerDevPixel) * aScaleX, + NSAppUnitsToFloatPixels(outer.YMost(), aAppUnitsPerDevPixel) * aScaleY); + aStickyInner.SetBox( + NSAppUnitsToFloatPixels(inner.X(), aAppUnitsPerDevPixel) * aScaleX, + NSAppUnitsToFloatPixels(inner.Y(), aAppUnitsPerDevPixel) * aScaleY, + NSAppUnitsToFloatPixels(inner.XMost(), aAppUnitsPerDevPixel) * aScaleX, + NSAppUnitsToFloatPixels(inner.YMost(), aAppUnitsPerDevPixel) * aScaleY); +} + +bool nsDisplayStickyPosition::UpdateScrollData( + WebRenderScrollData* aData, WebRenderLayerScrollData* aLayerData) { + bool hasDynamicToolbar = HasDynamicToolbar(); + if (aLayerData && hasDynamicToolbar) { + StickyScrollContainer* stickyScrollContainer = GetStickyScrollContainer(); + if (stickyScrollContainer) { + float auPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel(); + float cumulativeResolution = + mFrame->PresShell()->GetCumulativeResolution(); + LayerRectAbsolute stickyOuter; + LayerRectAbsolute stickyInner; + CalculateLayerScrollRanges(stickyScrollContainer, auPerDevPixel, + cumulativeResolution, cumulativeResolution, + stickyOuter, stickyInner); + aLayerData->SetStickyScrollRangeOuter(stickyOuter); + aLayerData->SetStickyScrollRangeInner(stickyInner); + + SideBits sides = + nsLayoutUtils::GetSideBitsForFixedPositionContent(mFrame); + aLayerData->SetFixedPositionSides(sides); + + ScrollableLayerGuid::ViewID scrollId = + nsLayoutUtils::FindOrCreateIDFor(stickyScrollContainer->ScrollFrame() + ->GetScrolledFrame() + ->GetContent()); + aLayerData->SetStickyPositionScrollContainerId(scrollId); + } + } + // Return true if either there is a dynamic toolbar affecting this sticky + // item or the OwnLayer base implementation returns true for some other + // reason. + bool ret = hasDynamicToolbar; + ret |= nsDisplayOwnLayer::UpdateScrollData(aData, aLayerData); + return ret; +} + +nsDisplayScrollInfoLayer::nsDisplayScrollInfoLayer( + nsDisplayListBuilder* aBuilder, nsIFrame* aScrolledFrame, + nsIFrame* aScrollFrame, const CompositorHitTestInfo& aHitInfo, + const nsRect& aHitArea) + : nsDisplayWrapList(aBuilder, aScrollFrame), + mScrollFrame(aScrollFrame), + mScrolledFrame(aScrolledFrame), + mScrollParentId(aBuilder->GetCurrentScrollParentId()), + mHitInfo(aHitInfo), + mHitArea(aHitArea) { +#ifdef NS_BUILD_REFCNT_LOGGING + MOZ_COUNT_CTOR(nsDisplayScrollInfoLayer); +#endif +} + +UniquePtr<ScrollMetadata> nsDisplayScrollInfoLayer::ComputeScrollMetadata( + nsDisplayListBuilder* aBuilder, WebRenderLayerManager* aLayerManager) { + ScrollMetadata metadata = nsLayoutUtils::ComputeScrollMetadata( + mScrolledFrame, mScrollFrame, mScrollFrame->GetContent(), Frame(), + ToReferenceFrame(), aLayerManager, mScrollParentId, + mScrollFrame->GetSize(), false); + metadata.GetMetrics().SetIsScrollInfoLayer(true); + nsIScrollableFrame* scrollableFrame = mScrollFrame->GetScrollTargetFrame(); + if (scrollableFrame) { + aBuilder->AddScrollFrameToNotify(scrollableFrame); + } + + return UniquePtr<ScrollMetadata>(new ScrollMetadata(metadata)); +} + +bool nsDisplayScrollInfoLayer::UpdateScrollData( + WebRenderScrollData* aData, WebRenderLayerScrollData* aLayerData) { + if (aLayerData) { + UniquePtr<ScrollMetadata> metadata = + ComputeScrollMetadata(aData->GetBuilder(), aData->GetManager()); + MOZ_ASSERT(aData); + MOZ_ASSERT(metadata); + aLayerData->AppendScrollMetadata(*aData, *metadata); + } + return true; +} + +bool nsDisplayScrollInfoLayer::CreateWebRenderCommands( + wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + ScrollableLayerGuid::ViewID scrollId = + nsLayoutUtils::FindOrCreateIDFor(mScrollFrame->GetContent()); + + const LayoutDeviceRect devRect = LayoutDeviceRect::FromAppUnits( + mHitArea, mScrollFrame->PresContext()->AppUnitsPerDevPixel()); + + const wr::LayoutRect rect = wr::ToLayoutRect(devRect); + + aBuilder.PushHitTest(rect, rect, !BackfaceIsHidden(), scrollId, mHitInfo, + SideBits::eNone); + + return true; +} + +void nsDisplayScrollInfoLayer::WriteDebugInfo(std::stringstream& aStream) { + aStream << " (scrollframe " << mScrollFrame << " scrolledFrame " + << mScrolledFrame << ")"; +} + +nsDisplayZoom::nsDisplayZoom(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + nsSubDocumentFrame* aSubDocFrame, + nsDisplayList* aList, int32_t aAPD, + int32_t aParentAPD, nsDisplayOwnLayerFlags aFlags) + : nsDisplaySubDocument(aBuilder, aFrame, aSubDocFrame, aList, aFlags), + mAPD(aAPD), + mParentAPD(aParentAPD) { + MOZ_COUNT_CTOR(nsDisplayZoom); +} + +nsRect nsDisplayZoom::GetBounds(nsDisplayListBuilder* aBuilder, + bool* aSnap) const { + nsRect bounds = nsDisplaySubDocument::GetBounds(aBuilder, aSnap); + *aSnap = false; + return bounds.ScaleToOtherAppUnitsRoundOut(mAPD, mParentAPD); +} + +void nsDisplayZoom::HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect, + HitTestState* aState, + nsTArray<nsIFrame*>* aOutFrames) { + nsRect rect; + // A 1x1 rect indicates we are just hit testing a point, so pass down a 1x1 + // rect as well instead of possibly rounding the width or height to zero. + if (aRect.width == 1 && aRect.height == 1) { + rect.MoveTo(aRect.TopLeft().ScaleToOtherAppUnits(mParentAPD, mAPD)); + rect.width = rect.height = 1; + } else { + rect = aRect.ScaleToOtherAppUnitsRoundOut(mParentAPD, mAPD); + } + mList.HitTest(aBuilder, rect, aState, aOutFrames); +} + +nsDisplayAsyncZoom::nsDisplayAsyncZoom( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayList* aList, + const ActiveScrolledRoot* aActiveScrolledRoot, FrameMetrics::ViewID aViewID) + : nsDisplayOwnLayer(aBuilder, aFrame, aList, aActiveScrolledRoot), + mViewID(aViewID) { + MOZ_COUNT_CTOR(nsDisplayAsyncZoom); +} + +#ifdef NS_BUILD_REFCNT_LOGGING +nsDisplayAsyncZoom::~nsDisplayAsyncZoom() { + MOZ_COUNT_DTOR(nsDisplayAsyncZoom); +} +#endif + +void nsDisplayAsyncZoom::HitTest(nsDisplayListBuilder* aBuilder, + const nsRect& aRect, HitTestState* aState, + nsTArray<nsIFrame*>* aOutFrames) { +#ifdef DEBUG + nsIScrollableFrame* scrollFrame = do_QueryFrame(mFrame); + MOZ_ASSERT(scrollFrame && ViewportUtils::IsZoomedContentRoot( + scrollFrame->GetScrolledFrame())); +#endif + nsRect rect = ViewportUtils::VisualToLayout(aRect, mFrame->PresShell()); + mList.HitTest(aBuilder, rect, aState, aOutFrames); +} + +bool nsDisplayAsyncZoom::UpdateScrollData( + WebRenderScrollData* aData, WebRenderLayerScrollData* aLayerData) { + bool ret = nsDisplayOwnLayer::UpdateScrollData(aData, aLayerData); + MOZ_ASSERT(ret); + if (aLayerData) { + aLayerData->SetAsyncZoomContainerId(mViewID); + } + return ret; +} + +/////////////////////////////////////////////////// +// nsDisplayTransform Implementation +// + +#ifndef DEBUG +static_assert(sizeof(nsDisplayTransform) <= 512, + "nsDisplayTransform has grown"); +#endif + +nsDisplayTransform::nsDisplayTransform(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame, nsDisplayList* aList, + const nsRect& aChildrenBuildingRect) + : nsPaintedDisplayItem(aBuilder, aFrame), + mChildren(aBuilder), + mTransform(Some(Matrix4x4())), + mChildrenBuildingRect(aChildrenBuildingRect), + mPrerenderDecision(PrerenderDecision::No), + mIsTransformSeparator(true), + mHasTransformGetter(false), + mHasAssociatedPerspective(false) { + MOZ_COUNT_CTOR(nsDisplayTransform); + MOZ_ASSERT(aFrame, "Must have a frame!"); + Init(aBuilder, aList); +} + +nsDisplayTransform::nsDisplayTransform(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame, nsDisplayList* aList, + const nsRect& aChildrenBuildingRect, + PrerenderDecision aPrerenderDecision) + : nsPaintedDisplayItem(aBuilder, aFrame), + mChildren(aBuilder), + mChildrenBuildingRect(aChildrenBuildingRect), + mPrerenderDecision(aPrerenderDecision), + mIsTransformSeparator(false), + mHasTransformGetter(false), + mHasAssociatedPerspective(false) { + MOZ_COUNT_CTOR(nsDisplayTransform); + MOZ_ASSERT(aFrame, "Must have a frame!"); + SetReferenceFrameToAncestor(aBuilder); + Init(aBuilder, aList); +} + +nsDisplayTransform::nsDisplayTransform(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame, nsDisplayList* aList, + const nsRect& aChildrenBuildingRect, + decltype(WithTransformGetter)) + : nsPaintedDisplayItem(aBuilder, aFrame), + mChildren(aBuilder), + mChildrenBuildingRect(aChildrenBuildingRect), + mPrerenderDecision(PrerenderDecision::No), + mIsTransformSeparator(false), + mHasTransformGetter(true), + mHasAssociatedPerspective(false) { + MOZ_COUNT_CTOR(nsDisplayTransform); + MOZ_ASSERT(aFrame, "Must have a frame!"); + MOZ_ASSERT(aFrame->GetTransformGetter()); + Init(aBuilder, aList); +} + +void nsDisplayTransform::SetReferenceFrameToAncestor( + nsDisplayListBuilder* aBuilder) { + if (mFrame == aBuilder->RootReferenceFrame()) { + return; + } + // We manually recompute mToReferenceFrame without going through the + // builder, since this won't apply the 'additional offset'. Our + // children will already be painting with that applied, and we don't + // want to include it a second time in our transform. We don't recompute + // our visible/building rects, since those should still include the additional + // offset. + // TODO: Are there are things computed using our ToReferenceFrame that should + // have the additional offset applied? Should we instead just manually remove + // the offset from our transform instead of this more general value? + // Can we instead apply the additional offset to us and not our children, like + // we do for all other offsets (and how reference frames are supposed to + // work)? + nsIFrame* outerFrame = nsLayoutUtils::GetCrossDocParentFrameInProcess(mFrame); + const nsIFrame* referenceFrame = aBuilder->FindReferenceFrameFor(outerFrame); + mToReferenceFrame = mFrame->GetOffsetToCrossDoc(referenceFrame); +} + +void nsDisplayTransform::Init(nsDisplayListBuilder* aBuilder, + nsDisplayList* aChildren) { + mChildren.AppendToTop(aChildren); + UpdateBounds(aBuilder); +} + +bool nsDisplayTransform::ShouldFlattenAway(nsDisplayListBuilder* aBuilder) { + return false; +} + +/* Returns the delta specified by the transform-origin property. + * This is a positive delta, meaning that it indicates the direction to move + * to get from (0, 0) of the frame to the transform origin. This function is + * called off the main thread. + */ +/* static */ +Point3D nsDisplayTransform::GetDeltaToTransformOrigin( + const nsIFrame* aFrame, TransformReferenceBox& aRefBox, + float aAppUnitsPerPixel) { + MOZ_ASSERT(aFrame, "Can't get delta for a null frame!"); + MOZ_ASSERT(aFrame->IsTransformed() || aFrame->BackfaceIsHidden() || + aFrame->Combines3DTransformWithAncestors(), + "Shouldn't get a delta for an untransformed frame!"); + + if (!aFrame->IsTransformed()) { + return Point3D(); + } + + /* For both of the coordinates, if the value of transform is a + * percentage, it's relative to the size of the frame. Otherwise, if it's + * a distance, it's already computed for us! + */ + const nsStyleDisplay* display = aFrame->StyleDisplay(); + + const StyleTransformOrigin& transformOrigin = display->mTransformOrigin; + CSSPoint origin = nsStyleTransformMatrix::Convert2DPosition( + transformOrigin.horizontal, transformOrigin.vertical, aRefBox); + + if (aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) { + // SVG frames (unlike other frames) have a reference box that can be (and + // typically is) offset from the TopLeft() of the frame. We need to account + // for that here. + origin.x += CSSPixel::FromAppUnits(aRefBox.X()); + origin.y += CSSPixel::FromAppUnits(aRefBox.Y()); + } + + float scale = AppUnitsPerCSSPixel() / float(aAppUnitsPerPixel); + float z = transformOrigin.depth._0; + return Point3D(origin.x * scale, origin.y * scale, z * scale); +} + +/* static */ +bool nsDisplayTransform::ComputePerspectiveMatrix(const nsIFrame* aFrame, + float aAppUnitsPerPixel, + Matrix4x4& aOutMatrix) { + MOZ_ASSERT(aFrame, "Can't get delta for a null frame!"); + MOZ_ASSERT(aFrame->IsTransformed() || aFrame->BackfaceIsHidden() || + aFrame->Combines3DTransformWithAncestors(), + "Shouldn't get a delta for an untransformed frame!"); + MOZ_ASSERT(aOutMatrix.IsIdentity(), "Must have a blank output matrix"); + + if (!aFrame->IsTransformed()) { + return false; + } + + // TODO: Is it possible that the perspectiveFrame's bounds haven't been set + // correctly yet (similar to the aBoundsOverride case for + // GetResultingTransformMatrix)? + nsIFrame* perspectiveFrame = + aFrame->GetClosestFlattenedTreeAncestorPrimaryFrame(); + if (!perspectiveFrame) { + return false; + } + + /* Grab the values for perspective and perspective-origin (if present) */ + const nsStyleDisplay* perspectiveDisplay = perspectiveFrame->StyleDisplay(); + if (perspectiveDisplay->mChildPerspective.IsNone()) { + return false; + } + + MOZ_ASSERT(perspectiveDisplay->mChildPerspective.IsLength()); + float perspective = + perspectiveDisplay->mChildPerspective.AsLength().ToCSSPixels(); + perspective = std::max(1.0f, perspective); + if (perspective < std::numeric_limits<Float>::epsilon()) { + return true; + } + + TransformReferenceBox refBox(perspectiveFrame); + + Point perspectiveOrigin = nsStyleTransformMatrix::Convert2DPosition( + perspectiveDisplay->mPerspectiveOrigin.horizontal, + perspectiveDisplay->mPerspectiveOrigin.vertical, refBox, + aAppUnitsPerPixel); + + /* GetOffsetTo computes the offset required to move from 0,0 in + * perspectiveFrame to 0,0 in aFrame. Although we actually want the inverse of + * this, it's faster to compute this way. + */ + nsPoint frameToPerspectiveOffset = -aFrame->GetOffsetTo(perspectiveFrame); + Point frameToPerspectiveGfxOffset( + NSAppUnitsToFloatPixels(frameToPerspectiveOffset.x, aAppUnitsPerPixel), + NSAppUnitsToFloatPixels(frameToPerspectiveOffset.y, aAppUnitsPerPixel)); + + /* Move the perspective origin to be relative to aFrame, instead of relative + * to the containing block which is how it was specified in the style system. + */ + perspectiveOrigin += frameToPerspectiveGfxOffset; + + aOutMatrix._34 = + -1.0 / NSAppUnitsToFloatPixels(CSSPixel::ToAppUnits(perspective), + aAppUnitsPerPixel); + + aOutMatrix.ChangeBasis(Point3D(perspectiveOrigin.x, perspectiveOrigin.y, 0)); + return true; +} + +nsDisplayTransform::FrameTransformProperties::FrameTransformProperties( + const nsIFrame* aFrame, TransformReferenceBox& aRefBox, + float aAppUnitsPerPixel) + : mFrame(aFrame), + mTranslate(aFrame->StyleDisplay()->mTranslate), + mRotate(aFrame->StyleDisplay()->mRotate), + mScale(aFrame->StyleDisplay()->mScale), + mTransform(aFrame->StyleDisplay()->mTransform), + mMotion(MotionPathUtils::ResolveMotionPath(aFrame, aRefBox)), + mToTransformOrigin( + GetDeltaToTransformOrigin(aFrame, aRefBox, aAppUnitsPerPixel)) {} + +/* Wraps up the transform matrix in a change-of-basis matrix pair that + * translates from local coordinate space to transform coordinate space, then + * hands it back. + */ +Matrix4x4 nsDisplayTransform::GetResultingTransformMatrix( + const FrameTransformProperties& aProperties, TransformReferenceBox& aRefBox, + float aAppUnitsPerPixel) { + return GetResultingTransformMatrixInternal(aProperties, aRefBox, nsPoint(), + aAppUnitsPerPixel, 0); +} + +Matrix4x4 nsDisplayTransform::GetResultingTransformMatrix( + const nsIFrame* aFrame, const nsPoint& aOrigin, float aAppUnitsPerPixel, + uint32_t aFlags) { + TransformReferenceBox refBox(aFrame); + FrameTransformProperties props(aFrame, refBox, aAppUnitsPerPixel); + return GetResultingTransformMatrixInternal(props, refBox, aOrigin, + aAppUnitsPerPixel, aFlags); +} + +Matrix4x4 nsDisplayTransform::GetResultingTransformMatrixInternal( + const FrameTransformProperties& aProperties, TransformReferenceBox& aRefBox, + const nsPoint& aOrigin, float aAppUnitsPerPixel, uint32_t aFlags) { + const nsIFrame* frame = aProperties.mFrame; + NS_ASSERTION(frame || !(aFlags & INCLUDE_PERSPECTIVE), + "Must have a frame to compute perspective!"); + + // Get the underlying transform matrix: + + /* Get the matrix, then change its basis to factor in the origin. */ + Matrix4x4 result; + // Call IsSVGTransformed() regardless of the value of + // aProperties.HasTransform(), since we still need any + // potential parentsChildrenOnlyTransform. + Matrix svgTransform, parentsChildrenOnlyTransform; + const bool hasSVGTransforms = + frame && frame->HasAnyStateBits(NS_FRAME_MAY_BE_TRANSFORMED) && + frame->IsSVGTransformed(&svgTransform, &parentsChildrenOnlyTransform); + bool shouldRound = nsLayoutUtils::ShouldSnapToGrid(frame); + + /* Transformed frames always have a transform, or are preserving 3d (and might + * still have perspective!) */ + if (aProperties.HasTransform()) { + result = nsStyleTransformMatrix::ReadTransforms( + aProperties.mTranslate, aProperties.mRotate, aProperties.mScale, + aProperties.mMotion, aProperties.mTransform, aRefBox, + aAppUnitsPerPixel); + } else if (hasSVGTransforms) { + // Correct the translation components for zoom: + float pixelsPerCSSPx = AppUnitsPerCSSPixel() / aAppUnitsPerPixel; + svgTransform._31 *= pixelsPerCSSPx; + svgTransform._32 *= pixelsPerCSSPx; + result = Matrix4x4::From2D(svgTransform); + } + + // Apply any translation due to 'transform-origin' and/or 'transform-box': + result.ChangeBasis(aProperties.mToTransformOrigin); + + // See the comment for SVGContainerFrame::HasChildrenOnlyTransform for + // an explanation of what children-only transforms are. + const bool parentHasChildrenOnlyTransform = + hasSVGTransforms && !parentsChildrenOnlyTransform.IsIdentity(); + + if (parentHasChildrenOnlyTransform) { + float pixelsPerCSSPx = AppUnitsPerCSSPixel() / aAppUnitsPerPixel; + parentsChildrenOnlyTransform._31 *= pixelsPerCSSPx; + parentsChildrenOnlyTransform._32 *= pixelsPerCSSPx; + + Point3D frameOffset( + NSAppUnitsToFloatPixels(-frame->GetPosition().x, aAppUnitsPerPixel), + NSAppUnitsToFloatPixels(-frame->GetPosition().y, aAppUnitsPerPixel), 0); + Matrix4x4 parentsChildrenOnlyTransform3D = + Matrix4x4::From2D(parentsChildrenOnlyTransform) + .ChangeBasis(frameOffset); + + result *= parentsChildrenOnlyTransform3D; + } + + Matrix4x4 perspectiveMatrix; + bool hasPerspective = aFlags & INCLUDE_PERSPECTIVE; + if (hasPerspective) { + if (ComputePerspectiveMatrix(frame, aAppUnitsPerPixel, perspectiveMatrix)) { + result *= perspectiveMatrix; + } + } + + if ((aFlags & INCLUDE_PRESERVE3D_ANCESTORS) && frame && + frame->Combines3DTransformWithAncestors()) { + // Include the transform set on our parent + nsIFrame* parentFrame = + frame->GetClosestFlattenedTreeAncestorPrimaryFrame(); + NS_ASSERTION(parentFrame && parentFrame->IsTransformed() && + parentFrame->Extend3DContext(), + "Preserve3D mismatch!"); + TransformReferenceBox refBox(parentFrame); + FrameTransformProperties props(parentFrame, refBox, aAppUnitsPerPixel); + + uint32_t flags = + aFlags & (INCLUDE_PRESERVE3D_ANCESTORS | INCLUDE_PERSPECTIVE); + + // If this frame isn't transformed (but we exist for backface-visibility), + // then we're not a reference frame so no offset to origin will be added. + // Otherwise we need to manually translate into our parent's coordinate + // space. + if (frame->IsTransformed()) { + nsLayoutUtils::PostTranslate(result, frame->GetPosition(), + aAppUnitsPerPixel, shouldRound); + } + Matrix4x4 parent = GetResultingTransformMatrixInternal( + props, refBox, nsPoint(0, 0), aAppUnitsPerPixel, flags); + result = result * parent; + } + + if (aFlags & OFFSET_BY_ORIGIN) { + nsLayoutUtils::PostTranslate(result, aOrigin, aAppUnitsPerPixel, + shouldRound); + } + + return result; +} + +bool nsDisplayOpacity::CanUseAsyncAnimations(nsDisplayListBuilder* aBuilder) { + static constexpr nsCSSPropertyIDSet opacitySet = + nsCSSPropertyIDSet::OpacityProperties(); + if (ActiveLayerTracker::IsStyleAnimated(aBuilder, mFrame, opacitySet)) { + return true; + } + + EffectCompositor::SetPerformanceWarning( + mFrame, opacitySet, + AnimationPerformanceWarning( + AnimationPerformanceWarning::Type::OpacityFrameInactive)); + + return false; +} + +bool nsDisplayTransform::CanUseAsyncAnimations(nsDisplayListBuilder* aBuilder) { + return mPrerenderDecision != PrerenderDecision::No; +} + +bool nsDisplayBackgroundColor::CanUseAsyncAnimations( + nsDisplayListBuilder* aBuilder) { + return StaticPrefs::gfx_omta_background_color(); +} + +static bool IsInStickyPositionedSubtree(const nsIFrame* aFrame) { + for (const nsIFrame* frame = aFrame; frame; + frame = nsLayoutUtils::GetCrossDocParentFrameInProcess(frame)) { + if (frame->IsStickyPositioned()) { + return true; + } + } + return false; +} + +static bool ShouldUsePartialPrerender(const nsIFrame* aFrame) { + return StaticPrefs::layout_animation_prerender_partial() && + // Bug 1642547: Support partial prerender for position:sticky elements. + !IsInStickyPositionedSubtree(aFrame); +} + +/* static */ +auto nsDisplayTransform::ShouldPrerenderTransformedContent( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsRect* aDirtyRect) + -> PrerenderInfo { + PrerenderInfo result; + // If we are in a preserve-3d tree, and we've disallowed async animations, we + // return No prerender decision directly. + if ((aFrame->Extend3DContext() || + aFrame->Combines3DTransformWithAncestors()) && + !aBuilder->GetPreserves3DAllowAsyncAnimation()) { + return result; + } + + // Elements whose transform has been modified recently, or which + // have a compositor-animated transform, can be prerendered. An element + // might have only just had its transform animated in which case + // the ActiveLayerManager may not have been notified yet. + static constexpr nsCSSPropertyIDSet transformSet = + nsCSSPropertyIDSet::TransformLikeProperties(); + if (!ActiveLayerTracker::IsTransformMaybeAnimated(aFrame) && + !EffectCompositor::HasAnimationsForCompositor( + aFrame, DisplayItemType::TYPE_TRANSFORM)) { + EffectCompositor::SetPerformanceWarning( + aFrame, transformSet, + AnimationPerformanceWarning( + AnimationPerformanceWarning::Type::TransformFrameInactive)); + + // This case happens when we're sure that the frame is not animated and its + // preserve-3d ancestors are not, either. So we don't need to pre-render. + // However, this decision shouldn't affect the decisions for other frames in + // the preserve-3d context. We need this flag to determine whether we should + // block async animations on other frames in the current preserve-3d tree. + result.mHasAnimations = false; + return result; + } + + // We should not allow prerender if any ancestor container element has + // mask/clip-path effects. + // + // With prerender and async transform animation, we do not need to restyle an + // animated element to respect position changes, since that transform is done + // by layer animation. As a result, the container element is not aware of + // position change of that containing element and loses the chance to update + // the content of mask/clip-path. + // + // Why do we need to update a mask? This is relative to how we generate a + // mask layer in ContainerState::SetupMaskLayerForCSSMask. While creating a + // mask layer, to reduce memory usage, we did not choose the size of the + // masked element as mask size. Instead, we read the union of bounds of all + // children display items by nsDisplayWrapList::GetBounds, which is smaller + // than or equal to the masked element's boundary, and use it as the position + // size of the mask layer. That union bounds is actually affected by the + // geometry of the animated element. To keep the content of mask up to date, + // forbidding of prerender is required. + for (nsIFrame* container = + nsLayoutUtils::GetCrossDocParentFrameInProcess(aFrame); + container; + container = nsLayoutUtils::GetCrossDocParentFrameInProcess(container)) { + const nsStyleSVGReset* svgReset = container->StyleSVGReset(); + if (svgReset->HasMask() || svgReset->HasClipPath()) { + return result; + } + } + + // If the incoming dirty rect already contains the entire overflow area, + // we are already rendering the entire content. + nsRect overflow = aFrame->InkOverflowRectRelativeToSelf(); + // UntransformRect will not touch the output rect (`&untranformedDirtyRect`) + // in cases of non-invertible transforms, so we set `untransformedRect` to + // `aDirtyRect` as an initial value for such cases. + nsRect untransformedDirtyRect = *aDirtyRect; + UntransformRect(*aDirtyRect, overflow, aFrame, &untransformedDirtyRect); + if (untransformedDirtyRect.Contains(overflow)) { + *aDirtyRect = untransformedDirtyRect; + result.mDecision = PrerenderDecision::Full; + return result; + } + + float viewportRatio = + StaticPrefs::layout_animation_prerender_viewport_ratio_limit(); + uint32_t absoluteLimitX = + StaticPrefs::layout_animation_prerender_absolute_limit_x(); + uint32_t absoluteLimitY = + StaticPrefs::layout_animation_prerender_absolute_limit_y(); + nsSize refSize = aBuilder->RootReferenceFrame()->GetSize(); + + float resolution = aFrame->PresShell()->GetCumulativeResolution(); + if (resolution < 1.0f) { + refSize.SizeTo( + NSCoordSaturatingNonnegativeMultiply(refSize.width, 1.0f / resolution), + NSCoordSaturatingNonnegativeMultiply(refSize.height, + 1.0f / resolution)); + } + + // Only prerender if the transformed frame's size is <= a multiple of the + // reference frame size (~viewport), and less than an absolute limit. + // Both the ratio and the absolute limit are configurable. + nscoord maxLength = std::max(nscoord(refSize.width * viewportRatio), + nscoord(refSize.height * viewportRatio)); + nsSize relativeLimit(maxLength, maxLength); + nsSize absoluteLimit( + aFrame->PresContext()->DevPixelsToAppUnits(absoluteLimitX), + aFrame->PresContext()->DevPixelsToAppUnits(absoluteLimitY)); + nsSize maxSize = Min(relativeLimit, absoluteLimit); + + const auto transform = nsLayoutUtils::GetTransformToAncestor( + RelativeTo{aFrame}, + RelativeTo{nsLayoutUtils::GetDisplayRootFrame(aFrame)}); + const gfxRect transformedBounds = transform.TransformAndClipBounds( + gfxRect(overflow.x, overflow.y, overflow.width, overflow.height), + gfxRect::MaxIntRect()); + const nsSize frameSize = + nsSize(transformedBounds.width, transformedBounds.height); + + uint64_t maxLimitArea = uint64_t(maxSize.width) * maxSize.height; + uint64_t frameArea = uint64_t(frameSize.width) * frameSize.height; + if (frameArea <= maxLimitArea && frameSize <= absoluteLimit) { + *aDirtyRect = overflow; + result.mDecision = PrerenderDecision::Full; + return result; + } + + if (ShouldUsePartialPrerender(aFrame)) { + *aDirtyRect = nsLayoutUtils::ComputePartialPrerenderArea( + aFrame, untransformedDirtyRect, overflow, maxSize); + result.mDecision = PrerenderDecision::Partial; + return result; + } + + if (frameArea > maxLimitArea) { + uint64_t appUnitsPerPixel = AppUnitsPerCSSPixel(); + EffectCompositor::SetPerformanceWarning( + aFrame, transformSet, + AnimationPerformanceWarning( + AnimationPerformanceWarning::Type::ContentTooLargeArea, + { + int(frameArea / (appUnitsPerPixel * appUnitsPerPixel)), + int(maxLimitArea / (appUnitsPerPixel * appUnitsPerPixel)), + })); + } else { + EffectCompositor::SetPerformanceWarning( + aFrame, transformSet, + AnimationPerformanceWarning( + AnimationPerformanceWarning::Type::ContentTooLarge, + { + nsPresContext::AppUnitsToIntCSSPixels(frameSize.width), + nsPresContext::AppUnitsToIntCSSPixels(frameSize.height), + nsPresContext::AppUnitsToIntCSSPixels(relativeLimit.width), + nsPresContext::AppUnitsToIntCSSPixels(relativeLimit.height), + nsPresContext::AppUnitsToIntCSSPixels(absoluteLimit.width), + nsPresContext::AppUnitsToIntCSSPixels(absoluteLimit.height), + })); + } + + return result; +} + +/* If the matrix is singular, or a hidden backface is shown, the frame won't be + * visible or hit. */ +static bool IsFrameVisible(nsIFrame* aFrame, const Matrix4x4& aMatrix) { + if (aMatrix.IsSingular()) { + return false; + } + if (aFrame->BackfaceIsHidden() && aMatrix.IsBackfaceVisible()) { + return false; + } + return true; +} + +const Matrix4x4Flagged& nsDisplayTransform::GetTransform() const { + if (mTransform) { + return *mTransform; + } + + float scale = mFrame->PresContext()->AppUnitsPerDevPixel(); + + if (mHasTransformGetter) { + mTransform.emplace((mFrame->GetTransformGetter())(mFrame, scale)); + Point3D newOrigin = + Point3D(NSAppUnitsToFloatPixels(mToReferenceFrame.x, scale), + NSAppUnitsToFloatPixels(mToReferenceFrame.y, scale), 0.0f); + mTransform->ChangeBasis(newOrigin.x, newOrigin.y, newOrigin.z); + } else if (!mIsTransformSeparator) { + DebugOnly<bool> isReference = mFrame->IsTransformed() || + mFrame->Combines3DTransformWithAncestors() || + mFrame->Extend3DContext(); + MOZ_ASSERT(isReference); + mTransform.emplace( + GetResultingTransformMatrix(mFrame, ToReferenceFrame(), scale, + INCLUDE_PERSPECTIVE | OFFSET_BY_ORIGIN)); + } else { + // Use identity matrix + mTransform.emplace(); + } + + return *mTransform; +} + +const Matrix4x4Flagged& nsDisplayTransform::GetInverseTransform() const { + if (mInverseTransform) { + return *mInverseTransform; + } + + MOZ_ASSERT(!GetTransform().IsSingular()); + + mInverseTransform.emplace(GetTransform().Inverse()); + + return *mInverseTransform; +} + +Matrix4x4 nsDisplayTransform::GetTransformForRendering( + LayoutDevicePoint* aOutOrigin) const { + if (!mFrame->HasPerspective() || mHasTransformGetter || + mIsTransformSeparator) { + if (!mHasTransformGetter && !mIsTransformSeparator && aOutOrigin) { + // If aOutOrigin is provided, put the offset to origin into it, because + // we need to keep it separate for webrender. The combination of + // *aOutOrigin and the returned matrix here should always be equivalent + // to what GetTransform() would have returned. + float scale = mFrame->PresContext()->AppUnitsPerDevPixel(); + *aOutOrigin = LayoutDevicePoint::FromAppUnits(ToReferenceFrame(), scale); + + // The rounding behavior should also be the same as GetTransform(). + if (nsLayoutUtils::ShouldSnapToGrid(mFrame)) { + aOutOrigin->Round(); + } + return GetResultingTransformMatrix(mFrame, nsPoint(0, 0), scale, + INCLUDE_PERSPECTIVE); + } + return GetTransform().GetMatrix(); + } + MOZ_ASSERT(!mHasTransformGetter); + + float scale = mFrame->PresContext()->AppUnitsPerDevPixel(); + // Don't include perspective transform, or the offset to origin, since + // nsDisplayPerspective will handle both of those. + return GetResultingTransformMatrix(mFrame, ToReferenceFrame(), scale, 0); +} + +const Matrix4x4& nsDisplayTransform::GetAccumulatedPreserved3DTransform( + nsDisplayListBuilder* aBuilder) { + MOZ_ASSERT(!mFrame->Extend3DContext() || IsLeafOf3DContext()); + + if (!IsLeafOf3DContext()) { + return GetTransform().GetMatrix(); + } + + if (!mTransformPreserves3D) { + const nsIFrame* establisher; // Establisher of the 3D rendering context. + for (establisher = mFrame; + establisher && establisher->Combines3DTransformWithAncestors(); + establisher = + establisher->GetClosestFlattenedTreeAncestorPrimaryFrame()) { + } + const nsIFrame* establisherReference = aBuilder->FindReferenceFrameFor( + nsLayoutUtils::GetCrossDocParentFrameInProcess(establisher)); + + nsPoint offset = establisher->GetOffsetToCrossDoc(establisherReference); + float scale = mFrame->PresContext()->AppUnitsPerDevPixel(); + uint32_t flags = + INCLUDE_PRESERVE3D_ANCESTORS | INCLUDE_PERSPECTIVE | OFFSET_BY_ORIGIN; + mTransformPreserves3D = MakeUnique<Matrix4x4>( + GetResultingTransformMatrix(mFrame, offset, scale, flags)); + } + + return *mTransformPreserves3D; +} + +bool nsDisplayTransform::CreateWebRenderCommands( + wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + // We want to make sure we don't pollute the transform property in the WR + // stacking context by including the position of this frame (relative to the + // parent reference frame). We need to keep those separate; the position of + // this frame goes into the stacking context bounds while the transform goes + // into the transform. + LayoutDevicePoint position; + Matrix4x4 newTransformMatrix = GetTransformForRendering(&position); + + gfx::Matrix4x4* transformForSC = &newTransformMatrix; + if (newTransformMatrix.IsIdentity()) { + // If the transform is an identity transform, strip it out so that WR + // doesn't turn this stacking context into a reference frame, as it + // affects positioning. Bug 1345577 tracks a better fix. + transformForSC = nullptr; + + // In ChooseScaleAndSetTransform, we round the offset from the reference + // frame used to adjust the transform, if there is no transform, or it + // is just a translation. We need to do the same here. + if (nsLayoutUtils::ShouldSnapToGrid(mFrame)) { + position.Round(); + } + } + + auto key = wr::SpatialKey(uint64_t(mFrame), GetPerFrameKey(), + wr::SpatialKeyKind::Transform); + + // We don't send animations for transform separator display items. + uint64_t animationsId = + mIsTransformSeparator + ? 0 + : AddAnimationsForWebRender( + this, aManager, aDisplayListBuilder, + IsPartialPrerender() ? Some(position) : Nothing()); + wr::WrAnimationProperty prop{wr::WrAnimationType::Transform, animationsId, + key}; + + nsDisplayTransform* deferredTransformItem = nullptr; + if (!mFrame->ChildrenHavePerspective()) { + // If it has perspective, we create a new scroll data via the + // UpdateScrollData call because that scenario is more complex. Otherwise + // we can just stash the transform on the StackingContextHelper and + // apply it to any scroll data that are created inside this + // nsDisplayTransform. + deferredTransformItem = this; + } + + // Determine if we're possibly animated (= would need an active layer in FLB). + bool animated = !mIsTransformSeparator && + ActiveLayerTracker::IsTransformMaybeAnimated(Frame()); + + wr::StackingContextParams params; + params.mBoundTransform = &newTransformMatrix; + params.animation = animationsId ? &prop : nullptr; + + wr::WrTransformInfo transform_info; + if (transformForSC) { + transform_info.transform = wr::ToLayoutTransform(newTransformMatrix); + transform_info.key = key; + params.mTransformPtr = &transform_info; + } else { + params.mTransformPtr = nullptr; + } + + params.prim_flags = !BackfaceIsHidden() + ? wr::PrimitiveFlags::IS_BACKFACE_VISIBLE + : wr::PrimitiveFlags{0}; + params.paired_with_perspective = mHasAssociatedPerspective; + params.mDeferredTransformItem = deferredTransformItem; + params.mAnimated = animated; + // Determine if we would have to rasterize any items in local raster space + // (i.e. disable subpixel AA). We don't always need to rasterize locally even + // if the stacking context is possibly animated (at the cost of potentially + // some false negatives with respect to will-change handling), so we pass in + // this determination separately to accurately match with when FLB would + // normally disable subpixel AA. + params.mRasterizeLocally = animated && Frame()->HasAnimationOfTransform(); + params.SetPreserve3D(mFrame->Extend3DContext() && !mIsTransformSeparator); + params.clip = + wr::WrStackingContextClip::ClipChain(aBuilder.CurrentClipChainId()); + + LayoutDeviceSize boundsSize = LayoutDeviceSize::FromAppUnits( + mChildBounds.Size(), mFrame->PresContext()->AppUnitsPerDevPixel()); + + StackingContextHelper sc(aSc, GetActiveScrolledRoot(), mFrame, this, aBuilder, + params, LayoutDeviceRect(position, boundsSize)); + + aManager->CommandBuilder().CreateWebRenderCommandsFromDisplayList( + GetChildren(), this, aDisplayListBuilder, sc, aBuilder, aResources); + return true; +} + +bool nsDisplayTransform::UpdateScrollData( + WebRenderScrollData* aData, WebRenderLayerScrollData* aLayerData) { + if (!mFrame->ChildrenHavePerspective()) { + // This case is handled in CreateWebRenderCommands by stashing the transform + // on the stacking context. + return false; + } + if (aLayerData) { + aLayerData->SetTransform(GetTransform().GetMatrix()); + aLayerData->SetTransformIsPerspective(true); + } + return true; +} + +bool nsDisplayTransform::ShouldSkipTransform( + nsDisplayListBuilder* aBuilder) const { + return (aBuilder->RootReferenceFrame() == mFrame) && + aBuilder->IsForGenerateGlyphMask(); +} + +void nsDisplayTransform::Collect3DTransformLeaves( + nsDisplayListBuilder* aBuilder, nsTArray<nsDisplayTransform*>& aLeaves) { + if (!IsParticipating3DContext() || IsLeafOf3DContext()) { + aLeaves.AppendElement(this); + return; + } + + FlattenedDisplayListIterator iter(aBuilder, &mChildren); + while (iter.HasNext()) { + nsDisplayItem* item = iter.GetNextItem(); + if (item->GetType() == DisplayItemType::TYPE_PERSPECTIVE) { + auto* perspective = static_cast<nsDisplayPerspective*>(item); + if (!perspective->GetChildren()->GetTop()) { + continue; + } + item = perspective->GetChildren()->GetTop(); + } + if (item->GetType() != DisplayItemType::TYPE_TRANSFORM) { + gfxCriticalError() << "Invalid child item within 3D transform of type: " + << item->Name(); + continue; + } + static_cast<nsDisplayTransform*>(item)->Collect3DTransformLeaves(aBuilder, + aLeaves); + } +} + +static RefPtr<gfx::Path> BuildPathFromPolygon(const RefPtr<DrawTarget>& aDT, + const gfx::Polygon& aPolygon) { + MOZ_ASSERT(!aPolygon.IsEmpty()); + + RefPtr<PathBuilder> pathBuilder = aDT->CreatePathBuilder(); + const nsTArray<Point4D>& points = aPolygon.GetPoints(); + + pathBuilder->MoveTo(points[0].As2DPoint()); + + for (size_t i = 1; i < points.Length(); ++i) { + pathBuilder->LineTo(points[i].As2DPoint()); + } + + pathBuilder->Close(); + return pathBuilder->Finish(); +} + +void nsDisplayTransform::CollectSorted3DTransformLeaves( + nsDisplayListBuilder* aBuilder, nsTArray<TransformPolygon>& aLeaves) { + std::list<TransformPolygon> inputLayers; + + nsTArray<nsDisplayTransform*> leaves; + Collect3DTransformLeaves(aBuilder, leaves); + for (nsDisplayTransform* item : leaves) { + auto bounds = LayoutDeviceRect::FromAppUnits( + item->mChildBounds, item->mFrame->PresContext()->AppUnitsPerDevPixel()); + Matrix4x4 transform = item->GetAccumulatedPreserved3DTransform(aBuilder); + + if (!IsFrameVisible(item->mFrame, transform)) { + continue; + } + gfx::Polygon polygon = + gfx::Polygon::FromRect(gfx::Rect(bounds.ToUnknownRect())); + + polygon.TransformToScreenSpace(transform); + + if (polygon.GetPoints().Length() >= 3) { + inputLayers.push_back(TransformPolygon(item, std::move(polygon))); + } + } + + if (inputLayers.empty()) { + return; + } + + BSPTree<nsDisplayTransform> tree(inputLayers); + nsTArray<TransformPolygon> orderedLayers(tree.GetDrawOrder()); + + for (TransformPolygon& polygon : orderedLayers) { + Matrix4x4 inverse = + polygon.data->GetAccumulatedPreserved3DTransform(aBuilder).Inverse(); + + MOZ_ASSERT(polygon.geometry); + polygon.geometry->TransformToLayerSpace(inverse); + } + + aLeaves = std::move(orderedLayers); +} + +void nsDisplayTransform::Paint(nsDisplayListBuilder* aBuilder, + gfxContext* aCtx) { + Paint(aBuilder, aCtx, Nothing()); +} + +void nsDisplayTransform::Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx, + const Maybe<gfx::Polygon>& aPolygon) { + if (IsParticipating3DContext() && !IsLeafOf3DContext()) { + MOZ_ASSERT(!aPolygon); + nsTArray<TransformPolygon> leaves; + CollectSorted3DTransformLeaves(aBuilder, leaves); + for (TransformPolygon& item : leaves) { + item.data->Paint(aBuilder, aCtx, item.geometry); + } + return; + } + + gfxContextMatrixAutoSaveRestore saveMatrix(aCtx); + Matrix4x4 trans = ShouldSkipTransform(aBuilder) + ? Matrix4x4() + : GetAccumulatedPreserved3DTransform(aBuilder); + if (!IsFrameVisible(mFrame, trans)) { + return; + } + + Matrix trans2d; + if (trans.CanDraw2D(&trans2d)) { + aCtx->Multiply(ThebesMatrix(trans2d)); + + if (aPolygon) { + RefPtr<gfx::Path> path = + BuildPathFromPolygon(aCtx->GetDrawTarget(), *aPolygon); + aCtx->GetDrawTarget()->PushClip(path); + } + + GetChildren()->Paint(aBuilder, aCtx, + mFrame->PresContext()->AppUnitsPerDevPixel()); + + if (aPolygon) { + aCtx->GetDrawTarget()->PopClip(); + } + return; + } + + // TODO: Implement 3d transform handling, including plane splitting and + // sorting. See BasicCompositor. + auto pixelBounds = LayoutDeviceRect::FromAppUnitsToOutside( + mChildBounds, mFrame->PresContext()->AppUnitsPerDevPixel()); + RefPtr<DrawTarget> untransformedDT = + gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget( + IntSize(pixelBounds.Width(), pixelBounds.Height()), + SurfaceFormat::B8G8R8A8, true); + if (!untransformedDT || !untransformedDT->IsValid()) { + return; + } + untransformedDT->SetTransform( + Matrix::Translation(-Point(pixelBounds.X(), pixelBounds.Y()))); + + gfxContext groupTarget(untransformedDT, /* aPreserveTransform */ true); + + if (aPolygon) { + RefPtr<gfx::Path> path = + BuildPathFromPolygon(aCtx->GetDrawTarget(), *aPolygon); + aCtx->GetDrawTarget()->PushClip(path); + } + + GetChildren()->Paint(aBuilder, &groupTarget, + mFrame->PresContext()->AppUnitsPerDevPixel()); + + if (aPolygon) { + aCtx->GetDrawTarget()->PopClip(); + } + + RefPtr<SourceSurface> untransformedSurf = untransformedDT->Snapshot(); + + trans.PreTranslate(pixelBounds.X(), pixelBounds.Y(), 0); + aCtx->GetDrawTarget()->Draw3DTransformedSurface(untransformedSurf, trans); +} + +bool nsDisplayTransform::MayBeAnimated(nsDisplayListBuilder* aBuilder) const { + // If EffectCompositor::HasAnimationsForCompositor() is true then we can + // completely bypass the main thread for this animation, so it is always + // worthwhile. + // For ActiveLayerTracker::IsTransformAnimated() cases the main thread is + // already involved so there is less to be gained. + // Therefore we check that the *post-transform* bounds of this item are + // big enough to justify an active layer. + return EffectCompositor::HasAnimationsForCompositor( + mFrame, DisplayItemType::TYPE_TRANSFORM) || + (ActiveLayerTracker::IsTransformAnimated(aBuilder, mFrame)); +} + +nsRect nsDisplayTransform::TransformUntransformedBounds( + nsDisplayListBuilder* aBuilder, const Matrix4x4Flagged& aMatrix) const { + bool snap; + const nsRect untransformedBounds = GetUntransformedBounds(aBuilder, &snap); + // GetTransform always operates in dev pixels. + const float factor = mFrame->PresContext()->AppUnitsPerDevPixel(); + return nsLayoutUtils::MatrixTransformRect(untransformedBounds, aMatrix, + factor); +} + +/** + * Returns the bounds for this transform. The bounds are calculated during + * display list building and merging, see |nsDisplayTransform::UpdateBounds()|. + */ +nsRect nsDisplayTransform::GetBounds(nsDisplayListBuilder* aBuilder, + bool* aSnap) const { + *aSnap = false; + return mBounds; +} + +void nsDisplayTransform::ComputeBounds(nsDisplayListBuilder* aBuilder) { + MOZ_ASSERT(mFrame->Extend3DContext() || IsLeafOf3DContext()); + + /* Some transforms can get empty bounds in 2D, but might get transformed again + * and get non-empty bounds. A simple example of this would be a 180 degree + * rotation getting applied twice. + * We should not depend on transforming bounds level by level. + * + * This function collects the bounds of this transform and stores it in + * nsDisplayListBuilder. If this is not a leaf of a 3D context, we recurse + * down and include the bounds of the child transforms. + * The bounds are transformed with the accumulated transformation matrix up to + * the 3D context root coordinate space. + */ + nsDisplayListBuilder::AutoAccumulateTransform accTransform(aBuilder); + accTransform.Accumulate(GetTransform().GetMatrix()); + + // Do not dive into another 3D context. + if (!IsLeafOf3DContext()) { + for (nsDisplayItem* i : *GetChildren()) { + i->DoUpdateBoundsPreserves3D(aBuilder); + } + } + + /* The child transforms that extend 3D context further will have empty bounds, + * so the untransformed bounds here is the bounds of all the non-preserve-3d + * content under this transform. + */ + const nsRect rect = TransformUntransformedBounds( + aBuilder, accTransform.GetCurrentTransform()); + aBuilder->AccumulateRect(rect); +} + +void nsDisplayTransform::DoUpdateBoundsPreserves3D( + nsDisplayListBuilder* aBuilder) { + MOZ_ASSERT(mFrame->Combines3DTransformWithAncestors() || + IsTransformSeparator()); + // Updating is not going through to child 3D context. + ComputeBounds(aBuilder); +} + +void nsDisplayTransform::UpdateBounds(nsDisplayListBuilder* aBuilder) { + UpdateUntransformedBounds(aBuilder); + + if (IsTransformSeparator()) { + MOZ_ASSERT(GetTransform().IsIdentity()); + mBounds = mChildBounds; + return; + } + + if (mFrame->Extend3DContext()) { + if (!Combines3DTransformWithAncestors()) { + // The transform establishes a 3D context. |UpdateBoundsFor3D()| will + // collect the bounds from the child transforms. + UpdateBoundsFor3D(aBuilder); + } else { + // With nested 3D transforms, the 2D bounds might not be useful. + mBounds = nsRect(); + } + + return; + } + + MOZ_ASSERT(!mFrame->Extend3DContext()); + + // We would like to avoid calculating 2D bounds here for nested 3D transforms, + // but mix-blend-mode relies on having bounds set. See bug 1556956. + + // A stand-alone transform. + mBounds = TransformUntransformedBounds(aBuilder, GetTransform()); +} + +void nsDisplayTransform::UpdateBoundsFor3D(nsDisplayListBuilder* aBuilder) { + MOZ_ASSERT(mFrame->Extend3DContext() && + !mFrame->Combines3DTransformWithAncestors() && + !IsTransformSeparator()); + + // Always start updating from an establisher of a 3D rendering context. + nsDisplayListBuilder::AutoAccumulateRect accRect(aBuilder); + nsDisplayListBuilder::AutoAccumulateTransform accTransform(aBuilder); + accTransform.StartRoot(); + ComputeBounds(aBuilder); + mBounds = aBuilder->GetAccumulatedRect(); +} + +void nsDisplayTransform::UpdateUntransformedBounds( + nsDisplayListBuilder* aBuilder) { + mChildBounds = GetChildren()->GetClippedBoundsWithRespectToASR( + aBuilder, mActiveScrolledRoot); +} + +#ifdef DEBUG_HIT +# include <time.h> +#endif + +/* HitTest does some fun stuff with matrix transforms to obtain the answer. */ +void nsDisplayTransform::HitTest(nsDisplayListBuilder* aBuilder, + const nsRect& aRect, HitTestState* aState, + nsTArray<nsIFrame*>* aOutFrames) { + if (aState->mInPreserves3D) { + GetChildren()->HitTest(aBuilder, aRect, aState, aOutFrames); + return; + } + + /* Here's how this works: + * 1. Get the matrix. If it's singular, abort (clearly we didn't hit + * anything). + * 2. Invert the matrix. + * 3. Use it to transform the rect into the correct space. + * 4. Pass that rect down through to the list's version of HitTest. + */ + // GetTransform always operates in dev pixels. + float factor = mFrame->PresContext()->AppUnitsPerDevPixel(); + Matrix4x4 matrix = GetAccumulatedPreserved3DTransform(aBuilder); + + if (!IsFrameVisible(mFrame, matrix)) { + return; + } + + const bool oldHitOccludingItem = aState->mHitOccludingItem; + + /* We want to go from transformed-space to regular space. + * Thus we have to invert the matrix, which normally does + * the reverse operation (e.g. regular->transformed) + */ + + /* Now, apply the transform and pass it down the channel. */ + matrix.Invert(); + nsRect resultingRect; + // Magic width/height indicating we're hit testing a point, not a rect + const bool testingPoint = aRect.width == 1 && aRect.height == 1; + if (testingPoint) { + Point4D point = + matrix.ProjectPoint(Point(NSAppUnitsToFloatPixels(aRect.x, factor), + NSAppUnitsToFloatPixels(aRect.y, factor))); + if (!point.HasPositiveWCoord()) { + return; + } + + Point point2d = point.As2DPoint(); + + resultingRect = + nsRect(NSFloatPixelsToAppUnits(float(point2d.x), factor), + NSFloatPixelsToAppUnits(float(point2d.y), factor), 1, 1); + + } else { + Rect originalRect(NSAppUnitsToFloatPixels(aRect.x, factor), + NSAppUnitsToFloatPixels(aRect.y, factor), + NSAppUnitsToFloatPixels(aRect.width, factor), + NSAppUnitsToFloatPixels(aRect.height, factor)); + + Rect childGfxBounds(NSAppUnitsToFloatPixels(mChildBounds.x, factor), + NSAppUnitsToFloatPixels(mChildBounds.y, factor), + NSAppUnitsToFloatPixels(mChildBounds.width, factor), + NSAppUnitsToFloatPixels(mChildBounds.height, factor)); + + Rect rect = matrix.ProjectRectBounds(originalRect, childGfxBounds); + + resultingRect = + nsRect(NSFloatPixelsToAppUnits(float(rect.X()), factor), + NSFloatPixelsToAppUnits(float(rect.Y()), factor), + NSFloatPixelsToAppUnits(float(rect.Width()), factor), + NSFloatPixelsToAppUnits(float(rect.Height()), factor)); + } + + if (resultingRect.IsEmpty()) { + return; + } + +#ifdef DEBUG_HIT + printf("Frame: %p\n", dynamic_cast<void*>(mFrame)); + printf(" Untransformed point: (%f, %f)\n", resultingRect.X(), + resultingRect.Y()); + uint32_t originalFrameCount = aOutFrames.Length(); +#endif + + GetChildren()->HitTest(aBuilder, resultingRect, aState, aOutFrames); + + if (aState->mHitOccludingItem && !testingPoint && + !mChildBounds.Contains(aRect)) { + MOZ_ASSERT(aBuilder->HitTestIsForVisibility()); + // We're hit-testing a rect that's bigger than our child bounds, but + // resultingRect is clipped by our bounds (in ProjectRectBounds above), so + // we can't stop hit-testing altogether. + // + // FIXME(emilio): I think this means that theoretically we might include + // some frames fully behind other transformed-but-opaque frames? Then again + // that's our pre-existing behavior for other untransformed content that + // doesn't fill the whole rect. To be fully correct I think we'd need proper + // "known occluded region" tracking, but that might be overkill for our + // purposes here. + aState->mHitOccludingItem = oldHitOccludingItem; + } + +#ifdef DEBUG_HIT + if (originalFrameCount != aOutFrames.Length()) + printf(" Hit! Time: %f, first frame: %p\n", static_cast<double>(clock()), + dynamic_cast<void*>(aOutFrames.ElementAt(0))); + printf("=== end of hit test ===\n"); +#endif +} + +float nsDisplayTransform::GetHitDepthAtPoint(nsDisplayListBuilder* aBuilder, + const nsPoint& aPoint) { + // GetTransform always operates in dev pixels. + float factor = mFrame->PresContext()->AppUnitsPerDevPixel(); + Matrix4x4 matrix = GetAccumulatedPreserved3DTransform(aBuilder); + + NS_ASSERTION(IsFrameVisible(mFrame, matrix), + "We can't have hit a frame that isn't visible!"); + + Matrix4x4 inverse = matrix; + inverse.Invert(); + Point4D point = + inverse.ProjectPoint(Point(NSAppUnitsToFloatPixels(aPoint.x, factor), + NSAppUnitsToFloatPixels(aPoint.y, factor))); + + Point point2d = point.As2DPoint(); + + Point3D transformed = matrix.TransformPoint(Point3D(point2d.x, point2d.y, 0)); + return transformed.z; +} + +/* The transform is opaque iff the transform consists solely of scales and + * translations and if the underlying content is opaque. Thus if the transform + * is of the form + * + * |a c e| + * |b d f| + * |0 0 1| + * + * We need b and c to be zero. + * + * We also need to check whether the underlying opaque content completely fills + * our visible rect. We use UntransformRect which expands to the axis-aligned + * bounding rect, but that's OK since if + * mStoredList.GetVisibleRect().Contains(untransformedVisible), then it + * certainly contains the actual (non-axis-aligned) untransformed rect. + */ +nsRegion nsDisplayTransform::GetOpaqueRegion(nsDisplayListBuilder* aBuilder, + bool* aSnap) const { + *aSnap = false; + + nsRect untransformedVisible; + if (!UntransformBuildingRect(aBuilder, &untransformedVisible)) { + return nsRegion(); + } + + const Matrix4x4Flagged& matrix = GetTransform(); + Matrix matrix2d; + if (!matrix.Is2D(&matrix2d) || !matrix2d.PreservesAxisAlignedRectangles()) { + return nsRegion(); + } + + nsRegion result; + + bool tmpSnap; + const nsRect bounds = GetUntransformedBounds(aBuilder, &tmpSnap); + const nsRegion opaque = + ::mozilla::GetOpaqueRegion(aBuilder, GetChildren(), bounds); + + if (opaque.Contains(untransformedVisible)) { + result = GetBuildingRect().Intersect(GetBounds(aBuilder, &tmpSnap)); + } + return result; +} + +nsRect nsDisplayTransform::GetComponentAlphaBounds( + nsDisplayListBuilder* aBuilder) const { + if (GetChildren()->GetComponentAlphaBounds(aBuilder).IsEmpty()) { + return nsRect(); + } + + bool snap; + return GetBounds(aBuilder, &snap); +} + +/* TransformRect takes in as parameters a rectangle (in app space) and returns + * the smallest rectangle (in app space) containing the transformed image of + * that rectangle. That is, it takes the four corners of the rectangle, + * transforms them according to the matrix associated with the specified frame, + * then returns the smallest rectangle containing the four transformed points. + * + * @param aUntransformedBounds The rectangle (in app units) to transform. + * @param aFrame The frame whose transformation should be applied. + * @param aOrigin The delta from the frame origin to the coordinate space origin + * @return The smallest rectangle containing the image of the transformed + * rectangle. + */ +nsRect nsDisplayTransform::TransformRect(const nsRect& aUntransformedBounds, + const nsIFrame* aFrame, + TransformReferenceBox& aRefBox) { + MOZ_ASSERT(aFrame, "Can't take the transform based on a null frame!"); + + float factor = aFrame->PresContext()->AppUnitsPerDevPixel(); + + FrameTransformProperties props(aFrame, aRefBox, factor); + return nsLayoutUtils::MatrixTransformRect( + aUntransformedBounds, + GetResultingTransformMatrixInternal(props, aRefBox, nsPoint(), factor, + kTransformRectFlags), + factor); +} + +bool nsDisplayTransform::UntransformRect(const nsRect& aTransformedBounds, + const nsRect& aChildBounds, + const nsIFrame* aFrame, + nsRect* aOutRect) { + MOZ_ASSERT(aFrame, "Can't take the transform based on a null frame!"); + + float factor = aFrame->PresContext()->AppUnitsPerDevPixel(); + Matrix4x4 transform = GetResultingTransformMatrix(aFrame, nsPoint(), factor, + kTransformRectFlags); + return UntransformRect(aTransformedBounds, aChildBounds, transform, factor, + aOutRect); +} + +bool nsDisplayTransform::UntransformRect(const nsRect& aTransformedBounds, + const nsRect& aChildBounds, + const Matrix4x4& aMatrix, + float aAppUnitsPerPixel, + nsRect* aOutRect) { + if (aMatrix.IsSingular()) { + return false; + } + + RectDouble result( + NSAppUnitsToFloatPixels(aTransformedBounds.x, aAppUnitsPerPixel), + NSAppUnitsToFloatPixels(aTransformedBounds.y, aAppUnitsPerPixel), + NSAppUnitsToFloatPixels(aTransformedBounds.width, aAppUnitsPerPixel), + NSAppUnitsToFloatPixels(aTransformedBounds.height, aAppUnitsPerPixel)); + + RectDouble childGfxBounds( + NSAppUnitsToFloatPixels(aChildBounds.x, aAppUnitsPerPixel), + NSAppUnitsToFloatPixels(aChildBounds.y, aAppUnitsPerPixel), + NSAppUnitsToFloatPixels(aChildBounds.width, aAppUnitsPerPixel), + NSAppUnitsToFloatPixels(aChildBounds.height, aAppUnitsPerPixel)); + + result = aMatrix.Inverse().ProjectRectBounds(result, childGfxBounds); + *aOutRect = nsLayoutUtils::RoundGfxRectToAppRect(ThebesRect(result), + aAppUnitsPerPixel); + return true; +} + +bool nsDisplayTransform::UntransformRect(nsDisplayListBuilder* aBuilder, + const nsRect& aRect, + nsRect* aOutRect) const { + if (GetTransform().IsSingular()) { + return false; + } + + // GetTransform always operates in dev pixels. + float factor = mFrame->PresContext()->AppUnitsPerDevPixel(); + RectDouble result(NSAppUnitsToFloatPixels(aRect.x, factor), + NSAppUnitsToFloatPixels(aRect.y, factor), + NSAppUnitsToFloatPixels(aRect.width, factor), + NSAppUnitsToFloatPixels(aRect.height, factor)); + + bool snap; + nsRect childBounds = GetUntransformedBounds(aBuilder, &snap); + RectDouble childGfxBounds( + NSAppUnitsToFloatPixels(childBounds.x, factor), + NSAppUnitsToFloatPixels(childBounds.y, factor), + NSAppUnitsToFloatPixels(childBounds.width, factor), + NSAppUnitsToFloatPixels(childBounds.height, factor)); + + /* We want to untransform the matrix, so invert the transformation first! */ + result = GetInverseTransform().ProjectRectBounds(result, childGfxBounds); + + *aOutRect = nsLayoutUtils::RoundGfxRectToAppRect(ThebesRect(result), factor); + + return true; +} + +void nsDisplayTransform::WriteDebugInfo(std::stringstream& aStream) { + aStream << GetTransform().GetMatrix(); + if (IsTransformSeparator()) { + aStream << " transform-separator"; + } + if (IsLeafOf3DContext()) { + aStream << " 3d-context-leaf"; + } + if (mFrame->Extend3DContext()) { + aStream << " extends-3d-context"; + } + if (mFrame->Combines3DTransformWithAncestors()) { + aStream << " combines-3d-with-ancestors"; + } + + aStream << " prerender("; + switch (mPrerenderDecision) { + case PrerenderDecision::No: + aStream << "no"; + break; + case PrerenderDecision::Partial: + aStream << "partial"; + break; + case PrerenderDecision::Full: + aStream << "full"; + break; + } + aStream << ")"; + aStream << " childrenBuildingRect" << mChildrenBuildingRect; +} + +nsDisplayPerspective::nsDisplayPerspective(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame, + nsDisplayList* aList) + : nsPaintedDisplayItem(aBuilder, aFrame), mList(aBuilder) { + mList.AppendToTop(aList); + MOZ_ASSERT(mList.Length() == 1); + MOZ_ASSERT(mList.GetTop()->GetType() == DisplayItemType::TYPE_TRANSFORM); +} + +void nsDisplayPerspective::Paint(nsDisplayListBuilder* aBuilder, + gfxContext* aCtx) { + // Just directly recurse into children, since we'll include the persepctive + // value in any nsDisplayTransform children. + GetChildren()->Paint(aBuilder, aCtx, + mFrame->PresContext()->AppUnitsPerDevPixel()); +} + +nsRegion nsDisplayPerspective::GetOpaqueRegion(nsDisplayListBuilder* aBuilder, + bool* aSnap) const { + if (!GetChildren()->GetTop()) { + *aSnap = false; + return nsRegion(); + } + + return GetChildren()->GetTop()->GetOpaqueRegion(aBuilder, aSnap); +} + +bool nsDisplayPerspective::CreateWebRenderCommands( + wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + float appUnitsPerPixel = mFrame->PresContext()->AppUnitsPerDevPixel(); + Matrix4x4 perspectiveMatrix; + DebugOnly<bool> hasPerspective = nsDisplayTransform::ComputePerspectiveMatrix( + mFrame, appUnitsPerPixel, perspectiveMatrix); + MOZ_ASSERT(hasPerspective, "Why did we create nsDisplayPerspective?"); + + /* + * ClipListToRange can remove our child after we were created. + */ + if (!GetChildren()->GetTop()) { + return false; + } + + /* + * The resulting matrix is still in the coordinate space of the transformed + * frame. Append a translation to the reference frame coordinates. + */ + nsDisplayTransform* transform = + static_cast<nsDisplayTransform*>(GetChildren()->GetTop()); + + Point3D newOrigin = + Point3D(NSAppUnitsToFloatPixels(transform->ToReferenceFrame().x, + appUnitsPerPixel), + NSAppUnitsToFloatPixels(transform->ToReferenceFrame().y, + appUnitsPerPixel), + 0.0f); + Point3D roundedOrigin(NS_round(newOrigin.x), NS_round(newOrigin.y), 0); + + perspectiveMatrix.PostTranslate(roundedOrigin); + + nsIFrame* perspectiveFrame = + mFrame->GetClosestFlattenedTreeAncestorPrimaryFrame(); + + // Passing true here is always correct, since perspective always combines + // transforms with the descendants. However that'd make WR do a lot of work + // that it doesn't really need to do if there aren't other transforms forming + // part of the 3D context. + // + // WR knows how to treat perspective in that case, so the only thing we need + // to do is to ensure we pass true when we're involved in a 3d context in any + // other way via the transform-style property on either the transformed frame + // or the perspective frame in order to not confuse WR's preserve-3d code in + // very awful ways. + bool preserve3D = + mFrame->Extend3DContext() || perspectiveFrame->Extend3DContext(); + + wr::StackingContextParams params; + + wr::WrTransformInfo transform_info; + transform_info.transform = wr::ToLayoutTransform(perspectiveMatrix); + transform_info.key = wr::SpatialKey(uint64_t(mFrame), GetPerFrameKey(), + wr::SpatialKeyKind::Perspective); + params.mTransformPtr = &transform_info; + + params.reference_frame_kind = wr::WrReferenceFrameKind::Perspective; + params.prim_flags = !BackfaceIsHidden() + ? wr::PrimitiveFlags::IS_BACKFACE_VISIBLE + : wr::PrimitiveFlags{0}; + params.SetPreserve3D(preserve3D); + params.clip = + wr::WrStackingContextClip::ClipChain(aBuilder.CurrentClipChainId()); + + Maybe<uint64_t> scrollingRelativeTo; + for (const auto* asr = GetActiveScrolledRoot(); asr; asr = asr->mParent) { + // In OOP documents, the root scrollable frame of the in-process root + // document is always active, so using IsAncestorFrameCrossDocInProcess + // should be fine here. + if (nsLayoutUtils::IsAncestorFrameCrossDocInProcess( + asr->mScrollableFrame->GetScrolledFrame(), perspectiveFrame)) { + scrollingRelativeTo.emplace(asr->GetViewId()); + break; + } + } + + // We put the perspective reference frame wrapping the transformed frame, + // even though there may be arbitrarily nested scroll frames in between. + // + // We need to know how many ancestor scroll-frames are we nested in, in order + // for the async scrolling code in WebRender to calculate the right + // transformation for the perspective contents. + params.scrolling_relative_to = scrollingRelativeTo.ptrOr(nullptr); + + StackingContextHelper sc(aSc, GetActiveScrolledRoot(), mFrame, this, aBuilder, + params); + + aManager->CommandBuilder().CreateWebRenderCommandsFromDisplayList( + GetChildren(), this, aDisplayListBuilder, sc, aBuilder, aResources); + + return true; +} + +nsDisplayText::nsDisplayText(nsDisplayListBuilder* aBuilder, + nsTextFrame* aFrame) + : nsPaintedDisplayItem(aBuilder, aFrame), + mVisIStartEdge(0), + mVisIEndEdge(0) { + MOZ_COUNT_CTOR(nsDisplayText); + mBounds = mFrame->InkOverflowRectRelativeToSelf() + ToReferenceFrame(); + // Bug 748228 + mBounds.Inflate(mFrame->PresContext()->AppUnitsPerDevPixel()); + mVisibleRect = aBuilder->GetVisibleRect() + + aBuilder->GetCurrentFrameOffsetToReferenceFrame(); +} + +bool nsDisplayText::CanApplyOpacity(WebRenderLayerManager* aManager, + nsDisplayListBuilder* aBuilder) const { + auto* f = static_cast<nsTextFrame*>(mFrame); + + if (f->IsSelected()) { + return false; + } + + const nsStyleText* textStyle = f->StyleText(); + if (textStyle->HasTextShadow()) { + return false; + } + + nsTextFrame::TextDecorations decorations; + f->GetTextDecorations(f->PresContext(), nsTextFrame::eResolvedColors, + decorations); + return !decorations.HasDecorationLines(); +} + +void nsDisplayText::Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) { + AUTO_PROFILER_LABEL("nsDisplayText::Paint", GRAPHICS); + // We don't pass mVisibleRect here, since this can be called from within + // the WebRender fallback painting path, and we don't want to issue + // recorded commands that are dependent on the visible/building rect. + RenderToContext(aCtx, aBuilder, GetPaintRect(aBuilder, aCtx)); +} + +bool nsDisplayText::CreateWebRenderCommands( + wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + auto* f = static_cast<nsTextFrame*>(mFrame); + auto appUnitsPerDevPixel = f->PresContext()->AppUnitsPerDevPixel(); + + nsRect bounds = f->WebRenderBounds() + ToReferenceFrame(); + // Bug 748228 + bounds.Inflate(appUnitsPerDevPixel); + + if (bounds.IsEmpty()) { + return true; + } + + // For large font sizes, punt to a blob image, to avoid the blurry rendering + // that results from WR clamping the glyph size used for rasterization. + // + // (See FONT_SIZE_LIMIT in webrender/src/glyph_rasterizer/mod.rs.) + // + // This is not strictly accurate, as final used font sizes might not be the + // same as claimed by the fontGroup's style.size (eg: due to font-size-adjust + // altering the used size of the font actually used). + // It also fails to consider how transforms might affect the device-font-size + // that webrender uses (and clamps). + // But it should be near enough for practical purposes; the limitations just + // mean we might sometimes end up with webrender still applying some bitmap + // scaling, or bail out when we didn't really need to. + constexpr float kWebRenderFontSizeLimit = 320.0; + f->EnsureTextRun(nsTextFrame::eInflated); + gfxTextRun* textRun = f->GetTextRun(nsTextFrame::eInflated); + if (textRun && + textRun->GetFontGroup()->GetStyle()->size > kWebRenderFontSizeLimit) { + return false; + } + + gfx::Point deviceOffset = + LayoutDevicePoint::FromAppUnits(bounds.TopLeft(), appUnitsPerDevPixel) + .ToUnknownPoint(); + + // Clipping the bounds to the PaintRect (factoring in what's covered by parent + // frames) lets us early reject a bunch of things. + nsRect visible = mVisibleRect; + + // Add the "source rect" area from which the given shadows could intersect + // with mVisibleRect, and which therefore needs to included in the paint + // operation, to the `visible` rect that we will use to limit the bounds of + // what we send to the renderer. + auto addShadowSourceToVisible = [&](Span<const StyleSimpleShadow> aShadows) { + for (const auto& shadow : aShadows) { + nsRect sourceRect = mVisibleRect; + // Negate the offsets, because we're looking for the "source" rect that + // could cast a shadow into the visible rect, rather than a "target" area + // onto which the visible rect would cast a shadow. + sourceRect.MoveBy(-shadow.horizontal.ToAppUnits(), + -shadow.vertical.ToAppUnits()); + // Inflate to account for the shadow blur. + sourceRect.Inflate(nsContextBoxBlur::GetBlurRadiusMargin( + shadow.blur.ToAppUnits(), appUnitsPerDevPixel)); + visible.OrWith(sourceRect); + } + }; + + // Shadows can translate things back into view, so we enlarge the notional + // "visible" rect to ensure we don't skip painting relevant parts that might + // cast a shadow within the visible area. + addShadowSourceToVisible(f->StyleText()->mTextShadow.AsSpan()); + + // Similarly for shadows that may be cast by ::selection. + if (f->IsSelected()) { + nsTextPaintStyle textPaint(f); + Span<const StyleSimpleShadow> shadows; + f->GetSelectionTextShadow(SelectionType::eNormal, textPaint, &shadows); + addShadowSourceToVisible(shadows); + } + + // Inflate a little extra to allow for potential antialiasing "blur". + visible.Inflate(3 * appUnitsPerDevPixel); + bounds = bounds.Intersect(visible); + + gfxContext* textDrawer = aBuilder.GetTextContext(aResources, aSc, aManager, + this, bounds, deviceOffset); + + aBuilder.StartGroup(this); + + RenderToContext(textDrawer, aDisplayListBuilder, mVisibleRect, + aBuilder.GetInheritedOpacity(), true); + const bool result = textDrawer->GetTextDrawer()->Finish(); + + if (result) { + aBuilder.FinishGroup(); + } else { + aBuilder.CancelGroup(true); + } + + return result; +} + +void nsDisplayText::RenderToContext(gfxContext* aCtx, + nsDisplayListBuilder* aBuilder, + const nsRect& aVisibleRect, float aOpacity, + bool aIsRecording) { + nsTextFrame* f = static_cast<nsTextFrame*>(mFrame); + + // Add 1 pixel of dirty area around mVisibleRect to allow us to paint + // antialiased pixels beyond the measured text extents. + // This is temporary until we do this in the actual calculation of text + // extents. + auto A2D = mFrame->PresContext()->AppUnitsPerDevPixel(); + LayoutDeviceRect extraVisible = + LayoutDeviceRect::FromAppUnits(aVisibleRect, A2D); + extraVisible.Inflate(1); + + gfxRect pixelVisible(extraVisible.x, extraVisible.y, extraVisible.width, + extraVisible.height); + pixelVisible.Inflate(2); + pixelVisible.RoundOut(); + + gfxClipAutoSaveRestore autoSaveClip(aCtx); + if (!aBuilder->IsForGenerateGlyphMask() && !aIsRecording) { + autoSaveClip.Clip(pixelVisible); + } + + NS_ASSERTION(mVisIStartEdge >= 0, "illegal start edge"); + NS_ASSERTION(mVisIEndEdge >= 0, "illegal end edge"); + + gfxContextMatrixAutoSaveRestore matrixSR; + + nsPoint framePt = ToReferenceFrame(); + if (f->Style()->IsTextCombined()) { + float scaleFactor = nsTextFrame::GetTextCombineScaleFactor(f); + if (scaleFactor != 1.0f) { + if (auto* textDrawer = aCtx->GetTextDrawer()) { + // WebRender doesn't support scaling text like this yet + textDrawer->FoundUnsupportedFeature(); + return; + } + matrixSR.SetContext(aCtx); + // Setup matrix to compress text for text-combine-upright if + // necessary. This is done here because we want selection be + // compressed at the same time as text. + gfxPoint pt = nsLayoutUtils::PointToGfxPoint(framePt, A2D); + gfxMatrix mat = aCtx->CurrentMatrixDouble() + .PreTranslate(pt) + .PreScale(scaleFactor, 1.0) + .PreTranslate(-pt); + aCtx->SetMatrixDouble(mat); + } + } + nsTextFrame::PaintTextParams params(aCtx); + params.framePt = gfx::Point(framePt.x, framePt.y); + params.dirtyRect = extraVisible; + + if (aBuilder->IsForGenerateGlyphMask()) { + params.state = nsTextFrame::PaintTextParams::GenerateTextMask; + } else { + params.state = nsTextFrame::PaintTextParams::PaintText; + } + + f->PaintText(params, mVisIStartEdge, mVisIEndEdge, ToReferenceFrame(), + f->IsSelected(), aOpacity); +} + +// This could go to nsDisplayListInvalidation.h, but +// |nsTextFrame::TextDecorations| requires including of nsTextFrame.h which +// would produce circular dependencies. +class nsDisplayTextGeometry : public nsDisplayItemGenericGeometry { + public: + nsDisplayTextGeometry(nsDisplayText* aItem, nsDisplayListBuilder* aBuilder) + : nsDisplayItemGenericGeometry(aItem, aBuilder), + mVisIStartEdge(aItem->VisIStartEdge()), + mVisIEndEdge(aItem->VisIEndEdge()) { + nsTextFrame* f = static_cast<nsTextFrame*>(aItem->Frame()); + f->GetTextDecorations(f->PresContext(), nsTextFrame::eResolvedColors, + mDecorations); + } + + /** + * We store the computed text decorations here since they are + * computed using style data from parent frames. Any changes to these + * styles will only invalidate the parent frame and not this frame. + */ + nsTextFrame::TextDecorations mDecorations; + nscoord mVisIStartEdge; + nscoord mVisIEndEdge; +}; + +nsDisplayItemGeometry* nsDisplayText::AllocateGeometry( + nsDisplayListBuilder* aBuilder) { + return new nsDisplayTextGeometry(this, aBuilder); +} + +void nsDisplayText::ComputeInvalidationRegion( + nsDisplayListBuilder* aBuilder, const nsDisplayItemGeometry* aGeometry, + nsRegion* aInvalidRegion) const { + const nsDisplayTextGeometry* geometry = + static_cast<const nsDisplayTextGeometry*>(aGeometry); + nsTextFrame* f = static_cast<nsTextFrame*>(mFrame); + + nsTextFrame::TextDecorations decorations; + f->GetTextDecorations(f->PresContext(), nsTextFrame::eResolvedColors, + decorations); + + bool snap; + const nsRect& newRect = geometry->mBounds; + nsRect oldRect = GetBounds(aBuilder, &snap); + if (decorations != geometry->mDecorations || + mVisIStartEdge != geometry->mVisIStartEdge || + mVisIEndEdge != geometry->mVisIEndEdge || + !oldRect.IsEqualInterior(newRect) || + !geometry->mBorderRect.IsEqualInterior(GetBorderRect())) { + aInvalidRegion->Or(oldRect, newRect); + } +} + +void nsDisplayText::WriteDebugInfo(std::stringstream& aStream) { +#ifdef DEBUG + aStream << " (\""; + + nsTextFrame* f = static_cast<nsTextFrame*>(mFrame); + nsCString buf; + f->ToCString(buf); + + aStream << buf.get() << "\")"; +#endif +} + +nsDisplayEffectsBase::nsDisplayEffectsBase( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayList* aList, + const ActiveScrolledRoot* aActiveScrolledRoot, bool aClearClipChain) + : nsDisplayWrapList(aBuilder, aFrame, aList, aActiveScrolledRoot, + aClearClipChain) { + MOZ_COUNT_CTOR(nsDisplayEffectsBase); +} + +nsDisplayEffectsBase::nsDisplayEffectsBase(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame, + nsDisplayList* aList) + : nsDisplayWrapList(aBuilder, aFrame, aList) { + MOZ_COUNT_CTOR(nsDisplayEffectsBase); +} + +nsRegion nsDisplayEffectsBase::GetOpaqueRegion(nsDisplayListBuilder* aBuilder, + bool* aSnap) const { + *aSnap = false; + return nsRegion(); +} + +void nsDisplayEffectsBase::HitTest(nsDisplayListBuilder* aBuilder, + const nsRect& aRect, HitTestState* aState, + nsTArray<nsIFrame*>* aOutFrames) { + nsPoint rectCenter(aRect.x + aRect.width / 2, aRect.y + aRect.height / 2); + if (SVGIntegrationUtils::HitTestFrameForEffects( + mFrame, rectCenter - ToReferenceFrame())) { + mList.HitTest(aBuilder, aRect, aState, aOutFrames); + } +} + +gfxRect nsDisplayEffectsBase::BBoxInUserSpace() const { + return SVGUtils::GetBBox(mFrame); +} + +gfxPoint nsDisplayEffectsBase::UserSpaceOffset() const { + return SVGUtils::FrameSpaceInCSSPxToUserSpaceOffset(mFrame); +} + +void nsDisplayEffectsBase::ComputeInvalidationRegion( + nsDisplayListBuilder* aBuilder, const nsDisplayItemGeometry* aGeometry, + nsRegion* aInvalidRegion) const { + const auto* geometry = + static_cast<const nsDisplaySVGEffectGeometry*>(aGeometry); + bool snap; + nsRect bounds = GetBounds(aBuilder, &snap); + if (geometry->mFrameOffsetToReferenceFrame != ToReferenceFrame() || + geometry->mUserSpaceOffset != UserSpaceOffset() || + !geometry->mBBox.IsEqualInterior(BBoxInUserSpace())) { + // Filter and mask output can depend on the location of the frame's user + // space and on the frame's BBox. We need to invalidate if either of these + // change relative to the reference frame. + // Invalidations from our inactive layer manager are not enough to catch + // some of these cases because filters can produce output even if there's + // nothing in the filter input. + aInvalidRegion->Or(bounds, geometry->mBounds); + } +} + +bool nsDisplayEffectsBase::ValidateSVGFrame() { + if (mFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) { + ISVGDisplayableFrame* svgFrame = do_QueryFrame(mFrame); + if (!svgFrame) { + return false; + } + if (auto* svgElement = SVGElement::FromNode(mFrame->GetContent())) { + // The SVG spec says only to draw filters if the element + // has valid dimensions. + return svgElement->HasValidDimensions(); + } + return false; + } + + return true; +} + +using PaintFramesParams = SVGIntegrationUtils::PaintFramesParams; + +static void ComputeMaskGeometry(PaintFramesParams& aParams) { + // Properties are added lazily and may have been removed by a restyle, so + // make sure all applicable ones are set again. + nsIFrame* firstFrame = + nsLayoutUtils::FirstContinuationOrIBSplitSibling(aParams.frame); + + const nsStyleSVGReset* svgReset = firstFrame->StyleSVGReset(); + + nsTArray<SVGMaskFrame*> maskFrames; + // XXX check return value? + SVGObserverUtils::GetAndObserveMasks(firstFrame, &maskFrames); + + if (maskFrames.Length() == 0) { + return; + } + + gfxContext& ctx = aParams.ctx; + nsIFrame* frame = aParams.frame; + + nsPoint offsetToUserSpace = + nsLayoutUtils::ComputeOffsetToUserSpace(aParams.builder, aParams.frame); + + auto cssToDevScale = frame->PresContext()->CSSToDevPixelScale(); + int32_t appUnitsPerDevPixel = frame->PresContext()->AppUnitsPerDevPixel(); + + gfxPoint devPixelOffsetToUserSpace = + nsLayoutUtils::PointToGfxPoint(offsetToUserSpace, appUnitsPerDevPixel); + + gfxContextMatrixAutoSaveRestore matSR(&ctx); + ctx.SetMatrixDouble( + ctx.CurrentMatrixDouble().PreTranslate(devPixelOffsetToUserSpace)); + + // Convert boaderArea and dirtyRect to user space. + nsRect userSpaceBorderArea = aParams.borderArea - offsetToUserSpace; + nsRect userSpaceDirtyRect = aParams.dirtyRect - offsetToUserSpace; + + // Union all mask layer rectangles in user space. + LayoutDeviceRect maskInUserSpace; + for (size_t i = 0; i < maskFrames.Length(); i++) { + SVGMaskFrame* maskFrame = maskFrames[i]; + LayoutDeviceRect currentMaskSurfaceRect; + + if (maskFrame) { + auto rect = maskFrame->GetMaskArea(aParams.frame); + currentMaskSurfaceRect = + CSSRect::FromUnknownRect(ToRect(rect)) * cssToDevScale; + } else { + nsCSSRendering::ImageLayerClipState clipState; + nsCSSRendering::GetImageLayerClip( + svgReset->mMask.mLayers[i], frame, *frame->StyleBorder(), + userSpaceBorderArea, userSpaceDirtyRect, + /* aWillPaintBorder = */ false, appUnitsPerDevPixel, &clipState); + currentMaskSurfaceRect = LayoutDeviceRect::FromUnknownRect( + ToRect(clipState.mDirtyRectInDevPx)); + } + + maskInUserSpace = maskInUserSpace.Union(currentMaskSurfaceRect); + } + + if (!maskInUserSpace.IsEmpty()) { + aParams.maskRect = Some(maskInUserSpace); + } else { + aParams.maskRect = Nothing(); + } +} + +nsDisplayMasksAndClipPaths::nsDisplayMasksAndClipPaths( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayList* aList, + const ActiveScrolledRoot* aActiveScrolledRoot, bool aWrapsBackdropFilter) + : nsDisplayEffectsBase(aBuilder, aFrame, aList, aActiveScrolledRoot, true), + mWrapsBackdropFilter(aWrapsBackdropFilter) { + MOZ_COUNT_CTOR(nsDisplayMasksAndClipPaths); + + nsPresContext* presContext = mFrame->PresContext(); + uint32_t flags = + aBuilder->GetBackgroundPaintFlags() | nsCSSRendering::PAINTBG_MASK_IMAGE; + const nsStyleSVGReset* svgReset = aFrame->StyleSVGReset(); + NS_FOR_VISIBLE_IMAGE_LAYERS_BACK_TO_FRONT(i, svgReset->mMask) { + const auto& layer = svgReset->mMask.mLayers[i]; + if (!layer.mImage.IsResolved()) { + continue; + } + const nsRect& borderArea = mFrame->GetRectRelativeToSelf(); + // NOTE(emilio): We only care about the dest rect so we don't bother + // computing a clip. + bool isTransformedFixed = false; + nsBackgroundLayerState state = nsCSSRendering::PrepareImageLayer( + presContext, aFrame, flags, borderArea, borderArea, layer, + &isTransformedFixed); + mDestRects.AppendElement(state.mDestArea); + } +} + +static bool CanMergeDisplayMaskFrame(nsIFrame* aFrame) { + // Do not merge items for box-decoration-break:clone elements, + // since each box should have its own mask in that case. + if (aFrame->StyleBorder()->mBoxDecorationBreak == + StyleBoxDecorationBreak::Clone) { + return false; + } + + // Do not merge if either frame has a mask. Continuation frames should apply + // the mask independently (just like nsDisplayBackgroundImage). + if (aFrame->StyleSVGReset()->HasMask()) { + return false; + } + + return true; +} + +bool nsDisplayMasksAndClipPaths::CanMerge(const nsDisplayItem* aItem) const { + // Items for the same content element should be merged into a single + // compositing group. + if (!HasDifferentFrame(aItem) || !HasSameTypeAndClip(aItem) || + !HasSameContent(aItem)) { + return false; + } + + return CanMergeDisplayMaskFrame(mFrame) && + CanMergeDisplayMaskFrame(aItem->Frame()); +} + +bool nsDisplayMasksAndClipPaths::IsValidMask() { + if (!ValidateSVGFrame()) { + return false; + } + + nsIFrame* firstFrame = + nsLayoutUtils::FirstContinuationOrIBSplitSibling(mFrame); + + return !(SVGObserverUtils::GetAndObserveClipPath(firstFrame, nullptr) == + SVGObserverUtils::eHasRefsSomeInvalid || + SVGObserverUtils::GetAndObserveMasks(firstFrame, nullptr) == + SVGObserverUtils::eHasRefsSomeInvalid); +} + +bool nsDisplayMasksAndClipPaths::PaintMask(nsDisplayListBuilder* aBuilder, + gfxContext* aMaskContext, + bool aHandleOpacity, + bool* aMaskPainted) { + MOZ_ASSERT(aMaskContext->GetDrawTarget()->GetFormat() == SurfaceFormat::A8); + + imgDrawingParams imgParams(aBuilder->GetImageDecodeFlags()); + nsRect borderArea = nsRect(ToReferenceFrame(), mFrame->GetSize()); + PaintFramesParams params(*aMaskContext, mFrame, mBounds, borderArea, aBuilder, + aHandleOpacity, imgParams); + ComputeMaskGeometry(params); + bool maskIsComplete = false; + bool painted = SVGIntegrationUtils::PaintMask(params, maskIsComplete); + if (aMaskPainted) { + *aMaskPainted = painted; + } + + return maskIsComplete && + (imgParams.result == ImgDrawResult::SUCCESS || + imgParams.result == ImgDrawResult::SUCCESS_NOT_COMPLETE || + imgParams.result == ImgDrawResult::WRONG_SIZE); +} + +void nsDisplayMasksAndClipPaths::ComputeInvalidationRegion( + nsDisplayListBuilder* aBuilder, const nsDisplayItemGeometry* aGeometry, + nsRegion* aInvalidRegion) const { + nsDisplayEffectsBase::ComputeInvalidationRegion(aBuilder, aGeometry, + aInvalidRegion); + + const auto* geometry = + static_cast<const nsDisplayMasksAndClipPathsGeometry*>(aGeometry); + bool snap; + nsRect bounds = GetBounds(aBuilder, &snap); + + if (mDestRects.Length() != geometry->mDestRects.Length()) { + aInvalidRegion->Or(bounds, geometry->mBounds); + } else { + for (size_t i = 0; i < mDestRects.Length(); i++) { + if (!mDestRects[i].IsEqualInterior(geometry->mDestRects[i])) { + aInvalidRegion->Or(bounds, geometry->mBounds); + break; + } + } + } +} + +void nsDisplayMasksAndClipPaths::PaintWithContentsPaintCallback( + nsDisplayListBuilder* aBuilder, gfxContext* aCtx, + const std::function<void()>& aPaintChildren) { + // Clip the drawing target by mVisibleRect, which contains the visible + // region of the target frame and its out-of-flow and inflow descendants. + Rect bounds = NSRectToRect(GetPaintRect(aBuilder, aCtx), + mFrame->PresContext()->AppUnitsPerDevPixel()); + bounds.RoundOut(); + gfxClipAutoSaveRestore autoSaveClip(aCtx); + autoSaveClip.Clip(bounds); + + imgDrawingParams imgParams(aBuilder->GetImageDecodeFlags()); + nsRect borderArea = nsRect(ToReferenceFrame(), mFrame->GetSize()); + PaintFramesParams params(*aCtx, mFrame, GetPaintRect(aBuilder, aCtx), + borderArea, aBuilder, false, imgParams); + + ComputeMaskGeometry(params); + + SVGIntegrationUtils::PaintMaskAndClipPath(params, aPaintChildren); +} + +void nsDisplayMasksAndClipPaths::Paint(nsDisplayListBuilder* aBuilder, + gfxContext* aCtx) { + if (!IsValidMask()) { + return; + } + PaintWithContentsPaintCallback(aBuilder, aCtx, [&] { + GetChildren()->Paint(aBuilder, aCtx, + mFrame->PresContext()->AppUnitsPerDevPixel()); + }); +} + +static Maybe<wr::WrClipChainId> CreateSimpleClipRegion( + const nsDisplayMasksAndClipPaths& aDisplayItem, + wr::DisplayListBuilder& aBuilder) { + nsIFrame* frame = aDisplayItem.Frame(); + const auto* style = frame->StyleSVGReset(); + MOZ_ASSERT(style->HasClipPath() || style->HasMask()); + if (!SVGIntegrationUtils::UsingSimpleClipPathForFrame(frame)) { + return Nothing(); + } + + const auto& clipPath = style->mClipPath; + const auto& shape = *clipPath.AsShape()._0; + + auto appUnitsPerDevPixel = frame->PresContext()->AppUnitsPerDevPixel(); + const nsRect refBox = + nsLayoutUtils::ComputeGeometryBox(frame, clipPath.AsShape()._1); + + wr::WrClipId clipId{}; + + switch (shape.tag) { + case StyleBasicShape::Tag::Inset: { + const nsRect insetRect = ShapeUtils::ComputeInsetRect(shape, refBox) + + aDisplayItem.ToReferenceFrame(); + + nscoord radii[8] = {0}; + + if (ShapeUtils::ComputeInsetRadii(shape, refBox, insetRect, radii)) { + clipId = aBuilder.DefineRoundedRectClip( + Nothing(), + wr::ToComplexClipRegion(insetRect, radii, appUnitsPerDevPixel)); + } else { + clipId = aBuilder.DefineRectClip( + Nothing(), wr::ToLayoutRect(LayoutDeviceRect::FromAppUnits( + insetRect, appUnitsPerDevPixel))); + } + + break; + } + case StyleBasicShape::Tag::Ellipse: + case StyleBasicShape::Tag::Circle: { + nsPoint center = ShapeUtils::ComputeCircleOrEllipseCenter(shape, refBox); + + nsSize radii; + if (shape.IsEllipse()) { + radii = ShapeUtils::ComputeEllipseRadii(shape, center, refBox); + } else { + nscoord radius = ShapeUtils::ComputeCircleRadius(shape, center, refBox); + radii = {radius, radius}; + } + + nsRect ellipseRect(aDisplayItem.ToReferenceFrame() + center - + nsPoint(radii.width, radii.height), + radii * 2); + + nscoord ellipseRadii[8]; + for (const auto corner : AllPhysicalHalfCorners()) { + ellipseRadii[corner] = + HalfCornerIsX(corner) ? radii.width : radii.height; + } + + clipId = aBuilder.DefineRoundedRectClip( + Nothing(), wr::ToComplexClipRegion(ellipseRect, ellipseRadii, + appUnitsPerDevPixel)); + + break; + } + default: + // Please don't add more exceptions, try to find a way to define the clip + // without using a mask image. + // + // And if you _really really_ need to add an exception, add it to + // SVGIntegrationUtils::UsingSimpleClipPathForFrame + MOZ_ASSERT_UNREACHABLE("Unhandled shape id?"); + return Nothing(); + } + + wr::WrClipChainId clipChainId = aBuilder.DefineClipChain({clipId}, true); + + return Some(clipChainId); +} + +static void FillPolygonDataForDisplayItem( + const nsDisplayMasksAndClipPaths& aDisplayItem, + nsTArray<wr::LayoutPoint>& aPoints, wr::FillRule& aFillRule) { + nsIFrame* frame = aDisplayItem.Frame(); + const auto* style = frame->StyleSVGReset(); + bool isPolygon = style->HasClipPath() && style->mClipPath.IsShape() && + style->mClipPath.AsShape()._0->IsPolygon(); + if (!isPolygon) { + return; + } + + const auto& clipPath = style->mClipPath; + const auto& shape = *clipPath.AsShape()._0; + const nsRect refBox = + nsLayoutUtils::ComputeGeometryBox(frame, clipPath.AsShape()._1); + + // We only fill polygon data for polygons that are below a complexity + // limit. + nsTArray<nsPoint> vertices = + ShapeUtils::ComputePolygonVertices(shape, refBox); + if (vertices.Length() > wr::POLYGON_CLIP_VERTEX_MAX) { + return; + } + + auto appUnitsPerDevPixel = frame->PresContext()->AppUnitsPerDevPixel(); + + for (size_t i = 0; i < vertices.Length(); ++i) { + wr::LayoutPoint point = wr::ToLayoutPoint( + LayoutDevicePoint::FromAppUnits(vertices[i], appUnitsPerDevPixel)); + aPoints.AppendElement(point); + } + + aFillRule = (shape.AsPolygon().fill == StyleFillRule::Nonzero) + ? wr::FillRule::Nonzero + : wr::FillRule::Evenodd; +} + +static Maybe<wr::WrClipChainId> CreateWRClipPathAndMasks( + nsDisplayMasksAndClipPaths* aDisplayItem, const LayoutDeviceRect& aBounds, + wr::IpcResourceUpdateQueue& aResources, wr::DisplayListBuilder& aBuilder, + const StackingContextHelper& aSc, RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + if (auto clip = CreateSimpleClipRegion(*aDisplayItem, aBuilder)) { + return clip; + } + + Maybe<wr::ImageMask> mask = aManager->CommandBuilder().BuildWrMaskImage( + aDisplayItem, aBuilder, aResources, aSc, aDisplayListBuilder, aBounds); + if (!mask) { + return Nothing(); + } + + // We couldn't create a simple clip region, but before we create an image + // mask clip, see if we can get a polygon clip to add to it. + nsTArray<wr::LayoutPoint> points; + wr::FillRule fillRule = wr::FillRule::Nonzero; + FillPolygonDataForDisplayItem(*aDisplayItem, points, fillRule); + + wr::WrClipId clipId = + aBuilder.DefineImageMaskClip(mask.ref(), points, fillRule); + + wr::WrClipChainId clipChainId = aBuilder.DefineClipChain({clipId}, true); + + return Some(clipChainId); +} + +bool nsDisplayMasksAndClipPaths::CreateWebRenderCommands( + wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + bool snap; + auto appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel(); + nsRect displayBounds = GetBounds(aDisplayListBuilder, &snap); + LayoutDeviceRect bounds = + LayoutDeviceRect::FromAppUnits(displayBounds, appUnitsPerDevPixel); + + Maybe<wr::WrClipChainId> clip = CreateWRClipPathAndMasks( + this, bounds, aResources, aBuilder, aSc, aManager, aDisplayListBuilder); + + float oldOpacity = aBuilder.GetInheritedOpacity(); + + Maybe<StackingContextHelper> layer; + const StackingContextHelper* sc = &aSc; + if (clip) { + // Create a new stacking context to attach the mask to, ensuring the mask is + // applied to the aggregate, and not the individual elements. + + // The stacking context shouldn't have any offset. + bounds.MoveTo(0, 0); + + Maybe<float> opacity = + (SVGIntegrationUtils::UsingSimpleClipPathForFrame(mFrame) && + aBuilder.GetInheritedOpacity() != 1.0f) + ? Some(aBuilder.GetInheritedOpacity()) + : Nothing(); + + wr::StackingContextParams params; + params.clip = wr::WrStackingContextClip::ClipChain(clip->id); + params.opacity = opacity.ptrOr(nullptr); + if (mWrapsBackdropFilter) { + params.flags |= wr::StackingContextFlags::WRAPS_BACKDROP_FILTER; + } + layer.emplace(aSc, GetActiveScrolledRoot(), mFrame, this, aBuilder, params, + bounds); + sc = layer.ptr(); + } + + aBuilder.SetInheritedOpacity(1.0f); + const DisplayItemClipChain* oldClipChain = aBuilder.GetInheritedClipChain(); + aBuilder.SetInheritedClipChain(nullptr); + CreateWebRenderCommandsNewClipListOption(aBuilder, aResources, *sc, aManager, + aDisplayListBuilder, layer.isSome()); + aBuilder.SetInheritedOpacity(oldOpacity); + aBuilder.SetInheritedClipChain(oldClipChain); + + return true; +} + +Maybe<nsRect> nsDisplayMasksAndClipPaths::GetClipWithRespectToASR( + nsDisplayListBuilder* aBuilder, const ActiveScrolledRoot* aASR) const { + if (const DisplayItemClip* clip = + DisplayItemClipChain::ClipForASR(GetClipChain(), aASR)) { + return Some(clip->GetClipRect()); + } + // This item does not have a clip with respect to |aASR|. However, we + // might still have finite bounds with respect to |aASR|. Check our + // children. + nsDisplayList* childList = GetSameCoordinateSystemChildren(); + if (childList) { + return Some(childList->GetClippedBoundsWithRespectToASR(aBuilder, aASR)); + } +#ifdef DEBUG + NS_ASSERTION(false, "item should have finite clip with respect to aASR"); +#endif + return Nothing(); +} + +#ifdef MOZ_DUMP_PAINTING +void nsDisplayMasksAndClipPaths::PrintEffects(nsACString& aTo) { + nsIFrame* firstFrame = + nsLayoutUtils::FirstContinuationOrIBSplitSibling(mFrame); + bool first = true; + aTo += " effects=("; + SVGClipPathFrame* clipPathFrame; + // XXX Check return value? + SVGObserverUtils::GetAndObserveClipPath(firstFrame, &clipPathFrame); + if (clipPathFrame) { + if (!first) { + aTo += ", "; + } + aTo += nsPrintfCString( + "clip(%s)", clipPathFrame->IsTrivial() ? "trivial" : "non-trivial"); + first = false; + } else if (mFrame->StyleSVGReset()->HasClipPath()) { + if (!first) { + aTo += ", "; + } + aTo += "clip(basic-shape)"; + first = false; + } + + nsTArray<SVGMaskFrame*> masks; + // XXX check return value? + SVGObserverUtils::GetAndObserveMasks(firstFrame, &masks); + if (!masks.IsEmpty() && masks[0]) { + if (!first) { + aTo += ", "; + } + aTo += "mask"; + } + aTo += ")"; +} +#endif + +bool nsDisplayBackdropFilters::CreateWebRenderCommands( + wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + WrFiltersHolder wrFilters; + const ComputedStyle& style = mStyle ? *mStyle : *mFrame->Style(); + auto filterChain = style.StyleEffects()->mBackdropFilters.AsSpan(); + bool initialized = true; + if (!SVGIntegrationUtils::CreateWebRenderCSSFilters(filterChain, mFrame, + wrFilters) && + !SVGIntegrationUtils::BuildWebRenderFilters(mFrame, filterChain, + wrFilters, initialized)) { + // TODO: If painting backdrop-filters on the content side is implemented, + // consider returning false to fall back to that. + wrFilters = {}; + } + + if (!initialized) { + wrFilters = {}; + } + + nsCSSRendering::ImageLayerClipState clip; + nsCSSRendering::GetImageLayerClip( + style.StyleBackground()->BottomLayer(), mFrame, *style.StyleBorder(), + mBackdropRect, mBackdropRect, false, + mFrame->PresContext()->AppUnitsPerDevPixel(), &clip); + + LayoutDeviceRect bounds = LayoutDeviceRect::FromAppUnits( + mBackdropRect, mFrame->PresContext()->AppUnitsPerDevPixel()); + + wr::ComplexClipRegion region = + wr::ToComplexClipRegion(clip.mBGClipArea, clip.mRadii, + mFrame->PresContext()->AppUnitsPerDevPixel()); + + aBuilder.PushBackdropFilter(wr::ToLayoutRect(bounds), region, + wrFilters.filters, wrFilters.filter_datas, + !BackfaceIsHidden()); + + wr::StackingContextParams params; + params.clip = + wr::WrStackingContextClip::ClipChain(aBuilder.CurrentClipChainId()); + StackingContextHelper sc(aSc, GetActiveScrolledRoot(), mFrame, this, aBuilder, + params); + + nsDisplayWrapList::CreateWebRenderCommands(aBuilder, aResources, sc, aManager, + aDisplayListBuilder); + return true; +} + +void nsDisplayBackdropFilters::Paint(nsDisplayListBuilder* aBuilder, + gfxContext* aCtx) { + // TODO: Implement backdrop filters + GetChildren()->Paint(aBuilder, aCtx, + mFrame->PresContext()->AppUnitsPerDevPixel()); +} + +nsRect nsDisplayBackdropFilters::GetBounds(nsDisplayListBuilder* aBuilder, + bool* aSnap) const { + nsRect childBounds = nsDisplayWrapList::GetBounds(aBuilder, aSnap); + + *aSnap = false; + + return mBackdropRect.Union(childBounds); +} + +/* static */ +nsDisplayFilters::nsDisplayFilters(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame, nsDisplayList* aList, + nsIFrame* aStyleFrame, + bool aWrapsBackdropFilter) + : nsDisplayEffectsBase(aBuilder, aFrame, aList), + mStyle(aFrame == aStyleFrame ? nullptr : aStyleFrame->Style()), + mEffectsBounds(aFrame->InkOverflowRectRelativeToSelf()), + mWrapsBackdropFilter(aWrapsBackdropFilter) { + MOZ_COUNT_CTOR(nsDisplayFilters); + mVisibleRect = aBuilder->GetVisibleRect() + + aBuilder->GetCurrentFrameOffsetToReferenceFrame(); +} + +void nsDisplayFilters::Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) { + PaintWithContentsPaintCallback(aBuilder, aCtx, [&](gfxContext* aContext) { + GetChildren()->Paint(aBuilder, aContext, + mFrame->PresContext()->AppUnitsPerDevPixel()); + }); +} + +void nsDisplayFilters::PaintWithContentsPaintCallback( + nsDisplayListBuilder* aBuilder, gfxContext* aCtx, + const std::function<void(gfxContext* aContext)>& aPaintChildren) { + imgDrawingParams imgParams(aBuilder->GetImageDecodeFlags()); + nsRect borderArea = nsRect(ToReferenceFrame(), mFrame->GetSize()); + PaintFramesParams params(*aCtx, mFrame, mVisibleRect, borderArea, aBuilder, + false, imgParams); + + gfxPoint userSpaceToFrameSpaceOffset = + SVGIntegrationUtils::GetOffsetToUserSpaceInDevPx(mFrame, params); + + auto filterChain = mStyle ? mStyle->StyleEffects()->mFilters.AsSpan() + : mFrame->StyleEffects()->mFilters.AsSpan(); + SVGIntegrationUtils::PaintFilter( + params, filterChain, + [&](gfxContext& aContext, imgDrawingParams&, const gfxMatrix*, + const nsIntRect*) { + gfxContextMatrixAutoSaveRestore autoSR(&aContext); + aContext.SetMatrixDouble(aContext.CurrentMatrixDouble().PreTranslate( + -userSpaceToFrameSpaceOffset)); + aPaintChildren(&aContext); + }); +} + +bool nsDisplayFilters::CanCreateWebRenderCommands() const { + return SVGIntegrationUtils::CanCreateWebRenderFiltersForFrame(mFrame); +} + +bool nsDisplayFilters::CreateWebRenderCommands( + wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + WrFiltersHolder wrFilters; + const ComputedStyle& style = mStyle ? *mStyle : *mFrame->Style(); + auto filterChain = style.StyleEffects()->mFilters.AsSpan(); + bool initialized = true; + if (!SVGIntegrationUtils::CreateWebRenderCSSFilters(filterChain, mFrame, + wrFilters) && + !SVGIntegrationUtils::BuildWebRenderFilters(mFrame, filterChain, + wrFilters, initialized)) { + if (mStyle) { + // TODO(bug 1769223): Support fallback filters in the root code-path, + // perhaps. For now treat it the same way as invalid filters. + wrFilters = {}; + } else { + // Draw using fallback. + return false; + } + } + + if (!initialized) { + // https://drafts.fxtf.org/filter-effects/#typedef-filter-url: + // + // If the filter references a non-existent object or the referenced object + // is not a filter element, then the whole filter chain is ignored. No + // filter is applied to the object. + // + // Note that other engines have a weird discrepancy between SVG and HTML + // content here, but the spec is clear. + wrFilters = {}; + } + + uint64_t clipChainId; + if (wrFilters.post_filters_clip) { + auto devPxRect = LayoutDeviceRect::FromAppUnits( + wrFilters.post_filters_clip.value() + ToReferenceFrame(), + mFrame->PresContext()->AppUnitsPerDevPixel()); + auto clipId = + aBuilder.DefineRectClip(Nothing(), wr::ToLayoutRect(devPxRect)); + clipChainId = aBuilder.DefineClipChain({clipId}, true).id; + } else { + clipChainId = aBuilder.CurrentClipChainId(); + } + wr::WrStackingContextClip clip = + wr::WrStackingContextClip::ClipChain(clipChainId); + + float opacity = aBuilder.GetInheritedOpacity(); + aBuilder.SetInheritedOpacity(1.0f); + const DisplayItemClipChain* oldClipChain = aBuilder.GetInheritedClipChain(); + aBuilder.SetInheritedClipChain(nullptr); + wr::StackingContextParams params; + params.mFilters = std::move(wrFilters.filters); + params.mFilterDatas = std::move(wrFilters.filter_datas); + params.opacity = opacity != 1.0f ? &opacity : nullptr; + params.clip = clip; + if (mWrapsBackdropFilter) { + params.flags |= wr::StackingContextFlags::WRAPS_BACKDROP_FILTER; + } + StackingContextHelper sc(aSc, GetActiveScrolledRoot(), mFrame, this, aBuilder, + params); + + nsDisplayEffectsBase::CreateWebRenderCommands(aBuilder, aResources, sc, + aManager, aDisplayListBuilder); + aBuilder.SetInheritedOpacity(opacity); + aBuilder.SetInheritedClipChain(oldClipChain); + + return true; +} + +#ifdef MOZ_DUMP_PAINTING +void nsDisplayFilters::PrintEffects(nsACString& aTo) { + nsIFrame* firstFrame = + nsLayoutUtils::FirstContinuationOrIBSplitSibling(mFrame); + bool first = true; + aTo += " effects=("; + // We may exist for a mix of CSS filter functions and/or references to SVG + // filters. If we have invalid references to SVG filters then we paint + // nothing, but otherwise we will apply one or more filters. + if (SVGObserverUtils::GetAndObserveFilters(firstFrame, nullptr) != + SVGObserverUtils::eHasRefsSomeInvalid) { + if (!first) { + aTo += ", "; + } + aTo += "filter"; + } + aTo += ")"; +} +#endif + +nsDisplaySVGWrapper::nsDisplaySVGWrapper(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame, nsDisplayList* aList) + : nsDisplayWrapList(aBuilder, aFrame, aList) { + MOZ_COUNT_CTOR(nsDisplaySVGWrapper); +} + +bool nsDisplaySVGWrapper::ShouldFlattenAway(nsDisplayListBuilder* aBuilder) { + return !aBuilder->GetWidgetLayerManager(); +} + +bool nsDisplaySVGWrapper::CreateWebRenderCommands( + wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + return CreateWebRenderCommandsNewClipListOption( + aBuilder, aResources, aSc, aManager, aDisplayListBuilder, false); +} + +nsDisplayForeignObject::nsDisplayForeignObject(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame, + nsDisplayList* aList) + : nsDisplayWrapList(aBuilder, aFrame, aList) { + MOZ_COUNT_CTOR(nsDisplayForeignObject); +} + +#ifdef NS_BUILD_REFCNT_LOGGING +nsDisplayForeignObject::~nsDisplayForeignObject() { + MOZ_COUNT_DTOR(nsDisplayForeignObject); +} +#endif + +bool nsDisplayForeignObject::ShouldFlattenAway(nsDisplayListBuilder* aBuilder) { + return !aBuilder->GetWidgetLayerManager(); +} + +bool nsDisplayForeignObject::CreateWebRenderCommands( + wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + AutoRestore<bool> restoreDoGrouping(aManager->CommandBuilder().mDoGrouping); + aManager->CommandBuilder().mDoGrouping = false; + return CreateWebRenderCommandsNewClipListOption( + aBuilder, aResources, aSc, aManager, aDisplayListBuilder, false); +} + +void nsDisplayLink::Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) { + auto appPerDev = mFrame->PresContext()->AppUnitsPerDevPixel(); + aCtx->GetDrawTarget()->Link( + mLinkSpec.get(), NSRectToRect(GetPaintRect(aBuilder, aCtx), appPerDev)); +} + +void nsDisplayDestination::Paint(nsDisplayListBuilder* aBuilder, + gfxContext* aCtx) { + auto appPerDev = mFrame->PresContext()->AppUnitsPerDevPixel(); + aCtx->GetDrawTarget()->Destination( + mDestinationName.get(), + NSPointToPoint(GetPaintRect(aBuilder, aCtx).TopLeft(), appPerDev)); +} + +void nsDisplayListCollection::SerializeWithCorrectZOrder( + nsDisplayList* aOutResultList, nsIContent* aContent) { + // Sort PositionedDescendants() in CSS 'z-order' order. The list is already + // in content document order and SortByZOrder is a stable sort which + // guarantees that boxes produced by the same element are placed together + // in the sort. Consider a position:relative inline element that breaks + // across lines and has absolutely positioned children; all the abs-pos + // children should be z-ordered after all the boxes for the position:relative + // element itself. + PositionedDescendants()->SortByZOrder(); + + // Now follow the rules of http://www.w3.org/TR/CSS21/zindex.html + // 1,2: backgrounds and borders + aOutResultList->AppendToTop(BorderBackground()); + // 3: negative z-index children. + for (auto* item : PositionedDescendants()->TakeItems()) { + if (item->ZIndex() < 0) { + aOutResultList->AppendToTop(item); + } else { + PositionedDescendants()->AppendToTop(item); + } + } + + // 4: block backgrounds + aOutResultList->AppendToTop(BlockBorderBackgrounds()); + // 5: floats + aOutResultList->AppendToTop(Floats()); + // 7: general content + aOutResultList->AppendToTop(Content()); + // 7.5: outlines, in content tree order. We need to sort by content order + // because an element with outline that breaks and has children with outline + // might have placed child outline items between its own outline items. + // The element's outline items need to all come before any child outline + // items. + if (aContent) { + Outlines()->SortByContentOrder(aContent); + } + aOutResultList->AppendToTop(Outlines()); + // 8, 9: non-negative z-index children + aOutResultList->AppendToTop(PositionedDescendants()); +} + +uint32_t PaintTelemetry::sPaintLevel = 0; + +PaintTelemetry::AutoRecordPaint::AutoRecordPaint() { + // Don't record nested paints. + if (sPaintLevel++ > 0) { + return; + } + + mStart = TimeStamp::Now(); +} + +PaintTelemetry::AutoRecordPaint::~AutoRecordPaint() { + MOZ_ASSERT(sPaintLevel != 0); + if (--sPaintLevel > 0) { + return; + } + + // If we're in multi-process mode, don't include paint times for the parent + // process. + if (gfxVars::BrowserTabsRemoteAutostart() && XRE_IsParentProcess()) { + return; + } + + double totalMs = (TimeStamp::Now() - mStart).ToMilliseconds(); + + // Record the total time. + Telemetry::Accumulate(Telemetry::CONTENT_PAINT_TIME, + static_cast<uint32_t>(totalMs)); +} + +static nsIFrame* GetSelfOrPlaceholderFor(nsIFrame* aFrame) { + if (aFrame->HasAnyStateBits(NS_FRAME_IS_PUSHED_FLOAT)) { + return aFrame; + } + + if (aFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW) && + !aFrame->GetPrevInFlow()) { + return aFrame->GetPlaceholderFrame(); + } + + return aFrame; +} + +static nsIFrame* GetAncestorFor(nsIFrame* aFrame) { + nsIFrame* f = GetSelfOrPlaceholderFor(aFrame); + MOZ_ASSERT(f); + return nsLayoutUtils::GetCrossDocParentFrameInProcess(f); +} + +nsDisplayListBuilder::AutoBuildingDisplayList::AutoBuildingDisplayList( + nsDisplayListBuilder* aBuilder, nsIFrame* aForChild, + const nsRect& aVisibleRect, const nsRect& aDirtyRect, + const bool aIsTransformed) + : mBuilder(aBuilder), + mPrevFrame(aBuilder->mCurrentFrame), + mPrevReferenceFrame(aBuilder->mCurrentReferenceFrame), + mPrevOffset(aBuilder->mCurrentOffsetToReferenceFrame), + mPrevAdditionalOffset(aBuilder->mAdditionalOffset), + mPrevVisibleRect(aBuilder->mVisibleRect), + mPrevDirtyRect(aBuilder->mDirtyRect), + mPrevCompositorHitTestInfo(aBuilder->mCompositorHitTestInfo), + mPrevAncestorHasApzAwareEventHandler( + aBuilder->mAncestorHasApzAwareEventHandler), + mPrevBuildingInvisibleItems(aBuilder->mBuildingInvisibleItems), + mPrevInInvalidSubtree(aBuilder->mInInvalidSubtree) { + if (aIsTransformed) { + aBuilder->mCurrentOffsetToReferenceFrame = + aBuilder->AdditionalOffset().refOr(nsPoint()); + aBuilder->mCurrentReferenceFrame = aForChild; + } else if (aBuilder->mCurrentFrame == aForChild->GetParent()) { + aBuilder->mCurrentOffsetToReferenceFrame += aForChild->GetPosition(); + } else { + aBuilder->mCurrentReferenceFrame = aBuilder->FindReferenceFrameFor( + aForChild, &aBuilder->mCurrentOffsetToReferenceFrame); + } + + // If aForChild is being visited from a frame other than it's ancestor frame, + // mInInvalidSubtree will need to be recalculated the slow way. + if (aForChild == mPrevFrame || GetAncestorFor(aForChild) == mPrevFrame) { + aBuilder->mInInvalidSubtree = + aBuilder->mInInvalidSubtree || aForChild->IsFrameModified(); + } else { + aBuilder->mInInvalidSubtree = AnyContentAncestorModified(aForChild); + } + + aBuilder->mCurrentFrame = aForChild; + aBuilder->mVisibleRect = aVisibleRect; + aBuilder->mDirtyRect = + aBuilder->mInInvalidSubtree ? aVisibleRect : aDirtyRect; +} + +} // namespace mozilla diff --git a/layout/painting/nsDisplayList.h b/layout/painting/nsDisplayList.h new file mode 100644 index 0000000000..501644867b --- /dev/null +++ b/layout/painting/nsDisplayList.h @@ -0,0 +1,6863 @@ +/* -*- 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/. + */ + +/* + * structures that represent things to be painted (ordered in z-order), + * used during painting and hit testing + */ + +#ifndef NSDISPLAYLIST_H_ +#define NSDISPLAYLIST_H_ + +#include "DisplayItemClipChain.h" +#include "DisplayListClipState.h" +#include "FrameMetrics.h" +#include "HitTestInfo.h" +#include "ImgDrawResult.h" +#include "RetainedDisplayListHelpers.h" +#include "Units.h" +#include "gfxContext.h" +#include "mozilla/ArenaAllocator.h" +#include "mozilla/Array.h" +#include "mozilla/ArrayIterator.h" +#include "mozilla/Assertions.h" +#include "mozilla/Attributes.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/EffectCompositor.h" +#include "mozilla/EnumSet.h" +#include "mozilla/EnumeratedArray.h" +#include "mozilla/Logging.h" +#include "mozilla/Maybe.h" +#include "mozilla/MotionPathUtils.h" +#include "mozilla/RefPtr.h" +#include "mozilla/TemplateLib.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/dom/EffectsInfo.h" +#include "mozilla/gfx/UserData.h" +#include "mozilla/layers/BSPTree.h" +#include "mozilla/layers/ScrollableLayerGuid.h" +#include "mozilla/layers/ScrollbarData.h" +#include "nsAutoLayoutPhase.h" +#include "nsCOMPtr.h" +#include "nsCSSRenderingBorders.h" +#include "nsContainerFrame.h" +#include "nsDisplayItemTypes.h" +#include "nsDisplayListInvalidation.h" +#include "nsPoint.h" +#include "nsPresArena.h" +#include "nsRect.h" +#include "nsRegion.h" +#include "nsClassHashtable.h" +#include "nsTHashSet.h" +#include "nsTHashMap.h" + +#include <algorithm> +#include <unordered_set> + +// XXX Includes that could be avoided by moving function implementations to the +// cpp file. +#include "gfxPlatform.h" + +class gfxContext; +class nsIContent; +class nsIScrollableFrame; +class nsSubDocumentFrame; +class nsCaret; +struct WrFiltersHolder; + +namespace nsStyleTransformMatrix { +class TransformReferenceBox; +} + +namespace mozilla { + +enum class nsDisplayOwnLayerFlags; +class nsDisplayCompositorHitTestInfo; +class nsDisplayScrollInfoLayer; +class PresShell; +class StickyScrollContainer; + +namespace layers { +struct FrameMetrics; +class RenderRootStateManager; +class Layer; +class ImageContainer; +class StackingContextHelper; +class WebRenderScrollData; +class WebRenderLayerScrollData; +class WebRenderLayerManager; +} // namespace layers + +namespace wr { +class DisplayListBuilder; +} // namespace wr + +namespace dom { +class RemoteBrowser; +class Selection; +} // namespace dom + +enum class DisplayListArenaObjectId { +#define DISPLAY_LIST_ARENA_OBJECT(name_) name_, +#include "nsDisplayListArenaTypes.h" +#undef DISPLAY_LIST_ARENA_OBJECT + COUNT +}; + +extern LazyLogModule sContentDisplayListLog; +extern LazyLogModule sParentDisplayListLog; + +LazyLogModule& GetLoggerByProcess(); + +#define DL_LOG(lvl, ...) MOZ_LOG(GetLoggerByProcess(), lvl, (__VA_ARGS__)) +#define DL_LOGI(...) DL_LOG(LogLevel::Info, __VA_ARGS__) +#define DL_LOG_TEST(lvl) MOZ_LOG_TEST(GetLoggerByProcess(), lvl) + +#ifdef DEBUG +# define DL_LOGD(...) DL_LOG(LogLevel::Debug, __VA_ARGS__) +# define DL_LOGV(...) DL_LOG(LogLevel::Verbose, __VA_ARGS__) +#else +// Disable Debug and Verbose logs for release builds. +# define DL_LOGD(...) +# define DL_LOGV(...) +#endif + +/* + * An nsIFrame can have many different visual parts. For example an image frame + * can have a background, border, and outline, the image itself, and a + * translucent selection overlay. In general these parts can be drawn at + * discontiguous z-levels; see CSS2.1 appendix E: + * http://www.w3.org/TR/CSS21/zindex.html + * + * We construct a display list for a frame tree that contains one item + * for each visual part. The display list is itself a tree since some items + * are containers for other items; however, its structure does not match + * the structure of its source frame tree. The display list items are sorted + * by z-order. A display list can be used to paint the frames, to determine + * which frame is the target of a mouse event, and to determine what areas + * need to be repainted when scrolling. The display lists built for each task + * may be different for efficiency; in particular some frames need special + * display list items only for event handling, and do not create these items + * when the display list will be used for painting (the common case). For + * example, when painting we avoid creating nsDisplayBackground items for + * frames that don't display a visible background, but for event handling + * we need those backgrounds because they are not transparent to events. + * + * We could avoid constructing an explicit display list by traversing the + * frame tree multiple times in clever ways. However, reifying the display list + * reduces code complexity and reduces the number of times each frame must be + * traversed to one, which seems to be good for performance. It also means + * we can share code for painting, event handling and scroll analysis. + * + * Display lists are short-lived; content and frame trees cannot change + * between a display list being created and destroyed. Display lists should + * not be created during reflow because the frame tree may be in an + * inconsistent state (e.g., a frame's stored overflow-area may not include + * the bounds of all its children). However, it should be fine to create + * a display list while a reflow is pending, before it starts. + * + * A display list covers the "extended" frame tree; the display list for + * a frame tree containing FRAME/IFRAME elements can include frames from + * the subdocuments. + * + * Display item's coordinates are relative to their nearest reference frame + * ancestor. Both the display root and any frame with a transform act as a + * reference frame for their frame subtrees. + */ + +/** + * An active scrolled root (ASR) is similar to an animated geometry root (AGR). + * The differences are: + * - ASRs are only created for async-scrollable scroll frames. This is a + * (hopefully) temporary restriction. In the future we will want to create + * ASRs for all the things that are currently creating AGRs, and then + * replace AGRs with ASRs and rename them from "active scrolled root" to + * "animated geometry root". + * - ASR objects are created during display list construction by the nsIFrames + * that induce ASRs. This is done using AutoCurrentActiveScrolledRootSetter. + * The current ASR is returned by + * nsDisplayListBuilder::CurrentActiveScrolledRoot(). + * - There is no way to go from an nsIFrame pointer to the ASR of that frame. + * If you need to look up an ASR after display list construction, you need + * to store it while the AutoCurrentActiveScrolledRootSetter that creates it + * is on the stack. + */ +struct ActiveScrolledRoot { + static already_AddRefed<ActiveScrolledRoot> CreateASRForFrame( + const ActiveScrolledRoot* aParent, nsIScrollableFrame* aScrollableFrame, + bool aIsRetained); + + static const ActiveScrolledRoot* PickAncestor( + const ActiveScrolledRoot* aOne, const ActiveScrolledRoot* aTwo) { + MOZ_ASSERT(IsAncestor(aOne, aTwo) || IsAncestor(aTwo, aOne)); + return Depth(aOne) <= Depth(aTwo) ? aOne : aTwo; + } + + static const ActiveScrolledRoot* PickDescendant( + const ActiveScrolledRoot* aOne, const ActiveScrolledRoot* aTwo) { + MOZ_ASSERT(IsAncestor(aOne, aTwo) || IsAncestor(aTwo, aOne)); + return Depth(aOne) >= Depth(aTwo) ? aOne : aTwo; + } + + static bool IsAncestor(const ActiveScrolledRoot* aAncestor, + const ActiveScrolledRoot* aDescendant); + static bool IsProperAncestor(const ActiveScrolledRoot* aAncestor, + const ActiveScrolledRoot* aDescendant); + + static nsCString ToString(const ActiveScrolledRoot* aActiveScrolledRoot); + + // Call this when inserting an ancestor. + void IncrementDepth() { mDepth++; } + + /** + * Find the view ID (or generate a new one) for the content element + * corresponding to the ASR. + */ + layers::ScrollableLayerGuid::ViewID GetViewId() const { + if (!mViewId.isSome()) { + mViewId = Some(ComputeViewId()); + } + return *mViewId; + } + + RefPtr<const ActiveScrolledRoot> mParent; + nsIScrollableFrame* mScrollableFrame; + + NS_INLINE_DECL_REFCOUNTING(ActiveScrolledRoot) + + private: + ActiveScrolledRoot() + : mScrollableFrame(nullptr), mDepth(0), mRetained(false) {} + + ~ActiveScrolledRoot(); + + static void DetachASR(ActiveScrolledRoot* aASR) { + aASR->mParent = nullptr; + aASR->mScrollableFrame = nullptr; + NS_RELEASE(aASR); + } + NS_DECLARE_FRAME_PROPERTY_WITH_DTOR(ActiveScrolledRootCache, + ActiveScrolledRoot, DetachASR) + + static uint32_t Depth(const ActiveScrolledRoot* aActiveScrolledRoot) { + return aActiveScrolledRoot ? aActiveScrolledRoot->mDepth : 0; + } + + layers::ScrollableLayerGuid::ViewID ComputeViewId() const; + + // This field is lazily populated in GetViewId(). We don't want to do the + // work of populating if webrender is disabled, because it is often not + // needed. + mutable Maybe<layers::ScrollableLayerGuid::ViewID> mViewId; + + uint32_t mDepth; + bool mRetained; +}; + +enum class nsDisplayListBuilderMode : uint8_t { + Painting, + PaintForPrinting, + EventDelivery, + FrameVisibility, + GenerateGlyph, +}; + +using ListArenaAllocator = ArenaAllocator<4096, 8>; + +class nsDisplayItem; +class nsPaintedDisplayItem; +class nsDisplayList; +class nsDisplayWrapList; +class nsDisplayTableBackgroundSet; +class nsDisplayTableItem; + +class RetainedDisplayList; + +/** + * This manages a display list and is passed as a parameter to + * nsIFrame::BuildDisplayList. + * It contains the parameters that don't change from frame to frame and manages + * the display list memory using an arena. It also establishes the reference + * coordinate system for all display list items. Some of the parameters are + * available from the prescontext/presshell, but we copy them into the builder + * for faster/more convenient access. + */ +class nsDisplayListBuilder { + /** + * This manages status of a 3d context to collect visible rects of + * descendants and passing a dirty rect. + * + * Since some transforms maybe singular, passing visible rects or + * the dirty rect level by level from parent to children may get a + * wrong result, being different from the result of appling with + * effective transform directly. + * + * nsIFrame::BuildDisplayListForStackingContext() uses + * AutoPreserves3DContext to install an instance on the builder. + * + * \see AutoAccumulateTransform, AutoAccumulateRect, + * AutoPreserves3DContext, Accumulate, GetCurrentTransform, + * StartRoot. + */ + class Preserves3DContext { + public: + Preserves3DContext() + : mAccumulatedRectLevels(0), mAllowAsyncAnimation(true) {} + + Preserves3DContext(const Preserves3DContext& aOther) + : mAccumulatedTransform(), + mAccumulatedRect(), + mAccumulatedRectLevels(0), + mVisibleRect(aOther.mVisibleRect), + mAllowAsyncAnimation(aOther.mAllowAsyncAnimation) {} + + // Accmulate transforms of ancestors on the preserves-3d chain. + gfx::Matrix4x4 mAccumulatedTransform; + // Accmulate visible rect of descendants in the preserves-3d context. + nsRect mAccumulatedRect; + // How far this frame is from the root of the current 3d context. + int mAccumulatedRectLevels; + nsRect mVisibleRect; + // Allow async animation for this 3D context. + bool mAllowAsyncAnimation; + }; + + public: + using ViewID = layers::ScrollableLayerGuid::ViewID; + + /** + * @param aReferenceFrame the frame at the root of the subtree; its origin + * is the origin of the reference coordinate system for this display list + * @param aMode encodes what the builder is being used for. + * @param aBuildCaret whether or not we should include the caret in any + * display lists that we make. + */ + nsDisplayListBuilder(nsIFrame* aReferenceFrame, + nsDisplayListBuilderMode aMode, bool aBuildCaret, + bool aRetainingDisplayList = false); + ~nsDisplayListBuilder(); + + void BeginFrame(); + void EndFrame(); + + void AddTemporaryItem(nsDisplayItem* aItem) { + mTemporaryItems.AppendElement(aItem); + } + + WindowRenderer* GetWidgetWindowRenderer(nsView** aView = nullptr); + layers::WebRenderLayerManager* GetWidgetLayerManager( + nsView** aView = nullptr); + + /** + * @return true if the display is being built in order to determine which + * frame is under the mouse position. + */ + bool IsForEventDelivery() const { + return mMode == nsDisplayListBuilderMode::EventDelivery; + } + + /** + * @return true if the display list is being built for painting. This + * includes both painting to a window or other buffer and painting to + * a print/pdf destination. + */ + bool IsForPainting() const { + return mMode == nsDisplayListBuilderMode::Painting || + mMode == nsDisplayListBuilderMode::PaintForPrinting; + } + + /** + * @return true if the display list is being built specifically for printing. + */ + bool IsForPrinting() const { + return mMode == nsDisplayListBuilderMode::PaintForPrinting; + } + + /** + * @return true if the display list is being built for determining frame + * visibility. + */ + bool IsForFrameVisibility() const { + return mMode == nsDisplayListBuilderMode::FrameVisibility; + } + + /** + * @return true if the display list is being built for creating the glyph + * mask from text items. + */ + bool IsForGenerateGlyphMask() const { + return mMode == nsDisplayListBuilderMode::GenerateGlyph; + } + + bool BuildCompositorHitTestInfo() const { + return mBuildCompositorHitTestInfo; + } + + /** + * @return true if "painting is suppressed" during page load and we + * should paint only the background of the document. + */ + bool IsBackgroundOnly() { + NS_ASSERTION(mPresShellStates.Length() > 0, + "don't call this if we're not in a presshell"); + return CurrentPresShellState()->mIsBackgroundOnly; + } + + /** + * @return the root of given frame's (sub)tree, whose origin + * establishes the coordinate system for the child display items. + */ + const nsIFrame* FindReferenceFrameFor(const nsIFrame* aFrame, + nsPoint* aOffset = nullptr) const; + + const Maybe<nsPoint>& AdditionalOffset() const { return mAdditionalOffset; } + + /** + * @return the root of the display list's frame (sub)tree, whose origin + * establishes the coordinate system for the display list + */ + nsIFrame* RootReferenceFrame() const { return mReferenceFrame; } + + /** + * @return a point pt such that adding pt to a coordinate relative to aFrame + * makes it relative to ReferenceFrame(), i.e., returns + * aFrame->GetOffsetToCrossDoc(ReferenceFrame()). The returned point is in + * the appunits of aFrame. + */ + const nsPoint ToReferenceFrame(const nsIFrame* aFrame) const { + nsPoint result; + FindReferenceFrameFor(aFrame, &result); + return result; + } + /** + * When building the display list, the scrollframe aFrame will be "ignored" + * for the purposes of clipping, and its scrollbars will be hidden. We use + * this to allow RenderOffscreen to render a whole document without beign + * clipped by the viewport or drawing the viewport scrollbars. + */ + void SetIgnoreScrollFrame(nsIFrame* aFrame) { mIgnoreScrollFrame = aFrame; } + /** + * Get the scrollframe to ignore, if any. + */ + nsIFrame* GetIgnoreScrollFrame() { return mIgnoreScrollFrame; } + void SetIsRelativeToLayoutViewport(); + bool IsRelativeToLayoutViewport() const { + return mIsRelativeToLayoutViewport; + } + /** + * Get the ViewID of the nearest scrolling ancestor frame. + */ + ViewID GetCurrentScrollParentId() const { return mCurrentScrollParentId; } + /** + * Get and set the flag that indicates if scroll parents should have layers + * forcibly created. This flag is set when a deeply nested scrollframe has + * a displayport, and for scroll handoff to work properly the ancestor + * scrollframes should also get their own scrollable layers. + */ + void ForceLayerForScrollParent() { mForceLayerForScrollParent = true; } + /** + * Set the flag that indicates there is a non-minimal display port in the + * current subtree. This is used to determine display port expiry. + */ + void SetContainsNonMinimalDisplayPort() { + mContainsNonMinimalDisplayPort = true; + } + /** + * Get the ViewID and the scrollbar flags corresponding to the scrollbar for + * which we are building display items at the moment. + */ + ViewID GetCurrentScrollbarTarget() const { return mCurrentScrollbarTarget; } + Maybe<layers::ScrollDirection> GetCurrentScrollbarDirection() const { + return mCurrentScrollbarDirection; + } + /** + * Returns true if building a scrollbar, and the scrollbar will not be + * layerized. + */ + bool IsBuildingNonLayerizedScrollbar() const { + return mIsBuildingScrollbar && !mCurrentScrollbarWillHaveLayer; + } + /** + * Calling this setter makes us include all out-of-flow descendant + * frames in the display list, wherever they may be positioned (even + * outside the dirty rects). + */ + void SetIncludeAllOutOfFlows() { mIncludeAllOutOfFlows = true; } + bool GetIncludeAllOutOfFlows() const { return mIncludeAllOutOfFlows; } + /** + * Calling this setter makes us exclude all leaf frames that aren't + * selected. + */ + void SetSelectedFramesOnly() { mSelectedFramesOnly = true; } + bool GetSelectedFramesOnly() { return mSelectedFramesOnly; } + /** + * @return Returns true if we should include the caret in any display lists + * that we make. + */ + bool IsBuildingCaret() const { return mBuildCaret; } + + bool IsRetainingDisplayList() const { return mRetainingDisplayList; } + + bool IsPartialUpdate() const { return mPartialUpdate; } + void SetPartialUpdate(bool aPartial) { mPartialUpdate = aPartial; } + + bool IsBuilding() const { return mIsBuilding; } + void SetIsBuilding(bool aIsBuilding) { mIsBuilding = aIsBuilding; } + + bool InInvalidSubtree() const { return mInInvalidSubtree; } + + /** + * Allows callers to selectively override the regular paint suppression + * checks, so that methods like GetFrameForPoint work when painting is + * suppressed. + */ + void IgnorePaintSuppression() { mIgnoreSuppression = true; } + /** + * @return Returns if this builder will ignore paint suppression. + */ + bool IsIgnoringPaintSuppression() { return mIgnoreSuppression; } + /** + * Call this if we're doing normal painting to the window. + */ + void SetPaintingToWindow(bool aToWindow) { mIsPaintingToWindow = aToWindow; } + bool IsPaintingToWindow() const { return mIsPaintingToWindow; } + /** + * Call this if we're using high quality scaling for image decoding. + * It is also implied by IsPaintingToWindow. + */ + void SetUseHighQualityScaling(bool aUseHighQualityScaling) { + mUseHighQualityScaling = aUseHighQualityScaling; + } + bool UseHighQualityScaling() const { + return mIsPaintingToWindow || mUseHighQualityScaling; + } + /** + * Call this if we're doing painting for WebRender + */ + void SetPaintingForWebRender(bool aForWebRender) { + mIsPaintingForWebRender = true; + } + bool IsPaintingForWebRender() const { return mIsPaintingForWebRender; } + /** + * Call this to prevent descending into subdocuments. + */ + void SetDescendIntoSubdocuments(bool aDescend) { + mDescendIntoSubdocuments = aDescend; + } + + bool GetDescendIntoSubdocuments() { return mDescendIntoSubdocuments; } + + /** + * Get dirty rect relative to current frame (the frame that we're calling + * BuildDisplayList on right now). + */ + const nsRect& GetVisibleRect() { return mVisibleRect; } + const nsRect& GetDirtyRect() { return mDirtyRect; } + + void SetVisibleRect(const nsRect& aVisibleRect) { + mVisibleRect = aVisibleRect; + } + + void IntersectVisibleRect(const nsRect& aVisibleRect) { + mVisibleRect.IntersectRect(mVisibleRect, aVisibleRect); + } + + void SetDirtyRect(const nsRect& aDirtyRect) { mDirtyRect = aDirtyRect; } + + void IntersectDirtyRect(const nsRect& aDirtyRect) { + mDirtyRect.IntersectRect(mDirtyRect, aDirtyRect); + } + + const nsIFrame* GetCurrentFrame() { return mCurrentFrame; } + const nsIFrame* GetCurrentReferenceFrame() { return mCurrentReferenceFrame; } + + const nsPoint& GetCurrentFrameOffsetToReferenceFrame() const { + return mCurrentOffsetToReferenceFrame; + } + + void Check() { mPool.Check(); } + + /* + * Get the paint sequence number of the current paint. + */ + static uint32_t GetPaintSequenceNumber() { return sPaintSequenceNumber; } + + /* + * Increment the paint sequence number. + */ + static void IncrementPaintSequenceNumber() { ++sPaintSequenceNumber; } + + /** + * Returns true if merging and flattening of display lists should be + * performed while computing visibility. + */ + bool AllowMergingAndFlattening() { return mAllowMergingAndFlattening; } + void SetAllowMergingAndFlattening(bool aAllow) { + mAllowMergingAndFlattening = aAllow; + } + + void SetCompositorHitTestInfo(const gfx::CompositorHitTestInfo& aInfo) { + mCompositorHitTestInfo = aInfo; + } + + const gfx::CompositorHitTestInfo& GetCompositorHitTestInfo() const { + return mCompositorHitTestInfo; + } + + /** + * Builds a new nsDisplayCompositorHitTestInfo for the frame |aFrame| if + * needed, and adds it to the top of |aList|. + */ + void BuildCompositorHitTestInfoIfNeeded(nsIFrame* aFrame, + nsDisplayList* aList); + + bool IsInsidePointerEventsNoneDoc() { + return CurrentPresShellState()->mInsidePointerEventsNoneDoc; + } + + bool IsTouchEventPrefEnabledDoc() { + return CurrentPresShellState()->mTouchEventPrefEnabledDoc; + } + + bool GetAncestorHasApzAwareEventHandler() const { + return mAncestorHasApzAwareEventHandler; + } + + void SetAncestorHasApzAwareEventHandler(bool aValue) { + mAncestorHasApzAwareEventHandler = aValue; + } + + bool HaveScrollableDisplayPort() const { return mHaveScrollableDisplayPort; } + void SetHaveScrollableDisplayPort() { mHaveScrollableDisplayPort = true; } + void ClearHaveScrollableDisplayPort() { mHaveScrollableDisplayPort = false; } + + bool SetIsCompositingCheap(bool aCompositingCheap) { + bool temp = mIsCompositingCheap; + mIsCompositingCheap = aCompositingCheap; + return temp; + } + + bool IsCompositingCheap() const { return mIsCompositingCheap; } + /** + * Display the caret if needed. + */ + bool DisplayCaret(nsIFrame* aFrame, nsDisplayList* aList) { + nsIFrame* frame = GetCaretFrame(); + if (aFrame == frame && !IsBackgroundOnly()) { + frame->DisplayCaret(this, aList); + return true; + } + return false; + } + /** + * Get the frame that the caret is supposed to draw in. + * If the caret is currently invisible, this will be null. + */ + nsIFrame* GetCaretFrame() { return mCaretFrame; } + /** + * Get the rectangle we're supposed to draw the caret into. + */ + const nsRect& GetCaretRect() { return mCaretRect; } + /** + * Get the caret associated with the current presshell. + */ + nsCaret* GetCaret(); + + /** + * Returns the root scroll frame for the current PresShell, if the PresShell + * is ignoring viewport scrolling. + */ + nsIFrame* GetPresShellIgnoreScrollFrame() { + return CurrentPresShellState()->mPresShellIgnoreScrollFrame; + } + + /** + * Notify the display list builder that we're entering a presshell. + * aReferenceFrame should be a frame in the new presshell. + * aPointerEventsNoneDoc should be set to true if the frame generating this + * document is pointer-events:none. + */ + void EnterPresShell(const nsIFrame* aReferenceFrame, + bool aPointerEventsNoneDoc = false); + /** + * For print-preview documents, we sometimes need to build display items for + * the same frames multiple times in the same presentation, with different + * clipping. Between each such batch of items, call + * ResetMarkedFramesForDisplayList to make sure that the results of + * MarkFramesForDisplayList do not carry over between batches. + */ + void ResetMarkedFramesForDisplayList(const nsIFrame* aReferenceFrame); + /** + * Notify the display list builder that we're leaving a presshell. + */ + void LeavePresShell(const nsIFrame* aReferenceFrame, + nsDisplayList* aPaintedContents); + + void IncrementPresShellPaintCount(PresShell* aPresShell); + + /** + * Returns true if we're currently building a display list that's + * directly or indirectly under an nsDisplayTransform. + */ + bool IsInTransform() const { return mInTransform; } + + bool InEventsOnly() const { return mInEventsOnly; } + /** + * Indicate whether or not we're directly or indirectly under and + * nsDisplayTransform or SVG foreignObject. + */ + void SetInTransform(bool aInTransform) { mInTransform = aInTransform; } + + /** + * Returns true if we're currently building a display list that's + * under an nsDisplayFilters. + */ + bool IsInFilter() const { return mInFilter; } + + /** + * Return true if we're currently building a display list for a + * nested presshell. + */ + bool IsInSubdocument() const { return mPresShellStates.Length() > 1; } + + void SetDisablePartialUpdates(bool aDisable) { + mDisablePartialUpdates = aDisable; + } + bool DisablePartialUpdates() const { return mDisablePartialUpdates; } + + void SetPartialBuildFailed(bool aFailed) { mPartialBuildFailed = aFailed; } + bool PartialBuildFailed() const { return mPartialBuildFailed; } + + bool IsInActiveDocShell() const { return mIsInActiveDocShell; } + void SetInActiveDocShell(bool aActive) { mIsInActiveDocShell = aActive; } + + /** + * Return true if we're currently building a display list for the presshell + * of a chrome document, or if we're building the display list for a popup. + */ + bool IsInChromeDocumentOrPopup() const { + return mIsInChromePresContext || mIsBuildingForPopup; + } + + /** + * @return true if images have been set to decode synchronously. + */ + bool ShouldSyncDecodeImages() const { return mSyncDecodeImages; } + + /** + * Indicates whether we should synchronously decode images. If true, we decode + * and draw whatever image data has been loaded. If false, we just draw + * whatever has already been decoded. + */ + void SetSyncDecodeImages(bool aSyncDecodeImages) { + mSyncDecodeImages = aSyncDecodeImages; + } + + nsDisplayTableBackgroundSet* SetTableBackgroundSet( + nsDisplayTableBackgroundSet* aTableSet) { + nsDisplayTableBackgroundSet* old = mTableBackgroundSet; + mTableBackgroundSet = aTableSet; + return old; + } + nsDisplayTableBackgroundSet* GetTableBackgroundSet() const { + return mTableBackgroundSet; + } + + void FreeClipChains(); + + /* + * Frees the temporary display items created during merging. + */ + void FreeTemporaryItems(); + + /** + * Helper method to generate background painting flags based on the + * information available in the display list builder. + */ + uint32_t GetBackgroundPaintFlags(); + + /** + * Helper method to generate nsImageRenderer flags based on the information + * available in the display list builder. + */ + uint32_t GetImageRendererFlags() const; + + /** + * Helper method to generate image decoding flags based on the + * information available in the display list builder. + */ + uint32_t GetImageDecodeFlags() const; + + /** + * Subtracts aRegion from *aVisibleRegion. We avoid letting + * aVisibleRegion become overcomplex by simplifying it if necessary. + */ + void SubtractFromVisibleRegion(nsRegion* aVisibleRegion, + const nsRegion& aRegion); + + /** + * Mark the frames in aFrames to be displayed if they intersect aDirtyRect + * (which is relative to aDirtyFrame). If the frames have placeholders + * that might not be displayed, we mark the placeholders and their ancestors + * to ensure that display list construction descends into them + * anyway. nsDisplayListBuilder will take care of unmarking them when it is + * destroyed. + */ + void MarkFramesForDisplayList(nsIFrame* aDirtyFrame, + const nsFrameList& aFrames); + void MarkFrameForDisplay(nsIFrame* aFrame, const nsIFrame* aStopAtFrame); + void MarkFrameForDisplayIfVisible(nsIFrame* aFrame, + const nsIFrame* aStopAtFrame); + void AddFrameMarkedForDisplayIfVisible(nsIFrame* aFrame); + + void ClearFixedBackgroundDisplayData(); + /** + * Mark all child frames that Preserve3D() as needing display. + * Because these frames include transforms set on their parent, dirty rects + * for intermediate frames may be empty, yet child frames could still be + * visible. + */ + void MarkPreserve3DFramesForDisplayList(nsIFrame* aDirtyFrame); + + /** + * Returns true if we need to descend into this frame when building + * the display list, even though it doesn't intersect the dirty + * rect, because it may have out-of-flows that do so. + */ + bool ShouldDescendIntoFrame(nsIFrame* aFrame, bool aVisible) const { + return aFrame->HasAnyStateBits(NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO) || + (aVisible && aFrame->ForceDescendIntoIfVisible()) || + GetIncludeAllOutOfFlows(); + } + + /** + * Returns the list of registered theme geometries. + */ + nsTArray<nsIWidget::ThemeGeometry> GetThemeGeometries() const { + nsTArray<nsIWidget::ThemeGeometry> geometries; + + for (const auto& data : mThemeGeometries.Values()) { + geometries.AppendElements(*data); + } + + return geometries; + } + + /** + * Notifies the builder that a particular themed widget exists + * at the given rectangle within the currently built display list. + * For certain appearance values (currently only StyleAppearance::Toolbar and + * StyleAppearance::WindowTitlebar) this gets called during every display list + * construction, for every themed widget of the right type within the + * display list, except for themed widgets which are transformed or have + * effects applied to them (e.g. CSS opacity or filters). + * + * @param aWidgetType the -moz-appearance value for the themed widget + * @param aItem the item associated with the theme geometry + * @param aRect the device-pixel rect relative to the widget's displayRoot + * for the themed widget + */ + void RegisterThemeGeometry(uint8_t aWidgetType, nsDisplayItem* aItem, + const LayoutDeviceIntRect& aRect) { + if (!mIsPaintingToWindow) { + return; + } + + nsTArray<nsIWidget::ThemeGeometry>* geometries = + mThemeGeometries.GetOrInsertNew(aItem); + geometries->AppendElement(nsIWidget::ThemeGeometry(aWidgetType, aRect)); + } + + /** + * Removes theme geometries associated with the given display item |aItem|. + */ + void UnregisterThemeGeometry(nsDisplayItem* aItem) { + mThemeGeometries.Remove(aItem); + } + + /** + * Adjusts mWindowDraggingRegion to take into account aFrame. If aFrame's + * -moz-window-dragging value is |drag|, its border box is added to the + * collected dragging region; if the value is |no-drag|, the border box is + * subtracted from the region; if the value is |default|, that frame does + * not influence the window dragging region. + */ + void AdjustWindowDraggingRegion(nsIFrame* aFrame); + + LayoutDeviceIntRegion GetWindowDraggingRegion() const; + + void RemoveModifiedWindowRegions(); + void ClearRetainedWindowRegions(); + + const nsTHashMap<nsPtrHashKey<dom::RemoteBrowser>, dom::EffectsInfo>& + GetEffectUpdates() const { + return mEffectsUpdates; + } + + void AddEffectUpdate(dom::RemoteBrowser* aBrowser, + const dom::EffectsInfo& aUpdate); + + /** + * Allocate memory in our arena. It will only be freed when this display list + * builder is destroyed. This memory holds nsDisplayItems and + * DisplayItemClipChain objects. + * + * Destructors are called as soon as the item is no longer used. + */ + void* Allocate(size_t aSize, DisplayListArenaObjectId aId) { + return mPool.Allocate(aId, aSize); + } + void* Allocate(size_t aSize, DisplayItemType aType) { +#define DECLARE_DISPLAY_ITEM_TYPE(name_, ...) \ + static_assert(size_t(DisplayItemType::TYPE_##name_) == \ + size_t(DisplayListArenaObjectId::name_), \ + ""); +#include "nsDisplayItemTypesList.h" + static_assert(size_t(DisplayItemType::TYPE_MAX) == + size_t(DisplayListArenaObjectId::CLIPCHAIN), + ""); + static_assert(size_t(DisplayItemType::TYPE_MAX) + 1 == + size_t(DisplayListArenaObjectId::LISTNODE), + ""); +#undef DECLARE_DISPLAY_ITEM_TYPE + return Allocate(aSize, DisplayListArenaObjectId(size_t(aType))); + } + + void Destroy(DisplayListArenaObjectId aId, void* aPtr) { + return mPool.Free(aId, aPtr); + } + void Destroy(DisplayItemType aType, void* aPtr) { + return Destroy(DisplayListArenaObjectId(size_t(aType)), aPtr); + } + + /** + * Allocate a new ActiveScrolledRoot in the arena. Will be cleaned up + * automatically when the arena goes away. + */ + ActiveScrolledRoot* AllocateActiveScrolledRoot( + const ActiveScrolledRoot* aParent, nsIScrollableFrame* aScrollableFrame); + + /** + * Allocate a new DisplayItemClipChain object in the arena. Will be cleaned + * up automatically when the arena goes away. + */ + const DisplayItemClipChain* AllocateDisplayItemClipChain( + const DisplayItemClip& aClip, const ActiveScrolledRoot* aASR, + const DisplayItemClipChain* aParent); + + /** + * Intersect two clip chains, allocating the new clip chain items in this + * builder's arena. The result is parented to aAncestor, and no intersections + * happen past aAncestor's ASR. + * That means aAncestor has to be living in this builder's arena already. + * aLeafClip1 and aLeafClip2 only need to outlive the call to this function, + * their values are copied into the newly-allocated intersected clip chain + * and this function does not hold on to any pointers to them. + */ + const DisplayItemClipChain* CreateClipChainIntersection( + const DisplayItemClipChain* aAncestor, + const DisplayItemClipChain* aLeafClip1, + const DisplayItemClipChain* aLeafClip2); + + /** + * Same as above, except aAncestor is computed as the nearest common + * ancestor of the two provided clips. + */ + const DisplayItemClipChain* CreateClipChainIntersection( + const DisplayItemClipChain* aLeafClip1, + const DisplayItemClipChain* aLeafClip2); + + /** + * Clone the supplied clip chain's chain items into this builder's arena. + */ + const DisplayItemClipChain* CopyWholeChain( + const DisplayItemClipChain* aClipChain); + + const ActiveScrolledRoot* GetFilterASR() const { return mFilterASR; } + + /** + * Merges the display items in |aMergedItems| and returns a new temporary + * display item. + * The display items in |aMergedItems| have to be mergeable with each other. + */ + nsDisplayWrapList* MergeItems(nsTArray<nsDisplayItem*>& aItems); + + /** + * A helper class used to temporarily set nsDisplayListBuilder properties for + * building display items. + * aVisibleRect and aDirtyRect are relative to aForChild. + */ + class AutoBuildingDisplayList { + public: + AutoBuildingDisplayList(nsDisplayListBuilder* aBuilder, nsIFrame* aForChild, + const nsRect& aVisibleRect, + const nsRect& aDirtyRect) + : AutoBuildingDisplayList(aBuilder, aForChild, aVisibleRect, aDirtyRect, + aForChild->IsTransformed()) {} + + AutoBuildingDisplayList(nsDisplayListBuilder* aBuilder, nsIFrame* aForChild, + const nsRect& aVisibleRect, + const nsRect& aDirtyRect, + const bool aIsTransformed); + + void SetReferenceFrameAndCurrentOffset(const nsIFrame* aFrame, + const nsPoint& aOffset) { + mBuilder->mCurrentReferenceFrame = aFrame; + mBuilder->mCurrentOffsetToReferenceFrame = aOffset; + } + + void SetAdditionalOffset(const nsPoint& aOffset) { + MOZ_ASSERT(!mBuilder->mAdditionalOffset); + mBuilder->mAdditionalOffset = Some(aOffset); + + mBuilder->mCurrentOffsetToReferenceFrame += aOffset; + } + + void RestoreBuildingInvisibleItemsValue() { + mBuilder->mBuildingInvisibleItems = mPrevBuildingInvisibleItems; + } + + ~AutoBuildingDisplayList() { + mBuilder->mCurrentFrame = mPrevFrame; + mBuilder->mCurrentReferenceFrame = mPrevReferenceFrame; + mBuilder->mCurrentOffsetToReferenceFrame = mPrevOffset; + mBuilder->mVisibleRect = mPrevVisibleRect; + mBuilder->mDirtyRect = mPrevDirtyRect; + mBuilder->mAncestorHasApzAwareEventHandler = + mPrevAncestorHasApzAwareEventHandler; + mBuilder->mBuildingInvisibleItems = mPrevBuildingInvisibleItems; + mBuilder->mInInvalidSubtree = mPrevInInvalidSubtree; + mBuilder->mAdditionalOffset = mPrevAdditionalOffset; + mBuilder->mCompositorHitTestInfo = mPrevCompositorHitTestInfo; + } + + private: + nsDisplayListBuilder* mBuilder; + const nsIFrame* mPrevFrame; + const nsIFrame* mPrevReferenceFrame; + nsPoint mPrevOffset; + Maybe<nsPoint> mPrevAdditionalOffset; + nsRect mPrevVisibleRect; + nsRect mPrevDirtyRect; + gfx::CompositorHitTestInfo mPrevCompositorHitTestInfo; + bool mPrevAncestorHasApzAwareEventHandler; + bool mPrevBuildingInvisibleItems; + bool mPrevInInvalidSubtree; + }; + + /** + * A helper class to temporarily set the value of mInTransform. + */ + class AutoInTransformSetter { + public: + AutoInTransformSetter(nsDisplayListBuilder* aBuilder, bool aInTransform) + : mBuilder(aBuilder), mOldValue(aBuilder->mInTransform) { + aBuilder->mInTransform = aInTransform; + } + + ~AutoInTransformSetter() { mBuilder->mInTransform = mOldValue; } + + private: + nsDisplayListBuilder* mBuilder; + bool mOldValue; + }; + + class AutoInEventsOnly { + public: + AutoInEventsOnly(nsDisplayListBuilder* aBuilder, bool aInEventsOnly) + : mBuilder(aBuilder), mOldValue(aBuilder->mInEventsOnly) { + aBuilder->mInEventsOnly |= aInEventsOnly; + } + + ~AutoInEventsOnly() { mBuilder->mInEventsOnly = mOldValue; } + + private: + nsDisplayListBuilder* mBuilder; + bool mOldValue; + }; + + /** + * A helper class to temporarily set the value of mFilterASR and + * mInFilter. + */ + class AutoEnterFilter { + public: + AutoEnterFilter(nsDisplayListBuilder* aBuilder, bool aUsingFilter) + : mBuilder(aBuilder), + mOldValue(aBuilder->mFilterASR), + mOldInFilter(aBuilder->mInFilter) { + if (!aBuilder->mFilterASR && aUsingFilter) { + aBuilder->mFilterASR = aBuilder->CurrentActiveScrolledRoot(); + aBuilder->mInFilter = true; + } + } + + ~AutoEnterFilter() { + mBuilder->mFilterASR = mOldValue; + mBuilder->mInFilter = mOldInFilter; + } + + private: + nsDisplayListBuilder* mBuilder; + const ActiveScrolledRoot* mOldValue; + bool mOldInFilter; + }; + + /** + * Used to update the current active scrolled root on the display list + * builder, and to create new active scrolled roots. + */ + class AutoCurrentActiveScrolledRootSetter { + public: + explicit AutoCurrentActiveScrolledRootSetter(nsDisplayListBuilder* aBuilder) + : mBuilder(aBuilder), + mSavedActiveScrolledRoot(aBuilder->mCurrentActiveScrolledRoot), + mContentClipASR(aBuilder->ClipState().GetContentClipASR()), + mDescendantsStartIndex(aBuilder->mActiveScrolledRoots.Length()), + mUsed(false), + mOldScrollParentId(aBuilder->mCurrentScrollParentId), + mOldForceLayer(aBuilder->mForceLayerForScrollParent), + mOldContainsNonMinimalDisplayPort( + mBuilder->mContainsNonMinimalDisplayPort), + mCanBeScrollParent(false) {} + + void SetCurrentScrollParentId(ViewID aScrollId) { + // Update the old scroll parent id. + mOldScrollParentId = mBuilder->mCurrentScrollParentId; + // If this AutoCurrentActiveScrolledRootSetter has the same aScrollId as + // the previous one on the stack, then that means the scrollframe that + // created this isn't actually scrollable and cannot participate in + // scroll handoff. We set mCanBeScrollParent to false to indicate this. + mCanBeScrollParent = (mOldScrollParentId != aScrollId); + mBuilder->mCurrentScrollParentId = aScrollId; + mBuilder->mForceLayerForScrollParent = false; + mBuilder->mContainsNonMinimalDisplayPort = false; + } + + bool ShouldForceLayerForScrollParent() const { + // Only scrollframes participating in scroll handoff can be forced to + // layerize + return mCanBeScrollParent && mBuilder->mForceLayerForScrollParent; + } + + bool GetContainsNonMinimalDisplayPort() const { + // Only for scrollframes participating in scroll handoff can we return + // true. + return mCanBeScrollParent && mBuilder->mContainsNonMinimalDisplayPort; + } + + ~AutoCurrentActiveScrolledRootSetter() { + mBuilder->mCurrentActiveScrolledRoot = mSavedActiveScrolledRoot; + mBuilder->mCurrentScrollParentId = mOldScrollParentId; + if (mCanBeScrollParent) { + // If this flag is set, caller code is responsible for having dealt + // with the current value of mBuilder->mForceLayerForScrollParent, so + // we can just restore the old value. + mBuilder->mForceLayerForScrollParent = mOldForceLayer; + } else { + // Otherwise we need to keep propagating the force-layerization flag + // upwards to the next ancestor scrollframe that does participate in + // scroll handoff. + mBuilder->mForceLayerForScrollParent |= mOldForceLayer; + } + mBuilder->mContainsNonMinimalDisplayPort |= + mOldContainsNonMinimalDisplayPort; + } + + void SetCurrentActiveScrolledRoot( + const ActiveScrolledRoot* aActiveScrolledRoot); + + void EnterScrollFrame(nsIScrollableFrame* aScrollableFrame) { + MOZ_ASSERT(!mUsed); + ActiveScrolledRoot* asr = mBuilder->AllocateActiveScrolledRoot( + mBuilder->mCurrentActiveScrolledRoot, aScrollableFrame); + mBuilder->mCurrentActiveScrolledRoot = asr; + mUsed = true; + } + + void InsertScrollFrame(nsIScrollableFrame* aScrollableFrame); + + private: + nsDisplayListBuilder* mBuilder; + /** + * The builder's mCurrentActiveScrolledRoot at construction time which + * needs to be restored at destruction time. + */ + const ActiveScrolledRoot* mSavedActiveScrolledRoot; + /** + * If there's a content clip on the builder at construction time, then + * mContentClipASR is that content clip's ASR, otherwise null. The + * assumption is that the content clip doesn't get relaxed while this + * object is on the stack. + */ + const ActiveScrolledRoot* mContentClipASR; + /** + * InsertScrollFrame needs to mutate existing ASRs (those that were + * created while this object was on the stack), and mDescendantsStartIndex + * makes it easier to skip ASRs that were created in the past. + */ + size_t mDescendantsStartIndex; + /** + * Flag to make sure that only one of SetCurrentActiveScrolledRoot / + * EnterScrollFrame / InsertScrollFrame is called per instance of this + * class. + */ + bool mUsed; + ViewID mOldScrollParentId; + bool mOldForceLayer; + bool mOldContainsNonMinimalDisplayPort; + bool mCanBeScrollParent; + }; + + /** + * Keeps track of the innermost ASR that can be used as the ASR for a + * container item that wraps all items that were created while this + * object was on the stack. + * The rule is: all child items of the container item need to have + * clipped bounds with respect to the container ASR. + */ + class AutoContainerASRTracker { + public: + explicit AutoContainerASRTracker(nsDisplayListBuilder* aBuilder); + + const ActiveScrolledRoot* GetContainerASR() { + return mBuilder->mCurrentContainerASR; + } + + ~AutoContainerASRTracker() { + mBuilder->mCurrentContainerASR = ActiveScrolledRoot::PickAncestor( + mBuilder->mCurrentContainerASR, mSavedContainerASR); + } + + private: + nsDisplayListBuilder* mBuilder; + const ActiveScrolledRoot* mSavedContainerASR; + }; + + /** + * A helper class to temporarily set the value of mCurrentScrollbarTarget + * and mCurrentScrollbarFlags. + */ + class AutoCurrentScrollbarInfoSetter { + public: + AutoCurrentScrollbarInfoSetter( + nsDisplayListBuilder* aBuilder, ViewID aScrollTargetID, + const Maybe<layers::ScrollDirection>& aScrollbarDirection, + bool aWillHaveLayer) + : mBuilder(aBuilder) { + aBuilder->mIsBuildingScrollbar = true; + aBuilder->mCurrentScrollbarTarget = aScrollTargetID; + aBuilder->mCurrentScrollbarDirection = aScrollbarDirection; + aBuilder->mCurrentScrollbarWillHaveLayer = aWillHaveLayer; + } + + ~AutoCurrentScrollbarInfoSetter() { + // No need to restore old values because scrollbars cannot be nested. + mBuilder->mIsBuildingScrollbar = false; + mBuilder->mCurrentScrollbarTarget = + layers::ScrollableLayerGuid::NULL_SCROLL_ID; + mBuilder->mCurrentScrollbarDirection.reset(); + mBuilder->mCurrentScrollbarWillHaveLayer = false; + } + + private: + nsDisplayListBuilder* mBuilder; + }; + + /** + * A helper class to temporarily set mBuildingExtraPagesForPageNum. + */ + class MOZ_RAII AutoPageNumberSetter { + public: + AutoPageNumberSetter(nsDisplayListBuilder* aBuilder, const uint8_t aPageNum) + : mBuilder(aBuilder), + mOldPageNum(aBuilder->GetBuildingExtraPagesForPageNum()) { + mBuilder->SetBuildingExtraPagesForPageNum(aPageNum); + } + ~AutoPageNumberSetter() { + mBuilder->SetBuildingExtraPagesForPageNum(mOldPageNum); + } + + private: + nsDisplayListBuilder* mBuilder; + uint8_t mOldPageNum; + }; + + /** + * A helper class to track current effective transform for items. + * + * For frames that is Combines3DTransformWithAncestors(), we need to + * apply all transforms of ancestors on the same preserves3D chain + * on the bounds of current frame to the coordination of the 3D + * context root. The 3D context root computes it's bounds from + * these transformed bounds. + */ + class AutoAccumulateTransform { + public: + explicit AutoAccumulateTransform(nsDisplayListBuilder* aBuilder) + : mBuilder(aBuilder), + mSavedTransform(aBuilder->mPreserves3DCtx.mAccumulatedTransform) {} + + ~AutoAccumulateTransform() { + mBuilder->mPreserves3DCtx.mAccumulatedTransform = mSavedTransform; + } + + void Accumulate(const gfx::Matrix4x4& aTransform) { + mBuilder->mPreserves3DCtx.mAccumulatedTransform = + aTransform * mBuilder->mPreserves3DCtx.mAccumulatedTransform; + } + + const gfx::Matrix4x4& GetCurrentTransform() { + return mBuilder->mPreserves3DCtx.mAccumulatedTransform; + } + + void StartRoot() { + mBuilder->mPreserves3DCtx.mAccumulatedTransform = gfx::Matrix4x4(); + } + + private: + nsDisplayListBuilder* mBuilder; + gfx::Matrix4x4 mSavedTransform; + }; + + /** + * A helper class to collect bounds rects of descendants. + * + * For a 3D context root, it's bounds is computed from the bounds of + * descendants. If we transform bounds frame by frame applying + * transforms, the bounds may turn to empty for any singular + * transform on the path, but it is not empty for the accumulated + * transform. + */ + class AutoAccumulateRect { + public: + explicit AutoAccumulateRect(nsDisplayListBuilder* aBuilder) + : mBuilder(aBuilder), + mSavedRect(aBuilder->mPreserves3DCtx.mAccumulatedRect) { + aBuilder->mPreserves3DCtx.mAccumulatedRect = nsRect(); + aBuilder->mPreserves3DCtx.mAccumulatedRectLevels++; + } + + ~AutoAccumulateRect() { + mBuilder->mPreserves3DCtx.mAccumulatedRect = mSavedRect; + mBuilder->mPreserves3DCtx.mAccumulatedRectLevels--; + } + + private: + nsDisplayListBuilder* mBuilder; + nsRect mSavedRect; + }; + + void AccumulateRect(const nsRect& aRect) { + mPreserves3DCtx.mAccumulatedRect.UnionRect(mPreserves3DCtx.mAccumulatedRect, + aRect); + } + + const nsRect& GetAccumulatedRect() { + return mPreserves3DCtx.mAccumulatedRect; + } + + /** + * The level is increased by one for items establishing 3D rendering + * context and starting a new accumulation. + */ + int GetAccumulatedRectLevels() { + return mPreserves3DCtx.mAccumulatedRectLevels; + } + + struct OutOfFlowDisplayData { + OutOfFlowDisplayData( + const DisplayItemClipChain* aContainingBlockClipChain, + const DisplayItemClipChain* aCombinedClipChain, + const ActiveScrolledRoot* aContainingBlockActiveScrolledRoot, + const ViewID& aScrollParentId, const nsRect& aVisibleRect, + const nsRect& aDirtyRect) + : mContainingBlockClipChain(aContainingBlockClipChain), + mCombinedClipChain(aCombinedClipChain), + mContainingBlockActiveScrolledRoot( + aContainingBlockActiveScrolledRoot), + mVisibleRect(aVisibleRect), + mDirtyRect(aDirtyRect), + mScrollParentId(aScrollParentId) {} + const DisplayItemClipChain* mContainingBlockClipChain; + const DisplayItemClipChain* + mCombinedClipChain; // only necessary for the special case of top layer + const ActiveScrolledRoot* mContainingBlockActiveScrolledRoot; + + // If this OutOfFlowDisplayData is associated with the ViewportFrame + // of a document that has a resolution (creating separate visual and + // layout viewports with their own coordinate spaces), these rects + // are in layout coordinates. Similarly, GetVisibleRectForFrame() in + // such a case returns a quantity in layout coordinates. + nsRect mVisibleRect; + nsRect mDirtyRect; + ViewID mScrollParentId; + + static nsRect ComputeVisibleRectForFrame(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame, + const nsRect& aVisibleRect, + const nsRect& aDirtyRect, + nsRect* aOutDirtyRect); + + nsRect GetVisibleRectForFrame(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame, nsRect* aDirtyRect) { + return ComputeVisibleRectForFrame(aBuilder, aFrame, mVisibleRect, + mDirtyRect, aDirtyRect); + } + }; + + NS_DECLARE_FRAME_PROPERTY_DELETABLE(OutOfFlowDisplayDataProperty, + OutOfFlowDisplayData) + + struct DisplayListBuildingData { + nsIFrame* mModifiedAGR = nullptr; + nsRect mDirtyRect; + }; + NS_DECLARE_FRAME_PROPERTY_DELETABLE(DisplayListBuildingRect, + DisplayListBuildingData) + + NS_DECLARE_FRAME_PROPERTY_DELETABLE(DisplayListBuildingDisplayPortRect, + nsRect) + + static OutOfFlowDisplayData* GetOutOfFlowData(nsIFrame* aFrame) { + if (!aFrame->GetParent()) { + return nullptr; + } + return aFrame->GetParent()->GetProperty(OutOfFlowDisplayDataProperty()); + } + + nsPresContext* CurrentPresContext(); + + OutOfFlowDisplayData* GetCurrentFixedBackgroundDisplayData() { + auto& displayData = CurrentPresShellState()->mFixedBackgroundDisplayData; + return displayData ? displayData.ptr() : nullptr; + } + + /** + * Accumulates the bounds of box frames that have moz-appearance + * -moz-win-exclude-glass style. Used in setting glass margins on + * Windows. + * + * We set the window opaque region (from which glass margins are computed) + * to the intersection of the glass region specified here and the opaque + * region computed during painting. So the excluded glass region actually + * *limits* the extent of the opaque area reported to Windows. We limit it + * so that changes to the computed opaque region (which can vary based on + * region optimizations and the placement of UI elements) outside the + * -moz-win-exclude-glass area don't affect the glass margins reported to + * Windows; changing those margins willy-nilly can cause the Windows 7 glass + * haze effect to jump around disconcertingly. + */ + void AddWindowExcludeGlassRegion(nsIFrame* aFrame, const nsRect& aBounds) { + mWindowExcludeGlassRegion.Add(aFrame, aBounds); + } + + /** + * Returns the window exclude glass region. + */ + nsRegion GetWindowExcludeGlassRegion() const { + return mWindowExcludeGlassRegion.ToRegion(); + } + + /** + * Accumulates opaque stuff into the window opaque region. + */ + void AddWindowOpaqueRegion(nsIFrame* aFrame, const nsRect& aBounds) { + if (IsRetainingDisplayList()) { + mRetainedWindowOpaqueRegion.Add(aFrame, aBounds); + return; + } + mWindowOpaqueRegion.Or(mWindowOpaqueRegion, aBounds); + } + /** + * Returns the window opaque region built so far. This may be incomplete + * since the opaque region is built during layer construction. + */ + const nsRegion GetWindowOpaqueRegion() { + return IsRetainingDisplayList() ? mRetainedWindowOpaqueRegion.ToRegion() + : mWindowOpaqueRegion; + } + + void SetGlassDisplayItem(nsDisplayItem* aItem); + void ClearGlassDisplayItem() { mGlassDisplayItem = nullptr; } + nsDisplayItem* GetGlassDisplayItem() { return mGlassDisplayItem; } + + bool NeedToForceTransparentSurfaceForItem(nsDisplayItem* aItem); + + /** + * mContainsBlendMode is true if we processed a display item that + * has a blend mode attached. We do this so we can insert a + * nsDisplayBlendContainer in the parent stacking context. + */ + void SetContainsBlendMode(bool aContainsBlendMode) { + mContainsBlendMode = aContainsBlendMode; + } + bool ContainsBlendMode() const { return mContainsBlendMode; } + + DisplayListClipState& ClipState() { return mClipState; } + const ActiveScrolledRoot* CurrentActiveScrolledRoot() { + return mCurrentActiveScrolledRoot; + } + const ActiveScrolledRoot* CurrentAncestorASRStackingContextContents() { + return mCurrentContainerASR; + } + + /** + * Add the current frame to the will-change budget if possible and + * remeber the outcome. Subsequent calls to IsInWillChangeBudget + * will return the same value as return here. + */ + bool AddToWillChangeBudget(nsIFrame* aFrame, const nsSize& aSize); + + /** + * This will add the current frame to the will-change budget the first + * time it is seen. On subsequent calls this will return the same + * answer. This effectively implements a first-come, first-served + * allocation of the will-change budget. + */ + bool IsInWillChangeBudget(nsIFrame* aFrame, const nsSize& aSize); + + /** + * Clears the will-change budget status for the given |aFrame|. + * This will also remove the frame from will-change budgets. + */ + void ClearWillChangeBudgetStatus(nsIFrame* aFrame); + + /** + * Removes the given |aFrame| from will-change budgets. + */ + void RemoveFromWillChangeBudgets(const nsIFrame* aFrame); + + /** + * Clears the will-change budgets. + */ + void ClearWillChangeBudgets(); + + void EnterSVGEffectsContents(nsIFrame* aEffectsFrame, + nsDisplayList* aHoistedItemsStorage); + void ExitSVGEffectsContents(); + + bool ShouldBuildScrollInfoItemsForHoisting() const; + + void AppendNewScrollInfoItemForHoisting( + nsDisplayScrollInfoLayer* aScrollInfoItem); + + /** + * A helper class to install/restore nsDisplayListBuilder::mPreserves3DCtx. + * + * mPreserves3DCtx is used by class AutoAccumulateTransform & + * AutoAccumulateRect to passing data between frames in the 3D + * context. If a frame create a new 3D context, it should restore + * the value of mPreserves3DCtx before returning back to the parent. + * This class do it for the users. + */ + class AutoPreserves3DContext { + public: + explicit AutoPreserves3DContext(nsDisplayListBuilder* aBuilder) + : mBuilder(aBuilder), mSavedCtx(aBuilder->mPreserves3DCtx) {} + + ~AutoPreserves3DContext() { mBuilder->mPreserves3DCtx = mSavedCtx; } + + private: + nsDisplayListBuilder* mBuilder; + Preserves3DContext mSavedCtx; + }; + + const nsRect GetPreserves3DRect() const { + return mPreserves3DCtx.mVisibleRect; + } + + void SavePreserves3DRect() { mPreserves3DCtx.mVisibleRect = mVisibleRect; } + + void SavePreserves3DAllowAsyncAnimation(bool aValue) { + mPreserves3DCtx.mAllowAsyncAnimation = aValue; + } + + bool GetPreserves3DAllowAsyncAnimation() const { + return mPreserves3DCtx.mAllowAsyncAnimation; + } + + bool IsBuildingInvisibleItems() const { return mBuildingInvisibleItems; } + + void SetBuildingInvisibleItems(bool aBuildingInvisibleItems) { + mBuildingInvisibleItems = aBuildingInvisibleItems; + } + + void SetBuildingExtraPagesForPageNum(uint8_t aPageNum) { + mBuildingExtraPagesForPageNum = aPageNum; + } + uint8_t GetBuildingExtraPagesForPageNum() const { + return mBuildingExtraPagesForPageNum; + } + + bool HitTestIsForVisibility() const { return mVisibleThreshold.isSome(); } + + float VisibilityThreshold() const { + MOZ_DIAGNOSTIC_ASSERT(HitTestIsForVisibility()); + return mVisibleThreshold.valueOr(1.0f); + } + + void SetHitTestIsForVisibility(float aVisibleThreshold) { + mVisibleThreshold = Some(aVisibleThreshold); + } + + bool ShouldBuildAsyncZoomContainer() const { + return mBuildAsyncZoomContainer; + } + void UpdateShouldBuildAsyncZoomContainer(); + + void UpdateShouldBuildBackdropRootContainer(); + + bool ShouldRebuildDisplayListDueToPrefChange(); + + /** + * Represents a region composed of frame/rect pairs. + * WeakFrames are used to track whether a rect still belongs to the region. + * Modified frames and rects are removed and re-added to the region if needed. + */ + struct WeakFrameRegion { + /** + * A wrapper to store WeakFrame and the pointer to the underlying frame. + * This is needed because WeakFrame does not store the frame pointer after + * the frame has been deleted. + */ + struct WeakFrameWrapper { + explicit WeakFrameWrapper(nsIFrame* aFrame) + : mWeakFrame(new WeakFrame(aFrame)), mFrame(aFrame) {} + + UniquePtr<WeakFrame> mWeakFrame; + void* mFrame; + }; + + nsTHashSet<void*> mFrameSet; + nsTArray<WeakFrameWrapper> mFrames; + nsTArray<pixman_box32_t> mRects; + + template <typename RectType> + void Add(nsIFrame* aFrame, const RectType& aRect) { + if (mFrameSet.Contains(aFrame)) { + return; + } + + mFrameSet.Insert(aFrame); + mFrames.AppendElement(WeakFrameWrapper(aFrame)); + mRects.AppendElement(nsRegion::RectToBox(aRect)); + } + + void Clear() { + mFrameSet.Clear(); + mFrames.Clear(); + mRects.Clear(); + } + + void RemoveModifiedFramesAndRects(); + + size_t SizeOfExcludingThis(MallocSizeOf) const; + + typedef gfx::ArrayView<pixman_box32_t> BoxArrayView; + + nsRegion ToRegion() const { return nsRegion(BoxArrayView(mRects)); } + + LayoutDeviceIntRegion ToLayoutDeviceIntRegion() const { + return LayoutDeviceIntRegion(BoxArrayView(mRects)); + } + }; + + void AddScrollFrameToNotify(nsIScrollableFrame* aScrollFrame); + void NotifyAndClearScrollFrames(); + + // Helper class to find what link spec (if any) to associate with a frame, + // recording it in the builder, and generate the corresponding DisplayItem. + // This also takes care of generating a named destination for internal links + // if the element has an id or name attribute. + class Linkifier { + public: + Linkifier(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + nsDisplayList* aList); + + ~Linkifier() { + if (mBuilderToReset) { + mBuilderToReset->mLinkSpec.Truncate(0); + } + } + + void MaybeAppendLink(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame); + + private: + nsDisplayListBuilder* mBuilderToReset = nullptr; + nsDisplayList* mList; + }; + + /** + * Returns the nearest ancestor frame to aFrame that is considered to have + * (or will have) animated geometry. This can return aFrame. + */ + nsIFrame* FindAnimatedGeometryRootFrameFor(nsIFrame* aFrame); + + /** + * Returns true if this is a retained builder and reuse stacking contexts + * mode is enabled by pref. + */ + bool IsReusingStackingContextItems() const { + return mIsReusingStackingContextItems; + } + + /** + * Adds display item |aItem| to the reuseable display items set. + */ + void AddReusableDisplayItem(nsDisplayItem* aItem); + + /** + * Removes display item |aItem| from the reuseable display items set. + * This is needed because display items are sometimes deleted during + * display list building. + * Called by |nsDisplayItem::Destroy()| when the item has been reused. + */ + void RemoveReusedDisplayItem(nsDisplayItem* aItem); + + /** + * Clears the reuseable display items set. + */ + void ClearReuseableDisplayItems(); + + /** + * Marks the given display item |aItem| as reused, and updates the necessary + * display list builder state. + */ + void ReuseDisplayItem(nsDisplayItem* aItem); + + private: + bool MarkOutOfFlowFrameForDisplay(nsIFrame* aDirtyFrame, nsIFrame* aFrame, + const nsRect& aVisibleRect, + const nsRect& aDirtyRect); + + friend class nsDisplayBackgroundImage; + friend class RetainedDisplayListBuilder; + + /** + * Returns whether a frame acts as an animated geometry root, optionally + * returning the next ancestor to check. + */ + bool IsAnimatedGeometryRoot(nsIFrame* aFrame, nsIFrame** aParent = nullptr); + + struct PresShellState { + PresShell* mPresShell; +#ifdef DEBUG + Maybe<nsAutoLayoutPhase> mAutoLayoutPhase; +#endif + Maybe<OutOfFlowDisplayData> mFixedBackgroundDisplayData; + uint32_t mFirstFrameMarkedForDisplay; + uint32_t mFirstFrameWithOOFData; + bool mIsBackgroundOnly; + // This is a per-document flag turning off event handling for all content + // in the document, and is set when we enter a subdocument for a pointer- + // events:none frame. + bool mInsidePointerEventsNoneDoc; + bool mTouchEventPrefEnabledDoc; + nsIFrame* mPresShellIgnoreScrollFrame; + }; + + PresShellState* CurrentPresShellState() { + NS_ASSERTION(mPresShellStates.Length() > 0, + "Someone forgot to enter a presshell"); + return &mPresShellStates[mPresShellStates.Length() - 1]; + } + + void AddSizeOfExcludingThis(nsWindowSizes&) const; + + struct FrameWillChangeBudget { + FrameWillChangeBudget() : mPresContext(nullptr), mUsage(0) {} + + FrameWillChangeBudget(const nsPresContext* aPresContext, uint32_t aUsage) + : mPresContext(aPresContext), mUsage(aUsage) {} + + const nsPresContext* mPresContext; + uint32_t mUsage; + }; + + // will-change budget tracker + typedef uint32_t DocumentWillChangeBudget; + + nsIFrame* const mReferenceFrame; + nsIFrame* mIgnoreScrollFrame; + + const ActiveScrolledRoot* mCurrentActiveScrolledRoot; + const ActiveScrolledRoot* mCurrentContainerASR; + // mCurrentFrame is the frame that we're currently calling (or about to call) + // BuildDisplayList on. + const nsIFrame* mCurrentFrame; + // The reference frame for mCurrentFrame. + const nsIFrame* mCurrentReferenceFrame; + + // The display item for the Windows window glass background, if any + // Set during full display list builds or during display list merging only, + // partial display list builds don't touch this. + nsDisplayItem* mGlassDisplayItem; + + nsIFrame* mCaretFrame; + // A temporary list that we append scroll info items to while building + // display items for the contents of frames with SVG effects. + // Only non-null when ShouldBuildScrollInfoItemsForHoisting() is true. + // This is a pointer and not a real nsDisplayList value because the + // nsDisplayList class is defined below this class, so we can't use it here. + nsDisplayList* mScrollInfoItemsForHoisting; + nsTArray<RefPtr<ActiveScrolledRoot>> mActiveScrolledRoots; + DisplayItemClipChain* mFirstClipChainToDestroy; + nsTArray<nsDisplayItem*> mTemporaryItems; + nsDisplayTableBackgroundSet* mTableBackgroundSet; + ViewID mCurrentScrollParentId; + ViewID mCurrentScrollbarTarget; + + nsTArray<nsIFrame*> mSVGEffectsFrames; + // When we are inside a filter, the current ASR at the time we entered the + // filter. Otherwise nullptr. + const ActiveScrolledRoot* mFilterASR; + nsCString mLinkSpec; // Destination of link currently being emitted, if any. + + // Optimized versions for non-retained display list. + LayoutDeviceIntRegion mWindowDraggingRegion; + LayoutDeviceIntRegion mWindowNoDraggingRegion; + nsRegion mWindowOpaqueRegion; + + nsClassHashtable<nsPtrHashKey<nsDisplayItem>, + nsTArray<nsIWidget::ThemeGeometry>> + mThemeGeometries; + DisplayListClipState mClipState; + nsTHashMap<nsPtrHashKey<const nsPresContext>, DocumentWillChangeBudget> + mDocumentWillChangeBudgets; + + // Any frame listed in this set is already counted in the budget + // and thus is in-budget. + nsTHashMap<nsPtrHashKey<const nsIFrame>, FrameWillChangeBudget> + mFrameWillChangeBudgets; + + nsTHashMap<nsPtrHashKey<dom::RemoteBrowser>, dom::EffectsInfo> + mEffectsUpdates; + + nsTHashSet<nsCString> mDestinations; // Destination names emitted. + + // Stores reusable items collected during display list preprocessing. + nsTHashSet<nsDisplayItem*> mReuseableItems; + + // Tracked regions used for retained display list. + WeakFrameRegion mWindowExcludeGlassRegion; + WeakFrameRegion mRetainedWindowDraggingRegion; + WeakFrameRegion mRetainedWindowNoDraggingRegion; + + // Window opaque region is calculated during layer building. + WeakFrameRegion mRetainedWindowOpaqueRegion; + + std::unordered_set<const DisplayItemClipChain*, DisplayItemClipChainHasher, + DisplayItemClipChainEqualer> + mClipDeduplicator; + std::unordered_set<nsIScrollableFrame*> mScrollFramesToNotify; + + AutoTArray<nsIFrame*, 20> mFramesWithOOFData; + AutoTArray<nsIFrame*, 40> mFramesMarkedForDisplayIfVisible; + AutoTArray<PresShellState, 8> mPresShellStates; + + using Arena = nsPresArena<32768, DisplayListArenaObjectId, + size_t(DisplayListArenaObjectId::COUNT)>; + Arena mPool; + + AutoTArray<nsIFrame*, 400> mFramesMarkedForDisplay; + + gfx::CompositorHitTestInfo mCompositorHitTestInfo; + + // The offset from mCurrentFrame to mCurrentReferenceFrame. + nsPoint mCurrentOffsetToReferenceFrame; + + Maybe<float> mVisibleThreshold; + + Maybe<nsPoint> mAdditionalOffset; + + // Relative to mCurrentFrame. + nsRect mVisibleRect; + nsRect mDirtyRect; + nsRect mCaretRect; + + Preserves3DContext mPreserves3DCtx; + + uint8_t mBuildingExtraPagesForPageNum; + + // If we've encountered a glass item yet, only used during partial display + // list builds. + bool mHasGlassItemDuringPartial; + + nsDisplayListBuilderMode mMode; + static uint32_t sPaintSequenceNumber; + + bool mContainsBlendMode; + bool mIsBuildingScrollbar; + bool mCurrentScrollbarWillHaveLayer; + bool mBuildCaret; + bool mRetainingDisplayList; + bool mPartialUpdate; + bool mIgnoreSuppression; + bool mIncludeAllOutOfFlows; + bool mDescendIntoSubdocuments; + bool mSelectedFramesOnly; + bool mAllowMergingAndFlattening; + // True when we're building a display list that's directly or indirectly + // under an nsDisplayTransform + bool mInTransform; + bool mInEventsOnly; + bool mInFilter; + bool mInPageSequence; + bool mIsInChromePresContext; + bool mSyncDecodeImages; + bool mIsPaintingToWindow; + bool mUseHighQualityScaling; + bool mIsPaintingForWebRender; + bool mIsCompositingCheap; + bool mAncestorHasApzAwareEventHandler; + // True when the first async-scrollable scroll frame for which we build a + // display list has a display port. An async-scrollable scroll frame is one + // which WantsAsyncScroll(). + bool mHaveScrollableDisplayPort; + bool mWindowDraggingAllowed; + bool mIsBuildingForPopup; + bool mForceLayerForScrollParent; + bool mContainsNonMinimalDisplayPort; + bool mAsyncPanZoomEnabled; + bool mBuildingInvisibleItems; + bool mIsBuilding; + bool mInInvalidSubtree; + bool mBuildCompositorHitTestInfo; + bool mDisablePartialUpdates; + bool mPartialBuildFailed; + bool mIsInActiveDocShell; + bool mBuildAsyncZoomContainer; + bool mIsRelativeToLayoutViewport; + bool mUseOverlayScrollbars; + bool mAlwaysLayerizeScrollbars; + + bool mIsReusingStackingContextItems; + + Maybe<layers::ScrollDirection> mCurrentScrollbarDirection; +}; + +// All types are defined in nsDisplayItemTypes.h +#define NS_DISPLAY_DECL_NAME(n, e) \ + const char* Name() const override { return n; } \ + constexpr static DisplayItemType ItemType() { return DisplayItemType::e; } \ + \ + private: \ + void* operator new(size_t aSize, nsDisplayListBuilder* aBuilder) { \ + return aBuilder->Allocate(aSize, DisplayItemType::e); \ + } \ + \ + template <typename T, typename F, typename... Args> \ + friend T* mozilla::MakeDisplayItemWithIndex( \ + nsDisplayListBuilder* aBuilder, F* aFrame, const uint16_t aIndex, \ + Args&&... aArgs); \ + \ + public: + +#define NS_DISPLAY_ALLOW_CLONING() \ + template <typename T> \ + friend T* mozilla::MakeClone(nsDisplayListBuilder* aBuilder, \ + const T* aItem); \ + \ + nsDisplayWrapList* Clone(nsDisplayListBuilder* aBuilder) const override { \ + return MakeClone(aBuilder, this); \ + } + +template <typename T> +MOZ_ALWAYS_INLINE T* MakeClone(nsDisplayListBuilder* aBuilder, const T* aItem) { + static_assert(std::is_base_of<nsDisplayWrapList, T>::value, + "Display item type should be derived from nsDisplayWrapList"); + T* item = new (aBuilder) T(aBuilder, *aItem); + item->SetType(T::ItemType()); + return item; +} + +#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED +void AssertUniqueItem(nsDisplayItem* aItem); +#endif + +/** + * Returns true, if a display item of given |aType| needs to be built within + * opacity:0 container. + */ +bool ShouldBuildItemForEvents(const DisplayItemType aType); + +/** + * Initializes the hit test information of |aItem| if the item type supports it. + */ +void InitializeHitTestInfo(nsDisplayListBuilder* aBuilder, + nsPaintedDisplayItem* aItem, + const DisplayItemType aType); + +template <typename T, typename F, typename... Args> +MOZ_ALWAYS_INLINE T* MakeDisplayItemWithIndex(nsDisplayListBuilder* aBuilder, + F* aFrame, const uint16_t aIndex, + Args&&... aArgs) { + static_assert(std::is_base_of<nsDisplayItem, T>::value, + "Display item type should be derived from nsDisplayItem"); + static_assert(std::is_base_of<nsIFrame, F>::value, + "Frame type should be derived from nsIFrame"); + + const DisplayItemType type = T::ItemType(); + if (aBuilder->InEventsOnly() && !ShouldBuildItemForEvents(type)) { + // This item is not needed for events. + return nullptr; + } + + T* item = new (aBuilder) T(aBuilder, aFrame, std::forward<Args>(aArgs)...); + + if (type != DisplayItemType::TYPE_GENERIC) { + item->SetType(type); + } + + item->SetPerFrameIndex(aIndex); + item->SetExtraPageForPageNum(aBuilder->GetBuildingExtraPagesForPageNum()); + + nsPaintedDisplayItem* paintedItem = item->AsPaintedDisplayItem(); + if (paintedItem) { + InitializeHitTestInfo(aBuilder, paintedItem, type); + } + + if (aBuilder->InInvalidSubtree() || + item->FrameForInvalidation()->IsFrameModified()) { + item->SetModifiedFrame(true); + } + +#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + if (aBuilder->IsRetainingDisplayList() && aBuilder->IsBuilding()) { + AssertUniqueItem(item); + } + + // Verify that InInvalidSubtree matches invalidation frame's modified state. + if (aBuilder->InInvalidSubtree()) { + MOZ_DIAGNOSTIC_ASSERT( + AnyContentAncestorModified(item->FrameForInvalidation())); + } + + DebugOnly<bool> isContainerType = + (GetDisplayItemFlagsForType(type) & TYPE_IS_CONTAINER); + + MOZ_ASSERT(item->HasChildren() == isContainerType, + "Container items must have container display item flag set."); +#endif + + DL_LOGV("Created display item %p (%s) (frame: %p)", item, item->Name(), + aFrame); + + return item; +} + +template <typename T, typename F, typename... Args> +MOZ_ALWAYS_INLINE T* MakeDisplayItem(nsDisplayListBuilder* aBuilder, F* aFrame, + Args&&... aArgs) { + return MakeDisplayItemWithIndex<T>(aBuilder, aFrame, 0, + std::forward<Args>(aArgs)...); +} + +/* + * nsDisplayItemBase is a base-class for all display items. It is mainly + * responsible for handling the frame-display item 1:n relationship, as well as + * storing the state needed for display list merging. + * + * Display items are arena-allocated during display list construction. + * + * Display items can be containers --- i.e., they can perform hit testing + * and painting by recursively traversing a list of child items. + * + * Display items belong to a list at all times (except temporarily as they + * move from one list to another). + */ +class nsDisplayItem { + public: + using LayerManager = layers::LayerManager; + using WebRenderLayerManager = layers::WebRenderLayerManager; + using StackingContextHelper = layers::StackingContextHelper; + using ViewID = layers::ScrollableLayerGuid::ViewID; + + /** + * Downcasts this item to nsPaintedDisplayItem, if possible. + */ + virtual nsPaintedDisplayItem* AsPaintedDisplayItem() { return nullptr; } + virtual const nsPaintedDisplayItem* AsPaintedDisplayItem() const { + return nullptr; + } + + /** + * Downcasts this item to nsDisplayWrapList, if possible. + */ + virtual nsDisplayWrapList* AsDisplayWrapList() { return nullptr; } + virtual const nsDisplayWrapList* AsDisplayWrapList() const { return nullptr; } + + /** + * Create a clone of this item. + */ + virtual nsDisplayWrapList* Clone(nsDisplayListBuilder* aBuilder) const { + return nullptr; + } + + /** + * Checks if the given display item can be merged with this item. + * @return true if the merging is possible, otherwise false. + */ + virtual bool CanMerge(const nsDisplayItem* aItem) const { return false; } + + /** + * Frees the memory allocated for this display item. + * The given display list builder must have allocated this display item. + */ + virtual void Destroy(nsDisplayListBuilder* aBuilder) { + const DisplayItemType type = GetType(); + DL_LOGV("Destroying display item %p (%s)", this, Name()); + + if (IsReusedItem()) { + aBuilder->RemoveReusedDisplayItem(this); + } + + this->~nsDisplayItem(); + aBuilder->Destroy(type, this); + } + + /** + * Returns the frame that this display item was created for. + * Never returns null. + */ + inline nsIFrame* Frame() const { + MOZ_ASSERT(mFrame, "Trying to use display item after frame deletion!"); + return mFrame; + } + + /** + * Called when the display item is prepared for deletion. The display item + * should not be used after calling this function. + */ + virtual void RemoveFrame(nsIFrame* aFrame) { + MOZ_ASSERT(aFrame); + + if (mFrame && aFrame == mFrame) { + mFrame = nullptr; + SetDeletedFrame(); + } + } + + /** + * A display item can depend on multiple different frames for invalidation. + */ + virtual nsIFrame* GetDependentFrame() { return nullptr; } + + /** + * Returns the frame that provides the style data, and should + * be checked when deciding if this display item can be reused. + */ + virtual nsIFrame* FrameForInvalidation() const { return Frame(); } + + /** + * Display items can override this to communicate that they won't + * contribute any visual information (for example fully transparent). + */ + virtual bool IsInvisible() const { return false; } + + /** + * Returns the printable name of this display item. + */ + virtual const char* Name() const = 0; + + /** + * Some consecutive items should be rendered together as a unit, e.g., + * outlines for the same element. For this, we need a way for items to + * identify their type. We use the type for other purposes too. + */ + DisplayItemType GetType() const { + MOZ_ASSERT(mType != DisplayItemType::TYPE_ZERO, + "Display item should have a valid type!"); + return mType; + } + + /** + * Pairing this with the Frame() pointer gives a key that + * uniquely identifies this display item in the display item tree. + */ + uint32_t GetPerFrameKey() const { + // The top 8 bits are the page index + // The middle 16 bits of the per frame key uniquely identify the display + // item when there are more than one item of the same type for a frame. + // The low 8 bits are the display item type. + return (static_cast<uint32_t>(mExtraPageForPageNum) + << (TYPE_BITS + (sizeof(mPerFrameIndex) * 8))) | + (static_cast<uint32_t>(mPerFrameIndex) << TYPE_BITS) | + static_cast<uint32_t>(mType); + } + + /** + * Returns true if this item was reused during display list merging. + */ + bool IsReused() const { return mItemFlags.contains(ItemFlag::ReusedItem); } + + void SetReused(bool aReused) { SetItemFlag(ItemFlag::ReusedItem, aReused); } + + /** + * Returns true if this item can be reused during display list merging. + */ + bool CanBeReused() const { + return !mItemFlags.contains(ItemFlag::CantBeReused); + } + + void SetCantBeReused() { mItemFlags += ItemFlag::CantBeReused; } + + bool CanBeCached() const { + return !mItemFlags.contains(ItemFlag::CantBeCached); + } + + void SetCantBeCached() { mItemFlags += ItemFlag::CantBeCached; } + + bool IsOldItem() const { return !!mOldList; } + + /** + * Returns true if the frame of this display item is in a modified subtree. + */ + bool HasModifiedFrame() const { + return mItemFlags.contains(ItemFlag::ModifiedFrame); + } + + void SetModifiedFrame(bool aModified) { + SetItemFlag(ItemFlag::ModifiedFrame, aModified); + } + + bool HasDeletedFrame() const; + + /** + * Set the nsDisplayList that this item belongs to, and what index it is + * within that list. + * Temporary state for merging used by RetainedDisplayListBuilder. + */ + void SetOldListIndex(nsDisplayList* aList, OldListIndex aIndex, + uint32_t aListKey, uint32_t aNestingDepth) { +#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + mOldListKey = aListKey; + mOldNestingDepth = aNestingDepth; +#endif + mOldList = reinterpret_cast<uintptr_t>(aList); + mOldListIndex = aIndex; + } + + bool GetOldListIndex(nsDisplayList* aList, uint32_t aListKey, + OldListIndex* aOutIndex) { + if (mOldList != reinterpret_cast<uintptr_t>(aList)) { +#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + MOZ_CRASH_UNSAFE_PRINTF( + "Item found was in the wrong list! type %d " + "(outer type was %d at depth %d, now is %d)", + GetPerFrameKey(), mOldListKey, mOldNestingDepth, aListKey); +#endif + return false; + } + *aOutIndex = mOldListIndex; + return true; + } + + /** + * Returns the display list containing the children of this display item. + * The children may be in a different coordinate system than this item. + */ + virtual RetainedDisplayList* GetChildren() const { return nullptr; } + bool HasChildren() const { return GetChildren(); } + + /** + * Display items with children may return true here. This causes the + * display list iterator to descend into the child display list. + */ + virtual bool ShouldFlattenAway(nsDisplayListBuilder* aBuilder) { + return false; + } + + virtual bool CreatesStackingContextHelper() { return false; } + + /** + * Returns true if this item can be moved asynchronously on the compositor, + * see RetainedDisplayListBuilder.cpp comments. + */ + virtual bool CanMoveAsync() { return false; } + + protected: + // This is never instantiated directly (it has pure virtual methods), so no + // need to count constructors and destructors. + nsDisplayItem(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame); + nsDisplayItem(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + const ActiveScrolledRoot* aActiveScrolledRoot); + + /** + * The custom copy-constructor is implemented to prevent copying the saved + * state of the item. + * This is currently only used when creating temporary items for merging. + */ + nsDisplayItem(nsDisplayListBuilder* aBuilder, const nsDisplayItem& aOther) + : mFrame(aOther.mFrame), + mItemFlags(aOther.mItemFlags), + mType(aOther.mType), + mExtraPageForPageNum(aOther.mExtraPageForPageNum), + mPerFrameIndex(aOther.mPerFrameIndex), + mBuildingRect(aOther.mBuildingRect), + mToReferenceFrame(aOther.mToReferenceFrame), + mActiveScrolledRoot(aOther.mActiveScrolledRoot), + mClipChain(aOther.mClipChain) { + MOZ_COUNT_CTOR(nsDisplayItem); + // TODO: It might be better to remove the flags that aren't copied. + if (aOther.ForceNotVisible()) { + mItemFlags += ItemFlag::ForceNotVisible; + } + if (mFrame->In3DContextAndBackfaceIsHidden()) { + mItemFlags += ItemFlag::BackfaceHidden; + } + if (aOther.Combines3DTransformWithAncestors()) { + mItemFlags += ItemFlag::Combines3DTransformWithAncestors; + } + } + + virtual ~nsDisplayItem() { + MOZ_COUNT_DTOR(nsDisplayItem); + if (mFrame) { + mFrame->RemoveDisplayItem(this); + } + } + + void SetType(const DisplayItemType aType) { mType = aType; } + + void SetPerFrameIndex(const uint16_t aIndex) { mPerFrameIndex = aIndex; } + + // Display list building for printing can build duplicate + // container display items when they contain a mixture of + // OOF and normal content that is spread across multiple + // pages. We include the page number for the duplicates + // to make our GetPerFrameKey unique. + void SetExtraPageForPageNum(const uint8_t aPageNum) { + mExtraPageForPageNum = aPageNum; + } + + void SetDeletedFrame(); + + public: + nsDisplayItem() = delete; + nsDisplayItem(const nsDisplayItem&) = delete; + + /** + * Invalidate cached information that depends on this node's contents, after + * a mutation of those contents. + * + * Specifically, if you mutate an |nsDisplayItem| in a way that would change + * the WebRender display list items generated for it, you should call this + * method. + * + * If a |RestoreState| method exists to restore some piece of state, that's a + * good indication that modifications to said state should be accompanied by a + * call to this method. Opacity flattening's effects on + * |nsDisplayBackgroundColor| items are one example. + */ + virtual void InvalidateItemCacheEntry() {} + + struct HitTestState { + explicit HitTestState() = default; + + ~HitTestState() { + NS_ASSERTION(mItemBuffer.Length() == 0, + "mItemBuffer should have been cleared"); + } + + // Handling transform items for preserve 3D frames. + bool mInPreserves3D = false; + // When hit-testing for visibility, we may hit an fully opaque item in a + // nested display list. We want to stop at that point, without looking + // further on other items. + bool mHitOccludingItem = false; + + float mCurrentOpacity = 1.0f; + + AutoTArray<nsDisplayItem*, 100> mItemBuffer; + }; + + uint8_t GetFlags() const { return GetDisplayItemFlagsForType(GetType()); } + + virtual bool IsContentful() const { return GetFlags() & TYPE_IS_CONTENTFUL; } + + /** + * This is called after we've constructed a display list for event handling. + * When this is called, we've already ensured that aRect intersects the + * item's bounds and that clipping has been taking into account. + * + * @param aRect the point or rect being tested, relative to the reference + * frame. If the width and height are both 1 app unit, it indicates we're + * hit testing a point, not a rect. + * @param aState must point to a HitTestState. If you don't have one, + * just create one with the default constructor and pass it in. + * @param aOutFrames each item appends the frame(s) in this display item that + * the rect is considered over (if any) to aOutFrames. + */ + virtual void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect, + HitTestState* aState, nsTArray<nsIFrame*>* aOutFrames) {} + + virtual nsIFrame* StyleFrame() const { return mFrame; } + + /** + * Compute the used z-index of our frame; returns zero for elements to which + * z-index does not apply, and for z-index:auto. + * @note This can be overridden, @see nsDisplayWrapList::SetOverrideZIndex. + */ + virtual int32_t ZIndex() const; + /** + * The default bounds is the frame border rect. + * @param aSnap *aSnap is set to true if the returned rect will be + * snapped to nearest device pixel edges during actual drawing. + * It might be set to false and snap anyway, so code computing the set of + * pixels affected by this display item needs to round outwards to pixel + * boundaries when *aSnap is set to false. + * This does not take the item's clipping into account. + * @return a rectangle relative to aBuilder->ReferenceFrame() that + * contains the area drawn by this display item + */ + virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) const { + *aSnap = false; + return nsRect(ToReferenceFrame(), Frame()->GetSize()); + } + + /** + * Returns the untransformed bounds of this display item. + */ + virtual nsRect GetUntransformedBounds(nsDisplayListBuilder* aBuilder, + bool* aSnap) const { + return GetBounds(aBuilder, aSnap); + } + + virtual nsRegion GetTightBounds(nsDisplayListBuilder* aBuilder, + bool* aSnap) const { + *aSnap = false; + return nsRegion(); + } + + /** + * Returns true if nothing will be rendered inside aRect, false if uncertain. + * aRect is assumed to be contained in this item's bounds. + */ + virtual bool IsInvisibleInRect(const nsRect& aRect) const { return false; } + + /** + * Returns the result of GetBounds intersected with the item's clip. + * The intersection is approximate since rounded corners are not taking into + * account. + */ + nsRect GetClippedBounds(nsDisplayListBuilder* aBuilder) const; + + nsRect GetBorderRect() const { + return nsRect(ToReferenceFrame(), Frame()->GetSize()); + } + + nsRect GetPaddingRect() const { + return Frame()->GetPaddingRectRelativeToSelf() + ToReferenceFrame(); + } + + nsRect GetContentRect() const { + return Frame()->GetContentRectRelativeToSelf() + ToReferenceFrame(); + } + + /** + * Checks if the frame(s) owning this display item have been marked as + * invalid, and needing repainting. + */ + virtual bool IsInvalid(nsRect& aRect) const { + bool result = mFrame ? mFrame->IsInvalid(aRect) : false; + aRect += ToReferenceFrame(); + return result; + } + + /** + * Creates and initializes an nsDisplayItemGeometry object that retains the + * current areas covered by this display item. These need to retain enough + * information such that they can be compared against a future nsDisplayItem + * of the same type, and determine if repainting needs to happen. + * + * Subclasses wishing to store more information need to override both this + * and ComputeInvalidationRegion, as well as implementing an + * nsDisplayItemGeometry subclass. + * + * The default implementation tracks both the display item bounds, and the + * frame's border rect. + */ + virtual nsDisplayItemGeometry* AllocateGeometry( + nsDisplayListBuilder* aBuilder) { + return new nsDisplayItemGenericGeometry(this, aBuilder); + } + + /** + * Compares an nsDisplayItemGeometry object from a previous paint against the + * current item. Computes if the geometry of the item has changed, and the + * invalidation area required for correct repainting. + * + * The existing geometry will have been created from a display item with a + * matching GetPerFrameKey()/mFrame pair to the current item. + * + * The default implementation compares the display item bounds, and the + * frame's border rect, and invalidates the entire bounds if either rect + * changes. + * + * @param aGeometry The geometry of the matching display item from the + * previous paint. + * @param aInvalidRegion Output param, the region to invalidate, or + * unchanged if none. + */ + virtual void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder, + const nsDisplayItemGeometry* aGeometry, + nsRegion* aInvalidRegion) const { + const nsDisplayItemGenericGeometry* geometry = + static_cast<const nsDisplayItemGenericGeometry*>(aGeometry); + bool snap; + if (!geometry->mBounds.IsEqualInterior(GetBounds(aBuilder, &snap)) || + !geometry->mBorderRect.IsEqualInterior(GetBorderRect())) { + aInvalidRegion->Or(GetBounds(aBuilder, &snap), geometry->mBounds); + } + } + + /** + * An alternative default implementation of ComputeInvalidationRegion, + * that instead invalidates only the changed area between the two items. + */ + void ComputeInvalidationRegionDifference( + nsDisplayListBuilder* aBuilder, + const nsDisplayItemBoundsGeometry* aGeometry, + nsRegion* aInvalidRegion) const { + bool snap; + nsRect bounds = GetBounds(aBuilder, &snap); + + if (!aGeometry->mBounds.IsEqualInterior(bounds)) { + nscoord radii[8]; + if (aGeometry->mHasRoundedCorners || Frame()->GetBorderRadii(radii)) { + aInvalidRegion->Or(aGeometry->mBounds, bounds); + } else { + aInvalidRegion->Xor(aGeometry->mBounds, bounds); + } + } + } + + /** + * This function is called when an item's list of children has been modified + * by RetainedDisplayListBuilder. + */ + virtual void InvalidateCachedChildInfo(nsDisplayListBuilder* aBuilder) {} + + virtual void AddSizeOfExcludingThis(nsWindowSizes&) const {} + + /** + * @param aSnap set to true if the edges of the rectangles of the opaque + * region would be snapped to device pixels when drawing + * @return a region of the item that is opaque --- that is, every pixel + * that is visible is painted with an opaque + * color. This is useful for determining when one piece + * of content completely obscures another so that we can do occlusion + * culling. + * This does not take clipping into account. + * This must return a simple region (1 rect) for painting display lists. + * It is only allowed to be a complex region for hit testing. + */ + virtual nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder, + bool* aSnap) const { + *aSnap = false; + return nsRegion(); + } + /** + * @return Some(nscolor) if the item is guaranteed to paint every pixel in its + * bounds with the same (possibly translucent) color + */ + virtual Maybe<nscolor> IsUniform(nsDisplayListBuilder* aBuilder) const { + return Nothing(); + } + + /** + * @return true if the contents of this item are rendered fixed relative + * to the nearest viewport. + */ + virtual bool ShouldFixToViewport(nsDisplayListBuilder* aBuilder) const { + return false; + } + + /** + * Returns true if all layers that can be active should be forced to be + * active. Requires setting the pref layers.force-active=true. + */ + static bool ForceActiveLayers(); + +#ifdef MOZ_DUMP_PAINTING + /** + * Mark this display item as being painted via + * FrameLayerBuilder::DrawPaintedLayer. + */ + bool Painted() const { return mItemFlags.contains(ItemFlag::Painted); } + + /** + * Check if this display item has been painted. + */ + void SetPainted() { mItemFlags += ItemFlag::Painted; } +#endif + + void SetIsGlassItem() { mItemFlags += ItemFlag::IsGlassItem; } + bool IsGlassItem() { return mItemFlags.contains(ItemFlag::IsGlassItem); } + + /** + * Function to create the WebRenderCommands. + * We should check if the layer state is + * active first and have an early return if the layer state is + * not active. + * + * @return true if successfully creating webrender commands. + */ + virtual bool CreateWebRenderCommands( + wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + return false; + } + + /** + * Updates the provided aLayerData with any APZ-relevant scroll data + * that is specific to this display item. This is stuff that would normally + * be put on the layer during BuildLayer, but this is only called in + * layers-free webrender mode, where we don't have layers. + * + * This function returns true if and only if it has APZ-relevant scroll data + * to provide. Note that the arguments passed in may be nullptr, in which case + * the function should still return true if and only if it has APZ-relevant + * scroll data, but obviously in this case it can't actually put the + * data onto aLayerData, because there isn't one. + * + * This function assumes that aData and aLayerData will either both be null, + * or will both be non-null. The caller is responsible for enforcing this. + */ + virtual bool UpdateScrollData(layers::WebRenderScrollData* aData, + layers::WebRenderLayerScrollData* aLayerData) { + return false; + } + + /** + * Returns true if this item needs to have its geometry updated, despite + * returning empty invalidation region. + */ + virtual bool NeedsGeometryUpdates() const { return false; } + + /** + * Some items such as those calling into the native themed widget machinery + * have to be painted on the content process. In this case it is best to avoid + * allocating layers that serializes and forwards the work to the compositor. + */ + virtual bool MustPaintOnContentSide() const { return false; } + + /** + * If this has a child list where the children are in the same coordinate + * system as this item (i.e., they have the same reference frame), + * return the list. + */ + virtual RetainedDisplayList* GetSameCoordinateSystemChildren() const { + return nullptr; + } + + virtual void UpdateBounds(nsDisplayListBuilder* aBuilder) {} + /** + * Do UpdateBounds() for items with frames establishing or extending + * 3D rendering context. + * + * This function is called by UpdateBoundsFor3D() of + * nsDisplayTransform(), and it is called by + * BuildDisplayListForStackingContext() on transform items + * establishing 3D rendering context. + * + * The bounds of a transform item with the frame establishing 3D + * rendering context should be computed by calling + * DoUpdateBoundsPreserves3D() on all descendants that participate + * the same 3d rendering context. + */ + virtual void DoUpdateBoundsPreserves3D(nsDisplayListBuilder* aBuilder) {} + + /** + * Returns the building rectangle used by nsDisplayListBuilder when + * this item was constructed. + */ + const nsRect& GetBuildingRect() const { return mBuildingRect; } + + void SetBuildingRect(const nsRect& aBuildingRect) { + mBuildingRect = aBuildingRect; + } + + /** + * Returns the building rect for the children, relative to their + * reference frame. Can be different from mBuildingRect for + * nsDisplayTransform, since the reference frame for the children is different + * from the reference frame for the item itself. + */ + virtual const nsRect& GetBuildingRectForChildren() const { + return mBuildingRect; + } + + virtual void WriteDebugInfo(std::stringstream& aStream) {} + + /** + * Returns the result of aBuilder->ToReferenceFrame(GetUnderlyingFrame()) + */ + const nsPoint& ToReferenceFrame() const { + NS_ASSERTION(mFrame, "No frame?"); + return mToReferenceFrame; + } + + /** + * Returns the reference frame for display item children of this item. + */ + virtual const nsIFrame* ReferenceFrameForChildren() const { return nullptr; } + + /** + * Checks if this display item (or any children) contains content that might + * be rendered with component alpha (e.g. subpixel antialiasing). Returns the + * bounds of the area that needs component alpha, or an empty rect if nothing + * in the item does. + */ + virtual nsRect GetComponentAlphaBounds(nsDisplayListBuilder* aBuilder) const { + return nsRect(); + } + + /** + * Check if we can add async animations to the layer for this display item. + */ + virtual bool CanUseAsyncAnimations(nsDisplayListBuilder* aBuilder) { + return false; + } + + virtual bool SupportsOptimizingToImage() const { return false; } + + virtual const DisplayItemClip& GetClip() const; + void IntersectClip(nsDisplayListBuilder* aBuilder, + const DisplayItemClipChain* aOther, bool aStore); + + virtual void SetActiveScrolledRoot( + const ActiveScrolledRoot* aActiveScrolledRoot) { + mActiveScrolledRoot = aActiveScrolledRoot; + } + const ActiveScrolledRoot* GetActiveScrolledRoot() const { + return mActiveScrolledRoot; + } + + virtual void SetClipChain(const DisplayItemClipChain* aClipChain, + bool aStore); + const DisplayItemClipChain* GetClipChain() const { return mClipChain; } + + bool BackfaceIsHidden() const { + return mItemFlags.contains(ItemFlag::BackfaceHidden); + } + + bool Combines3DTransformWithAncestors() const { + return mItemFlags.contains(ItemFlag::Combines3DTransformWithAncestors); + } + + bool ForceNotVisible() const { + return mItemFlags.contains(ItemFlag::ForceNotVisible); + } + + bool In3DContextAndBackfaceIsHidden() const { + return mItemFlags.contains(ItemFlag::BackfaceHidden) && + mItemFlags.contains(ItemFlag::Combines3DTransformWithAncestors); + } + + bool HasDifferentFrame(const nsDisplayItem* aOther) const { + return mFrame != aOther->mFrame; + } + + bool HasHitTestInfo() const { + return mItemFlags.contains(ItemFlag::HasHitTestInfo); + } + + bool HasSameTypeAndClip(const nsDisplayItem* aOther) const { + return GetPerFrameKey() == aOther->GetPerFrameKey() && + GetClipChain() == aOther->GetClipChain(); + } + + bool HasSameContent(const nsDisplayItem* aOther) const { + return mFrame->GetContent() == aOther->Frame()->GetContent(); + } + + virtual void NotifyUsed(nsDisplayListBuilder* aBuilder) {} + + virtual Maybe<nsRect> GetClipWithRespectToASR( + nsDisplayListBuilder* aBuilder, const ActiveScrolledRoot* aASR) const; + + virtual const nsRect& GetUntransformedPaintRect() const { + return GetBuildingRect(); + } + + nsRect GetPaintRect(nsDisplayListBuilder* aBuilder, gfxContext* aCtx); + + virtual const HitTestInfo& GetHitTestInfo() { return HitTestInfo::Empty(); } + + enum class ReuseState : uint8_t { + None, + // Set during display list building. + Reusable, + // Set during display list preprocessing. + PreProcessed, + // Set during partial display list build. + Reused, + }; + + void SetReusable() { + MOZ_ASSERT(mReuseState == ReuseState::None || + mReuseState == ReuseState::Reused); + mReuseState = ReuseState::Reusable; + } + + bool IsReusable() const { return mReuseState == ReuseState::Reusable; } + + void SetPreProcessed() { + MOZ_ASSERT(mReuseState == ReuseState::Reusable); + mReuseState = ReuseState::PreProcessed; + } + + bool IsPreProcessed() const { + return mReuseState == ReuseState::PreProcessed; + } + + void SetReusedItem() { + MOZ_ASSERT(mReuseState == ReuseState::PreProcessed); + mReuseState = ReuseState::Reused; + } + + bool IsReusedItem() const { return mReuseState == ReuseState::Reused; } + + void ResetReuseState() { mReuseState = ReuseState::None; } + + ReuseState GetReuseState() const { return mReuseState; } + + nsIFrame* mFrame; // 8 + + private: + enum class ItemFlag : uint16_t { + CantBeReused, + CantBeCached, + DeletedFrame, + ModifiedFrame, + ReusedItem, +#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + MergedItem, + PreProcessedItem, +#endif + BackfaceHidden, + Combines3DTransformWithAncestors, + ForceNotVisible, + HasHitTestInfo, + IsGlassItem, +#ifdef MOZ_DUMP_PAINTING + // True if this frame has been painted. + Painted, +#endif + }; + + EnumSet<ItemFlag, uint16_t> mItemFlags; // 2 + DisplayItemType mType = DisplayItemType::TYPE_ZERO; // 1 + uint8_t mExtraPageForPageNum = 0; // 1 + uint16_t mPerFrameIndex = 0; // 2 + ReuseState mReuseState = ReuseState::None; + OldListIndex mOldListIndex; // 4 + uintptr_t mOldList = 0; // 8 + + // This is the rectangle that nsDisplayListBuilder was using as the visible + // rect to decide which items to construct. + nsRect mBuildingRect; + + protected: + void SetItemFlag(ItemFlag aFlag, const bool aValue) { + if (aValue) { + mItemFlags += aFlag; + } else { + mItemFlags -= aFlag; + } + } + + void SetHasHitTestInfo() { mItemFlags += ItemFlag::HasHitTestInfo; } + + // Result of ToReferenceFrame(mFrame), if mFrame is non-null + nsPoint mToReferenceFrame; + + RefPtr<const ActiveScrolledRoot> mActiveScrolledRoot; + RefPtr<const DisplayItemClipChain> mClipChain; + +#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + public: + bool IsMergedItem() const { + return mItemFlags.contains(ItemFlag::MergedItem); + } + + bool IsPreProcessedItem() const { + return mItemFlags.contains(ItemFlag::PreProcessedItem); + } + + void SetMergedPreProcessed(bool aMerged, bool aPreProcessed) { + SetItemFlag(ItemFlag::MergedItem, aMerged); + SetItemFlag(ItemFlag::PreProcessedItem, aPreProcessed); + } + + uint32_t mOldListKey = 0; + uint32_t mOldNestingDepth = 0; +#endif +}; + +class nsPaintedDisplayItem : public nsDisplayItem { + public: + nsPaintedDisplayItem* AsPaintedDisplayItem() final { return this; } + const nsPaintedDisplayItem* AsPaintedDisplayItem() const final { + return this; + } + + /** + * Returns true if this display item would return true from ApplyOpacity + * without actually applying the opacity. Otherwise returns false. + */ + virtual bool CanApplyOpacity(WebRenderLayerManager* aManager, + nsDisplayListBuilder* aBuilder) const { + return false; + } + + /** + * Returns true if this item supports PaintWithClip, where the clipping + * is used directly as the primitive geometry instead of needing an explicit + * clip. + */ + virtual bool CanPaintWithClip(const DisplayItemClip& aClip) { return false; } + + /** + * Same as |Paint()|, except provides a clip to use the geometry to draw with. + * Must not be called unless |CanPaintWithClip()| returned true. + */ + virtual void PaintWithClip(nsDisplayListBuilder* aBuilder, gfxContext* aCtx, + const DisplayItemClip& aClip) { + MOZ_ASSERT_UNREACHABLE("PaintWithClip() is not implemented!"); + } + + /** + * Paint this item to some rendering context. + */ + virtual void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) = 0; + + /** + * External storage used by |DisplayItemCache| to avoid hashmap lookups. + * If an item is reused and has the cache index set, it means that + * |DisplayItemCache| has assigned a cache slot for the item. + */ + Maybe<uint16_t>& CacheIndex() { return mCacheIndex; } + + void InvalidateItemCacheEntry() override { + // |nsPaintedDisplayItem|s may have |DisplayItemCache| entries + // that no longer match after a mutation. The cache will notice + // on its own that the entry is no longer in use, and free it. + mCacheIndex = Nothing(); + } + + const HitTestInfo& GetHitTestInfo() final { return mHitTestInfo; } + void InitializeHitTestInfo(nsDisplayListBuilder* aBuilder) { + mHitTestInfo.Initialize(aBuilder, Frame()); + SetHasHitTestInfo(); + } + + protected: + nsPaintedDisplayItem(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame) + : nsPaintedDisplayItem(aBuilder, aFrame, + aBuilder->CurrentActiveScrolledRoot()) {} + + nsPaintedDisplayItem(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + const ActiveScrolledRoot* aActiveScrolledRoot) + : nsDisplayItem(aBuilder, aFrame, aActiveScrolledRoot) {} + + nsPaintedDisplayItem(nsDisplayListBuilder* aBuilder, + const nsPaintedDisplayItem& aOther) + : nsDisplayItem(aBuilder, aOther), mHitTestInfo(aOther.mHitTestInfo) {} + + protected: + HitTestInfo mHitTestInfo; + Maybe<uint16_t> mCacheIndex; +}; + +template <typename T> +struct MOZ_HEAP_CLASS LinkedListNode { + explicit LinkedListNode(T aValue) : mNext(nullptr), mValue(aValue) {} + LinkedListNode* mNext; + T mValue; +}; + +template <typename T> +struct LinkedListIterator { + using iterator_category = std::forward_iterator_tag; + using difference_type = std::ptrdiff_t; + using value_type = T; + using pointer = T*; + using reference = T&; + using Node = LinkedListNode<T>; + + explicit LinkedListIterator(Node* aNode = nullptr) : mNode(aNode) {} + + bool HasNext() const { return mNode != nullptr; } + + LinkedListIterator<T>& operator++() { + MOZ_ASSERT(mNode); + mNode = mNode->mNext; + return *this; + } + + bool operator==(const LinkedListIterator<T>& aOther) const { + return mNode == aOther.mNode; + } + + bool operator!=(const LinkedListIterator<T>& aOther) const { + return mNode != aOther.mNode; + } + + const T operator*() const { + MOZ_ASSERT(mNode); + return mNode->mValue; + } + + T operator*() { + MOZ_ASSERT(mNode); + return mNode->mValue; + } + + Node* mNode; +}; + +/** + * Manages a singly-linked list of display list items. + * + * Stepping upward through this list is very fast. Stepping downward is very + * slow so we don't support it. The methods that need to step downward + * (HitTest()) internally build a temporary array of all + * the items while they do the downward traversal, so overall they're still + * linear time. We have optimized for efficient AppendToTop() of both + * items and lists, with minimal codesize. + * + * Internal linked list nodes are allocated using arena allocator. + * */ +class nsDisplayList { + public: + using Node = LinkedListNode<nsDisplayItem*>; + using iterator = LinkedListIterator<nsDisplayItem*>; + using const_iterator = iterator; + + iterator begin() { return iterator(mBottom); } + iterator end() { return iterator(nullptr); } + const_iterator begin() const { return iterator(mBottom); } + const_iterator end() const { return iterator(nullptr); } + + explicit nsDisplayList(nsDisplayListBuilder* aBuilder) : mBuilder(aBuilder) {} + + nsDisplayList() = delete; + nsDisplayList(const nsDisplayList&) = delete; + nsDisplayList& operator=(const nsDisplayList&) = delete; + + virtual ~nsDisplayList() { +#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + if (!mAllowNonEmptyDestruction) { + MOZ_RELEASE_ASSERT(IsEmpty(), "Nonempty list left over?"); + } +#endif + + DeallocateNodes(); + } + + nsDisplayList(nsDisplayList&& aOther) + : mBottom(aOther.mBottom), + mTop(aOther.mTop), + mLength(aOther.mLength), + mBuilder(aOther.mBuilder) { + aOther.SetEmpty(); + } + + nsDisplayList& operator=(nsDisplayList&& aOther) { + MOZ_RELEASE_ASSERT(mBuilder == aOther.mBuilder); + + if (this != &aOther) { + MOZ_RELEASE_ASSERT(IsEmpty()); + mBottom = std::move(aOther.mBottom); + mTop = std::move(aOther.mTop); + mLength = std::move(aOther.mLength); + aOther.SetEmpty(); + } + return *this; + } + + /** + * Append an item to the top of the list. + **/ + void AppendToTop(nsDisplayItem* aItem) { + if (!aItem) { + return; + } + + auto* next = Allocate(aItem); + MOZ_ASSERT(next); + + if (IsEmpty()) { + mBottom = next; + mTop = next; + } else { + mTop->mNext = next; + mTop = next; + } + + mLength++; + + MOZ_ASSERT(mBottom && mTop); + MOZ_ASSERT(mTop->mNext == nullptr); + } + + template <typename T, typename F, typename... Args> + void AppendNewToTop(nsDisplayListBuilder* aBuilder, F* aFrame, + Args&&... aArgs) { + AppendNewToTopWithIndex<T>(aBuilder, aFrame, 0, + std::forward<Args>(aArgs)...); + } + + template <typename T, typename F, typename... Args> + void AppendNewToTopWithIndex(nsDisplayListBuilder* aBuilder, F* aFrame, + const uint16_t aIndex, Args&&... aArgs) { + nsDisplayItem* item = MakeDisplayItemWithIndex<T>( + aBuilder, aFrame, aIndex, std::forward<Args>(aArgs)...); + AppendToTop(item); + } + + /** + * Removes all items from aList and appends them to the top of this list. + */ + void AppendToTop(nsDisplayList* aList) { + MOZ_ASSERT(aList != this); + MOZ_RELEASE_ASSERT(mBuilder == aList->mBuilder); + + if (aList->IsEmpty()) { + return; + } + + if (IsEmpty()) { + std::swap(mBottom, aList->mBottom); + std::swap(mTop, aList->mTop); + std::swap(mLength, aList->mLength); + } else { + MOZ_ASSERT(mTop && mTop->mNext == nullptr); + mTop->mNext = aList->mBottom; + mTop = aList->mTop; + mLength += aList->mLength; + + aList->SetEmpty(); + } + } + + /** + * Clears the display list. + */ + void Clear() { + DeallocateNodes(); + SetEmpty(); + } + + /** + * Creates a shallow copy of this display list to |aDestination|. + */ + void CopyTo(nsDisplayList* aDestination) const { + for (auto* item : *this) { + aDestination->AppendToTop(item); + } + } + + /** + * Calls the function |aFn| for each display item in the display list. + */ + void ForEach(const std::function<void(nsDisplayItem*)>& aFn) { + for (auto* item : *this) { + aFn(item); + } + } + /** + * Remove all items from the list and call their destructors. + */ + virtual void DeleteAll(nsDisplayListBuilder* aBuilder); + + /** + * @return the item at the bottom of the list, or null if the list is empty + */ + nsDisplayItem* GetBottom() const { + return mBottom ? mBottom->mValue : nullptr; + } + + /** + * @return the item at the top of the list, or null if the list is empty + */ + nsDisplayItem* GetTop() const { return mTop ? mTop->mValue : nullptr; } + + bool IsEmpty() const { return mBottom == nullptr; } + + /** + * @return the number of items in the list + */ + size_t Length() const { return mLength; } + + /** + * Stable sort the list by the z-order of Frame() on + * each item. 'auto' is counted as zero. + * It is assumed that the list is already in content document order. + */ + void SortByZOrder(); + + /** + * Stable sort the list by the tree order of the content of + * Frame() on each item. z-index is ignored. + * @param aCommonAncestor a common ancestor of all the content elements + * associated with the display items, for speeding up tree order + * checks, or nullptr if not known; it's only a hint, if it is not an + * ancestor of some elements, then we lose performance but not correctness + */ + void SortByContentOrder(nsIContent* aCommonAncestor); + + /** + * Sort the display list using a stable sort. + * aComparator(Item item1, Item item2) should return true if item1 should go + * before item2. + * We sort the items into increasing order. + */ + template <typename Item, typename Comparator> + void Sort(const Comparator& aComparator) { + if (Length() < 2) { + // Only sort lists with more than one item. + return; + } + + // Some casual local browsing testing suggests that a local preallocated + // array of 20 items should be able to avoid a lot of dynamic allocations + // here. + AutoTArray<Item, 20> items; + + for (nsDisplayItem* item : TakeItems()) { + items.AppendElement(Item(item)); + } + + std::stable_sort(items.begin(), items.end(), aComparator); + + for (Item& item : items) { + AppendToTop(item); + } + } + + nsDisplayList TakeItems() { + nsDisplayList list = std::move(*this); +#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + list.mAllowNonEmptyDestruction = true; +#endif + return list; + } + + nsDisplayItem* RemoveBottom() { + if (!mBottom) { + return nullptr; + } + + nsDisplayItem* bottom = mBottom->mValue; + + auto next = mBottom->mNext; + Deallocate(mBottom); + mBottom = next; + + if (!mBottom) { + // No bottom item means no items at all. + mTop = nullptr; + } + + MOZ_ASSERT(mLength > 0); + mLength--; + + return bottom; + } + + /** + * Paint the list to the rendering context. We assume that (0,0) in aCtx + * corresponds to the origin of the reference frame. For best results, + * aCtx's current transform should make (0,0) pixel-aligned. The + * rectangle in aDirtyRect is painted, which *must* be contained in the + * dirty rect used to construct the display list. + * + * If aFlags contains PAINT_USE_WIDGET_LAYERS and + * ShouldUseWidgetLayerManager() is set, then we will paint using + * the reference frame's widget's layer manager (and ctx may be null), + * otherwise we will use a temporary BasicLayerManager and ctx must + * not be null. + * + * If PAINT_EXISTING_TRANSACTION is set, the reference frame's widget's + * layer manager has already had BeginTransaction() called on it and + * we should not call it again. + * + * This must only be called on the root display list of the display list + * tree. + * + * We return the layer manager used for painting --- mainly so that + * callers can dump its layer tree if necessary. + */ + enum { + PAINT_DEFAULT = 0, + PAINT_USE_WIDGET_LAYERS = 0x01, + PAINT_EXISTING_TRANSACTION = 0x04, + PAINT_IDENTICAL_DISPLAY_LIST = 0x08 + }; + void PaintRoot(nsDisplayListBuilder* aBuilder, gfxContext* aCtx, + uint32_t aFlags, Maybe<double> aDisplayListBuildTime); + + void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx, + int32_t aAppUnitsPerDevPixel); + + /** + * Get the bounds. Takes the union of the bounds of all children. + * The result is not cached. + */ + nsRect GetClippedBounds(nsDisplayListBuilder* aBuilder) const; + + /** + * Get this list's bounds, respecting clips relative to aASR. The result is + * the union of each item's clipped bounds with respect to aASR. That means + * that if an item can move asynchronously with an ASR that is a descendant + * of aASR, then the clipped bounds with respect to aASR will be the clip of + * that item for aASR, because the item can move anywhere inside that clip. + * If there is an item in this list which is not bounded with respect to + * aASR (i.e. which does not have "finite bounds" with respect to aASR), + * then this method trigger an assertion failure. + * The optional aBuildingRect out argument can be set to non-null if the + * caller is also interested to know the building rect. This can be used + * to get the visible rect efficiently without traversing the display list + * twice. + */ + nsRect GetClippedBoundsWithRespectToASR( + nsDisplayListBuilder* aBuilder, const ActiveScrolledRoot* aASR, + nsRect* aBuildingRect = nullptr) const; + + /** + * Returns the opaque region of this display list. + */ + nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder) { + nsRegion result; + bool snap; + for (nsDisplayItem* item : *this) { + result.OrWith(item->GetOpaqueRegion(aBuilder, &snap)); + } + return result; + } + + /** + * Returns the bounds of the area that needs component alpha. + */ + nsRect GetComponentAlphaBounds(nsDisplayListBuilder* aBuilder) const { + nsRect bounds; + for (nsDisplayItem* item : *this) { + bounds.UnionRect(bounds, item->GetComponentAlphaBounds(aBuilder)); + } + return bounds; + } + + /** + * Find the topmost display item that returns a non-null frame, and return + * the frame. + */ + void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect, + nsDisplayItem::HitTestState* aState, + nsTArray<nsIFrame*>* aOutFrames) const; + /** + * Compute the union of the visible rects of the items in the list. The + * result is not cached. + */ + nsRect GetBuildingRect() const; + + private: + inline Node* Allocate(nsDisplayItem* aItem) { + void* ptr = + mBuilder->Allocate(sizeof(Node), DisplayListArenaObjectId::LISTNODE); + return new (ptr) Node(aItem); + } + + inline void Deallocate(Node* aNode) { + aNode->~Node(); + mBuilder->Destroy(DisplayListArenaObjectId::LISTNODE, aNode); + } + + void DeallocateNodes() { + Node* current = mBottom; + Node* next = nullptr; + + while (current) { + next = current->mNext; + Deallocate(current); + current = next; + } + } + + inline void SetEmpty() { + mBottom = nullptr; + mTop = nullptr; + mLength = 0; + } + + Node* mBottom = nullptr; + Node* mTop = nullptr; + size_t mLength = 0; + nsDisplayListBuilder* mBuilder = nullptr; + +#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + // This checks that the invariant of display lists owning their items is held. + bool mAllowNonEmptyDestruction = false; +#endif +}; + +/** + * This is passed as a parameter to nsIFrame::BuildDisplayList. That method + * will put any generated items onto the appropriate list given here. It's + * basically just a collection with one list for each separate stacking layer. + * The lists themselves are external to this object and thus can be shared + * with others. Some of the list pointers may even refer to the same list. + */ +class nsDisplayListSet { + public: + /** + * @return a list where one should place the border and/or background for + * this frame (everything from steps 1 and 2 of CSS 2.1 appendix E) + */ + nsDisplayList* BorderBackground() const { return mLists[0]; } + /** + * @return a list where one should place the borders and/or backgrounds for + * block-level in-flow descendants (step 4 of CSS 2.1 appendix E) + */ + nsDisplayList* BlockBorderBackgrounds() const { return mLists[1]; } + /** + * @return a list where one should place descendant floats (step 5 of + * CSS 2.1 appendix E) + */ + nsDisplayList* Floats() const { return mLists[2]; } + /** + * @return a list where one should place the (pseudo) stacking contexts + * for descendants of this frame (everything from steps 3, 7 and 8 + * of CSS 2.1 appendix E) + */ + nsDisplayList* PositionedDescendants() const { return mLists[3]; } + /** + * @return a list where one should place the outlines + * for this frame and its descendants (step 9 of CSS 2.1 appendix E) + */ + nsDisplayList* Outlines() const { return mLists[4]; } + /** + * @return a list where one should place all other content + */ + nsDisplayList* Content() const { return mLists[5]; } + + const std::array<nsDisplayList*, 6>& Lists() const { return mLists; } + + /** + * Clears all the display lists in the set. + */ + void Clear() { + for (auto* list : mLists) { + MOZ_ASSERT(list); + list->Clear(); + } + } + + /** + * Deletes all the display items in the set. + */ + void DeleteAll(nsDisplayListBuilder* aBuilder) { + for (auto* list : mLists) { + list->DeleteAll(aBuilder); + } + } + + nsDisplayListSet(nsDisplayList* aBorderBackground, + nsDisplayList* aBlockBorderBackgrounds, + nsDisplayList* aFloats, nsDisplayList* aContent, + nsDisplayList* aPositionedDescendants, + nsDisplayList* aOutlines) + : mLists{aBorderBackground, aBlockBorderBackgrounds, aFloats, + aContent, aPositionedDescendants, aOutlines} {} + + /** + * A copy constructor that lets the caller override the BorderBackground + * list. + */ + nsDisplayListSet(const nsDisplayListSet& aLists, + nsDisplayList* aBorderBackground) + : mLists(aLists.mLists) { + mLists[0] = aBorderBackground; + } + + /** + * Returns true if all the display lists in the display list set are empty. + */ + bool IsEmpty() const { + for (auto* list : mLists) { + if (!list->IsEmpty()) { + return false; + } + } + + return true; + } + + /** + * Calls the function |aFn| for each display item in the display list set. + */ + void ForEach(const std::function<void(nsDisplayItem*)>& aFn) const { + for (auto* list : mLists) { + list->ForEach(aFn); + } + } + + /** + * Creates a shallow copy of this display list set to |aDestination|. + */ + void CopyTo(const nsDisplayListSet& aDestination) const; + + /** + * Move all display items in our lists to top of the corresponding lists in + * the destination. + */ + void MoveTo(const nsDisplayListSet& aDestination) const; + + private: + // This class is only used on stack, so we don't have to worry about leaking + // it. Don't let us be heap-allocated! + void* operator new(size_t sz) noexcept(true); + + std::array<nsDisplayList*, 6> mLists; +}; + +/** + * A specialization of nsDisplayListSet where the lists are actually internal + * to the object, and all distinct. + */ +struct nsDisplayListCollection : public nsDisplayListSet { + explicit nsDisplayListCollection(nsDisplayListBuilder* aBuilder) + : nsDisplayListSet(&mLists[0], &mLists[1], &mLists[2], &mLists[3], + &mLists[4], &mLists[5]), + mLists{nsDisplayList{aBuilder}, nsDisplayList{aBuilder}, + nsDisplayList{aBuilder}, nsDisplayList{aBuilder}, + nsDisplayList{aBuilder}, nsDisplayList{aBuilder}} {} + + /** + * Sort all lists by content order. + */ + void SortAllByContentOrder(nsIContent* aCommonAncestor) { + for (auto& mList : mLists) { + mList.SortByContentOrder(aCommonAncestor); + } + } + + /** + * Serialize this display list collection into a display list with the items + * in the correct Z order. + * @param aOutList the result display list + * @param aContent the content element to use for content ordering + */ + void SerializeWithCorrectZOrder(nsDisplayList* aOutResultList, + nsIContent* aContent); + + private: + // This class is only used on stack, so we don't have to worry about leaking + // it. Don't let us be heap-allocated! + void* operator new(size_t sz) noexcept(true); + + nsDisplayList mLists[6]; +}; + +/** + * A display list that also retains the partial build + * information (in the form of a DAG) used to create it. + * + * Display lists built from a partial list aren't necessarily + * in the same order as a full build, and the DAG retains + * the information needing to interpret the current + * order correctly. + */ +class RetainedDisplayList : public nsDisplayList { + public: + explicit RetainedDisplayList(nsDisplayListBuilder* aBuilder) + : nsDisplayList(aBuilder) {} + + RetainedDisplayList(RetainedDisplayList&& aOther) + : nsDisplayList(std::move(aOther)), mDAG(std::move(aOther.mDAG)) {} + + RetainedDisplayList(const RetainedDisplayList&) = delete; + RetainedDisplayList& operator=(const RetainedDisplayList&) = delete; + + ~RetainedDisplayList() override { + MOZ_ASSERT(mOldItems.IsEmpty(), "Must empty list before destroying"); + } + + RetainedDisplayList& operator=(RetainedDisplayList&& aOther) { + MOZ_ASSERT(IsEmpty(), "Can only move into an empty list!"); + MOZ_ASSERT(mOldItems.IsEmpty(), "Can only move into an empty list!"); + + nsDisplayList::operator=(std::move(aOther)); + mDAG = std::move(aOther.mDAG); + mOldItems = std::move(aOther.mOldItems); + return *this; + } + + RetainedDisplayList& operator=(nsDisplayList&& aOther) { + MOZ_ASSERT(IsEmpty(), "Can only move into an empty list!"); + MOZ_ASSERT(mOldItems.IsEmpty(), "Can only move into an empty list!"); + nsDisplayList::operator=(std::move(aOther)); + return *this; + } + + void DeleteAll(nsDisplayListBuilder* aBuilder) override { + for (OldItemInfo& i : mOldItems) { + if (i.mItem && i.mOwnsItem) { + i.mItem->Destroy(aBuilder); + MOZ_ASSERT(!GetBottom(), + "mOldItems should not be owning items if we also have items " + "in the normal list"); + } + } + mOldItems.Clear(); + mDAG.Clear(); + nsDisplayList::DeleteAll(aBuilder); + } + + void AddSizeOfExcludingThis(nsWindowSizes&) const; + + DirectedAcyclicGraph<MergedListUnits> mDAG; + + // Temporary state initialized during the preprocess pass + // of RetainedDisplayListBuilder and then used during merging. + nsTArray<OldItemInfo> mOldItems; +}; + +class nsDisplayContainer final : public nsDisplayItem { + public: + nsDisplayContainer(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + const ActiveScrolledRoot* aActiveScrolledRoot, + nsDisplayList* aList); + + ~nsDisplayContainer() override { MOZ_COUNT_DTOR(nsDisplayContainer); } + + NS_DISPLAY_DECL_NAME("nsDisplayContainer", TYPE_CONTAINER) + + void Destroy(nsDisplayListBuilder* aBuilder) override { + mChildren.DeleteAll(aBuilder); + nsDisplayItem::Destroy(aBuilder); + } + + bool CreateWebRenderCommands( + wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) override; + + nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) const override; + + nsRect GetComponentAlphaBounds(nsDisplayListBuilder* aBuilder) const override; + + nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder, + bool* aSnap) const override; + + Maybe<nscolor> IsUniform(nsDisplayListBuilder* aBuilder) const override { + return Nothing(); + } + + RetainedDisplayList* GetChildren() const override { return &mChildren; } + RetainedDisplayList* GetSameCoordinateSystemChildren() const override { + return GetChildren(); + } + + Maybe<nsRect> GetClipWithRespectToASR( + nsDisplayListBuilder* aBuilder, + const ActiveScrolledRoot* aASR) const override; + + void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect, + HitTestState* aState, nsTArray<nsIFrame*>* aOutFrames) override; + + bool ShouldFlattenAway(nsDisplayListBuilder* aBuilder) override { + return true; + } + + void SetClipChain(const DisplayItemClipChain* aClipChain, + bool aStore) override { + MOZ_ASSERT_UNREACHABLE("nsDisplayContainer does not support clipping"); + } + + void UpdateBounds(nsDisplayListBuilder* aBuilder) override; + + private: + mutable RetainedDisplayList mChildren; + nsRect mBounds; +}; + +/** + * Use this class to implement not-very-frequently-used display items + * that are not opaque, do not receive events, and are bounded by a frame's + * border-rect. + * + * This should not be used for display items which are created frequently, + * because each item is one or two pointers bigger than an item from a + * custom display item class could be, and fractionally slower. However it does + * save code size. We use this for infrequently-used item types. + */ +class nsDisplayGeneric : public nsPaintedDisplayItem { + public: + typedef void (*PaintCallback)(nsIFrame* aFrame, gfx::DrawTarget* aDrawTarget, + const nsRect& aDirtyRect, nsPoint aFramePt); + + // XXX: should be removed eventually + typedef void (*OldPaintCallback)(nsIFrame* aFrame, gfxContext* aCtx, + const nsRect& aDirtyRect, nsPoint aFramePt); + + nsDisplayGeneric(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + PaintCallback aPaint, const char* aName, + DisplayItemType aType) + : nsPaintedDisplayItem(aBuilder, aFrame), + mPaint(aPaint), + mOldPaint(nullptr), + mName(aName) { + MOZ_COUNT_CTOR(nsDisplayGeneric); + SetType(aType); + } + + // XXX: should be removed eventually + nsDisplayGeneric(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + OldPaintCallback aOldPaint, const char* aName, + DisplayItemType aType) + : nsPaintedDisplayItem(aBuilder, aFrame), + mPaint(nullptr), + mOldPaint(aOldPaint), + mName(aName) { + MOZ_COUNT_CTOR(nsDisplayGeneric); + SetType(aType); + } + + constexpr static DisplayItemType ItemType() { + return DisplayItemType::TYPE_GENERIC; + } + + MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayGeneric) + + void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override { + MOZ_ASSERT(!!mPaint != !!mOldPaint); + if (mPaint) { + mPaint(mFrame, aCtx->GetDrawTarget(), GetPaintRect(aBuilder, aCtx), + ToReferenceFrame()); + } else { + mOldPaint(mFrame, aCtx, GetPaintRect(aBuilder, aCtx), ToReferenceFrame()); + } + } + + const char* Name() const override { return mName; } + + // This override is needed because GetType() for nsDisplayGeneric subclasses + // does not match TYPE_GENERIC that was used to allocate the object. + void Destroy(nsDisplayListBuilder* aBuilder) override { + this->~nsDisplayGeneric(); + aBuilder->Destroy(DisplayItemType::TYPE_GENERIC, this); + } + + protected: + void* operator new(size_t aSize, nsDisplayListBuilder* aBuilder) { + return aBuilder->Allocate(aSize, DisplayItemType::TYPE_GENERIC); + } + + template <typename T, typename F, typename... Args> + friend T* MakeDisplayItemWithIndex(nsDisplayListBuilder* aBuilder, F* aFrame, + const uint16_t aIndex, Args&&... aArgs); + + PaintCallback mPaint; + OldPaintCallback mOldPaint; // XXX: should be removed eventually + const char* mName; +}; + +#if defined(MOZ_REFLOW_PERF_DSP) && defined(MOZ_REFLOW_PERF) +/** + * This class implements painting of reflow counts. Ideally, we would simply + * make all the frame names be those returned by nsIFrame::GetFrameName + * (except that tosses in the content tag name!) and support only one color + * and eliminate this class altogether in favor of nsDisplayGeneric, but for + * the time being we can't pass args to a PaintCallback, so just have a + * separate class to do the right thing. Sadly, this alsmo means we need to + * hack all leaf frame classes to handle this. + * + * XXXbz the color thing is a bit of a mess, but 0 basically means "not set" + * here... I could switch it all to nscolor, but why bother? + */ +class nsDisplayReflowCount : public nsPaintedDisplayItem { + public: + nsDisplayReflowCount(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + const char* aFrameName, uint32_t aColor = 0) + : nsPaintedDisplayItem(aBuilder, aFrame), + mFrameName(aFrameName), + mColor(aColor) { + MOZ_COUNT_CTOR(nsDisplayReflowCount); + } + + MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayReflowCount) + + NS_DISPLAY_DECL_NAME("nsDisplayReflowCount", TYPE_REFLOW_COUNT) + + void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override; + + protected: + const char* mFrameName; + nscolor mColor; +}; + +# define DO_GLOBAL_REFLOW_COUNT_DSP(_name) \ + PR_BEGIN_MACRO \ + if (!aBuilder->IsBackgroundOnly() && !aBuilder->IsForEventDelivery() && \ + PresShell()->IsPaintingFrameCounts()) { \ + aLists.Outlines()->AppendNewToTop<mozilla::nsDisplayReflowCount>( \ + aBuilder, this, _name); \ + } \ + PR_END_MACRO + +# define DO_GLOBAL_REFLOW_COUNT_DSP_COLOR(_name, _color) \ + PR_BEGIN_MACRO \ + if (!aBuilder->IsBackgroundOnly() && !aBuilder->IsForEventDelivery() && \ + PresShell()->IsPaintingFrameCounts()) { \ + aLists.Outlines()->AppendNewToTop<mozilla::nsDisplayReflowCount>( \ + aBuilder, this, _name, _color); \ + } \ + PR_END_MACRO + +/* + Macro to be used for classes that don't actually implement BuildDisplayList + */ +# define DECL_DO_GLOBAL_REFLOW_COUNT_DSP(_class, _super) \ + void BuildDisplayList(nsDisplayListBuilder* aBuilder, \ + const nsRect& aDirtyRect, \ + const nsDisplayListSet& aLists) { \ + DO_GLOBAL_REFLOW_COUNT_DSP(#_class); \ + _super::BuildDisplayList(aBuilder, aDirtyRect, aLists); \ + } + +#else // MOZ_REFLOW_PERF_DSP && MOZ_REFLOW_PERF + +# define DO_GLOBAL_REFLOW_COUNT_DSP(_name) +# define DO_GLOBAL_REFLOW_COUNT_DSP_COLOR(_name, _color) +# define DECL_DO_GLOBAL_REFLOW_COUNT_DSP(_class, _super) + +#endif // MOZ_REFLOW_PERF_DSP && MOZ_REFLOW_PERF + +class nsDisplayCaret : public nsPaintedDisplayItem { + public: + nsDisplayCaret(nsDisplayListBuilder* aBuilder, nsIFrame* aCaretFrame); + +#ifdef NS_BUILD_REFCNT_LOGGING + ~nsDisplayCaret() override; +#endif + + NS_DISPLAY_DECL_NAME("Caret", TYPE_CARET) + + nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) const override; + void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override; + bool CreateWebRenderCommands( + wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) override; + + protected: + RefPtr<nsCaret> mCaret; + nsRect mBounds; +}; + +/** + * The standard display item to paint the CSS borders of a frame. + */ +class nsDisplayBorder : public nsPaintedDisplayItem { + public: + nsDisplayBorder(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame); + + MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayBorder) + + NS_DISPLAY_DECL_NAME("Border", TYPE_BORDER) + + bool IsInvisibleInRect(const nsRect& aRect) const override; + nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) const override; + bool CreateWebRenderCommands( + wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) override; + void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override; + nsDisplayItemGeometry* AllocateGeometry( + nsDisplayListBuilder* aBuilder) override; + void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder, + const nsDisplayItemGeometry* aGeometry, + nsRegion* aInvalidRegion) const override; + + nsRegion GetTightBounds(nsDisplayListBuilder* aBuilder, + bool* aSnap) const override { + *aSnap = true; + return CalculateBounds<nsRegion>(*mFrame->StyleBorder()); + } + + protected: + template <typename T> + T CalculateBounds(const nsStyleBorder& aStyleBorder) const { + nsRect borderBounds(ToReferenceFrame(), mFrame->GetSize()); + if (aStyleBorder.IsBorderImageSizeAvailable()) { + borderBounds.Inflate(aStyleBorder.GetImageOutset()); + return borderBounds; + } + + nsMargin border = aStyleBorder.GetComputedBorder(); + T result; + if (border.top > 0) { + result = nsRect(borderBounds.X(), borderBounds.Y(), borderBounds.Width(), + border.top); + } + if (border.right > 0) { + result.OrWith(nsRect(borderBounds.XMost() - border.right, + borderBounds.Y(), border.right, + borderBounds.Height())); + } + if (border.bottom > 0) { + result.OrWith(nsRect(borderBounds.X(), + borderBounds.YMost() - border.bottom, + borderBounds.Width(), border.bottom)); + } + if (border.left > 0) { + result.OrWith(nsRect(borderBounds.X(), borderBounds.Y(), border.left, + borderBounds.Height())); + } + + nscoord radii[8]; + if (mFrame->GetBorderRadii(radii)) { + if (border.left > 0 || border.top > 0) { + nsSize cornerSize(radii[eCornerTopLeftX], radii[eCornerTopLeftY]); + result.OrWith(nsRect(borderBounds.TopLeft(), cornerSize)); + } + if (border.top > 0 || border.right > 0) { + nsSize cornerSize(radii[eCornerTopRightX], radii[eCornerTopRightY]); + result.OrWith( + nsRect(borderBounds.TopRight() - nsPoint(cornerSize.width, 0), + cornerSize)); + } + if (border.right > 0 || border.bottom > 0) { + nsSize cornerSize(radii[eCornerBottomRightX], + radii[eCornerBottomRightY]); + result.OrWith(nsRect(borderBounds.BottomRight() - + nsPoint(cornerSize.width, cornerSize.height), + cornerSize)); + } + if (border.bottom > 0 || border.left > 0) { + nsSize cornerSize(radii[eCornerBottomLeftX], radii[eCornerBottomLeftY]); + result.OrWith( + nsRect(borderBounds.BottomLeft() - nsPoint(0, cornerSize.height), + cornerSize)); + } + } + return result; + } + + nsRect mBounds; +}; + +/** + * A simple display item that just renders a solid color across the + * specified bounds. For canvas frames (in the CSS sense) we split off the + * drawing of the background color into this class (from nsDisplayBackground + * via nsDisplayCanvasBackground). This is done so that we can always draw a + * background color to avoid ugly flashes of white when we can't draw a full + * frame tree (ie when a page is loading). The bounds can differ from the + * frame's bounds -- this is needed when a frame/iframe is loading and there + * is not yet a frame tree to go in the frame/iframe so we use the subdoc + * frame of the parent document as a standin. + */ +class nsDisplaySolidColorBase : public nsPaintedDisplayItem { + public: + nsDisplaySolidColorBase(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + nscolor aColor) + : nsPaintedDisplayItem(aBuilder, aFrame), mColor(aColor) {} + + nsDisplayItemGeometry* AllocateGeometry( + nsDisplayListBuilder* aBuilder) override { + return new nsDisplaySolidColorGeometry(this, aBuilder, mColor); + } + + void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder, + const nsDisplayItemGeometry* aGeometry, + nsRegion* aInvalidRegion) const override { + const nsDisplaySolidColorGeometry* geometry = + static_cast<const nsDisplaySolidColorGeometry*>(aGeometry); + if (mColor != geometry->mColor) { + bool dummy; + aInvalidRegion->Or(geometry->mBounds, GetBounds(aBuilder, &dummy)); + return; + } + ComputeInvalidationRegionDifference(aBuilder, geometry, aInvalidRegion); + } + + nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder, + bool* aSnap) const override { + *aSnap = false; + nsRegion result; + if (NS_GET_A(mColor) == 255) { + result = GetBounds(aBuilder, aSnap); + } + return result; + } + + Maybe<nscolor> IsUniform(nsDisplayListBuilder* aBuilder) const override { + return Some(mColor); + } + + protected: + nscolor mColor; +}; + +class nsDisplaySolidColor : public nsDisplaySolidColorBase { + public: + nsDisplaySolidColor(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + const nsRect& aBounds, nscolor aColor, + bool aCanBeReused = true) + : nsDisplaySolidColorBase(aBuilder, aFrame, aColor), + mBounds(aBounds), + mIsCheckerboardBackground(false) { + NS_ASSERTION(NS_GET_A(aColor) > 0, + "Don't create invisible nsDisplaySolidColors!"); + MOZ_COUNT_CTOR(nsDisplaySolidColor); + if (!aCanBeReused) { + SetCantBeReused(); + } + } + + MOZ_COUNTED_DTOR_OVERRIDE(nsDisplaySolidColor) + + NS_DISPLAY_DECL_NAME("SolidColor", TYPE_SOLID_COLOR) + + nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) const override; + void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override; + void WriteDebugInfo(std::stringstream& aStream) override; + void SetIsCheckerboardBackground() { mIsCheckerboardBackground = true; } + bool CreateWebRenderCommands( + wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) override; + + int32_t ZIndex() const override { + if (mOverrideZIndex) { + return mOverrideZIndex.value(); + } + return nsDisplaySolidColorBase::ZIndex(); + } + + void SetOverrideZIndex(int32_t aZIndex) { mOverrideZIndex = Some(aZIndex); } + + private: + nsRect mBounds; + bool mIsCheckerboardBackground; + Maybe<int32_t> mOverrideZIndex; +}; + +/** + * A display item that renders a solid color over a region. This is not + * exposed through CSS, its only purpose is efficient invalidation of + * the find bar highlighter dimmer. + */ +class nsDisplaySolidColorRegion : public nsPaintedDisplayItem { + public: + nsDisplaySolidColorRegion(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + const nsRegion& aRegion, nscolor aColor) + : nsPaintedDisplayItem(aBuilder, aFrame), + mRegion(aRegion), + mColor(gfx::sRGBColor::FromABGR(aColor)) { + NS_ASSERTION(NS_GET_A(aColor) > 0, + "Don't create invisible nsDisplaySolidColorRegions!"); + MOZ_COUNT_CTOR(nsDisplaySolidColorRegion); + } + + MOZ_COUNTED_DTOR_OVERRIDE(nsDisplaySolidColorRegion) + + NS_DISPLAY_DECL_NAME("SolidColorRegion", TYPE_SOLID_COLOR_REGION) + + nsDisplayItemGeometry* AllocateGeometry( + nsDisplayListBuilder* aBuilder) override { + return new nsDisplaySolidColorRegionGeometry(this, aBuilder, mRegion, + mColor); + } + + void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder, + const nsDisplayItemGeometry* aGeometry, + nsRegion* aInvalidRegion) const override { + const nsDisplaySolidColorRegionGeometry* geometry = + static_cast<const nsDisplaySolidColorRegionGeometry*>(aGeometry); + if (mColor == geometry->mColor) { + aInvalidRegion->Xor(geometry->mRegion, mRegion); + } else { + aInvalidRegion->Or(geometry->mRegion.GetBounds(), mRegion.GetBounds()); + } + } + + bool CreateWebRenderCommands( + wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) override; + + protected: + nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) const override; + void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override; + void WriteDebugInfo(std::stringstream& aStream) override; + + private: + nsRegion mRegion; + gfx::sRGBColor mColor; +}; + +enum class AppendedBackgroundType : uint8_t { + None, + Background, + ThemedBackground, +}; + +/** + * A display item to paint one background-image for a frame. Each background + * image layer gets its own nsDisplayBackgroundImage. + */ +class nsDisplayBackgroundImage : public nsPaintedDisplayItem { + public: + struct InitData { + nsDisplayListBuilder* builder; + const ComputedStyle* backgroundStyle; + nsCOMPtr<imgIContainer> image; + nsRect backgroundRect; + nsRect fillArea; + nsRect destArea; + uint32_t layer; + bool isRasterImage; + bool shouldFixToViewport; + }; + + /** + * aLayer signifies which background layer this item represents. + * aIsThemed should be the value of aFrame->IsThemed. + * aBackgroundStyle should be the result of + * nsCSSRendering::FindBackground, or null if FindBackground returned false. + * aBackgroundRect is relative to aFrame. + */ + static InitData GetInitData(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + uint16_t aLayer, const nsRect& aBackgroundRect, + const ComputedStyle* aBackgroundStyle); + + explicit nsDisplayBackgroundImage(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame, const InitData& aInitData, + nsIFrame* aFrameForBounds = nullptr); + ~nsDisplayBackgroundImage() override; + + NS_DISPLAY_DECL_NAME("Background", TYPE_BACKGROUND) + + /** + * This will create and append new items for all the layers of the + * background. Returns the type of background that was appended. + * aAllowWillPaintBorderOptimization should usually be left at true, unless + * aFrame has special border drawing that causes opaque borders to not + * actually be opaque. + */ + static AppendedBackgroundType AppendBackgroundItemsToTop( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + const nsRect& aBackgroundRect, nsDisplayList* aList, + bool aAllowWillPaintBorderOptimization = true, + const nsRect& aBackgroundOriginRect = nsRect(), + nsIFrame* aSecondaryReferenceFrame = nullptr, + Maybe<nsDisplayListBuilder::AutoBuildingDisplayList>* + aAutoBuildingDisplayList = nullptr); + + bool CreateWebRenderCommands( + wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) override; + void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect, + HitTestState* aState, nsTArray<nsIFrame*>* aOutFrames) override; + nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder, + bool* aSnap) const override; + Maybe<nscolor> IsUniform(nsDisplayListBuilder* aBuilder) const override; + + bool CanApplyOpacity(WebRenderLayerManager* aManager, + nsDisplayListBuilder* aBuilder) const override; + + /** + * GetBounds() returns the background painting area. + */ + nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) const override; + + void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override; + + /** + * Return the background positioning area. + * (GetBounds() returns the background painting area.) + * Can be called only when mBackgroundStyle is non-null. + */ + nsRect GetPositioningArea() const; + + /** + * Returns true if existing rendered pixels of this display item may need + * to be redrawn if the positioning area size changes but its position does + * not. + * If false, only the changed painting area needs to be redrawn when the + * positioning area size changes but its position does not. + */ + bool RenderingMightDependOnPositioningAreaSizeChange() const; + + nsDisplayItemGeometry* AllocateGeometry( + nsDisplayListBuilder* aBuilder) override { + return new nsDisplayBackgroundGeometry(this, aBuilder); + } + + void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder, + const nsDisplayItemGeometry* aGeometry, + nsRegion* aInvalidRegion) const override; + bool ShouldFixToViewport(nsDisplayListBuilder* aBuilder) const override { + return mShouldFixToViewport; + } + + nsRect GetDestRect() const { return mDestRect; } + + nsIFrame* GetDependentFrame() override { return mDependentFrame; } + + void SetDependentFrame(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame) { + if (!aBuilder->IsRetainingDisplayList() || mDependentFrame == aFrame) { + return; + } + mDependentFrame = aFrame; + if (aFrame) { + mDependentFrame->AddDisplayItem(this); + } + } + + void RemoveFrame(nsIFrame* aFrame) override { + if (aFrame == mDependentFrame) { + mDependentFrame = nullptr; + } + nsPaintedDisplayItem::RemoveFrame(aFrame); + } + + // Match https://w3c.github.io/paint-timing/#contentful-image + bool IsContentful() const override { + const auto& styleImage = + mBackgroundStyle->StyleBackground()->mImage.mLayers[mLayer].mImage; + + return styleImage.IsSizeAvailable() && styleImage.FinalImage().IsUrl(); + } + + protected: + bool CanBuildWebRenderDisplayItems(layers::WebRenderLayerManager* aManager, + nsDisplayListBuilder* aBuilder) const; + nsRect GetBoundsInternal(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrameForBounds = nullptr); + + void PaintInternal(nsDisplayListBuilder* aBuilder, gfxContext* aCtx, + const nsRect& aBounds, nsRect* aClipRect); + + // Cache the result of nsCSSRendering::FindBackground. Always null if + // mIsThemed is true or if FindBackground returned false. + RefPtr<const ComputedStyle> mBackgroundStyle; + nsCOMPtr<imgIContainer> mImage; + nsIFrame* mDependentFrame; + nsRect mBackgroundRect; // relative to the reference frame + nsRect mFillRect; + nsRect mDestRect; + /* Bounds of this display item */ + nsRect mBounds; + uint16_t mLayer; + bool mIsRasterImage; + /* Whether the image should be treated as fixed to the viewport. */ + bool mShouldFixToViewport; +}; + +/** + * A display item to paint background image for table. For table parts, such + * as row, row group, col, col group, when drawing its background, we'll + * create separate background image display item for its containning cell. + * Those background image display items will reference to same DisplayItemData + * if we keep the mFrame point to cell's ancestor frame. We don't want to this + * happened bacause share same DisplatItemData will cause many bugs. So that + * we let mFrame point to cell frame and store the table type of the ancestor + * frame. And use mFrame and table type as key to generate DisplayItemData to + * avoid sharing DisplayItemData. + * + * Also store ancestor frame as mStyleFrame for all rendering informations. + */ +class nsDisplayTableBackgroundImage : public nsDisplayBackgroundImage { + public: + nsDisplayTableBackgroundImage(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame, const InitData& aData, + nsIFrame* aCellFrame); + ~nsDisplayTableBackgroundImage() override; + + NS_DISPLAY_DECL_NAME("TableBackgroundImage", TYPE_TABLE_BACKGROUND_IMAGE) + + bool IsInvalid(nsRect& aRect) const override; + + nsIFrame* FrameForInvalidation() const override { return mStyleFrame; } + + void RemoveFrame(nsIFrame* aFrame) override { + if (aFrame == mStyleFrame) { + mStyleFrame = nullptr; + SetDeletedFrame(); + } + nsDisplayBackgroundImage::RemoveFrame(aFrame); + } + + protected: + nsIFrame* StyleFrame() const override { return mStyleFrame; } + nsIFrame* mStyleFrame; +}; + +/** + * A display item to paint the native theme background for a frame. + */ +class nsDisplayThemedBackground : public nsPaintedDisplayItem { + public: + nsDisplayThemedBackground(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + const nsRect& aBackgroundRect); + + MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayThemedBackground) + + NS_DISPLAY_DECL_NAME("ThemedBackground", TYPE_THEMED_BACKGROUND) + + void Init(nsDisplayListBuilder* aBuilder); + + void Destroy(nsDisplayListBuilder* aBuilder) override { + aBuilder->UnregisterThemeGeometry(this); + nsPaintedDisplayItem::Destroy(aBuilder); + } + + void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect, + HitTestState* aState, nsTArray<nsIFrame*>* aOutFrames) override; + nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder, + bool* aSnap) const override; + Maybe<nscolor> IsUniform(nsDisplayListBuilder* aBuilder) const override; + bool CreateWebRenderCommands( + wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) override; + + bool MustPaintOnContentSide() const override { return true; } + + /** + * GetBounds() returns the background painting area. + */ + nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) const override; + + void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override; + + /** + * Return the background positioning area. + * (GetBounds() returns the background painting area.) + * Can be called only when mBackgroundStyle is non-null. + */ + nsRect GetPositioningArea() const; + + /** + * Return whether our frame's document does not have the state + * NS_DOCUMENT_STATE_WINDOW_INACTIVE. + */ + bool IsWindowActive() const; + + nsDisplayItemGeometry* AllocateGeometry( + nsDisplayListBuilder* aBuilder) override { + return new nsDisplayThemedBackgroundGeometry(this, aBuilder); + } + + void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder, + const nsDisplayItemGeometry* aGeometry, + nsRegion* aInvalidRegion) const override; + + void WriteDebugInfo(std::stringstream& aStream) override; + + protected: + nsRect GetBoundsInternal(); + + void PaintInternal(nsDisplayListBuilder* aBuilder, gfxContext* aCtx, + const nsRect& aBounds, nsRect* aClipRect); + + nsRect mBackgroundRect; + nsRect mBounds; + nsITheme::Transparency mThemeTransparency; + StyleAppearance mAppearance; +}; + +class nsDisplayTableThemedBackground : public nsDisplayThemedBackground { + public: + nsDisplayTableThemedBackground(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame, + const nsRect& aBackgroundRect, + nsIFrame* aAncestorFrame) + : nsDisplayThemedBackground(aBuilder, aFrame, aBackgroundRect), + mAncestorFrame(aAncestorFrame) { + if (aBuilder->IsRetainingDisplayList()) { + mAncestorFrame->AddDisplayItem(this); + } + } + + ~nsDisplayTableThemedBackground() override { + if (mAncestorFrame) { + mAncestorFrame->RemoveDisplayItem(this); + } + } + + NS_DISPLAY_DECL_NAME("TableThemedBackground", + TYPE_TABLE_THEMED_BACKGROUND_IMAGE) + + nsIFrame* FrameForInvalidation() const override { return mAncestorFrame; } + + void RemoveFrame(nsIFrame* aFrame) override { + if (aFrame == mAncestorFrame) { + mAncestorFrame = nullptr; + SetDeletedFrame(); + } + nsDisplayThemedBackground::RemoveFrame(aFrame); + } + + protected: + nsIFrame* StyleFrame() const override { return mAncestorFrame; } + nsIFrame* mAncestorFrame; +}; + +class nsDisplayBackgroundColor : public nsPaintedDisplayItem { + public: + nsDisplayBackgroundColor(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + const nsRect& aBackgroundRect, + const ComputedStyle* aBackgroundStyle, + const nscolor& aColor) + : nsPaintedDisplayItem(aBuilder, aFrame), + mBackgroundRect(aBackgroundRect), + mHasStyle(aBackgroundStyle), + mDependentFrame(nullptr), + mColor(gfx::sRGBColor::FromABGR(aColor)) { + if (mHasStyle) { + mBottomLayerClip = + aBackgroundStyle->StyleBackground()->BottomLayer().mClip; + } else { + MOZ_ASSERT(aBuilder->IsForEventDelivery()); + } + } + + ~nsDisplayBackgroundColor() override { + if (mDependentFrame) { + mDependentFrame->RemoveDisplayItem(this); + } + } + + NS_DISPLAY_DECL_NAME("BackgroundColor", TYPE_BACKGROUND_COLOR) + + bool HasBackgroundClipText() const { + MOZ_ASSERT(mHasStyle); + return mBottomLayerClip == StyleGeometryBox::Text; + } + + void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override; + void PaintWithClip(nsDisplayListBuilder* aBuilder, gfxContext* aCtx, + const DisplayItemClip& aClip) override; + bool CreateWebRenderCommands( + wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) override; + nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder, + bool* aSnap) const override; + Maybe<nscolor> IsUniform(nsDisplayListBuilder* aBuilder) const override; + void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect, + HitTestState* aState, nsTArray<nsIFrame*>* aOutFrames) override; + bool CanApplyOpacity(WebRenderLayerManager* aManager, + nsDisplayListBuilder* aBuilder) const override; + + float GetOpacity() const { return mColor.a; } + + nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) const override { + *aSnap = true; + return mBackgroundRect; + } + + bool CanPaintWithClip(const DisplayItemClip& aClip) override { + if (HasBackgroundClipText()) { + return false; + } + + if (aClip.GetRoundedRectCount() > 1) { + return false; + } + + return true; + } + + nsDisplayItemGeometry* AllocateGeometry( + nsDisplayListBuilder* aBuilder) override { + return new nsDisplaySolidColorGeometry(this, aBuilder, mColor.ToABGR()); + } + + void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder, + const nsDisplayItemGeometry* aGeometry, + nsRegion* aInvalidRegion) const override { + const nsDisplaySolidColorGeometry* geometry = + static_cast<const nsDisplaySolidColorGeometry*>(aGeometry); + + if (mColor.ToABGR() != geometry->mColor) { + bool dummy; + aInvalidRegion->Or(geometry->mBounds, GetBounds(aBuilder, &dummy)); + return; + } + ComputeInvalidationRegionDifference(aBuilder, geometry, aInvalidRegion); + } + + nsIFrame* GetDependentFrame() override { return mDependentFrame; } + + void SetDependentFrame(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame) { + if (!aBuilder->IsRetainingDisplayList() || mDependentFrame == aFrame) { + return; + } + mDependentFrame = aFrame; + if (aFrame) { + mDependentFrame->AddDisplayItem(this); + } + } + + void RemoveFrame(nsIFrame* aFrame) override { + if (aFrame == mDependentFrame) { + mDependentFrame = nullptr; + } + + nsPaintedDisplayItem::RemoveFrame(aFrame); + } + + void WriteDebugInfo(std::stringstream& aStream) override; + + bool CanUseAsyncAnimations(nsDisplayListBuilder* aBuilder) override; + + protected: + const nsRect mBackgroundRect; + const bool mHasStyle; + StyleGeometryBox mBottomLayerClip; + nsIFrame* mDependentFrame; + gfx::sRGBColor mColor; +}; + +class nsDisplayTableBackgroundColor : public nsDisplayBackgroundColor { + public: + nsDisplayTableBackgroundColor(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame, const nsRect& aBackgroundRect, + const ComputedStyle* aBackgroundStyle, + const nscolor& aColor, nsIFrame* aAncestorFrame) + : nsDisplayBackgroundColor(aBuilder, aFrame, aBackgroundRect, + aBackgroundStyle, aColor), + mAncestorFrame(aAncestorFrame) { + if (aBuilder->IsRetainingDisplayList()) { + mAncestorFrame->AddDisplayItem(this); + } + } + + ~nsDisplayTableBackgroundColor() override { + if (mAncestorFrame) { + mAncestorFrame->RemoveDisplayItem(this); + } + } + + NS_DISPLAY_DECL_NAME("TableBackgroundColor", TYPE_TABLE_BACKGROUND_COLOR) + + nsIFrame* FrameForInvalidation() const override { return mAncestorFrame; } + + void RemoveFrame(nsIFrame* aFrame) override { + if (aFrame == mAncestorFrame) { + mAncestorFrame = nullptr; + SetDeletedFrame(); + } + nsDisplayBackgroundColor::RemoveFrame(aFrame); + } + + bool CanUseAsyncAnimations(nsDisplayListBuilder* aBuilder) override { + return false; + } + + protected: + nsIFrame* mAncestorFrame; +}; + +/** + * The standard display item to paint the outer CSS box-shadows of a frame. + */ +class nsDisplayBoxShadowOuter final : public nsPaintedDisplayItem { + public: + nsDisplayBoxShadowOuter(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame) + : nsPaintedDisplayItem(aBuilder, aFrame) { + MOZ_COUNT_CTOR(nsDisplayBoxShadowOuter); + mBounds = GetBoundsInternal(); + } + + MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayBoxShadowOuter) + + NS_DISPLAY_DECL_NAME("BoxShadowOuter", TYPE_BOX_SHADOW_OUTER) + + void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override; + nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) const override; + bool IsInvisibleInRect(const nsRect& aRect) const override; + void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder, + const nsDisplayItemGeometry* aGeometry, + nsRegion* aInvalidRegion) const override; + + bool CanApplyOpacity(WebRenderLayerManager* aManager, + nsDisplayListBuilder* aBuilder) const override { + return CanBuildWebRenderDisplayItems(); + } + + bool CanBuildWebRenderDisplayItems() const; + bool CreateWebRenderCommands( + wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) override; + nsRect GetBoundsInternal(); + + private: + nsRect mBounds; +}; + +/** + * The standard display item to paint the inner CSS box-shadows of a frame. + */ +class nsDisplayBoxShadowInner : public nsPaintedDisplayItem { + public: + nsDisplayBoxShadowInner(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame) + : nsPaintedDisplayItem(aBuilder, aFrame) { + MOZ_COUNT_CTOR(nsDisplayBoxShadowInner); + } + + MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayBoxShadowInner) + + NS_DISPLAY_DECL_NAME("BoxShadowInner", TYPE_BOX_SHADOW_INNER) + + void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override; + + nsDisplayItemGeometry* AllocateGeometry( + nsDisplayListBuilder* aBuilder) override { + return new nsDisplayBoxShadowInnerGeometry(this, aBuilder); + } + + void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder, + const nsDisplayItemGeometry* aGeometry, + nsRegion* aInvalidRegion) const override { + const nsDisplayBoxShadowInnerGeometry* geometry = + static_cast<const nsDisplayBoxShadowInnerGeometry*>(aGeometry); + if (!geometry->mPaddingRect.IsEqualInterior(GetPaddingRect())) { + // nsDisplayBoxShadowInner is based around the padding rect, but it can + // touch pixels outside of this. We should invalidate the entire bounds. + bool snap; + aInvalidRegion->Or(geometry->mBounds, GetBounds(aBuilder, &snap)); + } + } + + static bool CanCreateWebRenderCommands(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame, + const nsPoint& aReferenceOffset); + static void CreateInsetBoxShadowWebRenderCommands( + wr::DisplayListBuilder& aBuilder, const StackingContextHelper& aSc, + nsRect& aVisibleRect, nsIFrame* aFrame, const nsRect& aBorderRect); + bool CreateWebRenderCommands( + wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) override; +}; + +/** + * The standard display item to paint the CSS outline of a frame. + */ +class nsDisplayOutline final : public nsPaintedDisplayItem { + public: + nsDisplayOutline(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame) + : nsPaintedDisplayItem(aBuilder, aFrame) { + MOZ_COUNT_CTOR(nsDisplayOutline); + } + + MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayOutline) + + NS_DISPLAY_DECL_NAME("Outline", TYPE_OUTLINE) + + bool MustPaintOnContentSide() const override { + MOZ_ASSERT(IsThemedOutline(), + "The only fallback path we have is for themed outlines"); + return true; + } + + bool CreateWebRenderCommands( + wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) override; + bool IsInvisibleInRect(const nsRect& aRect) const override; + nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) const override; + void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override; + + private: + nsRect GetInnerRect() const; + bool IsThemedOutline() const; + bool HasRadius() const; +}; + +/** + * A class that lets you receive events within the frame bounds but never + * paints. + */ +class nsDisplayEventReceiver final : public nsDisplayItem { + public: + nsDisplayEventReceiver(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame) + : nsDisplayItem(aBuilder, aFrame) { + MOZ_COUNT_CTOR(nsDisplayEventReceiver); + } + + MOZ_COUNTED_DTOR_FINAL(nsDisplayEventReceiver) + + NS_DISPLAY_DECL_NAME("EventReceiver", TYPE_EVENT_RECEIVER) + + void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect, + HitTestState* aState, nsTArray<nsIFrame*>* aOutFrames) final; +}; + +/** + * Similar to nsDisplayEventReceiver in that it is used for hit-testing. However + * this gets built when we're doing widget painting and we need to send the + * compositor some hit-test info for a frame. This is effectively a dummy item + * whose sole purpose is to carry the hit-test info to the compositor. + */ +class nsDisplayCompositorHitTestInfo final : public nsDisplayItem { + public: + nsDisplayCompositorHitTestInfo(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame) + : nsDisplayItem(aBuilder, aFrame) { + MOZ_COUNT_CTOR(nsDisplayCompositorHitTestInfo); + mHitTestInfo.Initialize(aBuilder, aFrame); + SetHasHitTestInfo(); + } + + nsDisplayCompositorHitTestInfo( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, const nsRect& aArea, + const gfx::CompositorHitTestInfo& aHitTestFlags) + : nsDisplayItem(aBuilder, aFrame) { + MOZ_COUNT_CTOR(nsDisplayCompositorHitTestInfo); + mHitTestInfo.SetAreaAndInfo(aArea, aHitTestFlags); + mHitTestInfo.InitializeScrollTarget(aBuilder); + SetHasHitTestInfo(); + } + + MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayCompositorHitTestInfo) + + NS_DISPLAY_DECL_NAME("CompositorHitTestInfo", TYPE_COMPOSITOR_HITTEST_INFO) + + bool CreateWebRenderCommands( + wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) override; + + bool isInvisible() const { return true; } + + int32_t ZIndex() const override; + void SetOverrideZIndex(int32_t aZIndex); + + nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) const override { + *aSnap = false; + return nsRect(); + } + + const HitTestInfo& GetHitTestInfo() final { return mHitTestInfo; } + + private: + HitTestInfo mHitTestInfo; + Maybe<int32_t> mOverrideZIndex; +}; + +class nsDisplayWrapper; + +/** + * A class that lets you wrap a display list as a display item. + * + * GetUnderlyingFrame() is troublesome for wrapped lists because if the wrapped + * list has many items, it's not clear which one has the 'underlying frame'. + * Thus we force the creator to specify what the underlying frame is. The + * underlying frame should be the root of a stacking context, because sorting + * a list containing this item will not get at the children. + * + * In some cases (e.g., clipping) we want to wrap a list but we don't have a + * particular underlying frame that is a stacking context root. In that case + * we allow the frame to be nullptr. Callers to GetUnderlyingFrame must + * detect and handle this case. + */ +class nsDisplayWrapList : public nsPaintedDisplayItem { + public: + /** + * Takes all the items from aList and puts them in our list. + */ + nsDisplayWrapList(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + nsDisplayList* aList); + + nsDisplayWrapList(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + nsDisplayItem* aItem); + + nsDisplayWrapList(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + nsDisplayList* aList, + const ActiveScrolledRoot* aActiveScrolledRoot, + bool aClearClipChain = false); + + nsDisplayWrapList(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame) + : nsPaintedDisplayItem(aBuilder, aFrame), + mList(aBuilder), + mFrameActiveScrolledRoot(aBuilder->CurrentActiveScrolledRoot()), + mOverrideZIndex(0), + mHasZIndexOverride(false) { + MOZ_COUNT_CTOR(nsDisplayWrapList); + mBaseBuildingRect = GetBuildingRect(); + mListPtr = &mList; + mOriginalClipChain = mClipChain; + } + + nsDisplayWrapList() = delete; + + /** + * A custom copy-constructor that does not copy mList, as this would mutate + * the other item. + */ + nsDisplayWrapList(const nsDisplayWrapList& aOther) = delete; + nsDisplayWrapList(nsDisplayListBuilder* aBuilder, + const nsDisplayWrapList& aOther) + : nsPaintedDisplayItem(aBuilder, aOther), + mList(aBuilder), + mListPtr(&mList), + mFrameActiveScrolledRoot(aOther.mFrameActiveScrolledRoot), + mMergedFrames(aOther.mMergedFrames.Clone()), + mBounds(aOther.mBounds), + mBaseBuildingRect(aOther.mBaseBuildingRect), + mOriginalClipChain(aOther.mClipChain), + mOverrideZIndex(aOther.mOverrideZIndex), + mHasZIndexOverride(aOther.mHasZIndexOverride), + mClearingClipChain(aOther.mClearingClipChain) { + MOZ_COUNT_CTOR(nsDisplayWrapList); + } + + ~nsDisplayWrapList() override; + + const nsDisplayWrapList* AsDisplayWrapList() const final { return this; } + nsDisplayWrapList* AsDisplayWrapList() final { return this; } + + void Destroy(nsDisplayListBuilder* aBuilder) override { + mList.DeleteAll(aBuilder); + nsPaintedDisplayItem::Destroy(aBuilder); + } + + /** + * Creates a new nsDisplayWrapper that holds a pointer to the display list + * owned by the given nsDisplayItem. + */ + nsDisplayWrapper* CreateShallowCopy(nsDisplayListBuilder* aBuilder); + + /** + * Call this if the wrapped list is changed. + */ + void UpdateBounds(nsDisplayListBuilder* aBuilder) override { + // Clear the clip chain up to the asr, but don't store it, so that we'll + // recover it when we reuse the item. + if (mClearingClipChain) { + const DisplayItemClipChain* clip = mOriginalClipChain; + while (clip && ActiveScrolledRoot::IsAncestor(GetActiveScrolledRoot(), + clip->mASR)) { + clip = clip->mParent; + } + SetClipChain(clip, false); + } + + nsRect buildingRect; + mBounds = mListPtr->GetClippedBoundsWithRespectToASR( + aBuilder, mActiveScrolledRoot, &buildingRect); + // The display list may contain content that's visible outside the visible + // rect (i.e. the current dirty rect) passed in when the item was created. + // This happens when the dirty rect has been restricted to the visual + // overflow rect of a frame for some reason (e.g. when setting up dirty + // rects in nsDisplayListBuilder::MarkOutOfFlowFrameForDisplay), but that + // frame contains placeholders for out-of-flows that aren't descendants of + // the frame. + buildingRect.UnionRect(mBaseBuildingRect, buildingRect); + SetBuildingRect(buildingRect); + } + + void SetClipChain(const DisplayItemClipChain* aClipChain, + bool aStore) override { + nsDisplayItem::SetClipChain(aClipChain, aStore); + + if (aStore) { + mOriginalClipChain = mClipChain; + } + } + + void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect, + HitTestState* aState, nsTArray<nsIFrame*>* aOutFrames) override; + nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) const override; + nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder, + bool* aSnap) const override; + Maybe<nscolor> IsUniform(nsDisplayListBuilder* aBuilder) const override; + + /** + * Try to merge with the other item (which is below us in the display + * list). This gets used by nsDisplayClip to coalesce clipping operations + * (optimization), by nsDisplayOpacity to merge rendering for the same + * content element into a single opacity group (correctness), and will be + * used by nsDisplayOutline to merge multiple outlines for the same element + * (also for correctness). + */ + virtual void Merge(const nsDisplayItem* aItem) { + MOZ_ASSERT(CanMerge(aItem)); + MOZ_ASSERT(Frame() != aItem->Frame()); + MergeFromTrackingMergedFrames(static_cast<const nsDisplayWrapList*>(aItem)); + } + + /** + * Returns the underlying frames of all display items that have been + * merged into this one (excluding this item's own underlying frame) + * to aFrames. + */ + const nsTArray<nsIFrame*>& GetMergedFrames() const { return mMergedFrames; } + + bool HasMergedFrames() const { return !mMergedFrames.IsEmpty(); } + + bool ShouldFlattenAway(nsDisplayListBuilder* aBuilder) override { + return true; + } + + bool IsInvalid(nsRect& aRect) const override { + if (mFrame->IsInvalid(aRect) && aRect.IsEmpty()) { + return true; + } + nsRect temp; + for (uint32_t i = 0; i < mMergedFrames.Length(); i++) { + if (mMergedFrames[i]->IsInvalid(temp) && temp.IsEmpty()) { + aRect.SetEmpty(); + return true; + } + aRect = aRect.Union(temp); + } + aRect += ToReferenceFrame(); + return !aRect.IsEmpty(); + } + + nsRect GetComponentAlphaBounds(nsDisplayListBuilder* aBuilder) const override; + + RetainedDisplayList* GetSameCoordinateSystemChildren() const override { + return mListPtr; + } + + RetainedDisplayList* GetChildren() const override { return mListPtr; } + + int32_t ZIndex() const override { + return (mHasZIndexOverride) ? mOverrideZIndex + : nsPaintedDisplayItem::ZIndex(); + } + + void SetOverrideZIndex(int32_t aZIndex) { + mHasZIndexOverride = true; + mOverrideZIndex = aZIndex; + } + + /** + * This creates a copy of this item, but wrapping aItem instead of + * our existing list. Only gets called if this item returned nullptr + * for GetUnderlyingFrame(). aItem is guaranteed to return non-null from + * GetUnderlyingFrame(). + */ + nsDisplayWrapList* WrapWithClone(nsDisplayListBuilder* aBuilder, + nsDisplayItem* aItem) { + MOZ_ASSERT_UNREACHABLE("We never returned nullptr for GetUnderlyingFrame!"); + return nullptr; + } + + bool CreateWebRenderCommands( + wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) override { + return CreateWebRenderCommandsNewClipListOption( + aBuilder, aResources, aSc, aManager, aDisplayListBuilder, true); + } + + // Same as the above but with the option to pass the aNewClipList argument to + // WebRenderCommandBuilder::CreateWebRenderCommandsFromDisplayList. + bool CreateWebRenderCommandsNewClipListOption( + wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder, bool aNewClipList); + + const ActiveScrolledRoot* GetFrameActiveScrolledRoot() { + return mFrameActiveScrolledRoot; + } + + protected: + void MergeFromTrackingMergedFrames(const nsDisplayWrapList* aOther) { + mBounds.UnionRect(mBounds, aOther->mBounds); + nsRect buildingRect; + buildingRect.UnionRect(GetBuildingRect(), aOther->GetBuildingRect()); + SetBuildingRect(buildingRect); + mMergedFrames.AppendElement(aOther->mFrame); + mMergedFrames.AppendElements(aOther->mMergedFrames.Clone()); + } + + RetainedDisplayList mList; + RetainedDisplayList* mListPtr; + // The active scrolled root for the frame that created this + // wrap list. + RefPtr<const ActiveScrolledRoot> mFrameActiveScrolledRoot; + // The frames from items that have been merged into this item, excluding + // this item's own frame. + nsTArray<nsIFrame*> mMergedFrames; + nsRect mBounds; + // Displaylist building rect contributed by this display item itself. + // Our mBuildingRect may include the visible areas of children. + nsRect mBaseBuildingRect; + RefPtr<const DisplayItemClipChain> mOriginalClipChain; + int32_t mOverrideZIndex; + bool mHasZIndexOverride; + bool mClearingClipChain = false; +}; + +class nsDisplayWrapper : public nsDisplayWrapList { + public: + NS_DISPLAY_DECL_NAME("WrapList", TYPE_WRAP_LIST) + + nsDisplayWrapper(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + nsDisplayList* aList, + const ActiveScrolledRoot* aActiveScrolledRoot, + bool aClearClipChain = false) + : nsDisplayWrapList(aBuilder, aFrame, aList, aActiveScrolledRoot, + aClearClipChain) {} + + nsDisplayWrapper(const nsDisplayWrapper& aOther) = delete; + nsDisplayWrapper(nsDisplayListBuilder* aBuilder, + const nsDisplayWrapList& aOther) + : nsDisplayWrapList(aBuilder, aOther) {} + + void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override; + + private: + NS_DISPLAY_ALLOW_CLONING() + friend class nsDisplayListBuilder; + friend class nsDisplayWrapList; +}; + +/** + * We call WrapDisplayList on the in-flow lists: BorderBackground(), + * BlockBorderBackgrounds() and Content(). + * We call WrapDisplayItem on each item of Outlines(), PositionedDescendants(), + * and Floats(). This is done to support special wrapping processing for frames + * that may not be in-flow descendants of the current frame. + */ +class nsDisplayItemWrapper { + public: + // This is never instantiated directly (it has pure virtual methods), so no + // need to count constructors and destructors. + + bool WrapBorderBackground() { return true; } + virtual nsDisplayItem* WrapList(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame, nsDisplayList* aList) = 0; + virtual nsDisplayItem* WrapItem(nsDisplayListBuilder* aBuilder, + nsDisplayItem* aItem) = 0; + + nsresult WrapLists(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + const nsDisplayListSet& aIn, const nsDisplayListSet& aOut); + nsresult WrapListsInPlace(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + const nsDisplayListSet& aLists); + + protected: + nsDisplayItemWrapper() = default; +}; + +/** + * The standard display item to paint a stacking context with translucency + * set by the stacking context root frame's 'opacity' style. + */ +class nsDisplayOpacity : public nsDisplayWrapList { + public: + nsDisplayOpacity(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + nsDisplayList* aList, + const ActiveScrolledRoot* aActiveScrolledRoot, + bool aForEventsOnly, bool aNeedsActiveLayer, + bool aWrapsBackdropFilter); + + nsDisplayOpacity(nsDisplayListBuilder* aBuilder, + const nsDisplayOpacity& aOther) + : nsDisplayWrapList(aBuilder, aOther), + mOpacity(aOther.mOpacity), + mForEventsOnly(aOther.mForEventsOnly), + mNeedsActiveLayer(aOther.mNeedsActiveLayer), + mChildOpacityState(ChildOpacityState::Unknown), + mWrapsBackdropFilter(aOther.mWrapsBackdropFilter) { + MOZ_COUNT_CTOR(nsDisplayOpacity); + // We should not try to merge flattened opacities. + MOZ_ASSERT(aOther.mChildOpacityState != ChildOpacityState::Applied); + } + + void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect, + HitTestState* aState, nsTArray<nsIFrame*>* aOutFrames) override; + + MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayOpacity) + + NS_DISPLAY_DECL_NAME("Opacity", TYPE_OPACITY) + + void InvalidateCachedChildInfo(nsDisplayListBuilder* aBuilder) override { + mChildOpacityState = ChildOpacityState::Unknown; + } + + nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder, + bool* aSnap) const override; + void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override; + + bool CanMerge(const nsDisplayItem* aItem) const override { + // items for the same content element should be merged into a single + // compositing group + // aItem->GetUnderlyingFrame() returns non-null because it's + // nsDisplayOpacity + return HasDifferentFrame(aItem) && HasSameTypeAndClip(aItem) && + HasSameContent(aItem); + } + + nsDisplayItemGeometry* AllocateGeometry( + nsDisplayListBuilder* aBuilder) override { + return new nsDisplayOpacityGeometry(this, aBuilder, mOpacity); + } + + void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder, + const nsDisplayItemGeometry* aGeometry, + nsRegion* aInvalidRegion) const override; + + bool IsInvalid(nsRect& aRect) const override { + if (mForEventsOnly) { + return false; + } + return nsDisplayWrapList::IsInvalid(aRect); + } + bool CanApplyOpacity(WebRenderLayerManager* aManager, + nsDisplayListBuilder* aBuilder) const override; + bool ShouldFlattenAway(nsDisplayListBuilder* aBuilder) override { + return false; + } + + bool CanApplyOpacityToChildren(WebRenderLayerManager* aManager, + nsDisplayListBuilder* aBuilder, + float aInheritedOpacity); + + bool NeedsGeometryUpdates() const override { + // For flattened nsDisplayOpacity items, ComputeInvalidationRegion() only + // handles invalidation for changed |mOpacity|. In order to keep track of + // the current bounds of the item for invalidation, nsDisplayOpacityGeometry + // for the corresponding DisplayItemData needs to be updated, even if the + // reported invalidation region is empty. + return mChildOpacityState == ChildOpacityState::Deferred; + } + + /** + * Returns true if ShouldFlattenAway() applied opacity to children. + */ + bool OpacityAppliedToChildren() const { + return mChildOpacityState == ChildOpacityState::Applied; + } + + static bool NeedsActiveLayer(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame); + void WriteDebugInfo(std::stringstream& aStream) override; + bool CanUseAsyncAnimations(nsDisplayListBuilder* aBuilder) override; + bool CreateWebRenderCommands( + wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) override; + + float GetOpacity() const { return mOpacity; } + + bool CreatesStackingContextHelper() override { return true; } + + private: + NS_DISPLAY_ALLOW_CLONING() + + bool CanApplyToChildren(WebRenderLayerManager* aManager, + nsDisplayListBuilder* aBuilder); + bool ApplyToMask(); + + float mOpacity; + bool mForEventsOnly : 1; + enum class ChildOpacityState : uint8_t { + // Our child list has changed since the last time ApplyToChildren was + // called. + Unknown, + // Our children defer opacity handling to us. + Deferred, + // Opacity is applied to our children. + Applied + }; + bool mNeedsActiveLayer : 1; +#ifndef __GNUC__ + ChildOpacityState mChildOpacityState : 2; +#else + ChildOpacityState mChildOpacityState; +#endif + bool mWrapsBackdropFilter; +}; + +class nsDisplayBlendMode : public nsDisplayWrapList { + public: + nsDisplayBlendMode(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + nsDisplayList* aList, StyleBlend aBlendMode, + const ActiveScrolledRoot* aActiveScrolledRoot, + const bool aIsForBackground); + nsDisplayBlendMode(nsDisplayListBuilder* aBuilder, + const nsDisplayBlendMode& aOther) + : nsDisplayWrapList(aBuilder, aOther), + mBlendMode(aOther.mBlendMode), + mIsForBackground(aOther.mIsForBackground) { + MOZ_COUNT_CTOR(nsDisplayBlendMode); + } + + MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayBlendMode) + + NS_DISPLAY_DECL_NAME("BlendMode", TYPE_BLEND_MODE) + + nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder, + bool* aSnap) const override; + void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder, + const nsDisplayItemGeometry* aGeometry, + nsRegion* aInvalidRegion) const override { + // We don't need to compute an invalidation region since we have + // LayerTreeInvalidation + } + + bool CreateWebRenderCommands( + wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) override; + void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override; + + bool CanMerge(const nsDisplayItem* aItem) const override; + + bool ShouldFlattenAway(nsDisplayListBuilder* aBuilder) override { + return false; + } + + gfx::CompositionOp BlendMode(); + + bool CreatesStackingContextHelper() override { return true; } + + protected: + StyleBlend mBlendMode; + bool mIsForBackground; + + private: + NS_DISPLAY_ALLOW_CLONING() +}; + +class nsDisplayTableBlendMode : public nsDisplayBlendMode { + public: + nsDisplayTableBlendMode(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + nsDisplayList* aList, StyleBlend aBlendMode, + const ActiveScrolledRoot* aActiveScrolledRoot, + nsIFrame* aAncestorFrame, const bool aIsForBackground) + : nsDisplayBlendMode(aBuilder, aFrame, aList, aBlendMode, + aActiveScrolledRoot, aIsForBackground), + mAncestorFrame(aAncestorFrame) { + if (aBuilder->IsRetainingDisplayList()) { + mAncestorFrame->AddDisplayItem(this); + } + } + + nsDisplayTableBlendMode(nsDisplayListBuilder* aBuilder, + const nsDisplayTableBlendMode& aOther) + : nsDisplayBlendMode(aBuilder, aOther), + mAncestorFrame(aOther.mAncestorFrame) { + if (aBuilder->IsRetainingDisplayList()) { + mAncestorFrame->AddDisplayItem(this); + } + } + + ~nsDisplayTableBlendMode() override { + if (mAncestorFrame) { + mAncestorFrame->RemoveDisplayItem(this); + } + } + + NS_DISPLAY_DECL_NAME("TableBlendMode", TYPE_TABLE_BLEND_MODE) + + nsIFrame* FrameForInvalidation() const override { return mAncestorFrame; } + + void RemoveFrame(nsIFrame* aFrame) override { + if (aFrame == mAncestorFrame) { + mAncestorFrame = nullptr; + SetDeletedFrame(); + } + nsDisplayBlendMode::RemoveFrame(aFrame); + } + + protected: + nsIFrame* mAncestorFrame; + + private: + NS_DISPLAY_ALLOW_CLONING() +}; + +class nsDisplayBlendContainer : public nsDisplayWrapList { + public: + static nsDisplayBlendContainer* CreateForMixBlendMode( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayList* aList, + const ActiveScrolledRoot* aActiveScrolledRoot); + + static nsDisplayBlendContainer* CreateForBackgroundBlendMode( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + nsIFrame* aSecondaryFrame, nsDisplayList* aList, + const ActiveScrolledRoot* aActiveScrolledRoot); + + MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayBlendContainer) + + NS_DISPLAY_DECL_NAME("BlendContainer", TYPE_BLEND_CONTAINER) + + void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override; + bool CreateWebRenderCommands( + wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) override; + + bool CanMerge(const nsDisplayItem* aItem) const override { + // Items for the same content element should be merged into a single + // compositing group. + return HasDifferentFrame(aItem) && HasSameTypeAndClip(aItem) && + HasSameContent(aItem) && + mIsForBackground == + static_cast<const nsDisplayBlendContainer*>(aItem) + ->mIsForBackground; + } + + bool ShouldFlattenAway(nsDisplayListBuilder* aBuilder) override { + return false; + } + + bool CreatesStackingContextHelper() override { return true; } + + protected: + nsDisplayBlendContainer(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + nsDisplayList* aList, + const ActiveScrolledRoot* aActiveScrolledRoot, + bool aIsForBackground); + nsDisplayBlendContainer(nsDisplayListBuilder* aBuilder, + const nsDisplayBlendContainer& aOther) + : nsDisplayWrapList(aBuilder, aOther), + mIsForBackground(aOther.mIsForBackground) { + MOZ_COUNT_CTOR(nsDisplayBlendContainer); + } + + // Used to distinguish containers created at building stacking + // context or appending background. + bool mIsForBackground; + + private: + NS_DISPLAY_ALLOW_CLONING() +}; + +class nsDisplayTableBlendContainer : public nsDisplayBlendContainer { + public: + NS_DISPLAY_DECL_NAME("TableBlendContainer", TYPE_TABLE_BLEND_CONTAINER) + + nsIFrame* FrameForInvalidation() const override { return mAncestorFrame; } + + void RemoveFrame(nsIFrame* aFrame) override { + if (aFrame == mAncestorFrame) { + mAncestorFrame = nullptr; + SetDeletedFrame(); + } + nsDisplayBlendContainer::RemoveFrame(aFrame); + } + + protected: + nsDisplayTableBlendContainer(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + nsDisplayList* aList, + const ActiveScrolledRoot* aActiveScrolledRoot, + bool aIsForBackground, nsIFrame* aAncestorFrame) + : nsDisplayBlendContainer(aBuilder, aFrame, aList, aActiveScrolledRoot, + aIsForBackground), + mAncestorFrame(aAncestorFrame) { + if (aBuilder->IsRetainingDisplayList()) { + mAncestorFrame->AddDisplayItem(this); + } + } + + nsDisplayTableBlendContainer(nsDisplayListBuilder* aBuilder, + const nsDisplayTableBlendContainer& aOther) + : nsDisplayBlendContainer(aBuilder, aOther), + mAncestorFrame(aOther.mAncestorFrame) {} + + ~nsDisplayTableBlendContainer() override { + if (mAncestorFrame) { + mAncestorFrame->RemoveDisplayItem(this); + } + } + + nsIFrame* mAncestorFrame; + + private: + NS_DISPLAY_ALLOW_CLONING() +}; + +/** + * nsDisplayOwnLayer constructor flags. If we nest this class inside + * nsDisplayOwnLayer then we can't forward-declare it up at the top of this + * file and that makes it hard to use in all the places that we need to use it. + */ +enum class nsDisplayOwnLayerFlags { + None = 0, + GenerateSubdocInvalidations = 1 << 0, + GenerateScrollableLayer = 1 << 1, +}; + +MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(nsDisplayOwnLayerFlags) + +/** + * A display item that has no purpose but to ensure its contents get + * their own layer. + */ +class nsDisplayOwnLayer : public nsDisplayWrapList { + public: + enum OwnLayerType { + OwnLayerForTransformWithRoundedClip, + OwnLayerForStackingContext, + OwnLayerForScrollbar, + OwnLayerForScrollThumb, + OwnLayerForSubdoc, + OwnLayerForBoxFrame + }; + + /** + * @param aFlags eGenerateSubdocInvalidations : + * Add UserData to the created ContainerLayer, so that invalidations + * for this layer are send to our nsPresContext. + * eGenerateScrollableLayer : only valid on nsDisplaySubDocument (and + * subclasses), indicates this layer is to be a scrollable layer, so call + * ComputeFrameMetrics, etc. + * @param aScrollTarget when eVerticalScrollbar or eHorizontalScrollbar + * is set in the flags, this parameter should be the ViewID of the + * scrollable content this scrollbar is for. + */ + nsDisplayOwnLayer( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayList* aList, + const ActiveScrolledRoot* aActiveScrolledRoot, + nsDisplayOwnLayerFlags aFlags = nsDisplayOwnLayerFlags::None, + const layers::ScrollbarData& aScrollbarData = layers::ScrollbarData{}, + bool aForceActive = true, bool aClearClipChain = false); + + nsDisplayOwnLayer(nsDisplayListBuilder* aBuilder, + const nsDisplayOwnLayer& aOther) + : nsDisplayWrapList(aBuilder, aOther), + mFlags(aOther.mFlags), + mScrollbarData(aOther.mScrollbarData), + mForceActive(aOther.mForceActive), + mWrAnimationId(aOther.mWrAnimationId) { + MOZ_COUNT_CTOR(nsDisplayOwnLayer); + } + + MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayOwnLayer) + + NS_DISPLAY_DECL_NAME("OwnLayer", TYPE_OWN_LAYER) + + bool CreateWebRenderCommands( + wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) override; + bool UpdateScrollData(layers::WebRenderScrollData* aData, + layers::WebRenderLayerScrollData* aLayerData) override; + void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override { + GetChildren()->Paint(aBuilder, aCtx, + mFrame->PresContext()->AppUnitsPerDevPixel()); + } + + bool CanMerge(const nsDisplayItem* aItem) const override { + // Don't allow merging, each sublist must have its own layer + return false; + } + + bool ShouldFlattenAway(nsDisplayListBuilder* aBuilder) override { + return false; + } + + void WriteDebugInfo(std::stringstream& aStream) override; + nsDisplayOwnLayerFlags GetFlags() { return mFlags; } + bool IsScrollThumbLayer() const; + bool IsScrollbarContainer() const; + bool IsRootScrollbarContainer() const; + bool IsZoomingLayer() const; + bool IsFixedPositionLayer() const; + bool IsStickyPositionLayer() const; + bool HasDynamicToolbar() const; + bool ShouldFixedAndStickyContentGetAnimationIds() const; + + bool CreatesStackingContextHelper() override { return true; } + + protected: + nsDisplayOwnLayerFlags mFlags; + + /** + * If this nsDisplayOwnLayer represents a scroll thumb layer or a + * scrollbar container layer, mScrollbarData stores information + * about the scrollbar. Otherwise, mScrollbarData will be + * default-constructed (in particular with mDirection == Nothing()) + * and can be ignored. + */ + layers::ScrollbarData mScrollbarData; + bool mForceActive; + uint64_t mWrAnimationId; +}; + +/** + * A display item for subdocuments. This is more or less the same as + * nsDisplayOwnLayer, except that it always populates the FrameMetrics instance + * on the ContainerLayer it builds. + */ +class nsDisplaySubDocument : public nsDisplayOwnLayer { + public: + nsDisplaySubDocument(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + nsSubDocumentFrame* aSubDocFrame, nsDisplayList* aList, + nsDisplayOwnLayerFlags aFlags); + ~nsDisplaySubDocument() override; + + NS_DISPLAY_DECL_NAME("SubDocument", TYPE_SUBDOCUMENT) + + nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) const override; + + virtual nsSubDocumentFrame* SubDocumentFrame() { return mSubDocFrame; } + + bool ShouldFlattenAway(nsDisplayListBuilder* aBuilder) override { + return mShouldFlatten; + } + + void SetShouldFlattenAway(bool aShouldFlatten) { + mShouldFlatten = aShouldFlatten; + } + + nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder, + bool* aSnap) const override; + + nsIFrame* FrameForInvalidation() const override; + void RemoveFrame(nsIFrame* aFrame) override; + + protected: + ViewID mScrollParentId; + bool mForceDispatchToContentRegion{}; + bool mShouldFlatten; + nsSubDocumentFrame* mSubDocFrame; +}; + +/** + * A display item used to represent sticky position elements. The contents + * gets its own layer and creates a stacking context, and the layer will have + * position-related metadata set on it. + */ +class nsDisplayStickyPosition : public nsDisplayOwnLayer { + public: + nsDisplayStickyPosition(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + nsDisplayList* aList, + const ActiveScrolledRoot* aActiveScrolledRoot, + const ActiveScrolledRoot* aContainerASR, + bool aClippedToDisplayPort); + nsDisplayStickyPosition(nsDisplayListBuilder* aBuilder, + const nsDisplayStickyPosition& aOther) + : nsDisplayOwnLayer(aBuilder, aOther), + mContainerASR(aOther.mContainerASR), + mClippedToDisplayPort(aOther.mClippedToDisplayPort), + mShouldFlatten(false) { + MOZ_COUNT_CTOR(nsDisplayStickyPosition); + } + + MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayStickyPosition) + + const DisplayItemClip& GetClip() const override { + return DisplayItemClip::NoClip(); + } + bool IsClippedToDisplayPort() const { return mClippedToDisplayPort; } + + NS_DISPLAY_DECL_NAME("StickyPosition", TYPE_STICKY_POSITION) + void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override { + GetChildren()->Paint(aBuilder, aCtx, + mFrame->PresContext()->AppUnitsPerDevPixel()); + } + + bool CreateWebRenderCommands( + wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) override; + + bool UpdateScrollData(layers::WebRenderScrollData* aData, + layers::WebRenderLayerScrollData* aLayerData) override; + + const ActiveScrolledRoot* GetContainerASR() const { return mContainerASR; } + + bool CreatesStackingContextHelper() override { return true; } + + bool CanMoveAsync() override { return true; } + + void SetShouldFlatten(bool aShouldFlatten) { + mShouldFlatten = aShouldFlatten; + } + + bool ShouldFlattenAway(nsDisplayListBuilder* aBuilder) final { + return mShouldFlatten; + } + + private: + NS_DISPLAY_ALLOW_CLONING() + + void CalculateLayerScrollRanges(StickyScrollContainer* aStickyScrollContainer, + float aAppUnitsPerDevPixel, float aScaleX, + float aScaleY, + LayerRectAbsolute& aStickyOuter, + LayerRectAbsolute& aStickyInner); + + StickyScrollContainer* GetStickyScrollContainer(); + + // This stores the ASR that this sticky container item would have assuming it + // has no fixed descendants. This may be the same as the ASR returned by + // GetActiveScrolledRoot(), or it may be a descendant of that. + RefPtr<const ActiveScrolledRoot> mContainerASR; + // This flag tracks if this sticky item is just clipped to the enclosing + // scrollframe's displayport, or if there are additional clips in play. In + // the former case, we can skip setting the displayport clip as the scrolled- + // clip of the corresponding layer. This allows sticky items to remain + // unclipped when the enclosing scrollframe is scrolled past the displayport. + // i.e. when the rest of the scrollframe checkerboards, the sticky item will + // not. This makes sense to do because the sticky item has abnormal scrolling + // behavior and may still be visible even if the rest of the scrollframe is + // checkerboarded. Note that the sticky item will still be subject to the + // scrollport clip. + bool mClippedToDisplayPort; + + // True if this item should be flattened away. + bool mShouldFlatten; +}; + +class nsDisplayFixedPosition : public nsDisplayOwnLayer { + public: + nsDisplayFixedPosition(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + nsDisplayList* aList, + const ActiveScrolledRoot* aActiveScrolledRoot, + const ActiveScrolledRoot* aScrollTargetASR); + nsDisplayFixedPosition(nsDisplayListBuilder* aBuilder, + const nsDisplayFixedPosition& aOther) + : nsDisplayOwnLayer(aBuilder, aOther), + mScrollTargetASR(aOther.mScrollTargetASR), + mIsFixedBackground(aOther.mIsFixedBackground) { + MOZ_COUNT_CTOR(nsDisplayFixedPosition); + } + + static nsDisplayFixedPosition* CreateForFixedBackground( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + nsIFrame* aSecondaryFrame, nsDisplayBackgroundImage* aImage, + const uint16_t aIndex, const ActiveScrolledRoot* aScrollTargetASR); + + MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayFixedPosition) + + NS_DISPLAY_DECL_NAME("FixedPosition", TYPE_FIXED_POSITION) + + void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override { + GetChildren()->Paint(aBuilder, aCtx, + mFrame->PresContext()->AppUnitsPerDevPixel()); + } + + bool ShouldFixToViewport(nsDisplayListBuilder* aBuilder) const override { + return mIsFixedBackground; + } + + bool CreateWebRenderCommands( + wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) override; + bool UpdateScrollData(layers::WebRenderScrollData* aData, + layers::WebRenderLayerScrollData* aLayerData) override; + void WriteDebugInfo(std::stringstream& aStream) override; + + protected: + // For background-attachment:fixed + nsDisplayFixedPosition(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + nsDisplayList* aList, + const ActiveScrolledRoot* aScrollTargetASR); + ViewID GetScrollTargetId(); + + RefPtr<const ActiveScrolledRoot> mScrollTargetASR; + bool mIsFixedBackground; + + private: + NS_DISPLAY_ALLOW_CLONING() +}; + +class nsDisplayTableFixedPosition : public nsDisplayFixedPosition { + public: + NS_DISPLAY_DECL_NAME("TableFixedPosition", TYPE_TABLE_FIXED_POSITION) + + nsIFrame* FrameForInvalidation() const override { return mAncestorFrame; } + + void RemoveFrame(nsIFrame* aFrame) override { + if (aFrame == mAncestorFrame) { + mAncestorFrame = nullptr; + SetDeletedFrame(); + } + nsDisplayFixedPosition::RemoveFrame(aFrame); + } + + protected: + nsDisplayTableFixedPosition(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + nsDisplayList* aList, nsIFrame* aAncestorFrame, + const ActiveScrolledRoot* aScrollTargetASR); + + nsDisplayTableFixedPosition(nsDisplayListBuilder* aBuilder, + const nsDisplayTableFixedPosition& aOther) + : nsDisplayFixedPosition(aBuilder, aOther), + mAncestorFrame(aOther.mAncestorFrame) {} + + ~nsDisplayTableFixedPosition() override { + if (mAncestorFrame) { + mAncestorFrame->RemoveDisplayItem(this); + } + } + + nsIFrame* mAncestorFrame; + + private: + NS_DISPLAY_ALLOW_CLONING() +}; + +/** + * This creates an empty scrollable layer. It has no child layers. + * It is used to record the existence of a scrollable frame in the layer + * tree. + */ +class nsDisplayScrollInfoLayer : public nsDisplayWrapList { + public: + nsDisplayScrollInfoLayer(nsDisplayListBuilder* aBuilder, + nsIFrame* aScrolledFrame, nsIFrame* aScrollFrame, + const gfx::CompositorHitTestInfo& aHitInfo, + const nsRect& aHitArea); + + MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayScrollInfoLayer) + + NS_DISPLAY_DECL_NAME("ScrollInfoLayer", TYPE_SCROLL_INFO_LAYER) + + nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder, + bool* aSnap) const override { + *aSnap = false; + return nsRegion(); + } + + void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override { + return; + } + + bool ShouldFlattenAway(nsDisplayListBuilder* aBuilder) override { + return false; + } + + void WriteDebugInfo(std::stringstream& aStream) override; + UniquePtr<layers::ScrollMetadata> ComputeScrollMetadata( + nsDisplayListBuilder* aBuilder, + layers::WebRenderLayerManager* aLayerManager); + bool UpdateScrollData(layers::WebRenderScrollData* aData, + layers::WebRenderLayerScrollData* aLayerData) override; + bool CreateWebRenderCommands( + wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) override; + + protected: + nsIFrame* mScrollFrame; + nsIFrame* mScrolledFrame; + ViewID mScrollParentId; + gfx::CompositorHitTestInfo mHitInfo; + nsRect mHitArea; +}; + +/** + * nsDisplayZoom is used for subdocuments that have a different full zoom than + * their parent documents. This item creates a container layer. + */ +class nsDisplayZoom : public nsDisplaySubDocument { + public: + /** + * @param aFrame is the root frame of the subdocument. + * @param aList contains the display items for the subdocument. + * @param aAPD is the app units per dev pixel ratio of the subdocument. + * @param aParentAPD is the app units per dev pixel ratio of the parent + * document. + * @param aFlags eGenerateSubdocInvalidations : + * Add UserData to the created ContainerLayer, so that invalidations + * for this layer are send to our nsPresContext. + */ + nsDisplayZoom(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + nsSubDocumentFrame* aSubDocFrame, nsDisplayList* aList, + int32_t aAPD, int32_t aParentAPD, + nsDisplayOwnLayerFlags aFlags = nsDisplayOwnLayerFlags::None); + + MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayZoom) + + NS_DISPLAY_DECL_NAME("Zoom", TYPE_ZOOM) + + nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) const override; + void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect, + HitTestState* aState, nsTArray<nsIFrame*>* aOutFrames) override; + // Get the app units per dev pixel ratio of the child document. + int32_t GetChildAppUnitsPerDevPixel() { return mAPD; } + // Get the app units per dev pixel ratio of the parent document. + int32_t GetParentAppUnitsPerDevPixel() { return mParentAPD; } + + private: + int32_t mAPD, mParentAPD; +}; + +/** + * nsDisplayAsyncZoom is used for APZ zooming. It wraps the contents of the + * root content document's scroll frame, including fixed position content. It + * does not contain the scroll frame's scrollbars. It is clipped to the scroll + * frame's scroll port clip. It is not scrolled; only its non-fixed contents + * are scrolled. This item creates a container layer. + */ +class nsDisplayAsyncZoom : public nsDisplayOwnLayer { + public: + nsDisplayAsyncZoom(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + nsDisplayList* aList, + const ActiveScrolledRoot* aActiveScrolledRoot, + layers::FrameMetrics::ViewID aViewID); + nsDisplayAsyncZoom(nsDisplayListBuilder* aBuilder, + const nsDisplayAsyncZoom& aOther) + : nsDisplayOwnLayer(aBuilder, aOther), mViewID(aOther.mViewID) { + MOZ_COUNT_CTOR(nsDisplayAsyncZoom); + } + +#ifdef NS_BUILD_REFCNT_LOGGING + virtual ~nsDisplayAsyncZoom(); +#endif + + NS_DISPLAY_DECL_NAME("AsyncZoom", TYPE_ASYNC_ZOOM) + + void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect, + HitTestState* aState, nsTArray<nsIFrame*>* aOutFrames) override; + bool UpdateScrollData(layers::WebRenderScrollData* aData, + layers::WebRenderLayerScrollData* aLayerData) override; + + protected: + layers::FrameMetrics::ViewID mViewID; +}; + +/** + * A base class for different effects types. + */ +class nsDisplayEffectsBase : public nsDisplayWrapList { + public: + nsDisplayEffectsBase(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + nsDisplayList* aList, + const ActiveScrolledRoot* aActiveScrolledRoot, + bool aClearClipChain = false); + nsDisplayEffectsBase(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + nsDisplayList* aList); + + nsDisplayEffectsBase(nsDisplayListBuilder* aBuilder, + const nsDisplayEffectsBase& aOther) + : nsDisplayWrapList(aBuilder, aOther), + mEffectsBounds(aOther.mEffectsBounds) { + MOZ_COUNT_CTOR(nsDisplayEffectsBase); + } + + MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayEffectsBase) + + nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder, + bool* aSnap) const override; + void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect, + HitTestState* aState, nsTArray<nsIFrame*>* aOutFrames) override; + + bool ShouldFlattenAway(nsDisplayListBuilder* aBuilder) override { + return false; + } + + gfxRect BBoxInUserSpace() const; + gfxPoint UserSpaceOffset() const; + + void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder, + const nsDisplayItemGeometry* aGeometry, + nsRegion* aInvalidRegion) const override; + + protected: + bool ValidateSVGFrame(); + + // relative to mFrame + nsRect mEffectsBounds; +}; + +/** + * A display item to paint a stacking context with 'mask' and 'clip-path' + * effects set by the stacking context root frame's style. The 'mask' and + * 'clip-path' properties may both contain multiple masks and clip paths, + * respectively. + * + * Note that 'mask' and 'clip-path' may just contain CSS simple-images and CSS + * basic shapes, respectively. That is, they don't necessarily reference + * resources such as SVG 'mask' and 'clipPath' elements. + */ +class nsDisplayMasksAndClipPaths : public nsDisplayEffectsBase { + public: + nsDisplayMasksAndClipPaths(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + nsDisplayList* aList, + const ActiveScrolledRoot* aActiveScrolledRoot, + bool aWrapsBackdropFilter); + nsDisplayMasksAndClipPaths(nsDisplayListBuilder* aBuilder, + const nsDisplayMasksAndClipPaths& aOther) + : nsDisplayEffectsBase(aBuilder, aOther), + mDestRects(aOther.mDestRects.Clone()), + mWrapsBackdropFilter(aOther.mWrapsBackdropFilter) { + MOZ_COUNT_CTOR(nsDisplayMasksAndClipPaths); + } + + MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayMasksAndClipPaths) + + NS_DISPLAY_DECL_NAME("Mask", TYPE_MASK) + + bool CanMerge(const nsDisplayItem* aItem) const override; + + void Merge(const nsDisplayItem* aItem) override { + nsDisplayWrapList::Merge(aItem); + + const nsDisplayMasksAndClipPaths* other = + static_cast<const nsDisplayMasksAndClipPaths*>(aItem); + mEffectsBounds.UnionRect( + mEffectsBounds, + other->mEffectsBounds + other->mFrame->GetOffsetTo(mFrame)); + } + + void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override; + + nsDisplayItemGeometry* AllocateGeometry( + nsDisplayListBuilder* aBuilder) override { + return new nsDisplayMasksAndClipPathsGeometry(this, aBuilder); + } + + void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder, + const nsDisplayItemGeometry* aGeometry, + nsRegion* aInvalidRegion) const override; +#ifdef MOZ_DUMP_PAINTING + void PrintEffects(nsACString& aTo); +#endif + + bool IsValidMask(); + + void PaintWithContentsPaintCallback( + nsDisplayListBuilder* aBuilder, gfxContext* aCtx, + const std::function<void()>& aPaintChildren); + + /* + * Paint mask onto aMaskContext in mFrame's coordinate space and + * return whether the mask layer was painted successfully. + */ + bool PaintMask(nsDisplayListBuilder* aBuilder, gfxContext* aMaskContext, + bool aHandleOpacity, bool* aMaskPainted = nullptr); + + const nsTArray<nsRect>& GetDestRects() { return mDestRects; } + + bool CreateWebRenderCommands( + wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) override; + + Maybe<nsRect> GetClipWithRespectToASR( + nsDisplayListBuilder* aBuilder, + const ActiveScrolledRoot* aASR) const override; + + bool CreatesStackingContextHelper() override { return true; } + + private: + NS_DISPLAY_ALLOW_CLONING() + + nsTArray<nsRect> mDestRects; + bool mWrapsBackdropFilter; +}; + +class nsDisplayBackdropFilters : public nsDisplayWrapList { + public: + nsDisplayBackdropFilters(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + nsDisplayList* aList, const nsRect& aBackdropRect, + nsIFrame* aStyleFrame) + : nsDisplayWrapList(aBuilder, aFrame, aList), + mStyle(aFrame == aStyleFrame ? nullptr : aStyleFrame->Style()), + mBackdropRect(aBackdropRect) { + MOZ_COUNT_CTOR(nsDisplayBackdropFilters); + } + + MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayBackdropFilters) + + NS_DISPLAY_DECL_NAME("BackdropFilter", TYPE_BACKDROP_FILTER) + + bool CreateWebRenderCommands( + wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) override; + void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override; + + bool ShouldFlattenAway(nsDisplayListBuilder* aBuilder) override { + return !aBuilder->IsPaintingForWebRender(); + } + + bool CreatesStackingContextHelper() override { return true; } + + nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) const override; + + private: + RefPtr<ComputedStyle> mStyle; + nsRect mBackdropRect; +}; + +/** + * A display item to paint a stacking context with filter effects set by the + * stacking context root frame's style. + * + * Note that the filters may just be simple CSS filter functions. That is, + * they won't necessarily be references to SVG 'filter' elements. + */ +class nsDisplayFilters : public nsDisplayEffectsBase { + public: + nsDisplayFilters(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + nsDisplayList* aList, nsIFrame* aStyleFrame, + bool aWrapsBackdropFilter); + + nsDisplayFilters(nsDisplayListBuilder* aBuilder, + const nsDisplayFilters& aOther) + : nsDisplayEffectsBase(aBuilder, aOther), + mStyle(aOther.mStyle), + mEffectsBounds(aOther.mEffectsBounds), + mWrapsBackdropFilter(aOther.mWrapsBackdropFilter) { + MOZ_COUNT_CTOR(nsDisplayFilters); + } + + MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayFilters) + + NS_DISPLAY_DECL_NAME("Filter", TYPE_FILTER) + + bool CanMerge(const nsDisplayItem* aItem) const override { + // Items for the same content element should be merged into a single + // compositing group. + return HasDifferentFrame(aItem) && HasSameTypeAndClip(aItem) && + HasSameContent(aItem); + } + + void Merge(const nsDisplayItem* aItem) override { + nsDisplayWrapList::Merge(aItem); + + const nsDisplayFilters* other = static_cast<const nsDisplayFilters*>(aItem); + mEffectsBounds.UnionRect( + mEffectsBounds, + other->mEffectsBounds + other->mFrame->GetOffsetTo(mFrame)); + } + + void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override; + + nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) const override { + *aSnap = false; + return mEffectsBounds + ToReferenceFrame(); + } + + nsDisplayItemGeometry* AllocateGeometry( + nsDisplayListBuilder* aBuilder) override { + return new nsDisplaySVGEffectGeometry(this, aBuilder); + } + +#ifdef MOZ_DUMP_PAINTING + void PrintEffects(nsACString& aTo); +#endif + + void PaintWithContentsPaintCallback( + nsDisplayListBuilder* aBuilder, gfxContext* aCtx, + const std::function<void(gfxContext* aContext)>& aPaintChildren); + + bool CreateWebRenderCommands( + wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) override; + bool CanCreateWebRenderCommands() const; + + bool CanApplyOpacity(WebRenderLayerManager* aManager, + nsDisplayListBuilder* aBuilder) const override { + return CanCreateWebRenderCommands(); + } + + bool CreatesStackingContextHelper() override { return true; } + + private: + NS_DISPLAY_ALLOW_CLONING() + + RefPtr<ComputedStyle> mStyle; + // relative to mFrame + nsRect mEffectsBounds; + nsRect mVisibleRect; + bool mWrapsBackdropFilter; +}; + +/* A display item that applies a transformation to all of its descendant + * elements. This wrapper should only be used if there is a transform applied + * to the root element. + * + * The reason that a "bounds" rect is involved in transform calculations is + * because CSS-transforms allow percentage values for the x and y components + * of <translation-value>s, where percentages are percentages of the element's + * border box. + * + * INVARIANT: The wrapped frame is transformed or we supplied a transform getter + * function. + * INVARIANT: The wrapped frame is non-null. + */ +class nsDisplayTransform : public nsPaintedDisplayItem { + using Matrix4x4 = gfx::Matrix4x4; + using Matrix4x4Flagged = gfx::Matrix4x4Flagged; + using TransformReferenceBox = nsStyleTransformMatrix::TransformReferenceBox; + + public: + enum class PrerenderDecision : uint8_t { No, Full, Partial }; + + enum { + WithTransformGetter, + }; + + /* Constructor accepts a display list, empties it, and wraps it up. It also + * ferries the underlying frame to the nsDisplayItem constructor. + */ + nsDisplayTransform(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + nsDisplayList* aList, const nsRect& aChildrenBuildingRect); + + nsDisplayTransform(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + nsDisplayList* aList, const nsRect& aChildrenBuildingRect, + PrerenderDecision aPrerenderDecision); + + nsDisplayTransform(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + nsDisplayList* aList, const nsRect& aChildrenBuildingRect, + decltype(WithTransformGetter)); + + MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayTransform) + + NS_DISPLAY_DECL_NAME("nsDisplayTransform", TYPE_TRANSFORM) + + void UpdateBounds(nsDisplayListBuilder* aBuilder) override; + + /** + * This function updates bounds for items with a frame establishing + * 3D rendering context. + */ + void UpdateBoundsFor3D(nsDisplayListBuilder* aBuilder); + + void DoUpdateBoundsPreserves3D(nsDisplayListBuilder* aBuilder) override; + + void Destroy(nsDisplayListBuilder* aBuilder) override { + GetChildren()->DeleteAll(aBuilder); + nsPaintedDisplayItem::Destroy(aBuilder); + } + + nsRect GetComponentAlphaBounds(nsDisplayListBuilder* aBuilder) const override; + + RetainedDisplayList* GetChildren() const override { return &mChildren; } + + nsRect GetUntransformedBounds(nsDisplayListBuilder* aBuilder, + bool* aSnap) const override { + *aSnap = false; + return mChildBounds; + } + + const nsRect& GetUntransformedPaintRect() const override { + return mChildrenBuildingRect; + } + + bool ShouldFlattenAway(nsDisplayListBuilder* aBuilder) override; + + void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect, + HitTestState* aState, nsTArray<nsIFrame*>* aOutFrames) override; + nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) const override; + nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder, + bool* aSnap) const override; + void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override; + void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx, + const Maybe<gfx::Polygon>& aPolygon); + bool CreateWebRenderCommands( + wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) override; + bool UpdateScrollData(layers::WebRenderScrollData* aData, + layers::WebRenderLayerScrollData* aLayerData) override; + + nsDisplayItemGeometry* AllocateGeometry( + nsDisplayListBuilder* aBuilder) override { + return new nsDisplayTransformGeometry( + this, aBuilder, GetTransformForRendering(), + mFrame->PresContext()->AppUnitsPerDevPixel()); + } + + void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder, + const nsDisplayItemGeometry* aGeometry, + nsRegion* aInvalidRegion) const override { + const nsDisplayTransformGeometry* geometry = + static_cast<const nsDisplayTransformGeometry*>(aGeometry); + + // This code is only called for flattened, inactive transform items. + // Only check if the transform has changed. The bounds invalidation should + // be handled by the children themselves. + if (!geometry->mTransform.FuzzyEqual(GetTransformForRendering())) { + bool snap; + aInvalidRegion->Or(GetBounds(aBuilder, &snap), geometry->mBounds); + } + } + + const nsIFrame* ReferenceFrameForChildren() const override { + // If we were created using a transform-getter, then we don't + // belong to a transformed frame, and aren't a reference frame + // for our children. + if (!mHasTransformGetter) { + return mFrame; + } + return nsPaintedDisplayItem::ReferenceFrameForChildren(); + } + + const nsRect& GetBuildingRectForChildren() const override { + return mChildrenBuildingRect; + } + + enum { INDEX_MAX = UINT32_MAX >> TYPE_BITS }; + + /** + * We include the perspective matrix from our containing block for the + * purposes of visibility calculations, but we exclude it from the transform + * we set on the layer (for rendering), since there will be an + * nsDisplayPerspective created for that. + */ + const Matrix4x4Flagged& GetTransform() const; + const Matrix4x4Flagged& GetInverseTransform() const; + + bool ShouldSkipTransform(nsDisplayListBuilder* aBuilder) const; + Matrix4x4 GetTransformForRendering( + LayoutDevicePoint* aOutOrigin = nullptr) const; + + /** + * Return the transform that is aggregation of all transform on the + * preserves3d chain. + */ + const Matrix4x4& GetAccumulatedPreserved3DTransform( + nsDisplayListBuilder* aBuilder); + + float GetHitDepthAtPoint(nsDisplayListBuilder* aBuilder, + const nsPoint& aPoint); + /** + * TransformRect takes in as parameters a rectangle (in aFrame's coordinate + * space) and returns the smallest rectangle (in aFrame's coordinate space) + * containing the transformed image of that rectangle. That is, it takes + * the four corners of the rectangle, transforms them according to the + * matrix associated with the specified frame, then returns the smallest + * rectangle containing the four transformed points. + * + * @param untransformedBounds The rectangle (in app units) to transform. + * @param aFrame The frame whose transformation should be applied. This + * function raises an assertion if aFrame is null or doesn't have a + * transform applied to it. + * @param aRefBox the reference box to use, which would usually be just + * TransformReferemceBox(aFrame), but callers may override it if + * needed. + */ + static nsRect TransformRect(const nsRect& aUntransformedBounds, + const nsIFrame* aFrame, + TransformReferenceBox& aRefBox); + + /* UntransformRect is like TransformRect, except that it inverts the + * transform. + */ + static bool UntransformRect(const nsRect& aTransformedBounds, + const nsRect& aChildBounds, + const nsIFrame* aFrame, nsRect* aOutRect); + static bool UntransformRect(const nsRect& aTransformedBounds, + const nsRect& aChildBounds, + const Matrix4x4& aMatrix, float aAppUnitsPerPixel, + nsRect* aOutRect); + + bool UntransformRect(nsDisplayListBuilder* aBuilder, const nsRect& aRect, + nsRect* aOutRect) const; + + bool UntransformBuildingRect(nsDisplayListBuilder* aBuilder, + nsRect* aOutRect) const { + return UntransformRect(aBuilder, GetBuildingRect(), aOutRect); + } + + static gfx::Point3D GetDeltaToTransformOrigin(const nsIFrame* aFrame, + TransformReferenceBox&, + float aAppUnitsPerPixel); + + /* + * Returns true if aFrame has perspective applied from its containing + * block. + * Returns the matrix to append to apply the persective (taking + * perspective-origin into account), relative to aFrames coordinate + * space). + * aOutMatrix is assumed to be the identity matrix, and isn't explicitly + * cleared. + */ + static bool ComputePerspectiveMatrix(const nsIFrame* aFrame, + float aAppUnitsPerPixel, + Matrix4x4& aOutMatrix); + + struct MOZ_STACK_CLASS FrameTransformProperties { + FrameTransformProperties(const nsIFrame* aFrame, + TransformReferenceBox& aRefBox, + float aAppUnitsPerPixel); + FrameTransformProperties(const StyleTranslate& aTranslate, + const StyleRotate& aRotate, + const StyleScale& aScale, + const StyleTransform& aTransform, + const Maybe<ResolvedMotionPathData>& aMotion, + const gfx::Point3D& aToTransformOrigin) + : mFrame(nullptr), + mTranslate(aTranslate), + mRotate(aRotate), + mScale(aScale), + mTransform(aTransform), + mMotion(aMotion), + mToTransformOrigin(aToTransformOrigin) {} + + bool HasTransform() const { + return !mTranslate.IsNone() || !mRotate.IsNone() || !mScale.IsNone() || + !mTransform.IsNone() || mMotion.isSome(); + } + + const nsIFrame* mFrame; + const StyleTranslate& mTranslate; + const StyleRotate& mRotate; + const StyleScale& mScale; + const StyleTransform& mTransform; + const Maybe<ResolvedMotionPathData> mMotion; + const gfx::Point3D mToTransformOrigin; + }; + + /** + * Given a frame with the transform property or an SVG transform, + * returns the transformation matrix for that frame. + * + * @param aFrame The frame to get the matrix from. + * @param aOrigin Relative to which point this transform should be applied. + * @param aAppUnitsPerPixel The number of app units per graphics unit. + * @param aBoundsOverride [optional] If this is nullptr (the default), the + * computation will use the value of TransformReferenceBox(aFrame). + * Otherwise, it will use the value of aBoundsOverride. This is + * mostly for internal use and in most cases you will not need to + * specify a value. + * @param aFlags OFFSET_BY_ORIGIN The resulting matrix will be translated + * by aOrigin. This translation is applied *before* the CSS transform. + * @param aFlags INCLUDE_PRESERVE3D_ANCESTORS The computed transform will + * include the transform of any ancestors participating in the same + * 3d rendering context. + * @param aFlags INCLUDE_PERSPECTIVE The resulting matrix will include the + * perspective transform from the containing block if applicable. + */ + enum { + OFFSET_BY_ORIGIN = 1 << 0, + INCLUDE_PRESERVE3D_ANCESTORS = 1 << 1, + INCLUDE_PERSPECTIVE = 1 << 2, + }; + static constexpr uint32_t kTransformRectFlags = + INCLUDE_PERSPECTIVE | OFFSET_BY_ORIGIN | INCLUDE_PRESERVE3D_ANCESTORS; + static Matrix4x4 GetResultingTransformMatrix(const nsIFrame* aFrame, + const nsPoint& aOrigin, + float aAppUnitsPerPixel, + uint32_t aFlags); + static Matrix4x4 GetResultingTransformMatrix( + const FrameTransformProperties& aProperties, TransformReferenceBox&, + float aAppUnitsPerPixel); + + struct PrerenderInfo { + bool CanUseAsyncAnimations() const { + return mDecision != PrerenderDecision::No && mHasAnimations; + } + PrerenderDecision mDecision = PrerenderDecision::No; + bool mHasAnimations = true; + }; + /** + * Decide whether we should prerender some or all of the contents of the + * transformed frame even when it's not completely visible (yet). + * Return PrerenderDecision::Full if the entire contents should be + * prerendered, PrerenderDecision::Partial if some but not all of the + * contents should be prerendered, or PrerenderDecision::No if only the + * visible area should be rendered. + * |mNoAffectDecisionInPreserve3D| is set if the prerender decision should not + * affect the decision on other frames in the preserve 3d tree. + * |aDirtyRect| is updated to the area that should be prerendered. + */ + static PrerenderInfo ShouldPrerenderTransformedContent( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsRect* aDirtyRect); + + bool CanUseAsyncAnimations(nsDisplayListBuilder* aBuilder) override; + + bool MayBeAnimated(nsDisplayListBuilder* aBuilder) const; + + void WriteDebugInfo(std::stringstream& aStream) override; + + bool CanMoveAsync() override { + return EffectCompositor::HasAnimationsForCompositor( + mFrame, DisplayItemType::TYPE_TRANSFORM); + } + + /** + * This item is an additional item as the boundary between parent + * and child 3D rendering context. + * \see nsIFrame::BuildDisplayListForStackingContext(). + */ + bool IsTransformSeparator() const { return mIsTransformSeparator; } + /** + * This item is the boundary between parent and child 3D rendering + * context. + */ + bool IsLeafOf3DContext() const { + return (IsTransformSeparator() || + (!mFrame->Extend3DContext() && Combines3DTransformWithAncestors())); + } + /** + * The backing frame of this item participates a 3D rendering + * context. + */ + bool IsParticipating3DContext() const { + return mFrame->Extend3DContext() || Combines3DTransformWithAncestors(); + } + + bool IsPartialPrerender() const { + return mPrerenderDecision == PrerenderDecision::Partial; + } + + /** + * Mark this item as created together with `nsDisplayPerspective`. + * \see nsIFrame::BuildDisplayListForStackingContext(). + */ + void MarkWithAssociatedPerspective() { mHasAssociatedPerspective = true; } + + void AddSizeOfExcludingThis(nsWindowSizes&) const override; + + bool CreatesStackingContextHelper() override { return true; } + + private: + void ComputeBounds(nsDisplayListBuilder* aBuilder); + nsRect TransformUntransformedBounds(nsDisplayListBuilder* aBuilder, + const Matrix4x4Flagged& aMatrix) const; + void UpdateUntransformedBounds(nsDisplayListBuilder* aBuilder); + + void SetReferenceFrameToAncestor(nsDisplayListBuilder* aBuilder); + void Init(nsDisplayListBuilder* aBuilder, nsDisplayList* aChildren); + + static Matrix4x4 GetResultingTransformMatrixInternal( + const FrameTransformProperties& aProperties, + TransformReferenceBox& aRefBox, const nsPoint& aOrigin, + float aAppUnitsPerPixel, uint32_t aFlags); + + void Collect3DTransformLeaves(nsDisplayListBuilder* aBuilder, + nsTArray<nsDisplayTransform*>& aLeaves); + using TransformPolygon = layers::BSPPolygon<nsDisplayTransform>; + void CollectSorted3DTransformLeaves(nsDisplayListBuilder* aBuilder, + nsTArray<TransformPolygon>& aLeaves); + + mutable RetainedDisplayList mChildren; + mutable Maybe<Matrix4x4Flagged> mTransform; + mutable Maybe<Matrix4x4Flagged> mInverseTransform; + // Accumulated transform of ancestors on the preserves-3d chain. + UniquePtr<Matrix4x4> mTransformPreserves3D; + nsRect mChildrenBuildingRect; + + // The untransformed bounds of |mChildren|. + nsRect mChildBounds; + // The transformed bounds of this display item. + nsRect mBounds; + PrerenderDecision mPrerenderDecision : 8; + // This item is a separator between 3D rendering contexts, and + // mTransform have been presetted by the constructor. + // This also forces us not to extend the 3D context. Since we don't create a + // transform item, a container layer, for every frame in a preserves3d + // context, the transform items of a child preserves3d context may extend the + // parent context unintendedly if the root of the child preserves3d context + // doesn't create a transform item. + bool mIsTransformSeparator : 1; + // True if we have a transform getter. + bool mHasTransformGetter : 1; + // True if this item is created together with `nsDisplayPerspective` + // from the same CSS stacking context. + bool mHasAssociatedPerspective : 1; +}; + +/* A display item that applies a perspective transformation to a single + * nsDisplayTransform child item. We keep this as a separate item since the + * perspective-origin is relative to an ancestor of the transformed frame, and + * APZ can scroll the child separately. + */ +class nsDisplayPerspective : public nsPaintedDisplayItem { + public: + nsDisplayPerspective(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + nsDisplayList* aList); + ~nsDisplayPerspective() override = default; + + NS_DISPLAY_DECL_NAME("nsDisplayPerspective", TYPE_PERSPECTIVE) + + void Destroy(nsDisplayListBuilder* aBuilder) override { + mList.DeleteAll(aBuilder); + nsPaintedDisplayItem::Destroy(aBuilder); + } + + void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect, + HitTestState* aState, nsTArray<nsIFrame*>* aOutFrames) override { + return GetChildren()->HitTest(aBuilder, aRect, aState, aOutFrames); + } + + nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) const override { + *aSnap = false; + return GetChildren()->GetClippedBoundsWithRespectToASR(aBuilder, + mActiveScrolledRoot); + } + + void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder, + const nsDisplayItemGeometry* aGeometry, + nsRegion* aInvalidRegion) const override {} + + nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder, + bool* aSnap) const override; + + bool CreateWebRenderCommands( + wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) override; + + void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override; + + RetainedDisplayList* GetSameCoordinateSystemChildren() const override { + return &mList; + } + + RetainedDisplayList* GetChildren() const override { return &mList; } + + nsRect GetComponentAlphaBounds( + nsDisplayListBuilder* aBuilder) const override { + return GetChildren()->GetComponentAlphaBounds(aBuilder); + } + + void DoUpdateBoundsPreserves3D(nsDisplayListBuilder* aBuilder) override { + if (GetChildren()->GetTop()) { + static_cast<nsDisplayTransform*>(GetChildren()->GetTop()) + ->DoUpdateBoundsPreserves3D(aBuilder); + } + } + + bool CreatesStackingContextHelper() override { return true; } + + private: + mutable RetainedDisplayList mList; +}; + +class nsDisplayTextGeometry; + +/** + * This class adds basic support for limiting the rendering (in the inline axis + * of the writing mode) to the part inside the specified edges. + * The two members, mVisIStartEdge and mVisIEndEdge, are relative to the edges + * of the frame's scrollable overflow rectangle and are the amount to suppress + * on each side. + * + * Setting none, both or only one edge is allowed. + * The values must be non-negative. + * The default value for both edges is zero, which means everything is painted. + */ +class nsDisplayText final : public nsPaintedDisplayItem { + public: + nsDisplayText(nsDisplayListBuilder* aBuilder, nsTextFrame* aFrame); + MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayText) + + NS_DISPLAY_DECL_NAME("Text", TYPE_TEXT) + + nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) const final { + *aSnap = false; + return mBounds; + } + + void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect, + HitTestState* aState, nsTArray<nsIFrame*>* aOutFrames) final { + if (nsRect(ToReferenceFrame(), mFrame->GetSize()).Intersects(aRect)) { + aOutFrames->AppendElement(mFrame); + } + } + + bool CreateWebRenderCommands(wr::DisplayListBuilder& aBuilder, + wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) final; + void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) final; + + nsRect GetComponentAlphaBounds(nsDisplayListBuilder* aBuilder) const final { + if (gfxPlatform::GetPlatform()->RespectsFontStyleSmoothing()) { + // On OS X, web authors can turn off subpixel text rendering using the + // CSS property -moz-osx-font-smoothing. If they do that, we don't need + // to use component alpha layers for the affected text. + if (mFrame->StyleFont()->mFont.smoothing == NS_FONT_SMOOTHING_GRAYSCALE) { + return nsRect(); + } + } + bool snap; + return GetBounds(aBuilder, &snap); + } + + nsDisplayItemGeometry* AllocateGeometry(nsDisplayListBuilder* aBuilder) final; + + void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder, + const nsDisplayItemGeometry* aGeometry, + nsRegion* aInvalidRegion) const final; + + void RenderToContext(gfxContext* aCtx, nsDisplayListBuilder* aBuilder, + const nsRect& aVisibleRect, float aOpacity = 1.0f, + bool aIsRecording = false); + + bool CanApplyOpacity(WebRenderLayerManager* aManager, + nsDisplayListBuilder* aBuilder) const final; + + void WriteDebugInfo(std::stringstream& aStream) final; + + static nsDisplayText* CheckCast(nsDisplayItem* aItem) { + return (aItem->GetType() == DisplayItemType::TYPE_TEXT) + ? static_cast<nsDisplayText*>(aItem) + : nullptr; + } + + nscoord& VisIStartEdge() { return mVisIStartEdge; } + nscoord& VisIEndEdge() { return mVisIEndEdge; } + + private: + nsRect mBounds; + nsRect mVisibleRect; + + // Lengths measured from the visual inline start and end sides + // (i.e. left and right respectively in horizontal writing modes, + // regardless of bidi directionality; top and bottom in vertical modes). + nscoord mVisIStartEdge; + nscoord mVisIEndEdge; +}; + +/** + * A display item that for webrender to handle SVG + */ +class nsDisplaySVGWrapper : public nsDisplayWrapList { + public: + nsDisplaySVGWrapper(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + nsDisplayList* aList); + + MOZ_COUNTED_DTOR_OVERRIDE(nsDisplaySVGWrapper) + + NS_DISPLAY_DECL_NAME("SVGWrapper", TYPE_SVG_WRAPPER) + + void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override { + GetChildren()->Paint(aBuilder, aCtx, + mFrame->PresContext()->AppUnitsPerDevPixel()); + } + bool ShouldFlattenAway(nsDisplayListBuilder* aBuilder) override; + bool CreateWebRenderCommands( + wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) override; +}; + +/** + * A display item for webrender to handle SVG foreign object + */ +class nsDisplayForeignObject : public nsDisplayWrapList { + public: + nsDisplayForeignObject(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + nsDisplayList* aList); +#ifdef NS_BUILD_REFCNT_LOGGING + virtual ~nsDisplayForeignObject(); +#endif + + NS_DISPLAY_DECL_NAME("ForeignObject", TYPE_FOREIGN_OBJECT) + + virtual bool ShouldFlattenAway(nsDisplayListBuilder* aBuilder) override; + void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override { + GetChildren()->Paint(aBuilder, aCtx, + mFrame->PresContext()->AppUnitsPerDevPixel()); + } + + bool CreateWebRenderCommands( + wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) override; +}; + +/** + * A display item to represent a hyperlink. + */ +class nsDisplayLink : public nsPaintedDisplayItem { + public: + nsDisplayLink(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + const char* aLinkSpec, const nsRect& aRect) + : nsPaintedDisplayItem(aBuilder, aFrame), + mLinkSpec(aLinkSpec), + mRect(aRect) {} + + NS_DISPLAY_DECL_NAME("Link", TYPE_LINK) + + void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override; + + private: + nsCString mLinkSpec; + nsRect mRect; +}; + +/** + * A display item to represent a destination within the document. + */ +class nsDisplayDestination : public nsPaintedDisplayItem { + public: + nsDisplayDestination(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + const char* aDestinationName, const nsPoint& aPosition) + : nsPaintedDisplayItem(aBuilder, aFrame), + mDestinationName(aDestinationName), + mPosition(aPosition) {} + + NS_DISPLAY_DECL_NAME("Destination", TYPE_DESTINATION) + + void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override; + + private: + nsCString mDestinationName; + nsPoint mPosition; +}; + +class MOZ_STACK_CLASS FlattenedDisplayListIterator { + public: + FlattenedDisplayListIterator(nsDisplayListBuilder* aBuilder, + nsDisplayList* aList) + : mBuilder(aBuilder), mStart(aList->begin()), mEnd(aList->end()) { + ResolveFlattening(); + } + + bool HasNext() const { return !AtEndOfCurrentList(); } + + nsDisplayItem* GetNextItem() { + MOZ_ASSERT(HasNext()); + + nsDisplayItem* current = NextItem(); + Advance(); + + if (!AtEndOfCurrentList() && current->CanMerge(NextItem())) { + // Since we can merge at least two display items, create an array and + // collect mergeable display items there. + AutoTArray<nsDisplayItem*, 2> willMerge{current}; + + auto it = mStart; + while (it != mEnd) { + nsDisplayItem* next = *it; + if (current->CanMerge(next)) { + willMerge.AppendElement(next); + ++it; + } else { + break; + } + } + mStart = it; + + current = mBuilder->MergeItems(willMerge); + } + + ResolveFlattening(); + return current; + } + + protected: + void Advance() { ++mStart; } + + bool AtEndOfNestedList() const { + return AtEndOfCurrentList() && mStack.Length() > 0; + } + + bool AtEndOfCurrentList() const { return mStart == mEnd; } + + nsDisplayItem* NextItem() { + MOZ_ASSERT(HasNext()); + return *mStart; + } + + bool ShouldFlattenNextItem() { + return HasNext() && NextItem()->ShouldFlattenAway(mBuilder); + } + + void ResolveFlattening() { + // Handle the case where we reach the end of a nested list, or the current + // item should start a new nested list. Repeat this until we find an actual + // item, or the very end of the outer list. + while (AtEndOfNestedList() || ShouldFlattenNextItem()) { + if (AtEndOfNestedList()) { + // We reached the end of the list, pop the next list from the stack. + std::tie(mStart, mEnd) = mStack.PopLastElement(); + } else { + // The next item wants to be flattened. This means that we will skip the + // flattened item and directly iterate over its sublist. + MOZ_ASSERT(ShouldFlattenNextItem()); + + nsDisplayList* sublist = NextItem()->GetChildren(); + MOZ_ASSERT(sublist); + + // Skip the flattened item. + Advance(); + + // Store the current position on the stack. + if (!AtEndOfCurrentList()) { + mStack.AppendElement(std::make_pair(mStart, mEnd)); + } + + // Iterate over the sublist. + mStart = sublist->begin(); + mEnd = sublist->end(); + } + } + } + + private: + nsDisplayListBuilder* mBuilder; + nsDisplayList::iterator mStart; + nsDisplayList::iterator mEnd; + AutoTArray<std::pair<nsDisplayList::iterator, nsDisplayList::iterator>, 3> + mStack; +}; + +class PaintTelemetry { + public: + class AutoRecordPaint { + public: + AutoRecordPaint(); + ~AutoRecordPaint(); + + private: + TimeStamp mStart; + }; + + private: + static uint32_t sPaintLevel; +}; + +} // namespace mozilla + +#endif /*NSDISPLAYLIST_H_*/ diff --git a/layout/painting/nsDisplayListArenaTypes.h b/layout/painting/nsDisplayListArenaTypes.h new file mode 100644 index 0000000000..79d04b298a --- /dev/null +++ b/layout/painting/nsDisplayListArenaTypes.h @@ -0,0 +1,15 @@ +/* -*- 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/. */ + +/* a list of all types that can be allocated in the display list's nsPresArena, + for preprocessing */ + +#define DECLARE_DISPLAY_ITEM_TYPE(name_, ...) DISPLAY_LIST_ARENA_OBJECT(name_) +DISPLAY_LIST_ARENA_OBJECT(UNUSED) // DisplayItemType::TYPE_ZERO +#include "nsDisplayItemTypesList.h" +#undef DECLARE_DISPLAY_ITEM_TYPE +DISPLAY_LIST_ARENA_OBJECT(CLIPCHAIN) +DISPLAY_LIST_ARENA_OBJECT(LISTNODE) diff --git a/layout/painting/nsDisplayListInvalidation.cpp b/layout/painting/nsDisplayListInvalidation.cpp new file mode 100644 index 0000000000..e5696d14c7 --- /dev/null +++ b/layout/painting/nsDisplayListInvalidation.cpp @@ -0,0 +1,124 @@ +/* -*- 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/. */ + +#include "nsDisplayListInvalidation.h" +#include "nsDisplayList.h" +#include "nsIFrame.h" +#include "nsTableFrame.h" + +namespace mozilla { + +nsDisplayItemGeometry::nsDisplayItemGeometry(nsDisplayItem* aItem, + nsDisplayListBuilder* aBuilder) { + MOZ_COUNT_CTOR(nsDisplayItemGeometry); + bool snap; + mBounds = aItem->GetBounds(aBuilder, &snap); +} + +nsDisplayItemGeometry::~nsDisplayItemGeometry() { + MOZ_COUNT_DTOR(nsDisplayItemGeometry); +} + +nsDisplayItemGenericGeometry::nsDisplayItemGenericGeometry( + nsDisplayItem* aItem, nsDisplayListBuilder* aBuilder) + : nsDisplayItemGeometry(aItem, aBuilder), + mBorderRect(aItem->GetBorderRect()) {} + +bool ShouldSyncDecodeImages(nsDisplayListBuilder* aBuilder) { + return aBuilder->ShouldSyncDecodeImages(); +} + +nsDisplayItemGeometry* GetPreviousGeometry(nsDisplayItem* aItem) { + if (RefPtr<layers::WebRenderFallbackData> data = + layers::GetWebRenderUserData<layers::WebRenderFallbackData>( + aItem->Frame(), aItem->GetPerFrameKey())) { + return data->GetGeometry(); + } + return nullptr; +} + +void nsDisplayItemGenericGeometry::MoveBy(const nsPoint& aOffset) { + nsDisplayItemGeometry::MoveBy(aOffset); + mBorderRect.MoveBy(aOffset); +} + +nsDisplayItemBoundsGeometry::nsDisplayItemBoundsGeometry( + nsDisplayItem* aItem, nsDisplayListBuilder* aBuilder) + : nsDisplayItemGeometry(aItem, aBuilder) { + nscoord radii[8]; + mHasRoundedCorners = aItem->Frame()->GetBorderRadii(radii); +} + +nsDisplayBorderGeometry::nsDisplayBorderGeometry(nsDisplayItem* aItem, + nsDisplayListBuilder* aBuilder) + : nsDisplayItemGeometry(aItem, aBuilder) {} + +nsDisplayBackgroundGeometry::nsDisplayBackgroundGeometry( + nsDisplayBackgroundImage* aItem, nsDisplayListBuilder* aBuilder) + : nsDisplayItemGeometry(aItem, aBuilder), + mPositioningArea(aItem->GetPositioningArea()), + mDestRect(aItem->GetDestRect()) {} + +void nsDisplayBackgroundGeometry::MoveBy(const nsPoint& aOffset) { + nsDisplayItemGeometry::MoveBy(aOffset); + mPositioningArea.MoveBy(aOffset); + mDestRect.MoveBy(aOffset); +} + +nsDisplayThemedBackgroundGeometry::nsDisplayThemedBackgroundGeometry( + nsDisplayThemedBackground* aItem, nsDisplayListBuilder* aBuilder) + : nsDisplayItemGeometry(aItem, aBuilder), + mPositioningArea(aItem->GetPositioningArea()), + mWindowIsActive(aItem->IsWindowActive()) {} + +void nsDisplayThemedBackgroundGeometry::MoveBy(const nsPoint& aOffset) { + nsDisplayItemGeometry::MoveBy(aOffset); + mPositioningArea.MoveBy(aOffset); +} + +nsDisplayBoxShadowInnerGeometry::nsDisplayBoxShadowInnerGeometry( + nsDisplayItem* aItem, nsDisplayListBuilder* aBuilder) + : nsDisplayItemGeometry(aItem, aBuilder), + mPaddingRect(aItem->GetPaddingRect()) {} + +void nsDisplayBoxShadowInnerGeometry::MoveBy(const nsPoint& aOffset) { + nsDisplayItemGeometry::MoveBy(aOffset); + mPaddingRect.MoveBy(aOffset); +} + +void nsDisplaySolidColorRegionGeometry::MoveBy(const nsPoint& aOffset) { + nsDisplayItemGeometry::MoveBy(aOffset); + mRegion.MoveBy(aOffset); +} + +nsDisplaySVGEffectGeometry::nsDisplaySVGEffectGeometry( + nsDisplayEffectsBase* aItem, nsDisplayListBuilder* aBuilder) + : nsDisplayItemGeometry(aItem, aBuilder), + mBBox(aItem->BBoxInUserSpace()), + mUserSpaceOffset(aItem->UserSpaceOffset()), + mFrameOffsetToReferenceFrame(aItem->ToReferenceFrame()) {} + +void nsDisplaySVGEffectGeometry::MoveBy(const nsPoint& aOffset) { + mBounds.MoveBy(aOffset); + mFrameOffsetToReferenceFrame += aOffset; +} + +nsDisplayMasksAndClipPathsGeometry::nsDisplayMasksAndClipPathsGeometry( + nsDisplayMasksAndClipPaths* aItem, nsDisplayListBuilder* aBuilder) + : nsDisplaySVGEffectGeometry(aItem, aBuilder), + mDestRects(aItem->GetDestRects().Clone()) {} + +nsDisplayFiltersGeometry::nsDisplayFiltersGeometry( + nsDisplayFilters* aItem, nsDisplayListBuilder* aBuilder) + : nsDisplaySVGEffectGeometry(aItem, aBuilder) {} + +nsDisplayTableItemGeometry::nsDisplayTableItemGeometry( + nsDisplayTableItem* aItem, nsDisplayListBuilder* aBuilder, + const nsPoint& aFrameOffsetToViewport) + : nsDisplayItemGenericGeometry(aItem, aBuilder), + mFrameOffsetToViewport(aFrameOffsetToViewport) {} + +} // namespace mozilla diff --git a/layout/painting/nsDisplayListInvalidation.h b/layout/painting/nsDisplayListInvalidation.h new file mode 100644 index 0000000000..3e07713301 --- /dev/null +++ b/layout/painting/nsDisplayListInvalidation.h @@ -0,0 +1,236 @@ +/* -*- 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/. */ + +#ifndef NSDISPLAYLISTINVALIDATION_H_ +#define NSDISPLAYLISTINVALIDATION_H_ + +#include "mozilla/Attributes.h" +#include "mozilla/layers/WebRenderUserData.h" +#include "ImgDrawResult.h" +#include "nsRect.h" +#include "nsColor.h" +#include "gfxRect.h" +#include "mozilla/gfx/MatrixFwd.h" + +namespace mozilla { +class nsDisplayBackgroundImage; +class nsCharClipDisplayItem; +class nsDisplayItem; +class nsDisplayListBuilder; +class nsDisplayTableItem; +class nsDisplayThemedBackground; +class nsDisplayEffectsBase; +class nsDisplayMasksAndClipPaths; +class nsDisplayFilters; + +namespace gfx { +struct sRGBColor; +} + +/** + * This stores the geometry of an nsDisplayItem, and the area + * that will be affected when painting the item. + * + * It is used to retain information about display items so they + * can be compared against new display items in the next paint. + */ +class nsDisplayItemGeometry { + public: + nsDisplayItemGeometry(nsDisplayItem* aItem, nsDisplayListBuilder* aBuilder); + virtual ~nsDisplayItemGeometry(); + + /** + * Compute the area required to be invalidated if this + * display item is removed. + */ + const nsRect& ComputeInvalidationRegion() { return mBounds; } + + /** + * Shifts all retained areas of the nsDisplayItemGeometry by the given offset. + * + * This is used to compensate for scrolling, since the destination buffer + * can scroll without requiring a full repaint. + * + * @param aOffset Offset to shift by. + */ + virtual void MoveBy(const nsPoint& aOffset) { mBounds.MoveBy(aOffset); } + + /** + * Bounds of the display item + */ + nsRect mBounds; +}; + +/** + * A default geometry implementation, used by nsDisplayItem. Retains + * and compares the bounds, and border rect. + * + * This should be sufficient for the majority of display items. + */ +class nsDisplayItemGenericGeometry : public nsDisplayItemGeometry { + public: + nsDisplayItemGenericGeometry(nsDisplayItem* aItem, + nsDisplayListBuilder* aBuilder); + + void MoveBy(const nsPoint& aOffset) override; + + nsRect mBorderRect; +}; + +bool ShouldSyncDecodeImages(nsDisplayListBuilder* aBuilder); + +nsDisplayItemGeometry* GetPreviousGeometry(nsDisplayItem*); + +class nsDisplayItemBoundsGeometry : public nsDisplayItemGeometry { + public: + nsDisplayItemBoundsGeometry(nsDisplayItem* aItem, + nsDisplayListBuilder* aBuilder); + + bool mHasRoundedCorners; +}; + +class nsDisplayBorderGeometry : public nsDisplayItemGeometry { + public: + nsDisplayBorderGeometry(nsDisplayItem* aItem, nsDisplayListBuilder* aBuilder); +}; + +class nsDisplayBackgroundGeometry : public nsDisplayItemGeometry { + public: + nsDisplayBackgroundGeometry(nsDisplayBackgroundImage* aItem, + nsDisplayListBuilder* aBuilder); + + void MoveBy(const nsPoint& aOffset) override; + + nsRect mPositioningArea; + nsRect mDestRect; +}; + +class nsDisplayThemedBackgroundGeometry : public nsDisplayItemGeometry { + public: + nsDisplayThemedBackgroundGeometry(nsDisplayThemedBackground* aItem, + nsDisplayListBuilder* aBuilder); + + void MoveBy(const nsPoint& aOffset) override; + + nsRect mPositioningArea; + bool mWindowIsActive; +}; + +class nsDisplayTreeBodyGeometry : public nsDisplayItemGenericGeometry { + public: + nsDisplayTreeBodyGeometry(nsDisplayItem* aItem, + nsDisplayListBuilder* aBuilder, + bool aWindowIsActive) + : nsDisplayItemGenericGeometry(aItem, aBuilder), + mWindowIsActive(aWindowIsActive) {} + + bool mWindowIsActive = false; +}; + +class nsDisplayBoxShadowInnerGeometry : public nsDisplayItemGeometry { + public: + nsDisplayBoxShadowInnerGeometry(nsDisplayItem* aItem, + nsDisplayListBuilder* aBuilder); + + void MoveBy(const nsPoint& aOffset) override; + + nsRect mPaddingRect; +}; + +class nsDisplaySolidColorGeometry : public nsDisplayItemBoundsGeometry { + public: + nsDisplaySolidColorGeometry(nsDisplayItem* aItem, + nsDisplayListBuilder* aBuilder, nscolor aColor) + : nsDisplayItemBoundsGeometry(aItem, aBuilder), mColor(aColor) {} + + nscolor mColor; +}; + +class nsDisplaySolidColorRegionGeometry : public nsDisplayItemBoundsGeometry { + public: + nsDisplaySolidColorRegionGeometry(nsDisplayItem* aItem, + nsDisplayListBuilder* aBuilder, + const nsRegion& aRegion, + mozilla::gfx::sRGBColor aColor) + : nsDisplayItemBoundsGeometry(aItem, aBuilder), + mRegion(aRegion), + mColor(aColor) {} + + void MoveBy(const nsPoint& aOffset) override; + + nsRegion mRegion; + mozilla::gfx::sRGBColor mColor; +}; + +class nsDisplaySVGEffectGeometry : public nsDisplayItemGeometry { + public: + nsDisplaySVGEffectGeometry(nsDisplayEffectsBase* aItem, + nsDisplayListBuilder* aBuilder); + + void MoveBy(const nsPoint& aOffset) override; + + gfxRect mBBox; + gfxPoint mUserSpaceOffset; + nsPoint mFrameOffsetToReferenceFrame; + bool mHandleOpacity; +}; + +class nsDisplayMasksAndClipPathsGeometry : public nsDisplaySVGEffectGeometry { + public: + nsDisplayMasksAndClipPathsGeometry(nsDisplayMasksAndClipPaths* aItem, + nsDisplayListBuilder* aBuilder); + + nsTArray<nsRect> mDestRects; +}; + +class nsDisplayFiltersGeometry : public nsDisplaySVGEffectGeometry { + public: + nsDisplayFiltersGeometry(nsDisplayFilters* aItem, + nsDisplayListBuilder* aBuilder); +}; + +class nsDisplayTableItemGeometry : public nsDisplayItemGenericGeometry { + public: + nsDisplayTableItemGeometry(nsDisplayTableItem* aItem, + nsDisplayListBuilder* aBuilder, + const nsPoint& aFrameOffsetToViewport); + + nsPoint mFrameOffsetToViewport; +}; + +class nsDisplayOpacityGeometry : public nsDisplayItemGenericGeometry { + public: + nsDisplayOpacityGeometry(nsDisplayItem* aItem, nsDisplayListBuilder* aBuilder, + float aOpacity) + : nsDisplayItemGenericGeometry(aItem, aBuilder), mOpacity(aOpacity) {} + + float mOpacity; +}; + +class nsDisplayTransformGeometry : public nsDisplayItemGeometry { + public: + nsDisplayTransformGeometry(nsDisplayItem* aItem, + nsDisplayListBuilder* aBuilder, + const mozilla::gfx::Matrix4x4Flagged& aTransform, + int32_t aAppUnitsPerDevPixel) + : nsDisplayItemGeometry(aItem, aBuilder), + mTransform(aTransform), + mAppUnitsPerDevPixel(aAppUnitsPerDevPixel) {} + + void MoveBy(const nsPoint& aOffset) override { + nsDisplayItemGeometry::MoveBy(aOffset); + mTransform.PostTranslate( + NSAppUnitsToFloatPixels(aOffset.x, mAppUnitsPerDevPixel), + NSAppUnitsToFloatPixels(aOffset.y, mAppUnitsPerDevPixel), 0.0f); + } + + mozilla::gfx::Matrix4x4Flagged mTransform; + int32_t mAppUnitsPerDevPixel; +}; + +} // namespace mozilla + +#endif /*NSDISPLAYLISTINVALIDATION_H_*/ diff --git a/layout/painting/nsImageRenderer.cpp b/layout/painting/nsImageRenderer.cpp new file mode 100644 index 0000000000..d2fa024d4d --- /dev/null +++ b/layout/painting/nsImageRenderer.cpp @@ -0,0 +1,1054 @@ +/* -*- 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/. */ + +/* utility code for drawing images as CSS borders, backgrounds, and shapes. */ + +#include "nsImageRenderer.h" + +#include "mozilla/webrender/WebRenderAPI.h" + +#include "gfxContext.h" +#include "gfxDrawable.h" +#include "ImageOps.h" +#include "ImageRegion.h" +#include "mozilla/image/WebRenderImageProvider.h" +#include "mozilla/layers/RenderRootStateManager.h" +#include "mozilla/layers/StackingContextHelper.h" +#include "mozilla/layers/WebRenderLayerManager.h" +#include "nsContentUtils.h" +#include "nsCSSRendering.h" +#include "nsCSSRenderingGradients.h" +#include "nsDeviceContext.h" +#include "nsIFrame.h" +#include "nsLayoutUtils.h" +#include "nsStyleStructInlines.h" +#include "mozilla/StaticPrefs_image.h" +#include "mozilla/ISVGDisplayableFrame.h" +#include "mozilla/SVGIntegrationUtils.h" +#include "mozilla/SVGPaintServerFrame.h" +#include "mozilla/SVGObserverUtils.h" + +using namespace mozilla; +using namespace mozilla::gfx; +using namespace mozilla::image; +using namespace mozilla::layers; + +nsSize CSSSizeOrRatio::ComputeConcreteSize() const { + NS_ASSERTION(CanComputeConcreteSize(), "Cannot compute"); + if (mHasWidth && mHasHeight) { + return nsSize(mWidth, mHeight); + } + if (mHasWidth) { + return nsSize(mWidth, mRatio.Inverted().ApplyTo(mWidth)); + } + + MOZ_ASSERT(mHasHeight); + return nsSize(mRatio.ApplyTo(mHeight), mHeight); +} + +nsImageRenderer::nsImageRenderer(nsIFrame* aForFrame, const StyleImage* aImage, + uint32_t aFlags) + : mForFrame(aForFrame), + mImage(&aImage->FinalImage()), + mImageResolution(aImage->GetResolution()), + mType(mImage->tag), + mImageContainer(nullptr), + mGradientData(nullptr), + mPaintServerFrame(nullptr), + mPrepareResult(ImgDrawResult::NOT_READY), + mSize(0, 0), + mFlags(aFlags), + mExtendMode(ExtendMode::CLAMP), + mMaskOp(StyleMaskMode::MatchSource) {} + +bool nsImageRenderer::PrepareImage() { + if (mImage->IsNone()) { + mPrepareResult = ImgDrawResult::BAD_IMAGE; + return false; + } + + const bool isImageRequest = mImage->IsImageRequestType(); + MOZ_ASSERT_IF(!isImageRequest, !mImage->GetImageRequest()); + imgRequestProxy* request = nullptr; + if (isImageRequest) { + request = mImage->GetImageRequest(); + if (!request) { + // request could be null here if the StyleImage refused + // to load a same-document URL, or the url was invalid, for example. + mPrepareResult = ImgDrawResult::BAD_IMAGE; + return false; + } + } + + if (!mImage->IsComplete()) { + MOZ_DIAGNOSTIC_ASSERT(isImageRequest); + + // Make sure the image is actually decoding. + bool frameComplete = request->StartDecodingWithResult( + imgIContainer::FLAG_ASYNC_NOTIFY | + imgIContainer::FLAG_AVOID_REDECODE_FOR_SIZE); + + // Boost the loading priority since we know we want to draw the image. + if (mFlags & nsImageRenderer::FLAG_PAINTING_TO_WINDOW) { + request->BoostPriority(imgIRequest::CATEGORY_DISPLAY); + } + + // Check again to see if we finished. + // We cannot prepare the image for rendering if it is not fully loaded. + if (!frameComplete && !mImage->IsComplete()) { + uint32_t imageStatus = 0; + request->GetImageStatus(&imageStatus); + if (imageStatus & imgIRequest::STATUS_ERROR) { + mPrepareResult = ImgDrawResult::BAD_IMAGE; + return false; + } + + // If not errored, and we requested a sync decode, and the image has + // loaded, push on through because the Draw() will do a sync decode then. + const bool syncDecodeWillComplete = + (mFlags & FLAG_SYNC_DECODE_IMAGES) && + (imageStatus & imgIRequest::STATUS_LOAD_COMPLETE); + + bool canDrawPartial = + (mFlags & nsImageRenderer::FLAG_DRAW_PARTIAL_FRAMES) && + isImageRequest && mImage->IsSizeAvailable() && !mImage->IsRect(); + + // If we are drawing a partial frame then we want to make sure there are + // some pixels to draw, otherwise we waste effort pushing through to draw + // nothing. + if (!syncDecodeWillComplete && canDrawPartial) { + nsCOMPtr<imgIContainer> image; + canDrawPartial = + canDrawPartial && + NS_SUCCEEDED(request->GetImage(getter_AddRefs(image))) && image && + image->GetType() == imgIContainer::TYPE_RASTER && + image->HasDecodedPixels(); + } + + // If we can draw partial then proceed if we at least have the size + // available. + if (!(syncDecodeWillComplete || canDrawPartial)) { + mPrepareResult = ImgDrawResult::NOT_READY; + return false; + } + } + } + + if (isImageRequest) { + nsCOMPtr<imgIContainer> srcImage; + nsresult rv = request->GetImage(getter_AddRefs(srcImage)); + MOZ_ASSERT(NS_SUCCEEDED(rv) && srcImage, + "If GetImage() is failing, mImage->IsComplete() " + "should have returned false"); + if (!NS_SUCCEEDED(rv)) { + srcImage = nullptr; + } + + if (srcImage) { + StyleImageOrientation orientation = + mForFrame->StyleVisibility()->UsedImageOrientation(request); + srcImage = nsLayoutUtils::OrientImage(srcImage, orientation); + } + + if (!mImage->IsRect()) { + mImageContainer.swap(srcImage); + } else { + auto croprect = mImage->ComputeActualCropRect(); + if (!croprect || croprect->mRect.IsEmpty()) { + // The cropped image has zero size + mPrepareResult = ImgDrawResult::BAD_IMAGE; + return false; + } + if (croprect->mIsEntireImage) { + // The cropped image is identical to the source image + mImageContainer.swap(srcImage); + } else { + nsCOMPtr<imgIContainer> subImage = + ImageOps::Clip(srcImage, croprect->mRect, Nothing()); + mImageContainer.swap(subImage); + } + } + mPrepareResult = ImgDrawResult::SUCCESS; + } else if (mImage->IsGradient()) { + mGradientData = &*mImage->AsGradient(); + mPrepareResult = ImgDrawResult::SUCCESS; + } else if (mImage->IsElement()) { + dom::Element* paintElement = // may be null + SVGObserverUtils::GetAndObserveBackgroundImage( + mForFrame->FirstContinuation(), mImage->AsElement().AsAtom()); + // If the referenced element is an <img>, <canvas>, or <video> element, + // prefer SurfaceFromElement as it's more reliable. + mImageElementSurface = nsLayoutUtils::SurfaceFromElement(paintElement); + + if (!mImageElementSurface.GetSourceSurface()) { + nsIFrame* paintServerFrame = + paintElement ? paintElement->GetPrimaryFrame() : nullptr; + // If there's no referenced frame, or the referenced frame is + // non-displayable SVG, then we have nothing valid to paint. + if (!paintServerFrame || + (paintServerFrame->IsFrameOfType(nsIFrame::eSVG) && + !static_cast<SVGPaintServerFrame*>( + do_QueryFrame(paintServerFrame)) && + !static_cast<ISVGDisplayableFrame*>( + do_QueryFrame(paintServerFrame)))) { + mPrepareResult = ImgDrawResult::BAD_IMAGE; + return false; + } + mPaintServerFrame = paintServerFrame; + } + + mPrepareResult = ImgDrawResult::SUCCESS; + } else if (mImage->IsCrossFade()) { + // See bug 546052 - cross-fade implementation still being worked + // on. + mPrepareResult = ImgDrawResult::BAD_IMAGE; + return false; + } else { + MOZ_ASSERT(mImage->IsNone(), "Unknown image type?"); + } + + return IsReady(); +} + +CSSSizeOrRatio nsImageRenderer::ComputeIntrinsicSize() { + NS_ASSERTION(IsReady(), + "Ensure PrepareImage() has returned true " + "before calling me"); + + CSSSizeOrRatio result; + switch (mType) { + case StyleImage::Tag::Rect: + case StyleImage::Tag::Url: { + bool haveWidth, haveHeight; + CSSIntSize imageIntSize; + nsLayoutUtils::ComputeSizeForDrawing(mImageContainer, mImageResolution, + imageIntSize, result.mRatio, + haveWidth, haveHeight); + if (haveWidth) { + result.SetWidth(CSSPixel::ToAppUnits(imageIntSize.width)); + } + if (haveHeight) { + result.SetHeight(CSSPixel::ToAppUnits(imageIntSize.height)); + } + + // If we know the aspect ratio and one of the dimensions, + // we can compute the other missing width or height. + if (!haveHeight && haveWidth && result.mRatio) { + nscoord intrinsicHeight = + result.mRatio.Inverted().ApplyTo(imageIntSize.width); + result.SetHeight(nsPresContext::CSSPixelsToAppUnits(intrinsicHeight)); + } else if (haveHeight && !haveWidth && result.mRatio) { + nscoord intrinsicWidth = result.mRatio.ApplyTo(imageIntSize.height); + result.SetWidth(nsPresContext::CSSPixelsToAppUnits(intrinsicWidth)); + } + + break; + } + case StyleImage::Tag::Element: { + // XXX element() should have the width/height of the referenced element, + // and that element's ratio, if it matches. If it doesn't match, it + // should have no width/height or ratio. See element() in CSS images: + // <http://dev.w3.org/csswg/css-images-4/#element-notation>. + // Make sure to change nsStyleImageLayers::Size::DependsOnFrameSize + // when fixing this! + if (mPaintServerFrame) { + // SVG images have no intrinsic size + if (!mPaintServerFrame->IsFrameOfType(nsIFrame::eSVG)) { + // The intrinsic image size for a generic nsIFrame paint server is + // the union of the border-box rects of all of its continuations, + // rounded to device pixels. + int32_t appUnitsPerDevPixel = + mForFrame->PresContext()->AppUnitsPerDevPixel(); + result.SetSize(IntSizeToAppUnits( + SVGIntegrationUtils::GetContinuationUnionSize(mPaintServerFrame) + .ToNearestPixels(appUnitsPerDevPixel), + appUnitsPerDevPixel)); + } + } else { + NS_ASSERTION(mImageElementSurface.GetSourceSurface(), + "Surface should be ready."); + IntSize surfaceSize = mImageElementSurface.mSize; + result.SetSize( + nsSize(nsPresContext::CSSPixelsToAppUnits(surfaceSize.width), + nsPresContext::CSSPixelsToAppUnits(surfaceSize.height))); + } + break; + } + case StyleImage::Tag::ImageSet: + MOZ_FALLTHROUGH_ASSERT("image-set should be resolved already"); + // Bug 546052 cross-fade not yet implemented. + case StyleImage::Tag::CrossFade: + // Per <http://dev.w3.org/csswg/css3-images/#gradients>, gradients have no + // intrinsic dimensions. + case StyleImage::Tag::Gradient: + case StyleImage::Tag::None: + break; + } + + return result; +} + +/* static */ +nsSize nsImageRenderer::ComputeConcreteSize( + const CSSSizeOrRatio& aSpecifiedSize, const CSSSizeOrRatio& aIntrinsicSize, + const nsSize& aDefaultSize) { + // The specified size is fully specified, just use that + if (aSpecifiedSize.IsConcrete()) { + return aSpecifiedSize.ComputeConcreteSize(); + } + + MOZ_ASSERT(!aSpecifiedSize.mHasWidth || !aSpecifiedSize.mHasHeight); + + if (!aSpecifiedSize.mHasWidth && !aSpecifiedSize.mHasHeight) { + // no specified size, try using the intrinsic size + if (aIntrinsicSize.CanComputeConcreteSize()) { + return aIntrinsicSize.ComputeConcreteSize(); + } + + if (aIntrinsicSize.mHasWidth) { + return nsSize(aIntrinsicSize.mWidth, aDefaultSize.height); + } + if (aIntrinsicSize.mHasHeight) { + return nsSize(aDefaultSize.width, aIntrinsicSize.mHeight); + } + + // couldn't use the intrinsic size either, revert to using the default size + return ComputeConstrainedSize(aDefaultSize, aIntrinsicSize.mRatio, CONTAIN); + } + + MOZ_ASSERT(aSpecifiedSize.mHasWidth || aSpecifiedSize.mHasHeight); + + // The specified height is partial, try to compute the missing part. + if (aSpecifiedSize.mHasWidth) { + nscoord height; + if (aIntrinsicSize.HasRatio()) { + height = aIntrinsicSize.mRatio.Inverted().ApplyTo(aSpecifiedSize.mWidth); + } else if (aIntrinsicSize.mHasHeight) { + height = aIntrinsicSize.mHeight; + } else { + height = aDefaultSize.height; + } + return nsSize(aSpecifiedSize.mWidth, height); + } + + MOZ_ASSERT(aSpecifiedSize.mHasHeight); + nscoord width; + if (aIntrinsicSize.HasRatio()) { + width = aIntrinsicSize.mRatio.ApplyTo(aSpecifiedSize.mHeight); + } else if (aIntrinsicSize.mHasWidth) { + width = aIntrinsicSize.mWidth; + } else { + width = aDefaultSize.width; + } + return nsSize(width, aSpecifiedSize.mHeight); +} + +/* static */ +nsSize nsImageRenderer::ComputeConstrainedSize( + const nsSize& aConstrainingSize, const AspectRatio& aIntrinsicRatio, + FitType aFitType) { + if (!aIntrinsicRatio) { + return aConstrainingSize; + } + + // Suppose we're doing a "contain" fit. If the image's aspect ratio has a + // "fatter" shape than the constraint area, then we need to use the + // constraint area's full width, and we need to use the aspect ratio to + // produce a height. On the other hand, if the aspect ratio is "skinnier", we + // use the constraint area's full height, and we use the aspect ratio to + // produce a width. (If instead we're doing a "cover" fit, then it can easily + // be seen that we should do precisely the opposite.) + // + // We check if the image's aspect ratio is "fatter" than the constraint area + // by simply applying the aspect ratio to the constraint area's height, to + // produce a "hypothetical width", and we check whether that + // aspect-ratio-provided "hypothetical width" is wider than the constraint + // area's actual width. If it is, then the aspect ratio is fatter than the + // constraint area. + // + // This is equivalent to the more descriptive alternative: + // + // AspectRatio::FromSize(aConstrainingSize) < aIntrinsicRatio + // + // But gracefully handling the case where one of the two dimensions from + // aConstrainingSize is zero. This is easy to prove since: + // + // aConstrainingSize.width / aConstrainingSize.height < aIntrinsicRatio + // + // Is trivially equivalent to: + // + // aIntrinsicRatio.width < aIntrinsicRatio * aConstrainingSize.height + // + // For the cases where height is not zero. + // + // We use float math here to avoid losing precision for very large backgrounds + // since we use saturating nscoord math otherwise. + const float constraintWidth = float(aConstrainingSize.width); + const float hypotheticalWidth = + aIntrinsicRatio.ApplyToFloat(aConstrainingSize.height); + + nsSize size; + if ((aFitType == CONTAIN) == (constraintWidth < hypotheticalWidth)) { + size.width = aConstrainingSize.width; + size.height = aIntrinsicRatio.Inverted().ApplyTo(aConstrainingSize.width); + // If we're reducing the size by less than one css pixel, then just use the + // constraining size. + if (aFitType == CONTAIN && + aConstrainingSize.height - size.height < AppUnitsPerCSSPixel()) { + size.height = aConstrainingSize.height; + } + } else { + size.height = aConstrainingSize.height; + size.width = aIntrinsicRatio.ApplyTo(aConstrainingSize.height); + if (aFitType == CONTAIN && + aConstrainingSize.width - size.width < AppUnitsPerCSSPixel()) { + size.width = aConstrainingSize.width; + } + } + return size; +} + +/** + * mSize is the image's "preferred" size for this particular rendering, while + * the drawn (aka concrete) size is the actual rendered size after accounting + * for background-size etc.. The preferred size is most often the image's + * intrinsic dimensions. But for images with incomplete intrinsic dimensions, + * the preferred size varies, depending on the specified and default sizes, see + * nsImageRenderer::Compute*Size. + * + * This distinction is necessary because the components of a vector image are + * specified with respect to its preferred size for a rendering situation, not + * to its actual rendered size. For example, consider a 4px wide background + * vector image with no height which contains a left-aligned + * 2px wide black rectangle with height 100%. If the background-size width is + * auto (or 4px), the vector image will render 4px wide, and the black rectangle + * will be 2px wide. If the background-size width is 8px, the vector image will + * render 8px wide, and the black rectangle will be 4px wide -- *not* 2px wide. + * In both cases mSize.width will be 4px; but in the first case the returned + * width will be 4px, while in the second case the returned width will be 8px. + */ +void nsImageRenderer::SetPreferredSize(const CSSSizeOrRatio& aIntrinsicSize, + const nsSize& aDefaultSize) { + mSize.width = + aIntrinsicSize.mHasWidth ? aIntrinsicSize.mWidth : aDefaultSize.width; + mSize.height = + aIntrinsicSize.mHasHeight ? aIntrinsicSize.mHeight : aDefaultSize.height; +} + +// Convert from nsImageRenderer flags to the flags we want to use for drawing in +// the imgIContainer namespace. +static uint32_t ConvertImageRendererToDrawFlags(uint32_t aImageRendererFlags) { + uint32_t drawFlags = imgIContainer::FLAG_ASYNC_NOTIFY; + if (aImageRendererFlags & nsImageRenderer::FLAG_SYNC_DECODE_IMAGES) { + drawFlags |= imgIContainer::FLAG_SYNC_DECODE; + } else { + drawFlags |= imgIContainer::FLAG_SYNC_DECODE_IF_FAST; + } + if (aImageRendererFlags & (nsImageRenderer::FLAG_PAINTING_TO_WINDOW | + nsImageRenderer::FLAG_HIGH_QUALITY_SCALING)) { + drawFlags |= imgIContainer::FLAG_HIGH_QUALITY_SCALING; + } + return drawFlags; +} + +ImgDrawResult nsImageRenderer::Draw(nsPresContext* aPresContext, + gfxContext& aRenderingContext, + const nsRect& aDirtyRect, + const nsRect& aDest, const nsRect& aFill, + const nsPoint& aAnchor, + const nsSize& aRepeatSize, + const CSSIntRect& aSrc, float aOpacity) { + if (!IsReady()) { + MOZ_ASSERT_UNREACHABLE( + "Ensure PrepareImage() has returned true before " + "calling me"); + return ImgDrawResult::TEMPORARY_ERROR; + } + + if (aDest.IsEmpty() || aFill.IsEmpty() || mSize.width <= 0 || + mSize.height <= 0) { + return ImgDrawResult::SUCCESS; + } + + SamplingFilter samplingFilter = + nsLayoutUtils::GetSamplingFilterForFrame(mForFrame); + ImgDrawResult result = ImgDrawResult::SUCCESS; + gfxContext* ctx = &aRenderingContext; + Maybe<gfxContext> tempCtx; + IntRect tmpDTRect; + + if (ctx->CurrentOp() != CompositionOp::OP_OVER || + mMaskOp == StyleMaskMode::Luminance) { + gfxRect clipRect = ctx->GetClipExtents(gfxContext::eDeviceSpace); + tmpDTRect = RoundedOut(ToRect(clipRect)); + if (tmpDTRect.IsEmpty()) { + return ImgDrawResult::SUCCESS; + } + RefPtr<DrawTarget> tempDT = ctx->GetDrawTarget()->CreateSimilarDrawTarget( + tmpDTRect.Size(), SurfaceFormat::B8G8R8A8); + if (!tempDT || !tempDT->IsValid()) { + gfxDevCrash(LogReason::InvalidContext) + << "ImageRenderer::Draw problem " << gfx::hexa(tempDT); + return ImgDrawResult::TEMPORARY_ERROR; + } + tempDT->SetTransform(ctx->GetDrawTarget()->GetTransform() * + Matrix::Translation(-tmpDTRect.TopLeft())); + tempCtx.emplace(tempDT, /* aPreserveTransform */ true); + ctx = &tempCtx.ref(); + if (!ctx) { + gfxDevCrash(LogReason::InvalidContext) + << "ImageRenderer::Draw problem " << gfx::hexa(tempDT); + return ImgDrawResult::TEMPORARY_ERROR; + } + } + + switch (mType) { + case StyleImage::Tag::Rect: + case StyleImage::Tag::Url: { + result = nsLayoutUtils::DrawBackgroundImage( + *ctx, mForFrame, aPresContext, mImageContainer, samplingFilter, aDest, + aFill, aRepeatSize, aAnchor, aDirtyRect, + ConvertImageRendererToDrawFlags(mFlags), mExtendMode, aOpacity); + break; + } + case StyleImage::Tag::Gradient: { + nsCSSGradientRenderer renderer = nsCSSGradientRenderer::Create( + aPresContext, mForFrame->Style(), *mGradientData, mSize); + + renderer.Paint(*ctx, aDest, aFill, aRepeatSize, aSrc, aDirtyRect, + aOpacity); + break; + } + case StyleImage::Tag::Element: { + RefPtr<gfxDrawable> drawable = DrawableForElement(aDest, *ctx); + if (!drawable) { + NS_WARNING("Could not create drawable for element"); + return ImgDrawResult::TEMPORARY_ERROR; + } + + nsCOMPtr<imgIContainer> image(ImageOps::CreateFromDrawable(drawable)); + result = nsLayoutUtils::DrawImage( + *ctx, mForFrame->Style(), aPresContext, image, samplingFilter, aDest, + aFill, aAnchor, aDirtyRect, ConvertImageRendererToDrawFlags(mFlags), + aOpacity); + break; + } + case StyleImage::Tag::ImageSet: + MOZ_FALLTHROUGH_ASSERT("image-set should be resolved already"); + // See bug 546052 - cross-fade implementation still being worked + // on. + case StyleImage::Tag::CrossFade: + case StyleImage::Tag::None: + break; + } + + if (!tmpDTRect.IsEmpty()) { + DrawTarget* dt = aRenderingContext.GetDrawTarget(); + Matrix oldTransform = dt->GetTransform(); + dt->SetTransform(Matrix()); + if (mMaskOp == StyleMaskMode::Luminance) { + RefPtr<SourceSurface> surf = ctx->GetDrawTarget()->IntoLuminanceSource( + LuminanceType::LUMINANCE, 1.0f); + dt->MaskSurface(ColorPattern(DeviceColor(0, 0, 0, 1.0f)), surf, + tmpDTRect.TopLeft(), + DrawOptions(1.0f, aRenderingContext.CurrentOp())); + } else { + RefPtr<SourceSurface> surf = ctx->GetDrawTarget()->Snapshot(); + dt->DrawSurface( + surf, + Rect(tmpDTRect.x, tmpDTRect.y, tmpDTRect.width, tmpDTRect.height), + Rect(0, 0, tmpDTRect.width, tmpDTRect.height), + DrawSurfaceOptions(SamplingFilter::POINT), + DrawOptions(1.0f, aRenderingContext.CurrentOp())); + } + + dt->SetTransform(oldTransform); + } + + if (!mImage->IsComplete()) { + result &= ImgDrawResult::SUCCESS_NOT_COMPLETE; + } + + return result; +} + +ImgDrawResult nsImageRenderer::BuildWebRenderDisplayItems( + nsPresContext* aPresContext, mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const mozilla::layers::StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, nsDisplayItem* aItem, + const nsRect& aDirtyRect, const nsRect& aDest, const nsRect& aFill, + const nsPoint& aAnchor, const nsSize& aRepeatSize, const CSSIntRect& aSrc, + float aOpacity) { + if (!IsReady()) { + MOZ_ASSERT_UNREACHABLE( + "Ensure PrepareImage() has returned true before " + "calling me"); + return ImgDrawResult::NOT_READY; + } + + if (aDest.IsEmpty() || aFill.IsEmpty() || mSize.width <= 0 || + mSize.height <= 0) { + return ImgDrawResult::SUCCESS; + } + + ImgDrawResult drawResult = ImgDrawResult::SUCCESS; + switch (mType) { + case StyleImage::Tag::Gradient: { + nsCSSGradientRenderer renderer = nsCSSGradientRenderer::Create( + aPresContext, mForFrame->Style(), *mGradientData, mSize); + + renderer.BuildWebRenderDisplayItems(aBuilder, aSc, aDest, aFill, + aRepeatSize, aSrc, + !aItem->BackfaceIsHidden(), aOpacity); + break; + } + case StyleImage::Tag::Rect: + case StyleImage::Tag::Url: { + ExtendMode extendMode = mExtendMode; + if (aDest.Contains(aFill)) { + extendMode = ExtendMode::CLAMP; + } + + uint32_t containerFlags = ConvertImageRendererToDrawFlags(mFlags); + if (extendMode == ExtendMode::CLAMP && + StaticPrefs::image_svg_blob_image() && + mImageContainer->GetType() == imgIContainer::TYPE_VECTOR) { + containerFlags |= imgIContainer::FLAG_RECORD_BLOB; + } + + CSSIntSize destCSSSize{ + nsPresContext::AppUnitsToIntCSSPixels(aDest.width), + nsPresContext::AppUnitsToIntCSSPixels(aDest.height)}; + + SVGImageContext svgContext(Some(destCSSSize)); + Maybe<ImageIntRegion> region; + + const int32_t appUnitsPerDevPixel = + mForFrame->PresContext()->AppUnitsPerDevPixel(); + LayoutDeviceRect destRect = + LayoutDeviceRect::FromAppUnits(aDest, appUnitsPerDevPixel); + LayoutDeviceRect clipRect = + LayoutDeviceRect::FromAppUnits(aFill, appUnitsPerDevPixel); + auto stretchSize = wr::ToLayoutSize(destRect.Size()); + + gfx::IntSize decodeSize = + nsLayoutUtils::ComputeImageContainerDrawingParameters( + mImageContainer, mForFrame, destRect, clipRect, aSc, + containerFlags, svgContext, region); + + RefPtr<image::WebRenderImageProvider> provider; + drawResult = mImageContainer->GetImageProvider( + aManager->LayerManager(), decodeSize, svgContext, region, + containerFlags, getter_AddRefs(provider)); + + Maybe<wr::ImageKey> key = + aManager->CommandBuilder().CreateImageProviderKey( + aItem, provider, drawResult, aResources); + if (key.isNothing()) { + break; + } + + auto rendering = + wr::ToImageRendering(aItem->Frame()->UsedImageRendering()); + wr::LayoutRect clip = wr::ToLayoutRect(clipRect); + + // If we provided a region to the provider, then it already took the + // dest rect into account when it did the recording. + wr::LayoutRect dest = region ? clip : wr::ToLayoutRect(destRect); + + if (extendMode == ExtendMode::CLAMP) { + // The image is not repeating. Just push as a regular image. + aBuilder.PushImage(dest, clip, !aItem->BackfaceIsHidden(), false, + rendering, key.value(), true, + wr::ColorF{1.0f, 1.0f, 1.0f, aOpacity}); + } else { + nsPoint firstTilePos = nsLayoutUtils::GetBackgroundFirstTilePos( + aDest.TopLeft(), aFill.TopLeft(), aRepeatSize); + LayoutDeviceRect fillRect = LayoutDeviceRect::FromAppUnits( + nsRect(firstTilePos.x, firstTilePos.y, + aFill.XMost() - firstTilePos.x, + aFill.YMost() - firstTilePos.y), + appUnitsPerDevPixel); + wr::LayoutRect fill = wr::ToLayoutRect(fillRect); + + switch (extendMode) { + case ExtendMode::REPEAT_Y: + fill.min.x = dest.min.x; + fill.max.x = dest.max.x; + stretchSize.width = dest.width(); + break; + case ExtendMode::REPEAT_X: + fill.min.y = dest.min.y; + fill.max.y = dest.max.y; + stretchSize.height = dest.height(); + break; + default: + break; + } + + LayoutDeviceSize gapSize = LayoutDeviceSize::FromAppUnits( + aRepeatSize - aDest.Size(), appUnitsPerDevPixel); + + aBuilder.PushRepeatingImage(fill, clip, !aItem->BackfaceIsHidden(), + stretchSize, wr::ToLayoutSize(gapSize), + rendering, key.value(), true, + wr::ColorF{1.0f, 1.0f, 1.0f, aOpacity}); + } + break; + } + default: + break; + } + + if (!mImage->IsComplete() && drawResult == ImgDrawResult::SUCCESS) { + return ImgDrawResult::SUCCESS_NOT_COMPLETE; + } + return drawResult; +} + +already_AddRefed<gfxDrawable> nsImageRenderer::DrawableForElement( + const nsRect& aImageRect, gfxContext& aContext) { + NS_ASSERTION(mType == StyleImage::Tag::Element, + "DrawableForElement only makes sense if backed by an element"); + if (mPaintServerFrame) { + // XXX(seth): In order to not pass FLAG_SYNC_DECODE_IMAGES here, + // DrawableFromPaintServer would have to return a ImgDrawResult indicating + // whether any images could not be painted because they weren't fully + // decoded. Even always passing FLAG_SYNC_DECODE_IMAGES won't eliminate all + // problems, as it won't help if there are image which haven't finished + // loading, but it's better than nothing. + int32_t appUnitsPerDevPixel = + mForFrame->PresContext()->AppUnitsPerDevPixel(); + nsRect destRect = aImageRect - aImageRect.TopLeft(); + nsIntSize roundedOut = destRect.ToOutsidePixels(appUnitsPerDevPixel).Size(); + IntSize imageSize(roundedOut.width, roundedOut.height); + + RefPtr<gfxDrawable> drawable; + + SurfaceFormat format = aContext.GetDrawTarget()->GetFormat(); + // Don't allow creating images that are too big + if (aContext.GetDrawTarget()->CanCreateSimilarDrawTarget(imageSize, + format)) { + drawable = SVGIntegrationUtils::DrawableFromPaintServer( + mPaintServerFrame, mForFrame, mSize, imageSize, + aContext.GetDrawTarget(), aContext.CurrentMatrixDouble(), + SVGIntegrationUtils::FLAG_SYNC_DECODE_IMAGES); + } + + return drawable.forget(); + } + NS_ASSERTION(mImageElementSurface.GetSourceSurface(), + "Surface should be ready."); + RefPtr<gfxDrawable> drawable = + new gfxSurfaceDrawable(mImageElementSurface.GetSourceSurface().get(), + mImageElementSurface.mSize); + return drawable.forget(); +} + +ImgDrawResult nsImageRenderer::DrawLayer( + nsPresContext* aPresContext, gfxContext& aRenderingContext, + const nsRect& aDest, const nsRect& aFill, const nsPoint& aAnchor, + const nsRect& aDirty, const nsSize& aRepeatSize, float aOpacity) { + if (!IsReady()) { + MOZ_ASSERT_UNREACHABLE( + "Ensure PrepareImage() has returned true before " + "calling me"); + return ImgDrawResult::TEMPORARY_ERROR; + } + + if (aDest.IsEmpty() || aFill.IsEmpty() || mSize.width <= 0 || + mSize.height <= 0) { + return ImgDrawResult::SUCCESS; + } + + return Draw( + aPresContext, aRenderingContext, aDirty, aDest, aFill, aAnchor, + aRepeatSize, + CSSIntRect(0, 0, nsPresContext::AppUnitsToIntCSSPixels(mSize.width), + nsPresContext::AppUnitsToIntCSSPixels(mSize.height)), + aOpacity); +} + +ImgDrawResult nsImageRenderer::BuildWebRenderDisplayItemsForLayer( + nsPresContext* aPresContext, mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const mozilla::layers::StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, nsDisplayItem* aItem, + const nsRect& aDest, const nsRect& aFill, const nsPoint& aAnchor, + const nsRect& aDirty, const nsSize& aRepeatSize, float aOpacity) { + if (!IsReady()) { + MOZ_ASSERT_UNREACHABLE( + "Ensure PrepareImage() has returned true before " + "calling me"); + return mPrepareResult; + } + + CSSIntRect srcRect(0, 0, nsPresContext::AppUnitsToIntCSSPixels(mSize.width), + nsPresContext::AppUnitsToIntCSSPixels(mSize.height)); + + if (aDest.IsEmpty() || aFill.IsEmpty() || srcRect.IsEmpty()) { + return ImgDrawResult::SUCCESS; + } + return BuildWebRenderDisplayItems(aPresContext, aBuilder, aResources, aSc, + aManager, aItem, aDirty, aDest, aFill, + aAnchor, aRepeatSize, srcRect, aOpacity); +} + +/** + * Compute the size and position of the master copy of the image. I.e., a single + * tile used to fill the dest rect. + * aFill The destination rect to be filled + * aHFill and aVFill are the repeat patterns for the component - + * StyleBorderImageRepeat - i.e., how a tiling unit is used to fill aFill + * aUnitSize The size of the source rect in dest coords. + */ +static nsRect ComputeTile(nsRect& aFill, StyleBorderImageRepeat aHFill, + StyleBorderImageRepeat aVFill, + const nsSize& aUnitSize, nsSize& aRepeatSize) { + nsRect tile; + switch (aHFill) { + case StyleBorderImageRepeat::Stretch: + tile.x = aFill.x; + tile.width = aFill.width; + aRepeatSize.width = tile.width; + break; + case StyleBorderImageRepeat::Repeat: + tile.x = aFill.x + aFill.width / 2 - aUnitSize.width / 2; + tile.width = aUnitSize.width; + aRepeatSize.width = tile.width; + break; + case StyleBorderImageRepeat::Round: + tile.x = aFill.x; + tile.width = + nsCSSRendering::ComputeRoundedSize(aUnitSize.width, aFill.width); + aRepeatSize.width = tile.width; + break; + case StyleBorderImageRepeat::Space: { + nscoord space; + aRepeatSize.width = nsCSSRendering::ComputeBorderSpacedRepeatSize( + aUnitSize.width, aFill.width, space); + tile.x = aFill.x + space; + tile.width = aUnitSize.width; + aFill.x = tile.x; + aFill.width = aFill.width - space * 2; + } break; + default: + MOZ_ASSERT_UNREACHABLE("unrecognized border-image fill style"); + } + + switch (aVFill) { + case StyleBorderImageRepeat::Stretch: + tile.y = aFill.y; + tile.height = aFill.height; + aRepeatSize.height = tile.height; + break; + case StyleBorderImageRepeat::Repeat: + tile.y = aFill.y + aFill.height / 2 - aUnitSize.height / 2; + tile.height = aUnitSize.height; + aRepeatSize.height = tile.height; + break; + case StyleBorderImageRepeat::Round: + tile.y = aFill.y; + tile.height = + nsCSSRendering::ComputeRoundedSize(aUnitSize.height, aFill.height); + aRepeatSize.height = tile.height; + break; + case StyleBorderImageRepeat::Space: { + nscoord space; + aRepeatSize.height = nsCSSRendering::ComputeBorderSpacedRepeatSize( + aUnitSize.height, aFill.height, space); + tile.y = aFill.y + space; + tile.height = aUnitSize.height; + aFill.y = tile.y; + aFill.height = aFill.height - space * 2; + } break; + default: + MOZ_ASSERT_UNREACHABLE("unrecognized border-image fill style"); + } + + return tile; +} + +/** + * Returns true if the given set of arguments will require the tiles which fill + * the dest rect to be scaled from the source tile. See comment on ComputeTile + * for argument descriptions. + */ +static bool RequiresScaling(const nsRect& aFill, StyleBorderImageRepeat aHFill, + StyleBorderImageRepeat aVFill, + const nsSize& aUnitSize) { + // If we have no tiling in either direction, we can skip the intermediate + // scaling step. + return (aHFill != StyleBorderImageRepeat::Stretch || + aVFill != StyleBorderImageRepeat::Stretch) && + (aUnitSize.width != aFill.width || aUnitSize.height != aFill.height); +} + +ImgDrawResult nsImageRenderer::DrawBorderImageComponent( + nsPresContext* aPresContext, gfxContext& aRenderingContext, + const nsRect& aDirtyRect, const nsRect& aFill, const CSSIntRect& aSrc, + StyleBorderImageRepeat aHFill, StyleBorderImageRepeat aVFill, + const nsSize& aUnitSize, uint8_t aIndex, + const Maybe<nsSize>& aSVGViewportSize, const bool aHasIntrinsicRatio) { + if (!IsReady()) { + MOZ_ASSERT_UNREACHABLE( + "Ensure PrepareImage() has returned true before " + "calling me"); + return ImgDrawResult::BAD_ARGS; + } + + if (aFill.IsEmpty() || aSrc.IsEmpty()) { + return ImgDrawResult::SUCCESS; + } + + const bool isRequestBacked = + mType == StyleImage::Tag::Url || mType == StyleImage::Tag::Rect; + MOZ_ASSERT(isRequestBacked == mImage->IsImageRequestType()); + + if (isRequestBacked || mType == StyleImage::Tag::Element) { + nsCOMPtr<imgIContainer> subImage; + + // To draw one portion of an image into a border component, we stretch that + // portion to match the size of that border component and then draw onto. + // However, preserveAspectRatio attribute of a SVG image may break this + // rule. To get correct rendering result, we add + // FLAG_FORCE_PRESERVEASPECTRATIO_NONE flag here, to tell mImage to ignore + // preserveAspectRatio attribute, and always do non-uniform stretch. + uint32_t drawFlags = ConvertImageRendererToDrawFlags(mFlags) | + imgIContainer::FLAG_FORCE_PRESERVEASPECTRATIO_NONE; + // For those SVG image sources which don't have fixed aspect ratio (i.e. + // without viewport size and viewBox), we should scale the source uniformly + // after the viewport size is decided by "Default Sizing Algorithm". + if (!aHasIntrinsicRatio) { + drawFlags = drawFlags | imgIContainer::FLAG_FORCE_UNIFORM_SCALING; + } + // Retrieve or create the subimage we'll draw. + nsIntRect srcRect(aSrc.x, aSrc.y, aSrc.width, aSrc.height); + if (isRequestBacked) { + subImage = ImageOps::Clip(mImageContainer, srcRect, aSVGViewportSize); + } else { + // This path, for eStyleImageType_Element, is currently slower than it + // needs to be because we don't cache anything. (In particular, if we have + // to draw to a temporary surface inside ClippedImage, we don't cache that + // temporary surface since we immediately throw the ClippedImage we create + // here away.) However, if we did cache, we'd need to know when to + // invalidate that cache, and it's not clear that it's worth the trouble + // since using border-image with -moz-element is rare. + + RefPtr<gfxDrawable> drawable = + DrawableForElement(nsRect(nsPoint(), mSize), aRenderingContext); + if (!drawable) { + NS_WARNING("Could not create drawable for element"); + return ImgDrawResult::TEMPORARY_ERROR; + } + + nsCOMPtr<imgIContainer> image(ImageOps::CreateFromDrawable(drawable)); + subImage = ImageOps::Clip(image, srcRect, aSVGViewportSize); + } + + MOZ_ASSERT(!aSVGViewportSize || + subImage->GetType() == imgIContainer::TYPE_VECTOR); + + SamplingFilter samplingFilter = + nsLayoutUtils::GetSamplingFilterForFrame(mForFrame); + + if (!RequiresScaling(aFill, aHFill, aVFill, aUnitSize)) { + ImgDrawResult result = nsLayoutUtils::DrawSingleImage( + aRenderingContext, aPresContext, subImage, samplingFilter, aFill, + aDirtyRect, SVGImageContext(), drawFlags); + + if (!mImage->IsComplete()) { + result &= ImgDrawResult::SUCCESS_NOT_COMPLETE; + } + + return result; + } + + nsSize repeatSize; + nsRect fillRect(aFill); + nsRect tile = ComputeTile(fillRect, aHFill, aVFill, aUnitSize, repeatSize); + + ImgDrawResult result = nsLayoutUtils::DrawBackgroundImage( + aRenderingContext, mForFrame, aPresContext, subImage, samplingFilter, + tile, fillRect, repeatSize, tile.TopLeft(), aDirtyRect, drawFlags, + ExtendMode::CLAMP, 1.0); + + if (!mImage->IsComplete()) { + result &= ImgDrawResult::SUCCESS_NOT_COMPLETE; + } + + return result; + } + + nsSize repeatSize(aFill.Size()); + nsRect fillRect(aFill); + nsRect destTile = + RequiresScaling(fillRect, aHFill, aVFill, aUnitSize) + ? ComputeTile(fillRect, aHFill, aVFill, aUnitSize, repeatSize) + : fillRect; + + return Draw(aPresContext, aRenderingContext, aDirtyRect, destTile, fillRect, + destTile.TopLeft(), repeatSize, aSrc); +} + +ImgDrawResult nsImageRenderer::DrawShapeImage(nsPresContext* aPresContext, + gfxContext& aRenderingContext) { + if (!IsReady()) { + MOZ_ASSERT_UNREACHABLE( + "Ensure PrepareImage() has returned true before " + "calling me"); + return ImgDrawResult::NOT_READY; + } + + if (mSize.width <= 0 || mSize.height <= 0) { + return ImgDrawResult::SUCCESS; + } + + if (mImage->IsImageRequestType()) { + uint32_t drawFlags = + ConvertImageRendererToDrawFlags(mFlags) | imgIContainer::FRAME_FIRST; + nsRect dest(nsPoint(0, 0), mSize); + // We have a tricky situation in our choice of SamplingFilter. Shape + // images define a float area based on the alpha values in the rendered + // pixels. When multiple device pixels are used for one css pixel, the + // sampling can change crisp edges into aliased edges. For visual pixels, + // that's usually the right choice. For defining a float area, it can + // cause problems. If a style is using a shape-image-threshold value that + // is less than the alpha of the edge pixels, any filtering may smear the + // alpha into adjacent pixels and expand the float area in a confusing + // way. Since the alpha threshold can be set precisely in CSS, and since a + // web author may be counting on that threshold to define a precise float + // area from an image, it is least confusing to have the rendered pixels + // have unfiltered alpha. We use SamplingFilter::POINT to ensure that each + // rendered pixel has an alpha that precisely matches the alpha of the + // closest pixel in the image. + return nsLayoutUtils::DrawSingleImage( + aRenderingContext, aPresContext, mImageContainer, SamplingFilter::POINT, + dest, dest, SVGImageContext(), drawFlags); + } + + if (mImage->IsGradient()) { + nsCSSGradientRenderer renderer = nsCSSGradientRenderer::Create( + aPresContext, mForFrame->Style(), *mGradientData, mSize); + nsRect dest(nsPoint(0, 0), mSize); + renderer.Paint(aRenderingContext, dest, dest, mSize, + CSSIntRect::FromAppUnitsRounded(dest), dest, 1.0); + return ImgDrawResult::SUCCESS; + } + + // Unsupported image type. + return ImgDrawResult::BAD_IMAGE; +} + +bool nsImageRenderer::IsRasterImage() { + return mImageContainer && + mImageContainer->GetType() == imgIContainer::TYPE_RASTER; +} + +already_AddRefed<imgIContainer> nsImageRenderer::GetImage() { + return do_AddRef(mImageContainer); +} diff --git a/layout/painting/nsImageRenderer.h b/layout/painting/nsImageRenderer.h new file mode 100644 index 0000000000..122564f886 --- /dev/null +++ b/layout/painting/nsImageRenderer.h @@ -0,0 +1,313 @@ +/* -*- 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/. */ + +#ifndef nsImageRenderer_h__ +#define nsImageRenderer_h__ + +#include "nsStyleStruct.h" +#include "Units.h" +#include "mozilla/AspectRatio.h" +#include "mozilla/SurfaceFromElementResult.h" + +class gfxDrawable; + +namespace mozilla { +class nsDisplayItem; + +namespace layers { +class StackingContextHelper; +class WebRenderParentCommand; +class RenderRootStateManager; +} // namespace layers + +namespace wr { +class DisplayListBuilder; +class IpcResourceUpdateQueue; +} // namespace wr + +// A CSSSizeOrRatio represents a (possibly partially specified) size for use +// in computing image sizes. Either or both of the width and height might be +// given. A ratio of width to height may also be given. If we at least two +// of these then we can compute a concrete size, that is a width and height. +struct CSSSizeOrRatio { + CSSSizeOrRatio() + : mWidth(0), mHeight(0), mHasWidth(false), mHasHeight(false) {} + + bool CanComputeConcreteSize() const { + return mHasWidth + mHasHeight + HasRatio() >= 2; + } + bool IsConcrete() const { return mHasWidth && mHasHeight; } + bool HasRatio() const { return !!mRatio; } + bool IsEmpty() const { + return (mHasWidth && mWidth <= 0) || (mHasHeight && mHeight <= 0) || + !mRatio; + } + + // CanComputeConcreteSize must return true when ComputeConcreteSize is + // called. + nsSize ComputeConcreteSize() const; + + void SetWidth(nscoord aWidth) { + mWidth = aWidth; + mHasWidth = true; + if (mHasHeight) { + mRatio = AspectRatio::FromSize(mWidth, mHeight); + } + } + void SetHeight(nscoord aHeight) { + mHeight = aHeight; + mHasHeight = true; + if (mHasWidth) { + mRatio = AspectRatio::FromSize(mWidth, mHeight); + } + } + void SetSize(const nsSize& aSize) { + mWidth = aSize.width; + mHeight = aSize.height; + mHasWidth = true; + mHasHeight = true; + mRatio = AspectRatio::FromSize(mWidth, mHeight); + } + void SetRatio(const AspectRatio& aRatio) { + MOZ_ASSERT( + !mHasWidth || !mHasHeight, + "Probably shouldn't be setting a ratio if we have a concrete size"); + mRatio = aRatio; + } + + AspectRatio mRatio; + nscoord mWidth; + nscoord mHeight; + bool mHasWidth; + bool mHasHeight; +}; + +/** + * This is a small wrapper class to encapsulate image drawing that can draw an + * StyleImage image, which may internally be a real image, a sub image, or a CSS + * gradient, etc... + * + * @note Always call the member functions in the order of PrepareImage(), + * SetSize(), and Draw*(). + */ +class nsImageRenderer { + public: + typedef mozilla::image::ImgDrawResult ImgDrawResult; + typedef mozilla::layers::ImageContainer ImageContainer; + + enum { + FLAG_SYNC_DECODE_IMAGES = 0x01, + FLAG_PAINTING_TO_WINDOW = 0x02, + FLAG_HIGH_QUALITY_SCALING = 0x04, + FLAG_DRAW_PARTIAL_FRAMES = 0x08 + }; + enum FitType { CONTAIN, COVER }; + + nsImageRenderer(nsIFrame* aForFrame, const mozilla::StyleImage* aImage, + uint32_t aFlags); + ~nsImageRenderer() = default; + /** + * Populates member variables to get ready for rendering. + * @return true iff the image is ready, and there is at least a pixel to + * draw. + */ + bool PrepareImage(); + + /** + * The three Compute*Size functions correspond to the sizing algorthms and + * definitions from the CSS Image Values and Replaced Content spec. See + * http://dev.w3.org/csswg/css-images-3/#sizing . + */ + + /** + * Compute the intrinsic size of the image as defined in the CSS Image Values + * spec. The intrinsic size is the unscaled size which the image would ideally + * like to be in app units. + */ + mozilla::CSSSizeOrRatio ComputeIntrinsicSize(); + + /** + * Computes the placement for a background image, or for the image data + * inside of a replaced element. + * + * @param aPos The CSS <position> value that specifies the image's position. + * @param aOriginBounds The box to which the tiling position should be + * relative. For background images, this should correspond to + * 'background-origin' for the frame, except when painting on the + * canvas, in which case the origin bounds should be the bounds + * of the root element's frame. For a replaced element, this should + * be the element's content-box. + * @param aTopLeft [out] The top-left corner where an image tile should be + * drawn. + * @param aAnchorPoint [out] A point which should be pixel-aligned by + * nsLayoutUtils::DrawImage. This is the same as aTopLeft, unless + * CSS specifies a percentage (including 'right' or 'bottom'), in + * which case it's that percentage within of aOriginBounds. So + * 'right' would set aAnchorPoint.x to aOriginBounds.XMost(). + * + * Points are returned relative to aOriginBounds. + */ + static void ComputeObjectAnchorPoint(const mozilla::Position& aPos, + const nsSize& aOriginBounds, + const nsSize& aImageSize, + nsPoint* aTopLeft, + nsPoint* aAnchorPoint); + + /** + * Compute the size of the rendered image using either the 'cover' or + * 'contain' constraints (aFitType). + */ + static nsSize ComputeConstrainedSize( + const nsSize& aConstrainingSize, + const mozilla::AspectRatio& aIntrinsicRatio, FitType aFitType); + /** + * Compute the size of the rendered image (the concrete size) where no cover/ + * contain constraints are given. The 'default algorithm' from the CSS Image + * Values spec. + */ + static nsSize ComputeConcreteSize( + const mozilla::CSSSizeOrRatio& aSpecifiedSize, + const mozilla::CSSSizeOrRatio& aIntrinsicSize, + const nsSize& aDefaultSize); + + /** + * Set this image's preferred size. This will be its intrinsic size where + * specified and the default size where it is not. Used as the unscaled size + * when rendering the image. + */ + void SetPreferredSize(const mozilla::CSSSizeOrRatio& aIntrinsicSize, + const nsSize& aDefaultSize); + + /** + * Draws the image to the target rendering context using + * {background|mask}-specific arguments. + * @see nsLayoutUtils::DrawImage() for parameters. + */ + ImgDrawResult DrawLayer(nsPresContext* aPresContext, + gfxContext& aRenderingContext, const nsRect& aDest, + const nsRect& aFill, const nsPoint& aAnchor, + const nsRect& aDirty, const nsSize& aRepeatSize, + float aOpacity); + + /** + * Builds WebRender DisplayItems for an image using + * {background|mask}-specific arguments. + * @see nsLayoutUtils::DrawImage() for parameters. + */ + ImgDrawResult BuildWebRenderDisplayItemsForLayer( + nsPresContext* aPresContext, mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResource, + const mozilla::layers::StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, nsDisplayItem* aItem, + const nsRect& aDest, const nsRect& aFill, const nsPoint& aAnchor, + const nsRect& aDirty, const nsSize& aRepeatSize, float aOpacity); + + /** + * Draw the image to a single component of a border-image style rendering. + * aFill The destination rect to be drawn into + * aSrc is the part of the image to be rendered into a tile (aUnitSize in + * aFill), if aSrc and the dest tile are different sizes, the image will be + * scaled to map aSrc onto the dest tile. + * aHFill and aVFill are the repeat patterns for the component - + * NS_STYLE_BORDER_IMAGE_REPEAT_* + * aUnitSize The scaled size of a single source rect (in destination coords) + * aIndex identifies the component: 0 1 2 + * 3 4 5 + * 6 7 8 + * aSVGViewportSize The image size evaluated by default sizing algorithm. + * Pass Nothing() if we can read a valid viewport size or aspect-ratio from + * the drawing image directly, otherwise, pass Some() with viewport size + * evaluated from default sizing algorithm. + * aHasIntrinsicRatio is used to record if the source image has fixed + * intrinsic ratio. + */ + ImgDrawResult DrawBorderImageComponent( + nsPresContext* aPresContext, gfxContext& aRenderingContext, + const nsRect& aDirtyRect, const nsRect& aFill, + const mozilla::CSSIntRect& aSrc, mozilla::StyleBorderImageRepeat aHFill, + mozilla::StyleBorderImageRepeat aVFill, const nsSize& aUnitSize, + uint8_t aIndex, const mozilla::Maybe<nsSize>& aSVGViewportSize, + const bool aHasIntrinsicRatio); + + /** + * Draw the image to aRenderingContext which can be used to define the + * float area in the presence of "shape-outside: <image>". + */ + ImgDrawResult DrawShapeImage(nsPresContext* aPresContext, + gfxContext& aRenderingContext); + + bool IsRasterImage(); + + /// Retrieves the image associated with this nsImageRenderer, if there is one. + already_AddRefed<imgIContainer> GetImage(); + + bool IsReady() const { return mPrepareResult == ImgDrawResult::SUCCESS; } + ImgDrawResult PrepareResult() const { return mPrepareResult; } + void SetExtendMode(mozilla::gfx::ExtendMode aMode) { mExtendMode = aMode; } + void SetMaskOp(mozilla::StyleMaskMode aMaskOp) { mMaskOp = aMaskOp; } + const nsSize& GetSize() const { return mSize; } + mozilla::StyleImage::Tag GetType() const { return mType; } + const mozilla::StyleGradient* GetGradientData() const { + return mGradientData; + } + + private: + /** + * Draws the image to the target rendering context. + * aSrc is a rect on the source image which will be mapped to aDest; it's + * currently only used for gradients. + * + * @see nsLayoutUtils::DrawImage() for other parameters. + */ + ImgDrawResult Draw(nsPresContext* aPresContext, gfxContext& aRenderingContext, + const nsRect& aDirtyRect, const nsRect& aDest, + const nsRect& aFill, const nsPoint& aAnchor, + const nsSize& aRepeatSize, const mozilla::CSSIntRect& aSrc, + float aOpacity = 1.0); + + /** + * Builds WebRender DisplayItems for the image. + * aSrc is a rect on the source image which will be mapped to aDest; it's + * currently only used for gradients. + * + * @see nsLayoutUtils::DrawImage() for other parameters. + */ + ImgDrawResult BuildWebRenderDisplayItems( + nsPresContext* aPresContext, mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const mozilla::layers::StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, nsDisplayItem* aItem, + const nsRect& aDirtyRect, const nsRect& aDest, const nsRect& aFill, + const nsPoint& aAnchor, const nsSize& aRepeatSize, + const mozilla::CSSIntRect& aSrc, float aOpacity = 1.0); + + /** + * Helper method for creating a gfxDrawable from mPaintServerFrame or + * mImageElementSurface. + * Requires mType to be Element. + * Returns null if we cannot create the drawable. + */ + already_AddRefed<gfxDrawable> DrawableForElement(const nsRect& aImageRect, + gfxContext& aContext); + + nsIFrame* mForFrame; + const mozilla::StyleImage* mImage; + ImageResolution mImageResolution; + mozilla::StyleImage::Tag mType; + nsCOMPtr<imgIContainer> mImageContainer; + const mozilla::StyleGradient* mGradientData; + nsIFrame* mPaintServerFrame; + SurfaceFromElementResult mImageElementSurface; + ImgDrawResult mPrepareResult; + nsSize mSize; // unscaled size of the image, in app units + uint32_t mFlags; + mozilla::gfx::ExtendMode mExtendMode; + mozilla::StyleMaskMode mMaskOp; +}; + +} // namespace mozilla + +#endif /* nsImageRenderer_h__ */ |