summaryrefslogtreecommitdiffstats
path: root/layout/painting
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
commit2aa4a82499d4becd2284cdb482213d541b8804dd (patch)
treeb80bf8bf13c3766139fbacc530efd0dd9d54394c /layout/painting
parentInitial commit. (diff)
downloadfirefox-upstream.tar.xz
firefox-upstream.zip
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'layout/painting')
-rw-r--r--layout/painting/ActiveLayerTracker.cpp614
-rw-r--r--layout/painting/ActiveLayerTracker.h163
-rw-r--r--layout/painting/BorderCache.h68
-rw-r--r--layout/painting/BorderConsts.h22
-rw-r--r--layout/painting/DashedCornerFinder.cpp414
-rw-r--r--layout/painting/DashedCornerFinder.h274
-rw-r--r--layout/painting/DisplayItemClip.cpp488
-rw-r--r--layout/painting/DisplayItemClip.h193
-rw-r--r--layout/painting/DisplayItemClipChain.cpp88
-rw-r--r--layout/painting/DisplayItemClipChain.h117
-rw-r--r--layout/painting/DisplayListClipState.cpp149
-rw-r--r--layout/painting/DisplayListClipState.h300
-rw-r--r--layout/painting/DottedCornerFinder.cpp538
-rw-r--r--layout/painting/DottedCornerFinder.h432
-rw-r--r--layout/painting/FrameLayerBuilder.cpp7576
-rw-r--r--layout/painting/FrameLayerBuilder.h732
-rw-r--r--layout/painting/LayerState.h29
-rw-r--r--layout/painting/MaskLayerImageCache.cpp64
-rw-r--r--layout/painting/MaskLayerImageCache.h257
-rw-r--r--layout/painting/MatrixStack.h61
-rw-r--r--layout/painting/PaintTracker.cpp13
-rw-r--r--layout/painting/PaintTracker.h31
-rw-r--r--layout/painting/RetainedDisplayListBuilder.cpp1506
-rw-r--r--layout/painting/RetainedDisplayListBuilder.h261
-rw-r--r--layout/painting/RetainedDisplayListHelpers.h181
-rw-r--r--layout/painting/TransformClipNode.h138
-rw-r--r--layout/painting/crashtests/1402183-1.html20
-rw-r--r--layout/painting/crashtests/1405881-1.html24
-rw-r--r--layout/painting/crashtests/1407470-1.html19
-rw-r--r--layout/painting/crashtests/1413073-1.html15
-rw-r--r--layout/painting/crashtests/1413073-2.html18
-rw-r--r--layout/painting/crashtests/1418177-1.html34
-rw-r--r--layout/painting/crashtests/1418722-1.html17
-rw-r--r--layout/painting/crashtests/1419917.html15
-rw-r--r--layout/painting/crashtests/1425271-1.html54
-rw-r--r--layout/painting/crashtests/1428906-1.html16
-rw-r--r--layout/painting/crashtests/1430589-1.html55
-rw-r--r--layout/painting/crashtests/1454105-1.html22
-rw-r--r--layout/painting/crashtests/1455944-1.html14
-rw-r--r--layout/painting/crashtests/1458145.html11
-rw-r--r--layout/painting/crashtests/1465305-1.html8
-rw-r--r--layout/painting/crashtests/1468124-1.html27
-rw-r--r--layout/painting/crashtests/1469472.html7
-rw-r--r--layout/painting/crashtests/1477831-1.html11
-rw-r--r--layout/painting/crashtests/1504033.html13
-rw-r--r--layout/painting/crashtests/1514544-1.html15
-rw-r--r--layout/painting/crashtests/1547420-1.html20
-rw-r--r--layout/painting/crashtests/1549909.html9
-rw-r--r--layout/painting/crashtests/1551389-1.html6
-rw-r--r--layout/painting/crashtests/1555819-1.html12
-rw-r--r--layout/painting/crashtests/1574392.html1
-rw-r--r--layout/painting/crashtests/1589800-1.html47
-rw-r--r--layout/painting/crashtests/crashtests.list26
-rw-r--r--layout/painting/moz.build74
-rw-r--r--layout/painting/nsCSSRendering.cpp4903
-rw-r--r--layout/painting/nsCSSRendering.h938
-rw-r--r--layout/painting/nsCSSRenderingBorders.cpp3899
-rw-r--r--layout/painting/nsCSSRenderingBorders.h355
-rw-r--r--layout/painting/nsCSSRenderingGradients.cpp1297
-rw-r--r--layout/painting/nsCSSRenderingGradients.h121
-rw-r--r--layout/painting/nsDisplayItemTypes.h73
-rw-r--r--layout/painting/nsDisplayItemTypesList.h133
-rw-r--r--layout/painting/nsDisplayList.cpp10324
-rw-r--r--layout/painting/nsDisplayList.h7616
-rw-r--r--layout/painting/nsDisplayListArenaTypes.h13
-rw-r--r--layout/painting/nsDisplayListInvalidation.cpp122
-rw-r--r--layout/painting/nsDisplayListInvalidation.h369
-rw-r--r--layout/painting/nsImageRenderer.cpp1060
-rw-r--r--layout/painting/nsImageRenderer.h316
69 files changed, 46858 insertions, 0 deletions
diff --git a/layout/painting/ActiveLayerTracker.cpp b/layout/painting/ActiveLayerTracker.cpp
new file mode 100644
index 0000000000..4000ee9191
--- /dev/null
+++ b/layout/painting/ActiveLayerTracker.cpp
@@ -0,0 +1,614 @@
+/* -*- 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 "gfx2DGlue.h"
+#include "nsExpirationTracker.h"
+#include "nsContainerFrame.h"
+#include "nsIContent.h"
+#include "nsIScrollableFrame.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_LEFT,
+ ACTIVITY_TOP,
+ ACTIVITY_RIGHT,
+ ACTIVITY_BOTTOM,
+ ACTIVITY_BACKGROUND_POSITION,
+
+ ACTIVITY_SCALE,
+ ACTIVITY_TRIGGERED_REPAINT,
+
+ // keep as last item
+ ACTIVITY_COUNT
+ };
+
+ explicit LayerActivity(nsIFrame* aFrame)
+ : mFrame(aFrame), mContent(nullptr), mContentActive(false) {
+ 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;
+ case eCSSProperty_left:
+ return ACTIVITY_LEFT;
+ case eCSSProperty_top:
+ return ACTIVITY_TOP;
+ case eCSSProperty_right:
+ return ACTIVITY_RIGHT;
+ case eCSSProperty_bottom:
+ return ACTIVITY_BOTTOM;
+ case eCSSProperty_background_position:
+ return ACTIVITY_BACKGROUND_POSITION;
+ case eCSSProperty_background_position_x:
+ return ACTIVITY_BACKGROUND_POSITION;
+ case eCSSProperty_background_position_y:
+ return ACTIVITY_BACKGROUND_POSITION;
+ 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<Size> mPreviousTransformScale;
+
+ // The scroll frame during for which we most recently received a call to
+ // NotifyAnimatedFromScrollHandler.
+ WeakFrame mAnimatingScrollHandlerFrame;
+ // The set of activities that were triggered during
+ // mAnimatingScrollHandlerFrame's scroll event handler.
+ EnumSet<ActivityIndex> mScrollHandlerInducedActivity;
+
+ // Number of restyle operations detected
+ uint8_t mRestyleCounts[ACTIVITY_COUNT];
+ bool mContentActive;
+};
+
+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),
+ mDestroying(false) {}
+ ~LayerActivityTracker() override {
+ mDestroying = true;
+ AgeAllGenerations();
+ }
+
+ void NotifyExpired(LayerActivity* aObject) override;
+
+ public:
+ WeakFrame mCurrentScrollHandlerFrame;
+
+ private:
+ bool mDestroying;
+};
+
+static LayerActivityTracker* gLayerActivityTracker = nullptr;
+
+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) {
+ if (!mDestroying && aObject->mAnimatingScrollHandlerFrame.IsAlive()) {
+ // Reset the restyle counts, but let the layer activity survive.
+ PodArrayZero(aObject->mRestyleCounts);
+ MarkUsed(aObject);
+ return;
+ }
+
+ 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) {
+ // The pres context might have been detached during the delay -
+ // that's fine, just skip the paint.
+ if (f->PresContext()->GetContainerWeak() && !gfxVars::UseWebRender()) {
+ f->SchedulePaint(nsIFrame::PAINT_DEFAULT, false);
+ }
+ 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;
+ }
+
+ Size 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 */
+void ActiveLayerTracker::NotifyOffsetRestyle(nsIFrame* aFrame) {
+ LayerActivity* layerActivity = GetLayerActivityForUpdate(aFrame);
+ IncrementMutationCount(
+ &layerActivity->mRestyleCounts[LayerActivity::ACTIVITY_LEFT]);
+ IncrementMutationCount(
+ &layerActivity->mRestyleCounts[LayerActivity::ACTIVITY_TOP]);
+ IncrementMutationCount(
+ &layerActivity->mRestyleCounts[LayerActivity::ACTIVITY_RIGHT]);
+ IncrementMutationCount(
+ &layerActivity->mRestyleCounts[LayerActivity::ACTIVITY_BOTTOM]);
+}
+
+/* static */
+void ActiveLayerTracker::NotifyAnimated(nsIFrame* aFrame,
+ nsCSSPropertyID aProperty,
+ const nsACString& aNewValue,
+ nsDOMCSSDeclaration* aDOMCSSDecl) {
+ LayerActivity* layerActivity = GetLayerActivityForUpdate(aFrame);
+ uint8_t& mutationCount = layerActivity->RestyleCountForProperty(aProperty);
+ if (mutationCount != 0xFF) {
+ nsAutoCString oldValue;
+ aDOMCSSDecl->GetPropertyValue(aProperty, oldValue);
+ if (oldValue != aNewValue) {
+ // We know this is animated, so just hack the mutation count.
+ mutationCount = 0xFF;
+ }
+ }
+}
+
+/* static */
+void ActiveLayerTracker::NotifyAnimatedFromScrollHandler(
+ nsIFrame* aFrame, nsCSSPropertyID aProperty, nsIFrame* aScrollFrame) {
+ if (aFrame->PresContext() != aScrollFrame->PresContext()) {
+ // Don't allow cross-document dependencies.
+ return;
+ }
+ LayerActivity* layerActivity = GetLayerActivityForUpdate(aFrame);
+ LayerActivity::ActivityIndex activityIndex =
+ LayerActivity::GetActivityIndexForProperty(aProperty);
+
+ if (layerActivity->mAnimatingScrollHandlerFrame.GetFrame() != aScrollFrame) {
+ // Discard any activity of a different scroll frame. We only track the
+ // most recent scroll handler induced activity.
+ layerActivity->mScrollHandlerInducedActivity.clear();
+ layerActivity->mAnimatingScrollHandlerFrame = aScrollFrame;
+ }
+
+ layerActivity->mScrollHandlerInducedActivity += activityIndex;
+}
+
+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, const nsACString& aNewValue,
+ nsDOMCSSDeclaration* aDOMCSSDecl) {
+ if (IsPresContextInScriptAnimationCallback(aFrame->PresContext())) {
+ NotifyAnimated(aFrame, aProperty, aNewValue, aDOMCSSDecl);
+ }
+ if (gLayerActivityTracker &&
+ gLayerActivityTracker->mCurrentScrollHandlerFrame.IsAlive()) {
+ NotifyAnimatedFromScrollHandler(
+ aFrame, aProperty,
+ gLayerActivityTracker->mCurrentScrollHandlerFrame.GetFrame());
+ }
+}
+
+/* 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 CheckScrollInducedActivity(
+ LayerActivity* aLayerActivity, LayerActivity::ActivityIndex aActivityIndex,
+ nsDisplayListBuilder* aBuilder) {
+ if (!aLayerActivity->mScrollHandlerInducedActivity.contains(aActivityIndex) ||
+ !aLayerActivity->mAnimatingScrollHandlerFrame.IsAlive()) {
+ return false;
+ }
+
+ nsIScrollableFrame* scrollFrame =
+ do_QueryFrame(aLayerActivity->mAnimatingScrollHandlerFrame.GetFrame());
+ if (scrollFrame && (!aBuilder || scrollFrame->IsScrollingActive(aBuilder))) {
+ return true;
+ }
+
+ // The scroll frame has been destroyed or has become inactive. Clear it from
+ // the layer activity so that it can expire.
+ aLayerActivity->mAnimatingScrollHandlerFrame = nullptr;
+ aLayerActivity->mScrollHandlerInducedActivity.clear();
+ return false;
+}
+
+/* static */
+bool ActiveLayerTracker::IsBackgroundPositionAnimated(
+ nsDisplayListBuilder* aBuilder, nsIFrame* aFrame) {
+ LayerActivity* layerActivity = GetLayerActivity(aFrame);
+ if (layerActivity) {
+ LayerActivity::ActivityIndex activityIndex =
+ LayerActivity::ActivityIndex::ACTIVITY_BACKGROUND_POSITION;
+ 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) {
+ return true;
+ }
+ }
+ if (CheckScrollInducedActivity(layerActivity, activityIndex, aBuilder)) {
+ return true;
+ }
+ }
+ return nsLayoutUtils::HasEffectiveAnimation(
+ aFrame, nsCSSPropertyIDSet({eCSSProperty_background_position_x,
+ eCSSProperty_background_position_y}));
+}
+
+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 true;
+ }
+
+ 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 (CheckScrollInducedActivity(layerActivity, activityIndex, aBuilder)) {
+ 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::IsOffsetStyleAnimated(nsIFrame* aFrame) {
+ LayerActivity* layerActivity = GetLayerActivity(aFrame);
+ if (layerActivity) {
+ if (layerActivity->mRestyleCounts[LayerActivity::ACTIVITY_LEFT] >= 2 ||
+ layerActivity->mRestyleCounts[LayerActivity::ACTIVITY_TOP] >= 2 ||
+ layerActivity->mRestyleCounts[LayerActivity::ACTIVITY_RIGHT] >= 2 ||
+ layerActivity->mRestyleCounts[LayerActivity::ACTIVITY_BOTTOM] >= 2) {
+ return true;
+ }
+ }
+ // We should also check for running CSS animations of these properties once
+ // bug 1009693 is fixed. Until that happens, layerization isn't useful for
+ // animations of these properties because we'll invalidate the layer contents
+ // on every change anyway.
+ // See bug 1151346 for a patch that adds a check for CSS animations.
+ return false;
+}
+
+/* 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::NotifyContentChange(nsIFrame* aFrame) {
+ LayerActivity* layerActivity = GetLayerActivityForUpdate(aFrame);
+ layerActivity->mContentActive = true;
+}
+
+/* static */
+bool ActiveLayerTracker::IsContentActive(nsIFrame* aFrame) {
+ LayerActivity* layerActivity = GetLayerActivity(aFrame);
+ return layerActivity && layerActivity->mContentActive;
+}
+
+/* static */
+void ActiveLayerTracker::SetCurrentScrollHandlerFrame(nsIFrame* aFrame) {
+ if (!gLayerActivityTracker) {
+ gLayerActivityTracker =
+ new LayerActivityTracker(GetMainThreadSerialEventTarget());
+ }
+ gLayerActivityTracker->mCurrentScrollHandlerFrame = aFrame;
+}
+
+/* static */
+void ActiveLayerTracker::Shutdown() {
+ delete gLayerActivityTracker;
+ gLayerActivityTracker = nullptr;
+}
+
+} // namespace mozilla
diff --git a/layout/painting/ActiveLayerTracker.h b/layout/painting/ActiveLayerTracker.h
new file mode 100644
index 0000000000..1bcd2b0e62
--- /dev/null
+++ b/layout/painting/ActiveLayerTracker.h
@@ -0,0 +1,163 @@
+/* -*- 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 nsDisplayListBuilder;
+class nsDOMCSSDeclaration;
+
+namespace mozilla {
+
+/**
+ * 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
+ * eCSSProperty_left, eCSSProperty_top,
+ * eCSSProperty_right, eCSSProperty_bottom
+ * 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 aFrame's left/top/right/bottom properties as having (maybe)
+ * 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.
+ */
+ static void NotifyOffsetRestyle(nsIFrame* aFrame);
+ /**
+ * Mark aFrame as being known to have an animation of aProperty.
+ * Any such marking will time out after a short period.
+ * aNewValue and aDOMCSSDecl are used to determine whether the property's
+ * value has changed.
+ */
+ static void NotifyAnimated(nsIFrame* aFrame, nsCSSPropertyID aProperty,
+ const nsACString& aNewValue,
+ nsDOMCSSDeclaration* aDOMCSSDecl);
+ /**
+ * Notify aFrame as being known to have an animation of aProperty through an
+ * inline style modification during aScrollFrame's scroll event handler.
+ */
+ static void NotifyAnimatedFromScrollHandler(nsIFrame* aFrame,
+ nsCSSPropertyID aProperty,
+ nsIFrame* aScrollFrame);
+ /**
+ * Notify that a property in the inline style rule of aFrame's element
+ * has been modified.
+ * This notification is incomplete --- not all modifications to inline
+ * style will trigger this.
+ * aNewValue and aDOMCSSDecl are used to determine whether the property's
+ * value has changed.
+ */
+ static void NotifyInlineStyleRuleModified(nsIFrame* aFrame,
+ nsCSSPropertyID aProperty,
+ const nsACString& aNewValue,
+ nsDOMCSSDeclaration* aDOMCSSDecl);
+ /**
+ * 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 any of aFrame's offset property styles should be considered
+ * as being animated for constructing active layers.
+ */
+ static bool IsOffsetStyleAnimated(nsIFrame* aFrame);
+ /**
+ * Return true if aFrame's background-position-x or background-position-y
+ * property is animated.
+ */
+ static bool IsBackgroundPositionAnimated(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame);
+ /**
+ * 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);
+
+ /*
+ * We track modifications to the content of certain frames (i.e. canvas
+ * frames) and use that to make layering decisions.
+ */
+
+ /**
+ * Mark aFrame's content as being active. This marking will time out after
+ * a short period.
+ */
+ static void NotifyContentChange(nsIFrame* aFrame);
+ /**
+ * Return true if this frame's content is still marked as active.
+ */
+ static bool IsContentActive(nsIFrame* aFrame);
+
+ /**
+ * Called before and after a scroll event handler is executed, with the
+ * scrollframe or nullptr, respectively. This acts as a hint to treat
+ * inline style changes during the handler differently.
+ */
+ static void SetCurrentScrollHandlerFrame(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..58b2dca9ba
--- /dev/null
+++ b/layout/painting/BorderCache.h
@@ -0,0 +1,68 @@
+/* -*- 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 "nsDataHashtable.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..2fe9ddcb82
--- /dev/null
+++ b/layout/painting/DashedCornerFinder.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 "DashedCornerFinder.h"
+
+#include <utility>
+
+#include "BorderCache.h"
+#include "BorderConsts.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;
+nsDataHashtable<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.Put(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..7572130ac9
--- /dev/null
+++ b/layout/painting/DisplayItemClip.cpp
@@ -0,0 +1,488 @@
+/* -*- 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/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 DisplayItemClip* gNoClip;
+
+const DisplayItemClip& DisplayItemClip::NoClip() {
+ if (!gNoClip) {
+ gNoClip = new DisplayItemClip();
+ }
+ return *gNoClip;
+}
+
+void DisplayItemClip::Shutdown() {
+ delete gNoClip;
+ 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..f38a6610e7
--- /dev/null
+++ b/layout/painting/DisplayListClipState.h
@@ -0,0 +1,300 @@
+/* -*- 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;
+class nsDisplayListBuilder;
+
+namespace mozilla {
+
+/**
+ * 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..49a09ebaf6
--- /dev/null
+++ b/layout/painting/DottedCornerFinder.cpp
@@ -0,0 +1,538 @@
+/* -*- 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"
+
+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;
+nsDataHashtable<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.Put(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/FrameLayerBuilder.cpp b/layout/painting/FrameLayerBuilder.cpp
new file mode 100644
index 0000000000..1a5142d723
--- /dev/null
+++ b/layout/painting/FrameLayerBuilder.cpp
@@ -0,0 +1,7576 @@
+/* -*- 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 "FrameLayerBuilder.h"
+
+#include <algorithm>
+#include <deque>
+#include <functional>
+#include <utility>
+
+#include "ActiveLayerTracker.h"
+#include "BasicLayers.h"
+#include "GeckoProfiler.h"
+#include "ImageContainer.h"
+#include "ImageLayers.h"
+#include "LayerTreeInvalidation.h"
+#include "LayerUserData.h"
+#include "Layers.h"
+#include "MaskLayerImageCache.h"
+#include "MatrixStack.h"
+#include "TransformClipNode.h"
+#include "UnitTransforms.h"
+#include "Units.h"
+#include "gfx2DGlue.h"
+#include "gfxContext.h"
+#include "gfxEnv.h"
+#include "gfxUtils.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/DisplayPortUtils.h"
+#include "mozilla/EffectCompositor.h"
+#include "mozilla/LayerAnimationInfo.h"
+#include "mozilla/LayerTimelineMarker.h"
+#include "mozilla/LookAndFeel.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/PerfStats.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/ReverseIterator.h"
+#include "mozilla/StaticPrefs_gfx.h"
+#include "mozilla/StaticPrefs_layers.h"
+#include "mozilla/StaticPrefs_layout.h"
+#include "mozilla/SVGIntegrationUtils.h"
+#include "mozilla/Unused.h"
+#include "mozilla/dom/EffectsInfo.h"
+#include "mozilla/dom/ProfileTimelineMarkerBinding.h"
+#include "mozilla/dom/RemoteBrowser.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/Matrix.h"
+#include "mozilla/gfx/Tools.h"
+#include "mozilla/layers/ShadowLayers.h"
+#include "mozilla/layers/TextureClient.h"
+#include "mozilla/layers/TextureWrapperImage.h"
+#include "mozilla/layers/WebRenderUserData.h"
+#include "nsDisplayList.h"
+#include "nsDocShell.h"
+#include "nsIScrollableFrame.h"
+#include "nsImageFrame.h"
+#include "nsLayoutUtils.h"
+#include "nsPresContext.h"
+#include "nsPrintfCString.h"
+#include "nsSubDocumentFrame.h"
+#include "nsTransitionManager.h"
+
+using namespace mozilla::layers;
+using namespace mozilla::gfx;
+using mozilla::UniquePtr;
+using mozilla::WrapUnique;
+
+// PaintedLayerData::mAssignedDisplayItems is a std::vector, which is
+// non-memmovable
+MOZ_DECLARE_RELOCATE_USING_MOVE_CONSTRUCTOR(mozilla::PaintedLayerData);
+
+namespace mozilla {
+
+class PaintedDisplayItemLayerUserData;
+
+static nsTHashtable<nsPtrHashKey<DisplayItemData>>* sAliveDisplayItemDatas;
+
+/**
+ * The address of gPaintedDisplayItemLayerUserData is used as the user
+ * data key for PaintedLayers created by FrameLayerBuilder.
+ * It identifies PaintedLayers used to draw non-layer content, which are
+ * therefore eligible for recycling. We want display items to be able to
+ * create their own dedicated PaintedLayers in BuildLayer, if necessary,
+ * and we wouldn't want to accidentally recycle those.
+ * The user data is a PaintedDisplayItemLayerUserData.
+ */
+uint8_t gPaintedDisplayItemLayerUserData;
+/**
+ * The address of gColorLayerUserData is used as the user
+ * data key for ColorLayers created by FrameLayerBuilder.
+ * The user data is null.
+ */
+uint8_t gColorLayerUserData;
+/**
+ * The address of gImageLayerUserData is used as the user
+ * data key for ImageLayers created by FrameLayerBuilder.
+ * The user data is null.
+ */
+uint8_t gImageLayerUserData;
+/**
+ * The address of gLayerManagerUserData is used as the user
+ * data key for retained LayerManagers managed by FrameLayerBuilder.
+ * The user data is a LayerManagerData.
+ */
+uint8_t gLayerManagerUserData;
+/**
+ * The address of gMaskLayerUserData is used as the user
+ * data key for mask layers managed by FrameLayerBuilder.
+ * The user data is a MaskLayerUserData.
+ */
+uint8_t gMaskLayerUserData;
+/**
+ * The address of gCSSMaskLayerUserData is used as the user
+ * data key for mask layers of css masking managed by FrameLayerBuilder.
+ * The user data is a CSSMaskLayerUserData.
+ */
+uint8_t gCSSMaskLayerUserData;
+
+// a global cache of image containers used for mask layers
+static MaskLayerImageCache* gMaskLayerImageCache = nullptr;
+
+static inline MaskLayerImageCache* GetMaskLayerImageCache() {
+ if (!gMaskLayerImageCache) {
+ gMaskLayerImageCache = new MaskLayerImageCache();
+ }
+
+ return gMaskLayerImageCache;
+}
+
+struct InactiveLayerData {
+ RefPtr<layers::BasicLayerManager> mLayerManager;
+ RefPtr<layers::Layer> mLayer;
+ UniquePtr<layers::LayerProperties> mProps;
+
+ ~InactiveLayerData();
+};
+
+struct AssignedDisplayItem {
+ AssignedDisplayItem(nsPaintedDisplayItem* aItem, LayerState aLayerState,
+ DisplayItemData* aData, const nsRect& aContentRect,
+ DisplayItemEntryType aType, const bool aHasOpacity,
+ const RefPtr<TransformClipNode>& aTransform,
+ const bool aIsMerged);
+ AssignedDisplayItem(AssignedDisplayItem&& aRhs) = default;
+
+ bool HasOpacity() const { return mHasOpacity; }
+
+ bool HasTransform() const { return mTransform; }
+
+ nsPaintedDisplayItem* mItem;
+ DisplayItemData* mDisplayItemData;
+
+ /**
+ * If the display item is being rendered as an inactive
+ * layer, then this stores the layer manager being
+ * used for the inactive transaction.
+ */
+ UniquePtr<InactiveLayerData> mInactiveLayerData;
+ RefPtr<TransformClipNode> mTransform;
+
+ nsRect mContentRect;
+ LayerState mLayerState;
+ DisplayItemEntryType mType;
+
+ bool mReused;
+ bool mMerged;
+ bool mHasOpacity;
+ bool mHasPaintRect;
+};
+
+struct DisplayItemEntry {
+ DisplayItemEntry(nsDisplayItem* aItem, DisplayItemEntryType aType)
+ : mItem(aItem), mType(aType) {}
+
+ nsDisplayItem* mItem;
+ DisplayItemEntryType mType;
+};
+
+/**
+ * Returns true if the given |aType| is an effect start marker.
+ */
+static bool IsEffectStartMarker(DisplayItemEntryType aType) {
+ return aType == DisplayItemEntryType::PushOpacity ||
+ aType == DisplayItemEntryType::PushOpacityWithBg ||
+ aType == DisplayItemEntryType::PushTransform;
+}
+
+/**
+ * Returns true if the given |aType| is an effect end marker.
+ */
+static bool IsEffectEndMarker(DisplayItemEntryType aType) {
+ return aType == DisplayItemEntryType::PopOpacity ||
+ aType == DisplayItemEntryType::PopTransform;
+}
+
+enum class MarkerType { StartMarker, EndMarker };
+
+/**
+ * Returns true if the given nsDisplayOpacity |aItem| has had opacity applied
+ * to its children and can be flattened away.
+ */
+static bool IsOpacityAppliedToChildren(nsDisplayItem* aItem) {
+ MOZ_ASSERT(aItem->GetType() == DisplayItemType::TYPE_OPACITY);
+ return static_cast<nsDisplayOpacity*>(aItem)->OpacityAppliedToChildren();
+}
+
+/**
+ * Returns true if the given display item type supports flattening with markers.
+ */
+static bool SupportsFlatteningWithMarkers(const DisplayItemType& aType) {
+ return aType == DisplayItemType::TYPE_OPACITY ||
+ aType == DisplayItemType::TYPE_TRANSFORM;
+}
+
+/**
+ * Adds the effect marker to |aMarkers| based on the type of |aItem| and whether
+ * |markerType| is a start or end marker.
+ */
+template <MarkerType markerType>
+static bool AddMarkerIfNeeded(nsDisplayItem* aItem,
+ std::deque<DisplayItemEntry>& aMarkers) {
+ const DisplayItemType type = aItem->GetType();
+ if (!SupportsFlatteningWithMarkers(type)) {
+ return false;
+ }
+
+ DisplayItemEntryType marker;
+
+// Just a fancy way to avoid writing two separate functions to select between
+// PUSH and POP markers. This is done during compile time based on |markerType|.
+#define GET_MARKER(start_marker, end_marker) \
+ std::conditional< \
+ markerType == MarkerType::StartMarker, \
+ std::integral_constant<DisplayItemEntryType, start_marker>, \
+ std::integral_constant<DisplayItemEntryType, end_marker>>::type::value;
+
+ switch (type) {
+ case DisplayItemType::TYPE_OPACITY:
+ if (IsOpacityAppliedToChildren(aItem)) {
+ // TODO(miko): I am not a fan of this. The more correct solution would
+ // be to return an enum from nsDisplayItem::ShouldFlattenAway(), so that
+ // we could distinguish between different flattening methods and avoid
+ // entering this function when markers are not needed.
+ return false;
+ }
+
+ marker = GET_MARKER(DisplayItemEntryType::PushOpacity,
+ DisplayItemEntryType::PopOpacity);
+ break;
+ case DisplayItemType::TYPE_TRANSFORM:
+ marker = GET_MARKER(DisplayItemEntryType::PushTransform,
+ DisplayItemEntryType::PopTransform);
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Invalid display item type!");
+ break;
+ }
+
+ aMarkers.emplace_back(aItem, marker);
+ return true;
+}
+
+DisplayItemData::DisplayItemData(LayerManagerData* aParent, uint32_t aKey,
+ Layer* aLayer, nsIFrame* aFrame)
+
+ : mRefCnt(0),
+ mParent(aParent),
+ mLayer(aLayer),
+ mDisplayItemKey(aKey),
+ mItem(nullptr),
+ mUsed(true),
+ mIsInvalid(false),
+ mReusedItem(false) {
+ MOZ_COUNT_CTOR(DisplayItemData);
+
+ if (!sAliveDisplayItemDatas) {
+ sAliveDisplayItemDatas = new nsTHashtable<nsPtrHashKey<DisplayItemData>>();
+ }
+ MOZ_RELEASE_ASSERT(!sAliveDisplayItemDatas->Contains(this));
+ sAliveDisplayItemDatas->PutEntry(this);
+
+ MOZ_RELEASE_ASSERT(mLayer);
+ if (aFrame) {
+ AddFrame(aFrame);
+ }
+}
+
+void DisplayItemData::Destroy() {
+ // Get the pres context.
+ RefPtr<nsPresContext> presContext = mFrameList[0]->PresContext();
+
+ // Call our destructor.
+ this->~DisplayItemData();
+
+ // Don't let the memory be freed, since it will be recycled
+ // instead. Don't call the global operator delete.
+ presContext->PresShell()->FreeByObjectID(eArenaObjectID_DisplayItemData,
+ this);
+}
+
+void DisplayItemData::AddFrame(nsIFrame* aFrame) {
+ MOZ_RELEASE_ASSERT(mLayer);
+ MOZ_RELEASE_ASSERT(!mFrameList.Contains(aFrame));
+ mFrameList.AppendElement(aFrame);
+
+ SmallPointerArray<DisplayItemData>& array = aFrame->DisplayItemData();
+ array.AppendElement(this);
+}
+
+void DisplayItemData::RemoveFrame(nsIFrame* aFrame) {
+ MOZ_RELEASE_ASSERT(mLayer);
+ bool result = mFrameList.RemoveElement(aFrame);
+ MOZ_RELEASE_ASSERT(result, "Can't remove a frame that wasn't added!");
+
+ SmallPointerArray<DisplayItemData>& array = aFrame->DisplayItemData();
+ array.RemoveElement(this);
+}
+
+void DisplayItemData::EndUpdate() {
+ MOZ_RELEASE_ASSERT(mLayer);
+ mIsInvalid = false;
+ mUsed = false;
+ mReusedItem = false;
+ mOldTransform = nullptr;
+}
+
+void DisplayItemData::EndUpdate(UniquePtr<nsDisplayItemGeometry>&& aGeometry) {
+ MOZ_RELEASE_ASSERT(mLayer);
+ MOZ_ASSERT(mItem);
+ MOZ_ASSERT(mGeometry || aGeometry);
+
+ if (aGeometry) {
+ mGeometry = std::move(aGeometry);
+ }
+ mClip = mItem->GetClip();
+ mChangedFrameInvalidations.SetEmpty();
+
+ EndUpdate();
+}
+
+void DisplayItemData::BeginUpdate(Layer* aLayer, LayerState aState,
+ bool aFirstUpdate,
+ nsPaintedDisplayItem* aItem /* = nullptr */) {
+ bool isReused = false;
+ bool isMerged = false;
+
+ if (aItem) {
+ isReused = !aFirstUpdate ? aItem->IsReused() : false;
+
+ const nsDisplayWrapList* wraplist = aItem->AsDisplayWrapList();
+ isMerged = wraplist && wraplist->HasMergedFrames();
+ }
+
+ BeginUpdate(aLayer, aState, aItem, isReused, isMerged);
+}
+
+void DisplayItemData::BeginUpdate(Layer* aLayer, LayerState aState,
+ nsPaintedDisplayItem* aItem, bool aIsReused,
+ bool aIsMerged) {
+ MOZ_RELEASE_ASSERT(mLayer);
+ MOZ_RELEASE_ASSERT(aLayer);
+ mLayer = aLayer;
+ mOptLayer = nullptr;
+ mInactiveManager = nullptr;
+ mLayerState = aState;
+ mUsed = true;
+
+ if (aLayer->AsPaintedLayer()) {
+ if (aItem != mItem) {
+ aItem->SetDisplayItemData(this, aLayer->Manager());
+ } else {
+ MOZ_ASSERT(aItem->GetDisplayItemData() == this);
+ }
+ mReusedItem = aIsReused;
+ }
+
+ if (!aItem) {
+ return;
+ }
+
+ if (!aIsMerged && mFrameList.Length() == 1) {
+ MOZ_ASSERT(mFrameList[0] == aItem->Frame());
+ return;
+ }
+
+ // We avoid adding or removing element unnecessarily
+ // since we have to modify userdata each time
+ CopyableAutoTArray<nsIFrame*, 4> copy(mFrameList);
+ if (!copy.RemoveElement(aItem->Frame())) {
+ AddFrame(aItem->Frame());
+ mChangedFrameInvalidations.Or(mChangedFrameInvalidations,
+ aItem->Frame()->InkOverflowRect());
+ }
+
+ if (aIsMerged) {
+ MOZ_ASSERT(aItem->AsDisplayWrapList());
+
+ for (nsIFrame* frame : aItem->AsDisplayWrapList()->GetMergedFrames()) {
+ if (!copy.RemoveElement(frame)) {
+ AddFrame(frame);
+ mChangedFrameInvalidations.Or(mChangedFrameInvalidations,
+ frame->InkOverflowRect());
+ }
+ }
+ }
+
+ for (nsIFrame* frame : copy) {
+ RemoveFrame(frame);
+ mChangedFrameInvalidations.Or(mChangedFrameInvalidations,
+ frame->InkOverflowRect());
+ }
+}
+
+static const nsIFrame* sDestroyedFrame = nullptr;
+DisplayItemData::~DisplayItemData() {
+ MOZ_COUNT_DTOR(DisplayItemData);
+
+ if (mItem) {
+ MOZ_ASSERT(mItem->GetDisplayItemData() == this);
+ mItem->SetDisplayItemData(nullptr, nullptr);
+ }
+
+ for (uint32_t i = 0; i < mFrameList.Length(); i++) {
+ nsIFrame* frame = mFrameList[i];
+ if (frame == sDestroyedFrame) {
+ continue;
+ }
+
+ SmallPointerArray<DisplayItemData>& array = frame->DisplayItemData();
+ array.RemoveElement(this);
+ }
+
+ MOZ_RELEASE_ASSERT(sAliveDisplayItemDatas);
+ nsPtrHashKey<mozilla::DisplayItemData>* entry =
+ sAliveDisplayItemDatas->GetEntry(this);
+ MOZ_RELEASE_ASSERT(entry);
+
+ sAliveDisplayItemDatas->RemoveEntry(entry);
+
+ if (sAliveDisplayItemDatas->Count() == 0) {
+ delete sAliveDisplayItemDatas;
+ sAliveDisplayItemDatas = nullptr;
+ }
+}
+
+void DisplayItemData::NotifyRemoved() {
+ if (mDisplayItemKey > static_cast<uint8_t>(DisplayItemType::TYPE_MAX)) {
+ // This is sort of a hack. The display item key has higher bits set, which
+ // means that it is not the only display item for the frame.
+ // This branch skips separator transforms.
+ return;
+ }
+
+ const DisplayItemType type = GetDisplayItemTypeFromKey(mDisplayItemKey);
+
+ if (type == DisplayItemType::TYPE_REMOTE) {
+ // TYPE_REMOTE doesn't support merging, so access it directly
+ MOZ_ASSERT(mFrameList.Length() == 1);
+ if (mFrameList.Length() != 1) {
+ return;
+ }
+
+ // This is a remote browser that is going away, notify it that it is now
+ // hidden
+ nsIFrame* frame = mFrameList[0];
+ nsSubDocumentFrame* subdoc = static_cast<nsSubDocumentFrame*>(frame);
+ nsFrameLoader* frameLoader = subdoc->FrameLoader();
+ if (frameLoader && frameLoader->GetRemoteBrowser()) {
+ frameLoader->GetRemoteBrowser()->UpdateEffects(
+ mozilla::dom::EffectsInfo::FullyHidden());
+ }
+ }
+
+ if (type != DisplayItemType::TYPE_TRANSFORM &&
+ type != DisplayItemType::TYPE_OPACITY &&
+ type != DisplayItemType::TYPE_BACKGROUND_COLOR) {
+ return;
+ }
+
+ for (nsIFrame* frame : mFrameList) {
+ EffectCompositor::ClearIsRunningOnCompositor(frame, type);
+ }
+}
+
+const nsRegion& DisplayItemData::GetChangedFrameInvalidations() {
+ return mChangedFrameInvalidations;
+}
+
+DisplayItemData* DisplayItemData::AssertDisplayItemData(
+ DisplayItemData* aData) {
+ MOZ_RELEASE_ASSERT(aData);
+ MOZ_RELEASE_ASSERT(sAliveDisplayItemDatas &&
+ sAliveDisplayItemDatas->Contains(aData));
+ MOZ_RELEASE_ASSERT(aData->mLayer);
+ return aData;
+}
+
+void* DisplayItemData::operator new(size_t sz, nsPresContext* aPresContext) {
+ // Check the recycle list first.
+ return aPresContext->PresShell()->AllocateByObjectID(
+ eArenaObjectID_DisplayItemData, sz);
+}
+
+/**
+ * This is the userdata we associate with a layer manager.
+ */
+class LayerManagerData : public LayerUserData {
+ public:
+ explicit LayerManagerData(LayerManager* aManager)
+ : mLayerManager(aManager),
+#ifdef DEBUG_DISPLAY_ITEM_DATA
+ mParent(nullptr),
+#endif
+ mInvalidateAllLayers(false) {
+ MOZ_COUNT_CTOR(LayerManagerData);
+ }
+ ~LayerManagerData() override { MOZ_COUNT_DTOR(LayerManagerData); }
+
+#ifdef DEBUG_DISPLAY_ITEM_DATA
+ void Dump(const char* aPrefix = "") {
+ printf_stderr("%sLayerManagerData %p\n", aPrefix, this);
+
+ for (auto& data : mDisplayItems) {
+ nsAutoCString prefix;
+ prefix += aPrefix;
+ prefix += " ";
+
+ const char* layerState;
+ switch (data->mLayerState) {
+ case LayerState::LAYER_NONE:
+ layerState = "LAYER_NONE";
+ break;
+ case LayerState::LAYER_INACTIVE:
+ layerState = "LAYER_INACTIVE";
+ break;
+ case LayerState::LAYER_ACTIVE:
+ layerState = "LAYER_ACTIVE";
+ break;
+ case LayerState::LAYER_ACTIVE_FORCE:
+ layerState = "LAYER_ACTIVE_FORCE";
+ break;
+ case LayerState::LAYER_ACTIVE_EMPTY:
+ layerState = "LAYER_ACTIVE_EMPTY";
+ break;
+ case LayerState::LAYER_SVG_EFFECTS:
+ layerState = "LAYER_SVG_EFFECTS";
+ break;
+ }
+ uint32_t mask = (1 << TYPE_BITS) - 1;
+
+ nsAutoCString str;
+ str += prefix;
+ str += nsPrintfCString("Frame %p ", data->mFrameList[0]);
+ str += nsDisplayItem::DisplayItemTypeName(
+ static_cast<nsDisplayItem::Type>(data->mDisplayItemKey & mask));
+ if ((data->mDisplayItemKey >> TYPE_BITS)) {
+ str += nsPrintfCString("(%i)", data->mDisplayItemKey >> TYPE_BITS);
+ }
+ str += nsPrintfCString(", %s, Layer %p", layerState, data->mLayer.get());
+ if (data->mOptLayer) {
+ str += nsPrintfCString(", OptLayer %p", data->mOptLayer.get());
+ }
+ if (data->mInactiveManager) {
+ str += nsPrintfCString(", InactiveLayerManager %p",
+ data->mInactiveManager.get());
+ }
+ str += "\n";
+
+ printf_stderr("%s", str.get());
+
+ if (data->mInactiveManager) {
+ prefix += " ";
+ printf_stderr("%sDumping inactive layer info:\n", prefix.get());
+ LayerManagerData* lmd = static_cast<LayerManagerData*>(
+ data->mInactiveManager->GetUserData(&gLayerManagerUserData));
+ lmd->Dump(prefix.get());
+ }
+ }
+ }
+#endif
+
+ /**
+ * Tracks which frames have layers associated with them.
+ */
+ LayerManager* mLayerManager;
+#ifdef DEBUG_DISPLAY_ITEM_DATA
+ LayerManagerData* mParent;
+#endif
+ std::vector<RefPtr<DisplayItemData>> mDisplayItems;
+ bool mInvalidateAllLayers;
+};
+
+/* static */
+void FrameLayerBuilder::DestroyDisplayItemDataFor(nsIFrame* aFrame) {
+ RemoveFrameFromLayerManager(aFrame, aFrame->DisplayItemData());
+ aFrame->DisplayItemData().Clear();
+
+ // Destroying a WebRenderUserDataTable can cause destruction of other objects
+ // which can remove frame properties in their destructor. If we delete a frame
+ // property it runs the destructor of the stored object in the middle of
+ // updating the frame property table, so if the destruction of that object
+ // causes another update to the frame property table it would leave the frame
+ // property table in an inconsistent state. So we remove it from the table and
+ // then destroy it. (bug 1530657)
+ WebRenderUserDataTable* userDataTable =
+ aFrame->TakeProperty(WebRenderUserDataProperty::Key());
+ if (userDataTable) {
+ for (auto iter = userDataTable->Iter(); !iter.Done(); iter.Next()) {
+ iter.UserData()->RemoveFromTable();
+ }
+ delete userDataTable;
+ }
+}
+
+/**
+ * We keep a stack of these to represent the PaintedLayers that are
+ * currently available to have display items added to.
+ * We use a stack here because as much as possible we want to
+ * assign display items to existing PaintedLayers, and to the lowest
+ * PaintedLayer in z-order. This reduces the number of layers and
+ * makes it more likely a display item will be rendered to an opaque
+ * layer, giving us the best chance of getting subpixel AA.
+ */
+class PaintedLayerData {
+ public:
+ PaintedLayerData()
+ : mAnimatedGeometryRoot(nullptr),
+ mASR(nullptr),
+ mClipChain(nullptr),
+ mReferenceFrame(nullptr),
+ mLayer(nullptr),
+ mSolidColor(NS_RGBA(0, 0, 0, 0)),
+ mIsSolidColorInVisibleRegion(false),
+ mNeedComponentAlpha(false),
+ mForceTransparentSurface(false),
+ mHideAllLayersBelow(false),
+ mOpaqueForAnimatedGeometryRootParent(false),
+ mBackfaceHidden(false),
+ mDTCRequiresTargetConfirmation(false),
+ mImage(nullptr),
+ mItemClip(nullptr),
+ mNewChildLayersIndex(-1)
+#ifdef DEBUG
+ ,
+ mTransformLevel(0)
+#endif
+ {
+ }
+
+ PaintedLayerData(PaintedLayerData&& aRhs) = default;
+
+ ~PaintedLayerData() { MOZ_ASSERT(mTransformLevel == 0); }
+
+#ifdef MOZ_DUMP_PAINTING
+ /**
+ * Keep track of important decisions for debugging.
+ */
+ nsCString mLog;
+
+# define FLB_LOG_PAINTED_LAYER_DECISION(pld, ...) \
+ if (StaticPrefs::layers_dump_decision()) { \
+ pld->mLog.AppendPrintf("\t\t\t\t"); \
+ pld->mLog.AppendPrintf(__VA_ARGS__); \
+ }
+#else
+# define FLB_LOG_PAINTED_LAYER_DECISION(...)
+#endif
+
+ /**
+ * Disables component alpha for |aItem| if the component alpha bounds are not
+ * contained in |mOpaqueRegion|. Alternatively if possible, sets
+ * |mNeedComponentAlpha| to true for this PaintedLayerData.
+ */
+ bool SetupComponentAlpha(ContainerState* aState, nsPaintedDisplayItem* aItem,
+ const nsIntRect& aVisibleRect,
+ const TransformClipNode* aTransform);
+
+ /**
+ * Record that an item has been added to the PaintedLayer, so we
+ * need to update our regions.
+ * @param aVisibleRect the area of the item that's visible
+ */
+ void Accumulate(ContainerState* aState, nsPaintedDisplayItem* aItem,
+ const nsIntRect& aVisibleRect, const nsRect& aContentRect,
+ const DisplayItemClip& aClip, LayerState aLayerState,
+ nsDisplayList* aList, DisplayItemEntryType aType,
+ nsTArray<size_t>& aOpacityIndices,
+ const RefPtr<TransformClipNode>& aTransform);
+
+ UniquePtr<InactiveLayerData> CreateInactiveLayerData(
+ ContainerState* aState, nsPaintedDisplayItem* aItem,
+ DisplayItemData* aData);
+
+ /**
+ * Updates the status of |mTransform| and |aOpacityIndices|, based on |aType|.
+ */
+ void UpdateEffectStatus(DisplayItemEntryType aType,
+ nsTArray<size_t>& aOpacityIndices);
+
+ AnimatedGeometryRoot* GetAnimatedGeometryRoot() {
+ return mAnimatedGeometryRoot;
+ }
+
+ /**
+ * A region including the horizontal pan, vertical pan, and no action regions.
+ */
+ nsRegion CombinedTouchActionRegion();
+
+ /**
+ * Add the given hit test info to the hit regions for this PaintedLayer.
+ */
+ void AccumulateHitTestItem(ContainerState* aState, nsDisplayItem* aItem,
+ const DisplayItemClip& aClip,
+ TransformClipNode* aTransform);
+
+ void HitRegionsUpdated();
+
+ /**
+ * If this represents only a nsDisplayImage, and the image type supports being
+ * optimized to an ImageLayer, returns true.
+ */
+ bool CanOptimizeToImageLayer(nsDisplayListBuilder* aBuilder);
+
+ /**
+ * If this represents only a nsDisplayImage, and the image type supports being
+ * optimized to an ImageLayer, returns an ImageContainer for the underlying
+ * image if one is available.
+ */
+ already_AddRefed<ImageContainer> GetContainerForImageLayer(
+ nsDisplayListBuilder* aBuilder);
+
+ bool VisibleAboveRegionIntersects(const nsIntRegion& aRegion) const {
+ return !mVisibleAboveRegion.Intersect(aRegion).IsEmpty();
+ }
+ bool VisibleRegionIntersects(const nsIntRegion& aRegion) const {
+ return !mVisibleRegion.Intersect(aRegion).IsEmpty();
+ }
+
+ /**
+ * The owning ContainerState that created this PaintedLayerData.
+ */
+ ContainerState* mState;
+
+ /**
+ * The region of visible content in the layer, relative to the
+ * container layer (which is at the snapped top-left of the display
+ * list reference frame).
+ */
+ nsIntRegion mVisibleRegion;
+ /**
+ * The region of visible content in the layer that is opaque.
+ * Same coordinate system as mVisibleRegion.
+ */
+ nsIntRegion mOpaqueRegion;
+ /**
+ * The definitely-hit region for this PaintedLayer.
+ */
+ nsRegion mHitRegion;
+ /**
+ * The maybe-hit region for this PaintedLayer.
+ */
+ nsRegion mMaybeHitRegion;
+ /**
+ * The dispatch-to-content hit region for this PaintedLayer.
+ */
+ nsRegion mDispatchToContentHitRegion;
+ /**
+ * The region for this PaintedLayer that is sensitive to events
+ * but disallows panning and zooming. This is an approximation
+ * and any deviation from the true region will be part of the
+ * mDispatchToContentHitRegion.
+ */
+ nsRegion mNoActionRegion;
+ /**
+ * The region for this PaintedLayer that is sensitive to events and
+ * allows horizontal panning but not zooming. This is an approximation
+ * and any deviation from the true region will be part of the
+ * mDispatchToContentHitRegion.
+ */
+ nsRegion mHorizontalPanRegion;
+ /**
+ * The region for this PaintedLayer that is sensitive to events and
+ * allows vertical panning but not zooming. This is an approximation
+ * and any deviation from the true region will be part of the
+ * mDispatchToContentHitRegion.
+ */
+ nsRegion mVerticalPanRegion;
+
+ bool mCollapsedTouchActions = false;
+ /**
+ * Scaled versions of the bounds of mHitRegion and mMaybeHitRegion.
+ * We store these because FindPaintedLayerFor() needs to consume them
+ * in this form, and it's a hot code path so we don't want to scale
+ * them inside that function.
+ */
+ nsIntRect mScaledHitRegionBounds;
+ nsIntRect mScaledMaybeHitRegionBounds;
+ /**
+ * The "active scrolled root" for all content in the layer. Must
+ * be non-null; all content in a PaintedLayer must have the same
+ * active scrolled root.
+ */
+ AnimatedGeometryRoot* mAnimatedGeometryRoot;
+ const ActiveScrolledRoot* mASR;
+ /**
+ * The chain of clips that should apply to this layer.
+ */
+ const DisplayItemClipChain* mClipChain;
+ /**
+ * The offset between mAnimatedGeometryRoot and the reference frame.
+ */
+ nsPoint mAnimatedGeometryRootOffset;
+ /**
+ * If non-null, the frame from which we'll extract "fixed positioning"
+ * metadata for this layer. This can be a position:fixed frame or a viewport
+ * frame; the latter case is used for background-attachment:fixed content.
+ */
+ const nsIFrame* mReferenceFrame;
+ PaintedLayer* mLayer;
+ /**
+ * If mIsSolidColorInVisibleRegion is true, this is the color of the visible
+ * region.
+ */
+ nscolor mSolidColor;
+ /**
+ * True if every pixel in mVisibleRegion will have color mSolidColor.
+ */
+ bool mIsSolidColorInVisibleRegion;
+ /**
+ * True if there is any text visible in the layer that's over
+ * transparent pixels in the layer.
+ */
+ bool mNeedComponentAlpha;
+ /**
+ * Set if the layer should be treated as transparent, even if its entire
+ * area is covered by opaque display items. For example, this needs to
+ * be set if something is going to "punch holes" in the layer by clearing
+ * part of its surface.
+ */
+ bool mForceTransparentSurface;
+ /**
+ * Set if all layers below this PaintedLayer should be hidden.
+ */
+ bool mHideAllLayersBelow;
+ /**
+ * Set if the opaque region for this layer can be applied to the parent
+ * animated geometry root of this layer's animated geometry root.
+ * We set this when a PaintedLayer's animated geometry root is a scrollframe
+ * and the PaintedLayer completely fills the displayport of the scrollframe.
+ */
+ bool mOpaqueForAnimatedGeometryRootParent;
+ /**
+ * Set if the backface of this region is hidden to the user.
+ * Content that backface is hidden should not be draw on the layer
+ * with visible backface.
+ */
+ bool mBackfaceHidden;
+ /**
+ * Set to true if events targeting the dispatch-to-content region
+ * require target confirmation.
+ * See CompositorHitTestFlags::eRequiresTargetConfirmation and
+ * EventRegions::mDTCRequiresTargetConfirmation.
+ */
+ bool mDTCRequiresTargetConfirmation;
+ /**
+ * Stores the pointer to the nsDisplayImage if we want to
+ * convert this to an ImageLayer.
+ */
+ nsDisplayImageContainer* mImage;
+ /**
+ * Stores the clip that we need to apply to the image or, if there is no
+ * image, a clip for SOME item in the layer. There is no guarantee which
+ * item's clip will be stored here and mItemClip should not be used to clip
+ * the whole layer - only some part of the clip should be used, as determined
+ * by PaintedDisplayItemLayerUserData::GetCommonClipCount() - which may even
+ * be no part at all.
+ */
+ const DisplayItemClip* mItemClip;
+ /**
+ * Index of this layer in mNewChildLayers.
+ */
+ int32_t mNewChildLayersIndex;
+ /**
+ * The region of visible content above the layer and below the
+ * next PaintedLayerData currently in the stack, if any.
+ * This is a conservative approximation: it contains the true region.
+ */
+ nsIntRegion mVisibleAboveRegion;
+ /**
+ * All the display items that have been assigned to this painted layer.
+ * These items get added by Accumulate().
+ */
+ std::vector<AssignedDisplayItem> mAssignedDisplayItems;
+
+#ifdef DEBUG
+ /**
+ * Tracks the level of transform to ensure balanced PUSH/POP markers.
+ */
+ int mTransformLevel;
+#endif
+};
+
+struct NewLayerEntry {
+ NewLayerEntry()
+ : mAnimatedGeometryRoot(nullptr),
+ mASR(nullptr),
+ mClipChain(nullptr),
+ mScrollMetadataASR(nullptr),
+ mLayerContentsVisibleRect(0, 0, -1, -1),
+ mLayerState(LayerState::LAYER_INACTIVE),
+ mHideAllLayersBelow(false),
+ mOpaqueForAnimatedGeometryRootParent(false),
+ mUntransformedVisibleRegion(false),
+ mIsFixedToRootScrollFrame(false) {}
+ // mLayer is null if the previous entry is for a PaintedLayer that hasn't
+ // been optimized to some other form (yet).
+ RefPtr<Layer> mLayer;
+ AnimatedGeometryRoot* mAnimatedGeometryRoot;
+ const ActiveScrolledRoot* mASR;
+ const DisplayItemClipChain* mClipChain;
+ const ActiveScrolledRoot* mScrollMetadataASR;
+ // If non-null, this ScrollMetadata is set to the be the first ScrollMetadata
+ // on the layer.
+ UniquePtr<ScrollMetadata> mBaseScrollMetadata;
+ // The following are only used for retained layers (for occlusion
+ // culling of those layers). These regions are all relative to the
+ // container reference frame.
+ nsIntRegion mVisibleRegion;
+ nsIntRegion mOpaqueRegion;
+ // This rect is in the layer's own coordinate space. The computed visible
+ // region for the layer cannot extend beyond this rect.
+ nsIntRect mLayerContentsVisibleRect;
+ LayerState mLayerState;
+ bool mHideAllLayersBelow;
+ // When mOpaqueForAnimatedGeometryRootParent is true, the opaque region of
+ // this layer is opaque in the same position even subject to the animation of
+ // geometry of mAnimatedGeometryRoot. For example when mAnimatedGeometryRoot
+ // is a scrolled frame and the scrolled content is opaque everywhere in the
+ // displayport, we can set this flag.
+ // When this flag is set, we can treat this opaque region as covering
+ // content whose animated geometry root is the animated geometry root for
+ // mAnimatedGeometryRoot->GetParent().
+ bool mOpaqueForAnimatedGeometryRootParent;
+
+ // mVisibleRegion is relative to the associated frame before
+ // transform.
+ bool mUntransformedVisibleRegion;
+ bool mIsFixedToRootScrollFrame;
+};
+
+class PaintedLayerDataTree;
+
+/**
+ * This is tree node type for PaintedLayerDataTree.
+ * Each node corresponds to a different animated geometry root, and contains
+ * a stack of PaintedLayerDatas, in bottom-to-top order.
+ * There is at most one node per animated geometry root. The ancestor and
+ * descendant relations in PaintedLayerDataTree tree mirror those in the frame
+ * tree.
+ * Each node can have clip that describes the potential extents that items in
+ * this node can cover. If mHasClip is false, it means that the node's contents
+ * can move anywhere.
+ * Testing against the clip instead of the node's actual contents has the
+ * advantage that the node's contents can move or animate without affecting
+ * content in other nodes. So we don't need to re-layerize during animations
+ * (sync or async), and during async animations everything is guaranteed to
+ * look correct.
+ * The contents of a node's PaintedLayerData stack all share the node's
+ * animated geometry root. The child nodes are on top of the PaintedLayerData
+ * stack, in z-order, and the clip rects of the child nodes are allowed to
+ * intersect with the visible region or visible above region of their parent
+ * node's PaintedLayerDatas.
+ */
+class PaintedLayerDataNode {
+ public:
+ PaintedLayerDataNode(PaintedLayerDataTree& aTree,
+ PaintedLayerDataNode* aParent,
+ AnimatedGeometryRoot* aAnimatedGeometryRoot);
+ ~PaintedLayerDataNode();
+
+ AnimatedGeometryRoot* GetAnimatedGeometryRoot() const {
+ return mAnimatedGeometryRoot;
+ }
+
+ /**
+ * Whether this node's contents can potentially intersect aRect.
+ * aRect is in our tree's ContainerState's coordinate space.
+ */
+ bool Intersects(const nsIntRect& aRect) const {
+ return !mHasClip || mClipRect.Intersects(aRect);
+ }
+
+ /**
+ * Create a PaintedLayerDataNode for aAnimatedGeometryRoot, add it to our
+ * children, and return it.
+ */
+ PaintedLayerDataNode* AddChildNodeFor(
+ AnimatedGeometryRoot* aAnimatedGeometryRoot);
+
+ /**
+ * Find a PaintedLayerData in our mPaintedLayerDataStack that aItem can be
+ * added to. Creates a new PaintedLayerData by calling
+ * aNewPaintedLayerCallback if necessary.
+ */
+ template <typename NewPaintedLayerCallbackType>
+ PaintedLayerData* FindPaintedLayerFor(
+ const nsIntRect& aVisibleRect, bool aBackfaceHidden,
+ const ActiveScrolledRoot* aASR, const DisplayItemClipChain* aClipChain,
+ NewPaintedLayerCallbackType aNewPaintedLayerCallback);
+
+ /**
+ * Find an opaque background color for aRegion. Pulls a color from the parent
+ * geometry root if appropriate, but only if that color is present underneath
+ * the whole clip of this node, so that this node's contents can animate or
+ * move (possibly async) without having to change the background color.
+ * @param aUnderIndex Searching will start in mPaintedLayerDataStack right
+ * below aUnderIndex.
+ */
+ enum { ABOVE_TOP = -1 };
+ nscolor FindOpaqueBackgroundColor(const nsIntRegion& aRegion,
+ int32_t aUnderIndex = ABOVE_TOP) const;
+ /**
+ * Same as FindOpaqueBackgroundColor, but only returns a color if absolutely
+ * nothing is in between, so that it can be used for a layer that can move
+ * anywhere inside our clip.
+ */
+ nscolor FindOpaqueBackgroundColorCoveringEverything() const;
+
+ /**
+ * Adds aRect to this node's top PaintedLayerData's mVisibleAboveRegion,
+ * or mVisibleAboveBackgroundRegion if mPaintedLayerDataStack is empty.
+ */
+ void AddToVisibleAboveRegion(const nsIntRect& aRect);
+ /**
+ * Call this if all of our existing content can potentially be covered, so
+ * nothing can merge with it and all new content needs to create new items
+ * on top. This will finish all of our children and pop our whole
+ * mPaintedLayerDataStack.
+ */
+ void SetAllDrawingAbove();
+
+ /**
+ * Finish this node: Finish all children, finish our PaintedLayer contents,
+ * and (if requested) adjust our parent's visible above region to include
+ * our clip.
+ */
+ void Finish(bool aParentNeedsAccurateVisibleAboveRegion);
+
+ /**
+ * Finish any children that intersect aRect.
+ */
+ void FinishChildrenIntersecting(const nsIntRect& aRect);
+
+ /**
+ * Finish all children.
+ */
+ void FinishAllChildren() { FinishAllChildren(true); }
+
+ protected:
+ /**
+ * Finish all items in mPaintedLayerDataStack and clear the stack.
+ */
+ void PopAllPaintedLayerData();
+ /**
+ * Finish all of our child nodes, but don't touch mPaintedLayerDataStack.
+ */
+ void FinishAllChildren(bool aThisNodeNeedsAccurateVisibleAboveRegion);
+ /**
+ * Pass off opaque background color searching to our parent node, if we have
+ * one.
+ */
+ nscolor FindOpaqueBackgroundColorInParentNode() const;
+
+ PaintedLayerDataTree& mTree;
+ PaintedLayerDataNode* mParent;
+ AnimatedGeometryRoot* mAnimatedGeometryRoot;
+
+ /**
+ * Our contents: a PaintedLayerData stack and our child nodes.
+ */
+ AutoTArray<PaintedLayerData, 3> mPaintedLayerDataStack;
+
+ /**
+ * UniquePtr is used here in the sense of "unique ownership", i.e. there is
+ * only one owner. Not in the sense of "this is the only pointer to the
+ * node": There are two other, non-owning, pointers to our child nodes: The
+ * node's respective children point to their parent node with their mParent
+ * pointer, and the tree keeps a map of animated geometry root to node in its
+ * mNodes member. These outside pointers are the reason that mChildren isn't
+ * just an nsTArray<PaintedLayerDataNode> (since the pointers would become
+ * invalid whenever the array expands its capacity).
+ */
+ nsTArray<UniquePtr<PaintedLayerDataNode>> mChildren;
+
+ /**
+ * The region that's covered between our "background" and the bottom of
+ * mPaintedLayerDataStack. This is used to indicate whether we can pull
+ * a background color from our parent node. If mVisibleAboveBackgroundRegion
+ * should be considered infinite, mAllDrawingAboveBackground will be true and
+ * the value of mVisibleAboveBackgroundRegion will be meaningless.
+ */
+ nsIntRegion mVisibleAboveBackgroundRegion;
+
+ /**
+ * Our clip, if we have any. If not, that means we can move anywhere, and
+ * mHasClip will be false and mClipRect will be meaningless.
+ */
+ nsIntRect mClipRect;
+ bool mHasClip;
+
+ /**
+ * Whether mVisibleAboveBackgroundRegion should be considered infinite.
+ */
+ bool mAllDrawingAboveBackground;
+};
+
+class ContainerState;
+
+/**
+ * A tree of PaintedLayerDataNodes. At any point in time, the tree only
+ * contains nodes for animated geometry roots that new items can potentially
+ * merge into. Any time content is added on top that overlaps existing things
+ * in such a way that we no longer want to merge new items with some existing
+ * content, that existing content gets "finished".
+ * The public-facing methods of this class are FindPaintedLayerFor,
+ * AddingOwnLayer, and Finish. The other public methods are for
+ * PaintedLayerDataNode.
+ * The tree calls out to its containing ContainerState for some things.
+ * All coordinates / rects in the tree or the tree nodes are in the
+ * ContainerState's coordinate space, i.e. relative to the reference frame and
+ * in layer pixels.
+ * The clip rects of sibling nodes never overlap. This is ensured by finishing
+ * existing nodes before adding new ones, if this property were to be violated.
+ * The root tree node doesn't get finished until the ContainerState is
+ * finished.
+ * The tree's root node is always the root reference frame of the builder. We
+ * don't stop at the container state's mContainerAnimatedGeometryRoot because
+ * some of our contents can have animated geometry roots that are not
+ * descendants of the container's animated geometry root. Every animated
+ * geometry root we encounter for our contents needs to have a defined place in
+ * the tree.
+ */
+class PaintedLayerDataTree {
+ public:
+ PaintedLayerDataTree(ContainerState& aContainerState,
+ nscolor& aBackgroundColor)
+ : mContainerState(aContainerState),
+ mContainerUniformBackgroundColor(aBackgroundColor),
+ mForInactiveLayer(false) {}
+
+ ~PaintedLayerDataTree() {
+ MOZ_ASSERT(!mRoot);
+ MOZ_ASSERT(mNodes.Count() == 0);
+ }
+
+ void InitializeForInactiveLayer(AnimatedGeometryRoot* aAnimatedGeometryRoot);
+
+ /**
+ * Notify our contents that some non-PaintedLayer content has been added.
+ * *aRect needs to be a rectangle that doesn't move with respect to
+ * aAnimatedGeometryRoot and that contains the added item.
+ * If aRect is null, the extents will be considered infinite.
+ * If aOutUniformBackgroundColor is non-null, it will be set to an opaque
+ * color that can be pulled into the background of the added content, or
+ * transparent if that is not possible.
+ */
+ void AddingOwnLayer(AnimatedGeometryRoot* aAnimatedGeometryRoot,
+ const nsIntRect* aRect,
+ nscolor* aOutUniformBackgroundColor);
+
+ /**
+ * Find a PaintedLayerData for aItem. This can either be an existing
+ * PaintedLayerData from inside a node in our tree, or a new one that gets
+ * created by a call out to aNewPaintedLayerCallback.
+ */
+ template <typename NewPaintedLayerCallbackType>
+ PaintedLayerData* FindPaintedLayerFor(
+ AnimatedGeometryRoot* aAnimatedGeometryRoot,
+ const ActiveScrolledRoot* aASR, const DisplayItemClipChain* aClipChain,
+ const nsIntRect& aVisibleRect, const bool aBackfaceHidden,
+ NewPaintedLayerCallbackType aNewPaintedLayerCallback);
+
+ /**
+ * Finish everything.
+ */
+ void Finish();
+
+ /**
+ * Get the parent animated geometry root of aAnimatedGeometryRoot.
+ * That's either aAnimatedGeometryRoot's animated geometry root, or, if
+ * that's aAnimatedGeometryRoot itself, then it's the animated geometry
+ * root for aAnimatedGeometryRoot's cross-doc parent frame.
+ */
+ AnimatedGeometryRoot* GetParentAnimatedGeometryRoot(
+ AnimatedGeometryRoot* aAnimatedGeometryRoot);
+
+ /**
+ * Whether aAnimatedGeometryRoot has an intrinsic clip that doesn't move with
+ * respect to aAnimatedGeometryRoot's parent animated geometry root.
+ * If aAnimatedGeometryRoot is a scroll frame, this will be the scroll frame's
+ * scroll port, otherwise there is no clip.
+ * This method doesn't have much to do with PaintedLayerDataTree, but this is
+ * where we have easy access to a display list builder, which we use to get
+ * the clip rect result into the right coordinate space.
+ */
+ bool IsClippedWithRespectToParentAnimatedGeometryRoot(
+ AnimatedGeometryRoot* aAnimatedGeometryRoot, nsIntRect* aOutClip);
+
+ /**
+ * Called by PaintedLayerDataNode when it is finished, so that we can drop
+ * our pointers to it.
+ */
+ void NodeWasFinished(AnimatedGeometryRoot* aAnimatedGeometryRoot);
+
+ nsDisplayListBuilder* Builder() const;
+ ContainerState& ContState() const { return mContainerState; }
+ nscolor UniformBackgroundColor() const {
+ return mContainerUniformBackgroundColor;
+ }
+
+ protected:
+ /**
+ * Finish all nodes that potentially intersect *aRect, where *aRect is a rect
+ * that doesn't move with respect to aAnimatedGeometryRoot.
+ * If aRect is null, *aRect will be considered infinite.
+ */
+ void FinishPotentiallyIntersectingNodes(
+ AnimatedGeometryRoot* aAnimatedGeometryRoot, const nsIntRect* aRect);
+
+ /**
+ * Make sure that there is a node for aAnimatedGeometryRoot and all of its
+ * ancestor geometry roots. Return the node for aAnimatedGeometryRoot.
+ */
+ PaintedLayerDataNode* EnsureNodeFor(
+ AnimatedGeometryRoot* aAnimatedGeometryRoot);
+
+ /**
+ * Find an existing node in the tree for an ancestor of aAnimatedGeometryRoot.
+ * *aOutAncestorChild will be set to the last ancestor that was encountered
+ * in the search up from aAnimatedGeometryRoot; it will be a child animated
+ * geometry root of the result, if neither are null.
+ */
+ PaintedLayerDataNode* FindNodeForAncestorAnimatedGeometryRoot(
+ AnimatedGeometryRoot* aAnimatedGeometryRoot,
+ AnimatedGeometryRoot** aOutAncestorChild);
+
+ ContainerState& mContainerState;
+ Maybe<PaintedLayerDataNode> mRoot;
+
+ /**
+ * The uniform opaque color from behind this container layer, or
+ * NS_RGBA(0,0,0,0) if the background behind this container layer is not
+ * uniform and opaque. This color can be pulled into PaintedLayers that are
+ * directly above the background.
+ */
+ nscolor mContainerUniformBackgroundColor;
+
+ /**
+ * A hash map for quick access the node belonging to a particular animated
+ * geometry root.
+ */
+ nsDataHashtable<nsPtrHashKey<AnimatedGeometryRoot>, PaintedLayerDataNode*>
+ mNodes;
+
+ bool mForInactiveLayer;
+};
+
+/**
+ * This is a helper object used to build up the layer children for
+ * a ContainerLayer.
+ */
+class ContainerState {
+ public:
+ ContainerState(nsDisplayListBuilder* aBuilder, LayerManager* aManager,
+ FrameLayerBuilder* aLayerBuilder, nsIFrame* aContainerFrame,
+ nsDisplayItem* aContainerItem, const nsRect& aContainerBounds,
+ ContainerLayer* aContainerLayer,
+ const ContainerLayerParameters& aParameters,
+ nscolor aBackgroundColor,
+ const ActiveScrolledRoot* aContainerASR,
+ const ActiveScrolledRoot* aContainerScrollMetadataASR,
+ const ActiveScrolledRoot* aContainerCompositorASR)
+ : mBuilder(aBuilder),
+ mManager(aManager),
+ mLayerBuilder(aLayerBuilder),
+ mContainerFrame(aContainerFrame),
+ mContainerLayer(aContainerLayer),
+ mContainerBounds(aContainerBounds),
+ mContainerASR(aContainerASR),
+ mContainerScrollMetadataASR(aContainerScrollMetadataASR),
+ mContainerCompositorASR(aContainerCompositorASR),
+ mParameters(aParameters),
+ mPaintedLayerDataTree(*this, aBackgroundColor),
+ mLastDisplayPortAGR(nullptr),
+ mContainerItem(aContainerItem) {
+ nsPresContext* presContext = aContainerFrame->PresContext();
+ mAppUnitsPerDevPixel = presContext->AppUnitsPerDevPixel();
+ mContainerReferenceFrame = const_cast<nsIFrame*>(
+ aContainerItem ? aContainerItem->ReferenceFrameForChildren()
+ : mBuilder->FindReferenceFrameFor(mContainerFrame));
+ bool isAtRoot = !aContainerItem ||
+ (aContainerItem->Frame() == mBuilder->RootReferenceFrame());
+ MOZ_ASSERT(!isAtRoot ||
+ mContainerReferenceFrame == mBuilder->RootReferenceFrame());
+ mContainerAnimatedGeometryRoot =
+ isAtRoot ? aBuilder->GetRootAnimatedGeometryRoot()
+ : aContainerItem->GetAnimatedGeometryRoot();
+ MOZ_ASSERT(
+ !mBuilder->IsPaintingToWindow() ||
+ nsLayoutUtils::IsAncestorFrameCrossDoc(
+ mBuilder->RootReferenceFrame(), *mContainerAnimatedGeometryRoot));
+ // When AllowResidualTranslation is false, display items will be drawn
+ // scaled with a translation by integer pixels, so we know how the snapping
+ // will work.
+ mSnappingEnabled = aManager->IsSnappingEffectiveTransforms() &&
+ !mParameters.AllowResidualTranslation();
+ CollectOldLayers();
+ }
+
+ /**
+ * This is the method that actually walks a display list and builds
+ * the child layers.
+ */
+ void ProcessDisplayItems(nsDisplayList* aList);
+ /**
+ * This finalizes all the open PaintedLayers by popping every element off
+ * mPaintedLayerDataStack, then sets the children of the container layer
+ * to be all the layers in mNewChildLayers in that order and removes any
+ * layers as children of the container that aren't in mNewChildLayers.
+ * @param aTextContentFlags if any child layer has CONTENT_COMPONENT_ALPHA,
+ * set *aTextContentFlags to CONTENT_COMPONENT_ALPHA
+ */
+ void Finish(uint32_t* aTextContentFlags,
+ const nsIntRect& aContainerPixelBounds,
+ nsDisplayList* aChildItems);
+
+ nscoord GetAppUnitsPerDevPixel() { return mAppUnitsPerDevPixel; }
+
+ nsIntRect ScaleToNearestPixels(const nsRect& aRect) const {
+ return aRect.ScaleToNearestPixels(mParameters.mXScale, mParameters.mYScale,
+ mAppUnitsPerDevPixel);
+ }
+ nsIntRect ScaleToOutsidePixels(const nsRect& aRect,
+ bool aSnap = false) const {
+ if (aRect.IsEmpty()) {
+ return nsIntRect();
+ }
+ if (aSnap && mSnappingEnabled) {
+ return ScaleToNearestPixels(aRect);
+ }
+ return aRect.ScaleToOutsidePixels(mParameters.mXScale, mParameters.mYScale,
+ mAppUnitsPerDevPixel);
+ }
+ nsIntRect ScaleToInsidePixels(const nsRect& aRect, bool aSnap = false) const {
+ if (aSnap && mSnappingEnabled) {
+ return ScaleToNearestPixels(aRect);
+ }
+ return aRect.ScaleToInsidePixels(mParameters.mXScale, mParameters.mYScale,
+ mAppUnitsPerDevPixel);
+ }
+ nsIntRegion ScaleRegionToNearestPixels(const nsRegion& aRegion) const {
+ return aRegion.ScaleToNearestPixels(
+ mParameters.mXScale, mParameters.mYScale, mAppUnitsPerDevPixel);
+ }
+ nsIntRegion ScaleRegionToInsidePixels(const nsRegion& aRegion,
+ bool aSnap = false) const {
+ if (aSnap && mSnappingEnabled) {
+ return ScaleRegionToNearestPixels(aRegion);
+ }
+ return aRegion.ScaleToInsidePixels(mParameters.mXScale, mParameters.mYScale,
+ mAppUnitsPerDevPixel);
+ }
+
+ nsIntRegion ScaleRegionToOutsidePixels(const nsRegion& aRegion,
+ bool aSnap = false) const {
+ if (aRegion.IsEmpty()) {
+ return nsIntRegion();
+ }
+ if (aSnap && mSnappingEnabled) {
+ return ScaleRegionToNearestPixels(aRegion);
+ }
+ return aRegion.ScaleToOutsidePixels(
+ mParameters.mXScale, mParameters.mYScale, mAppUnitsPerDevPixel);
+ }
+
+ nsIFrame* GetContainerFrame() const { return mContainerFrame; }
+ nsDisplayListBuilder* Builder() const { return mBuilder; }
+ FrameLayerBuilder* LayerBuilder() const { return mLayerBuilder; }
+
+ /**
+ * Check if we are currently inside an inactive layer.
+ */
+ bool IsInInactiveLayer() const {
+ return mLayerBuilder->GetContainingPaintedLayerData();
+ }
+
+ /**
+ * Sets aOuterVisibleRegion as aLayer's visible region.
+ * @param aOuterVisibleRegion
+ * is in the coordinate space of the container reference frame.
+ * @param aLayerContentsVisibleRect, if non-null, is in the layer's own
+ * coordinate system.
+ * @param aOuterUntransformed is true if the given aOuterVisibleRegion
+ * is already untransformed with the matrix of the layer.
+ */
+ void SetOuterVisibleRegionForLayer(
+ Layer* aLayer, const nsIntRegion& aOuterVisibleRegion,
+ const nsIntRect* aLayerContentsVisibleRect = nullptr,
+ bool aOuterUntransformed = false) const;
+
+ /**
+ * Try to determine whether the PaintedLayer aData has a single opaque color
+ * covering aRect. If successful, return that color, otherwise return
+ * NS_RGBA(0,0,0,0).
+ * If aRect turns out not to intersect any content in the layer,
+ * *aOutIntersectsLayer will be set to false.
+ */
+ nscolor FindOpaqueBackgroundColorInLayer(const PaintedLayerData* aData,
+ const nsIntRect& aRect,
+ bool* aOutIntersectsLayer) const;
+
+ /**
+ * Indicate that we are done adding items to the PaintedLayer represented by
+ * aData. Make sure that a real PaintedLayer exists for it, and set the final
+ * visible region and opaque-content.
+ */
+ template <typename FindOpaqueBackgroundColorCallbackType>
+ void FinishPaintedLayerData(
+ PaintedLayerData& aData,
+ FindOpaqueBackgroundColorCallbackType aFindOpaqueBackgroundColor);
+
+ protected:
+ friend class PaintedLayerData;
+ friend class FLBDisplayListIterator;
+
+ LayerManager::PaintedLayerCreationHint GetLayerCreationHint(
+ AnimatedGeometryRoot* aAnimatedGeometryRoot);
+
+ /**
+ * Creates a new PaintedLayer and sets up the transform on the PaintedLayer
+ * to account for scrolling.
+ */
+ already_AddRefed<PaintedLayer> CreatePaintedLayer(PaintedLayerData* aData);
+
+ /**
+ * Find a PaintedLayer for recycling, recycle it and prepare it for use, or
+ * return null if no suitable layer was found.
+ */
+ already_AddRefed<PaintedLayer> AttemptToRecyclePaintedLayer(
+ AnimatedGeometryRoot* aAnimatedGeometryRoot, nsDisplayItem* aItem,
+ const nsPoint& aTopLeft, const nsIFrame* aReferenceFrame);
+ /**
+ * Recycle aLayer and do any necessary invalidation.
+ */
+ PaintedDisplayItemLayerUserData* RecyclePaintedLayer(
+ PaintedLayer* aLayer, AnimatedGeometryRoot* aAnimatedGeometryRoot,
+ bool& didResetScrollPositionForLayerPixelAlignment);
+
+ /**
+ * Perform the last step of CreatePaintedLayer / AttemptToRecyclePaintedLayer:
+ * Initialize aData, set up the layer's transform for scrolling, and
+ * invalidate the layer for layer pixel alignment changes if necessary.
+ */
+ void PreparePaintedLayerForUse(
+ PaintedLayer* aLayer, PaintedDisplayItemLayerUserData* aData,
+ AnimatedGeometryRoot* aAnimatedGeometryRoot,
+ const nsIFrame* aReferenceFrame, const nsPoint& aTopLeft,
+ bool aDidResetScrollPositionForLayerPixelAlignment);
+
+ /**
+ * Attempt to prepare an ImageLayer based upon the provided PaintedLayerData.
+ * Returns nullptr on failure.
+ */
+ already_AddRefed<Layer> PrepareImageLayer(PaintedLayerData* aData);
+
+ /**
+ * Attempt to prepare a ColorLayer based upon the provided PaintedLayerData.
+ * Returns nullptr on failure.
+ */
+ already_AddRefed<Layer> PrepareColorLayer(PaintedLayerData* aData);
+
+ /**
+ * Grab the next recyclable ColorLayer, or create one if there are no
+ * more recyclable ColorLayers.
+ */
+ already_AddRefed<ColorLayer> CreateOrRecycleColorLayer(
+ PaintedLayer* aPainted);
+ /**
+ * Grab the next recyclable ImageLayer, or create one if there are no
+ * more recyclable ImageLayers.
+ */
+ already_AddRefed<ImageLayer> CreateOrRecycleImageLayer(
+ PaintedLayer* aPainted);
+ /**
+ * Grab a recyclable ImageLayer for use as a mask layer for aLayer (that is a
+ * mask layer which has been used for aLayer before), or create one if such
+ * a layer doesn't exist.
+ *
+ * Since mask layers can exist either on the layer directly, or as a side-
+ * attachment to FrameMetrics (for ancestor scrollframe clips), we key the
+ * recycle operation on both the originating layer and the mask layer's
+ * index in the layer, if any.
+ */
+ struct MaskLayerKey;
+ template <typename UserData>
+ already_AddRefed<ImageLayer> CreateOrRecycleMaskImageLayerFor(
+ const MaskLayerKey& aKey, UserData* (*aGetUserData)(Layer* aLayer),
+ void (*aSetDefaultUserData)(Layer* aLayer));
+ /**
+ * Grabs all PaintedLayers and ColorLayers from the ContainerLayer and makes
+ * them available for recycling.
+ */
+ void CollectOldLayers();
+ /**
+ * If aItem used to belong to a PaintedLayer, invalidates the area of
+ * aItem in that layer. If aNewLayer is a PaintedLayer, invalidates the area
+ * of aItem in that layer.
+ */
+ void InvalidateForLayerChange(nsDisplayItem* aItem, PaintedLayer* aNewLayer,
+ DisplayItemData* aData);
+ /**
+ * Returns true if aItem's opaque area (in aOpaque) covers the entire
+ * scrollable area of its presshell.
+ */
+ bool ItemCoversScrollableArea(nsDisplayItem* aItem, const nsRegion& aOpaque);
+
+ /**
+ * Set ScrollMetadata and scroll-induced clipping on aEntry's layer.
+ */
+ void SetupScrollingMetadata(NewLayerEntry* aEntry);
+
+ /**
+ * Applies occlusion culling.
+ * For each layer in mNewChildLayers, remove from its visible region the
+ * opaque regions of the layers at higher z-index, but only if they have
+ * the same animated geometry root and fixed-pos frame ancestor.
+ * The opaque region for the child layers that share the same animated
+ * geometry root as the container frame is returned in
+ * *aOpaqueRegionForContainer.
+ *
+ * Also sets scroll metadata on the layers.
+ */
+ void PostprocessRetainedLayers(nsIntRegion* aOpaqueRegionForContainer);
+
+ /**
+ * Computes the snapped opaque area of aItem. Sets aList's opaque flag
+ * if it covers the entire list bounds. Sets *aHideAllLayersBelow to true
+ * this item covers the entire viewport so that all layers below are
+ * permanently invisible.
+ */
+ nsIntRegion ComputeOpaqueRect(nsDisplayItem* aItem,
+ AnimatedGeometryRoot* aAnimatedGeometryRoot,
+ const ActiveScrolledRoot* aASR,
+ const DisplayItemClip& aClip,
+ nsDisplayList* aList, bool* aHideAllLayersBelow,
+ bool* aOpaqueForAnimatedGeometryRootParent);
+
+ /**
+ * Fills a PaintedLayerData object that is initialized for a layer that the
+ * current item will be assigned to. Also creates mNewChildLayers entries.
+ * @param aData The PaintedLayerData that will be filled.
+ * @param aVisibleRect The visible rect of the item.
+ * @param aAnimatedGeometryRoot The item's animated geometry root.
+ * @param aASR The active scrolled root that moves this
+ * PaintedLayer.
+ * @param aClipChain The clip chain that the compositor needs to
+ * apply to this layer.
+ * @param aScrollMetadataASR The leaf ASR for which scroll metadata needs
+ * to be set on the layer, because either the layer itself or its scrolled
+ * clip need to move with that ASR.
+ * @param aTopLeft The offset between aAnimatedGeometryRoot and
+ * the reference frame.
+ * @param aReferenceFrame The reference frame for the item.
+ * @param aBackfaceHidden The backface visibility for the item frame.
+ */
+ void NewPaintedLayerData(
+ PaintedLayerData* aData, AnimatedGeometryRoot* aAnimatedGeometryRoot,
+ const ActiveScrolledRoot* aASR, const DisplayItemClipChain* aClipChain,
+ const ActiveScrolledRoot* aScrollMetadataASR, const nsPoint& aTopLeft,
+ const nsIFrame* aReferenceFrame, const bool aBackfaceHidden);
+
+ /* Build a mask layer to represent the clipping region. Will return null if
+ * there is no clipping specified or a mask layer cannot be built.
+ * Builds an ImageLayer for the appropriate backend; the mask is relative to
+ * aLayer's visible region.
+ * aLayer is the layer to be clipped.
+ * relative to the container reference frame
+ * aRoundedRectClipCount is used when building mask layers for PaintedLayers,
+ */
+ void SetupMaskLayer(Layer* aLayer, const DisplayItemClip& aClip);
+
+ /**
+ * If |aClip| has rounded corners, create a mask layer for them, and
+ * add it to |aLayer|'s ancestor mask layers, returning an index into
+ * the array of ancestor mask layers. Returns an empty Maybe if
+ * |aClip| does not have rounded corners, or if no mask layer could
+ * be created.
+ */
+ Maybe<size_t> SetupMaskLayerForScrolledClip(Layer* aLayer,
+ const DisplayItemClip& aClip);
+
+ /**
+ * Create/find a mask layer with suitable size for aMaskItem to paint
+ * css-positioned-masking onto.
+ */
+ void SetupMaskLayerForCSSMask(Layer* aLayer,
+ nsDisplayMasksAndClipPaths* aMaskItem);
+
+ already_AddRefed<Layer> CreateMaskLayer(
+ Layer* aLayer, const DisplayItemClip& aClip,
+ const Maybe<size_t>& aForAncestorMaskLayer);
+
+ /**
+ * Get the display port for an AGR.
+ * The result would be cached for later reusing.
+ */
+ nsRect GetDisplayPortForAnimatedGeometryRoot(
+ AnimatedGeometryRoot* aAnimatedGeometryRoot);
+
+ nsDisplayListBuilder* mBuilder;
+ LayerManager* mManager;
+ FrameLayerBuilder* mLayerBuilder;
+ nsIFrame* mContainerFrame;
+ nsIFrame* mContainerReferenceFrame;
+ AnimatedGeometryRoot* mContainerAnimatedGeometryRoot;
+ ContainerLayer* mContainerLayer;
+ nsRect mContainerBounds;
+
+ // Due to the way we store scroll annotations in the layer tree, we need to
+ // keep track of three (possibly different) ASRs here.
+ // mContainerASR is the ASR of the container display item that this
+ // ContainerState was created for.
+ // mContainerScrollMetadataASR is the ASR of the leafmost scroll metadata
+ // that's in effect on mContainerLayer.
+ // mContainerCompositorASR is the ASR that mContainerLayer moves with on
+ // the compositor / APZ side, taking into account both the scroll meta data
+ // and the fixed position annotation on itself and its ancestors.
+ const ActiveScrolledRoot* mContainerASR;
+ const ActiveScrolledRoot* mContainerScrollMetadataASR;
+ const ActiveScrolledRoot* mContainerCompositorASR;
+#ifdef DEBUG
+ nsRect mAccumulatedChildBounds;
+#endif
+ ContainerLayerParameters mParameters;
+ /**
+ * The region of PaintedLayers that should be invalidated every time
+ * we recycle one.
+ */
+ nsIntRegion mInvalidPaintedContent;
+ PaintedLayerDataTree mPaintedLayerDataTree;
+ /**
+ * We collect the list of children in here. During ProcessDisplayItems,
+ * the layers in this array either have mContainerLayer as their parent,
+ * or no parent.
+ * PaintedLayers have two entries in this array: the second one is used only
+ * if the PaintedLayer is optimized away to a ColorLayer or ImageLayer. It's
+ * essential that this array is only appended to, since PaintedLayerData
+ * records the index of its PaintedLayer in this array.
+ */
+ typedef AutoTArray<NewLayerEntry, 1> AutoLayersArray;
+ AutoLayersArray mNewChildLayers;
+ nsTHashtable<nsRefPtrHashKey<PaintedLayer>>
+ mPaintedLayersAvailableForRecycling;
+ nscoord mAppUnitsPerDevPixel;
+ bool mSnappingEnabled;
+
+ struct MaskLayerKey {
+ MaskLayerKey() : mLayer(nullptr) {}
+ MaskLayerKey(Layer* aLayer, const Maybe<size_t>& aAncestorIndex)
+ : mLayer(aLayer), mAncestorIndex(aAncestorIndex) {}
+
+ PLDHashNumber Hash() const {
+ // Hash the layer and add the layer index to the hash.
+ return (NS_PTR_TO_UINT32(mLayer) >> 2) +
+ (mAncestorIndex ? (*mAncestorIndex + 1) : 0);
+ }
+ bool operator==(const MaskLayerKey& aOther) const {
+ return mLayer == aOther.mLayer && mAncestorIndex == aOther.mAncestorIndex;
+ }
+
+ Layer* mLayer;
+ Maybe<size_t> mAncestorIndex;
+ };
+
+ nsDataHashtable<nsGenericHashKey<MaskLayerKey>, RefPtr<ImageLayer>>
+ mRecycledMaskImageLayers;
+ // Keep display port of AGR to avoid wasting time on doing the same
+ // thing repeatly.
+ AnimatedGeometryRoot* mLastDisplayPortAGR;
+ nsRect mLastDisplayPortRect;
+
+ nsDisplayItem* mContainerItem;
+
+ // Cache ScrollMetadata so it doesn't need recomputed if the ASR and clip are
+ // unchanged. If mASR == nullptr then mMetadata is not valid.
+ struct CachedScrollMetadata {
+ const ActiveScrolledRoot* mASR;
+ const DisplayItemClip* mClip;
+ Maybe<ScrollMetadata> mMetadata;
+
+ CachedScrollMetadata() : mASR(nullptr), mClip(nullptr) {}
+ };
+ CachedScrollMetadata mCachedScrollMetadata;
+};
+
+class FLBDisplayListIterator : public FlattenedDisplayListIterator {
+ public:
+ FLBDisplayListIterator(nsDisplayListBuilder* aBuilder, nsDisplayList* aList,
+ ContainerState* aState)
+ : FlattenedDisplayListIterator(aBuilder, aList, false), mState(aState) {
+ MOZ_ASSERT(mState);
+
+ if (mState->mContainerItem) {
+ // Add container item hit test information for processing, if needed.
+ AddHitTestMarkerIfNeeded(mState->mContainerItem);
+ }
+
+ ResolveFlattening();
+ }
+
+ DisplayItemEntry GetNextEntry() {
+ if (!mMarkers.empty()) {
+ DisplayItemEntry entry = mMarkers.front();
+ mMarkers.pop_front();
+ return entry;
+ }
+
+ return DisplayItemEntry{GetNextItem(), DisplayItemEntryType::Item};
+ }
+
+ bool HasNext() const override {
+ return FlattenedDisplayListIterator::HasNext() || !mMarkers.empty();
+ }
+
+ private:
+ void AddHitTestMarkerIfNeeded(nsDisplayItem* aItem) {
+ if (aItem->HasHitTestInfo()) {
+ mMarkers.emplace_back(aItem, DisplayItemEntryType::HitTestInfo);
+ }
+ }
+
+ bool ShouldFlattenNextItem() override {
+ if (!FlattenedDisplayListIterator::ShouldFlattenNextItem()) {
+ return false;
+ }
+
+ nsDisplayItem* next = PeekNext();
+ const DisplayItemType type = next->GetType();
+
+ if (type == DisplayItemType::TYPE_SVG_WRAPPER) {
+ // We mark SetContainsSVG for the CONTENT_FRAME_TIME_WITH_SVG metric
+ if (RefPtr<LayerManager> lm = mState->mBuilder->GetWidgetLayerManager()) {
+ lm->SetContainsSVG(true);
+ }
+ }
+
+ if (!SupportsFlatteningWithMarkers(type)) {
+ return true;
+ }
+
+ if (type == DisplayItemType::TYPE_OPACITY &&
+ IsOpacityAppliedToChildren(next)) {
+ // This is the previous opacity flattening path, where the opacity has
+ // been applied to children.
+ return true;
+ }
+
+ if (mState->IsInInactiveLayer() || !ItemWantsInactiveLayer(next)) {
+ // Do not flatten nested inactive display items, or display items that
+ // want an active layer.
+ return false;
+ }
+
+ // If we reach here, we will emit an effect start marker for
+ // nsDisplayTransform or nsDisplayOpacity.
+ MOZ_ASSERT(type == DisplayItemType::TYPE_TRANSFORM ||
+ !IsOpacityAppliedToChildren(next));
+ return true;
+ }
+
+ void EnterChildList(nsDisplayItem* aContainerItem) override {
+ mFlattenedLists.AppendElement(aContainerItem);
+ AddMarkerIfNeeded<MarkerType::StartMarker>(aContainerItem, mMarkers);
+ AddHitTestMarkerIfNeeded(aContainerItem);
+ }
+
+ void ExitChildList() override {
+ MOZ_ASSERT(!mFlattenedLists.IsEmpty());
+ nsDisplayItem* aContainerItem = mFlattenedLists.PopLastElement();
+ AddMarkerIfNeeded<MarkerType::EndMarker>(aContainerItem, mMarkers);
+ }
+
+ bool ItemWantsInactiveLayer(nsDisplayItem* aItem) {
+ const LayerState layerState = aItem->GetLayerState(
+ mState->mBuilder, mState->mManager, mState->mParameters);
+
+ return layerState == LayerState::LAYER_INACTIVE;
+ }
+
+ std::deque<DisplayItemEntry> mMarkers;
+ AutoTArray<nsDisplayItem*, 16> mFlattenedLists;
+ ContainerState* mState;
+};
+
+class PaintedDisplayItemLayerUserData : public LayerUserData {
+ public:
+ PaintedDisplayItemLayerUserData()
+ : mForcedBackgroundColor(NS_RGBA(0, 0, 0, 0)),
+ mXScale(1.f),
+ mYScale(1.f),
+ mAppUnitsPerDevPixel(0),
+ mTranslation(0, 0),
+ mAnimatedGeometryRootPosition(0, 0),
+ mLastItemCount(0),
+ mContainerLayerFrame(nullptr),
+ mDisabledAlpha(false) {}
+
+ NS_INLINE_DECL_REFCOUNTING(PaintedDisplayItemLayerUserData);
+
+ /**
+ * A color that should be painted over the bounds of the layer's visible
+ * region before any other content is painted.
+ */
+ nscolor mForcedBackgroundColor;
+
+ /**
+ * The resolution scale used.
+ */
+ float mXScale, mYScale;
+
+ /**
+ * The appunits per dev pixel for the items in this layer.
+ */
+ nscoord mAppUnitsPerDevPixel;
+
+ /**
+ * The offset from the PaintedLayer's 0,0 to the
+ * reference frame. This isn't necessarily the same as the transform
+ * set on the PaintedLayer since we might also be applying an extra
+ * offset specified by the parent ContainerLayer/
+ */
+ nsIntPoint mTranslation;
+
+ /**
+ * We try to make 0,0 of the PaintedLayer be the top-left of the
+ * border-box of the "active scrolled root" frame (i.e. the nearest ancestor
+ * frame for the display items that is being actively scrolled). But
+ * we force the PaintedLayer transform to be an integer translation, and we
+ * may have a resolution scale, so we have to snap the PaintedLayer transform,
+ * so 0,0 may not be exactly the top-left of the active scrolled root. Here we
+ * store the coordinates in PaintedLayer space of the top-left of the
+ * active scrolled root.
+ */
+ gfxPoint mAnimatedGeometryRootPosition;
+
+ nsIntRegion mRegionToInvalidate;
+
+ // The offset between the active scrolled root of this layer
+ // and the root of the container for the previous and current
+ // paints respectively.
+ nsPoint mLastAnimatedGeometryRootOrigin;
+ nsPoint mAnimatedGeometryRootOrigin;
+
+ RefPtr<ColorLayer> mColorLayer;
+ RefPtr<ImageLayer> mImageLayer;
+
+ // The region for which display item visibility for this layer has already
+ // been calculated. Used to reduce the number of calls to
+ // RecomputeVisibilityForItems if it is known in advance that a larger
+ // region will be painted during a transaction than in a single call to
+ // DrawPaintedLayer, for example when progressive paint is enabled.
+ nsIntRegion mVisibilityComputedRegion;
+
+ // The area for which we called RecomputeVisibilityForItems on the
+ // previous paint.
+ nsRect mPreviousRecomputeVisibilityRect;
+
+ // The number of items assigned to this layer on the previous paint.
+ size_t mLastItemCount;
+
+ // The translation set on this PaintedLayer during the previous paint. This
+ // is needed when invalidating based on a display item's geometry information
+ // from the previous paint.
+ Maybe<nsIntPoint> mLastPaintOffset;
+
+ // Temporary state only valid during the FrameLayerBuilder's lifetime.
+ // FLB's mPaintedLayerItems is responsible for cleaning these up when
+ // we finish painting to avoid dangling pointers.
+ std::vector<AssignedDisplayItem> mItems;
+ nsIFrame* mContainerLayerFrame;
+
+ /**
+ * This is set when the painted layer has no component alpha.
+ */
+ bool mDisabledAlpha;
+
+ protected:
+ ~PaintedDisplayItemLayerUserData() override = default;
+};
+
+FrameLayerBuilder::FrameLayerBuilder()
+ : mRetainingManager(nullptr),
+ mDisplayListBuilder(nullptr),
+ mContainingPaintedLayer(nullptr),
+ mInactiveLayerClip(nullptr),
+ mInvalidateAllLayers(false),
+ mInLayerTreeCompressionMode(false),
+ mIsInactiveLayerManager(false) {
+ MOZ_COUNT_CTOR(FrameLayerBuilder);
+}
+
+FrameLayerBuilder::~FrameLayerBuilder() {
+ GetMaskLayerImageCache()->Sweep();
+ for (PaintedDisplayItemLayerUserData* userData : mPaintedLayerItems) {
+ userData->mLastPaintOffset = Some(userData->mTranslation);
+ userData->mItems.clear();
+ userData->mContainerLayerFrame = nullptr;
+ }
+ MOZ_COUNT_DTOR(FrameLayerBuilder);
+}
+
+void FrameLayerBuilder::AddPaintedLayerItemsEntry(
+ PaintedDisplayItemLayerUserData* aData) {
+ mPaintedLayerItems.AppendElement(aData);
+}
+
+/*
+ * User data for layers which will be used as masks.
+ */
+struct MaskLayerUserData : public LayerUserData {
+ MaskLayerUserData()
+ : mScaleX(-1.0f), mScaleY(-1.0f), mAppUnitsPerDevPixel(-1) {}
+ MaskLayerUserData(const DisplayItemClip& aClip, int32_t aAppUnitsPerDevPixel,
+ const ContainerLayerParameters& aParams)
+ : mScaleX(aParams.mXScale),
+ mScaleY(aParams.mYScale),
+ mOffset(aParams.mOffset),
+ mAppUnitsPerDevPixel(aAppUnitsPerDevPixel) {
+ aClip.AppendRoundedRects(&mRoundedClipRects);
+ }
+
+ void operator=(MaskLayerUserData&& aOther) {
+ mScaleX = aOther.mScaleX;
+ mScaleY = aOther.mScaleY;
+ mOffset = aOther.mOffset;
+ mAppUnitsPerDevPixel = aOther.mAppUnitsPerDevPixel;
+ mRoundedClipRects = std::move(aOther.mRoundedClipRects);
+ }
+
+ bool operator==(const MaskLayerUserData& aOther) const {
+ return mRoundedClipRects == aOther.mRoundedClipRects &&
+ mScaleX == aOther.mScaleX && mScaleY == aOther.mScaleY &&
+ mOffset == aOther.mOffset &&
+ mAppUnitsPerDevPixel == aOther.mAppUnitsPerDevPixel;
+ }
+
+ // Keeps a MaskLayerImageKey alive by managing its mLayerCount member-var
+ MaskLayerImageCache::MaskLayerImageKeyRef mImageKey;
+ // properties of the mask layer; the mask layer may be re-used if these
+ // remain unchanged.
+ nsTArray<DisplayItemClip::RoundedRect> mRoundedClipRects;
+ // scale from the masked layer which is applied to the mask
+ float mScaleX, mScaleY;
+ // The ContainerLayerParameters offset which is applied to the mask's
+ // transform.
+ nsIntPoint mOffset;
+ int32_t mAppUnitsPerDevPixel;
+};
+
+/*
+ * User data for layers which will be used as masks for css positioned mask.
+ */
+struct CSSMaskLayerUserData : public LayerUserData {
+ CSSMaskLayerUserData() : mMaskStyle(nsStyleImageLayers::LayerType::Mask) {}
+
+ CSSMaskLayerUserData(nsIFrame* aFrame, const nsIntRect& aMaskBounds,
+ const nsPoint& aMaskLayerOffset)
+ : mMaskBounds(aMaskBounds),
+ mMaskStyle(aFrame->StyleSVGReset()->mMask),
+ mMaskLayerOffset(aMaskLayerOffset) {}
+
+ void operator=(CSSMaskLayerUserData&& aOther) {
+ mMaskBounds = aOther.mMaskBounds;
+ mMaskStyle = std::move(aOther.mMaskStyle);
+ mMaskLayerOffset = aOther.mMaskLayerOffset;
+ }
+
+ bool operator==(const CSSMaskLayerUserData& aOther) const {
+ if (!mMaskBounds.IsEqualInterior(aOther.mMaskBounds)) {
+ return false;
+ }
+
+ // Make sure we draw the same portion of the mask onto mask layer.
+ if (mMaskLayerOffset != aOther.mMaskLayerOffset) {
+ return false;
+ }
+
+ return mMaskStyle == aOther.mMaskStyle;
+ }
+
+ private:
+ nsIntRect mMaskBounds;
+ nsStyleImageLayers mMaskStyle;
+ nsPoint mMaskLayerOffset; // The offset from the origin of mask bounds to
+ // the origin of mask layer.
+};
+
+/*
+ * A helper object to create a draw target for painting mask and create a
+ * image container to hold the drawing result. The caller can then bind this
+ * image container with a image mask layer via ImageLayer::SetContainer.
+ */
+class MaskImageData {
+ public:
+ MaskImageData(const gfx::IntSize& aSize, LayerManager* aLayerManager)
+ : mTextureClientLocked(false),
+ mSize(aSize),
+ mLayerManager(aLayerManager) {
+ MOZ_ASSERT(!mSize.IsEmpty());
+ MOZ_ASSERT(mLayerManager);
+ }
+
+ ~MaskImageData() {
+ if (mTextureClientLocked) {
+ MOZ_ASSERT(mTextureClient);
+ // Clear DrawTarget before Unlock.
+ mDrawTarget = nullptr;
+ mTextureClient->Unlock();
+ }
+ }
+
+ gfx::DrawTarget* CreateDrawTarget() {
+ if (mDrawTarget) {
+ return mDrawTarget;
+ }
+
+ if (mLayerManager->GetBackendType() == LayersBackend::LAYERS_BASIC) {
+ mDrawTarget = mLayerManager->CreateOptimalMaskDrawTarget(mSize);
+ return mDrawTarget;
+ }
+
+ MOZ_ASSERT(mLayerManager->GetBackendType() ==
+ LayersBackend::LAYERS_CLIENT ||
+ mLayerManager->GetBackendType() == LayersBackend::LAYERS_WR);
+
+ KnowsCompositor* knowsCompositor = mLayerManager->AsKnowsCompositor();
+ if (!knowsCompositor) {
+ return nullptr;
+ }
+ mTextureClient = TextureClient::CreateForDrawing(
+ knowsCompositor, SurfaceFormat::A8, mSize, BackendSelector::Content,
+ TextureFlags::DISALLOW_BIGIMAGE,
+ TextureAllocationFlags::ALLOC_CLEAR_BUFFER);
+ if (!mTextureClient) {
+ return nullptr;
+ }
+
+ mTextureClientLocked = mTextureClient->Lock(OpenMode::OPEN_READ_WRITE);
+ if (!mTextureClientLocked) {
+ return nullptr;
+ }
+
+ mDrawTarget = mTextureClient->BorrowDrawTarget();
+ return mDrawTarget;
+ }
+
+ already_AddRefed<ImageContainer> CreateImageAndImageContainer() {
+ RefPtr<ImageContainer> container = LayerManager::CreateImageContainer();
+ RefPtr<Image> image = CreateImage();
+
+ if (!image) {
+ return nullptr;
+ }
+ container->SetCurrentImageInTransaction(image);
+
+ return container.forget();
+ }
+
+ private:
+ already_AddRefed<Image> CreateImage() {
+ if (mLayerManager->GetBackendType() == LayersBackend::LAYERS_BASIC &&
+ mDrawTarget) {
+ RefPtr<SourceSurface> surface = mDrawTarget->Snapshot();
+ RefPtr<SourceSurfaceImage> image = new SourceSurfaceImage(mSize, surface);
+ // Disallow BIGIMAGE (splitting into multiple textures) for mask
+ // layer images
+ image->SetTextureFlags(TextureFlags::DISALLOW_BIGIMAGE);
+ return image.forget();
+ }
+
+ if ((mLayerManager->GetBackendType() == LayersBackend::LAYERS_CLIENT ||
+ mLayerManager->GetBackendType() == LayersBackend::LAYERS_WR) &&
+ mTextureClient && mDrawTarget) {
+ RefPtr<TextureWrapperImage> image = new TextureWrapperImage(
+ mTextureClient, gfx::IntRect(gfx::IntPoint(0, 0), mSize));
+ return image.forget();
+ }
+
+ return nullptr;
+ }
+
+ bool mTextureClientLocked;
+ gfx::IntSize mSize;
+ LayerManager* mLayerManager;
+ RefPtr<gfx::DrawTarget> mDrawTarget;
+ RefPtr<TextureClient> mTextureClient;
+};
+
+/* static */
+void FrameLayerBuilder::Shutdown() {
+ if (gMaskLayerImageCache) {
+ delete gMaskLayerImageCache;
+ gMaskLayerImageCache = nullptr;
+ }
+}
+
+void FrameLayerBuilder::Init(nsDisplayListBuilder* aBuilder,
+ LayerManager* aManager,
+ PaintedLayerData* aLayerData,
+ bool aIsInactiveLayerManager,
+ const DisplayItemClip* aInactiveLayerClip) {
+ mDisplayListBuilder = aBuilder;
+ mRootPresContext =
+ aBuilder->RootReferenceFrame()->PresContext()->GetRootPresContext();
+ mContainingPaintedLayer = aLayerData;
+ mIsInactiveLayerManager = aIsInactiveLayerManager;
+ mInactiveLayerClip = aInactiveLayerClip;
+ aManager->SetUserData(&gLayerManagerLayerBuilder, this);
+}
+
+void FrameLayerBuilder::FlashPaint(gfxContext* aContext) {
+ float r = float(rand()) / float(RAND_MAX);
+ float g = float(rand()) / float(RAND_MAX);
+ float b = float(rand()) / float(RAND_MAX);
+ aContext->SetColor(sRGBColor(r, g, b, 0.4f));
+ aContext->Paint();
+}
+
+DisplayItemData* FrameLayerBuilder::GetDisplayItemData(nsIFrame* aFrame,
+ uint32_t aKey) {
+ const SmallPointerArray<DisplayItemData>& array = aFrame->DisplayItemData();
+ for (uint32_t i = 0; i < array.Length(); i++) {
+ DisplayItemData* item =
+ DisplayItemData::AssertDisplayItemData(array.ElementAt(i));
+ if (item->mDisplayItemKey == aKey && item->FirstFrame() == aFrame &&
+ item->mLayer->Manager() == mRetainingManager) {
+ return item;
+ }
+ }
+ return nullptr;
+}
+
+#ifdef MOZ_DUMP_PAINTING
+static nsACString& AppendToString(nsACString& s, const nsIntRect& r,
+ const char* pfx = "", const char* sfx = "") {
+ s += pfx;
+ s += nsPrintfCString("(x=%d, y=%d, w=%d, h=%d)", r.x, r.y, r.width, r.height);
+ return s += sfx;
+}
+
+static nsACString& AppendToString(nsACString& s, const nsIntRegion& r,
+ const char* pfx = "", const char* sfx = "") {
+ s += pfx;
+
+ s += "< ";
+ for (auto iter = r.RectIter(); !iter.Done(); iter.Next()) {
+ AppendToString(s, iter.Get()) += "; ";
+ }
+ s += ">";
+
+ return s += sfx;
+}
+#endif // MOZ_DUMP_PAINTING
+
+/**
+ * Invalidate aRegion in aLayer. aLayer is in the coordinate system
+ * *after* aTranslation has been applied, so we need to
+ * apply the inverse of that transform before calling InvalidateRegion.
+ */
+static void InvalidatePostTransformRegion(PaintedLayer* aLayer,
+ const nsIntRegion& aRegion,
+ const nsIntPoint& aTranslation) {
+ // Convert the region from the coordinates of the container layer
+ // (relative to the snapped top-left of the display list reference frame)
+ // to the PaintedLayer's own coordinates
+ nsIntRegion rgn = aRegion;
+
+ rgn.MoveBy(-aTranslation);
+ aLayer->InvalidateRegion(rgn);
+#ifdef MOZ_DUMP_PAINTING
+ if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) {
+ nsAutoCString str;
+ AppendToString(str, rgn);
+ printf_stderr("Invalidating layer %p: %s\n", aLayer, str.get());
+ }
+#endif
+}
+
+static PaintedDisplayItemLayerUserData* GetPaintedDisplayItemLayerUserData(
+ Layer* aLayer) {
+ return static_cast<PaintedDisplayItemLayerUserData*>(
+ aLayer->GetUserData(&gPaintedDisplayItemLayerUserData));
+}
+
+static nsIntPoint GetTranslationForPaintedLayer(PaintedLayer* aLayer) {
+ PaintedDisplayItemLayerUserData* layerData =
+ GetPaintedDisplayItemLayerUserData(aLayer);
+ NS_ASSERTION(layerData, "Must be a tracked painted layer!");
+
+ return layerData->mTranslation;
+}
+
+/**
+ * Get the translation transform that was in aLayer when we last painted. It's
+ * either the transform saved by ~FrameLayerBuilder(), or else the transform
+ * that's currently in the layer (which must be an integer translation).
+ */
+static nsIntPoint GetLastPaintOffset(PaintedLayer* aLayer) {
+ auto* layerData = GetPaintedDisplayItemLayerUserData(aLayer);
+ MOZ_ASSERT(layerData);
+ return layerData->mLastPaintOffset.valueOr(layerData->mTranslation);
+}
+
+static void InvalidatePreTransformRect(PaintedLayer* aLayer,
+ const nsRect& aRect,
+ const DisplayItemClip& aClip,
+ const nsIntPoint& aTranslation,
+ TransformClipNode* aTransform) {
+ auto* data = GetPaintedDisplayItemLayerUserData(aLayer);
+
+ nsRect rect = aClip.ApplyNonRoundedIntersection(aRect);
+
+ if (aTransform) {
+ rect = aTransform->TransformRect(rect, data->mAppUnitsPerDevPixel);
+ }
+
+ nsIntRect pixelRect = rect.ScaleToOutsidePixels(data->mXScale, data->mYScale,
+ data->mAppUnitsPerDevPixel);
+
+ InvalidatePostTransformRegion(aLayer, pixelRect, aTranslation);
+}
+
+/**
+ * Some frames can have multiple, nested, retaining layer managers
+ * associated with them (normal manager, inactive managers, SVG effects).
+ * In these cases we store the 'outermost' LayerManager data property
+ * on the frame since we can walk down the chain from there.
+ *
+ * If one of these frames has just been destroyed, we will free the inner
+ * layer manager when removing the entry from mFramesWithLayers. Destroying
+ * the layer manager destroys the LayerManagerData and calls into
+ * the DisplayItemData destructor. If the inner layer manager had any
+ * items with the same frame, then we attempt to retrieve properties
+ * from the deleted frame.
+ *
+ * Cache the destroyed frame pointer here so we can avoid crashing in this case.
+ */
+
+/* static */
+void FrameLayerBuilder::RemoveFrameFromLayerManager(
+ const nsIFrame* aFrame, SmallPointerArray<DisplayItemData>& aArray) {
+ MOZ_RELEASE_ASSERT(!sDestroyedFrame);
+ sDestroyedFrame = aFrame;
+
+ // Hold a reference to all the items so that they don't get
+ // deleted from under us.
+ nsTArray<RefPtr<DisplayItemData>> arrayCopy;
+ for (DisplayItemData* data : aArray) {
+ arrayCopy.AppendElement(data);
+ }
+
+#ifdef DEBUG_DISPLAY_ITEM_DATA
+ if (aArray->Length()) {
+ LayerManagerData* rootData = aArray->ElementAt(0)->mParent;
+ while (rootData->mParent) {
+ rootData = rootData->mParent;
+ }
+ printf_stderr("Removing frame %p - dumping display data\n", aFrame);
+ rootData->Dump();
+ }
+#endif
+
+ for (DisplayItemData* data : aArray) {
+ PaintedLayer* t = data->mLayer ? data->mLayer->AsPaintedLayer() : nullptr;
+ if (t) {
+ auto* paintedData = GetPaintedDisplayItemLayerUserData(t);
+ if (paintedData && data->mGeometry) {
+ const int32_t appUnitsPerDevPixel = paintedData->mAppUnitsPerDevPixel;
+ nsRegion rgn = data->mGeometry->ComputeInvalidationRegion();
+ nsIntRegion pixelRgn = rgn.ToOutsidePixels(appUnitsPerDevPixel);
+
+ if (data->mTransform) {
+ pixelRgn = data->mTransform->TransformRegion(pixelRgn);
+ }
+
+ pixelRgn =
+ pixelRgn.ScaleRoundOut(paintedData->mXScale, paintedData->mYScale);
+
+ pixelRgn.MoveBy(-GetTranslationForPaintedLayer(t));
+
+ paintedData->mRegionToInvalidate.Or(paintedData->mRegionToInvalidate,
+ pixelRgn);
+ paintedData->mRegionToInvalidate.SimplifyOutward(8);
+ }
+ }
+
+ auto it = std::find(data->mParent->mDisplayItems.begin(),
+ data->mParent->mDisplayItems.end(), data);
+ MOZ_ASSERT(it != data->mParent->mDisplayItems.end());
+ std::iter_swap(it, data->mParent->mDisplayItems.end() - 1);
+ data->mParent->mDisplayItems.pop_back();
+ }
+
+ if (aFrame->IsSubDocumentFrame()) {
+ const nsSubDocumentFrame* subdoc =
+ static_cast<const nsSubDocumentFrame*>(aFrame);
+ nsFrameLoader* frameLoader = subdoc->FrameLoader();
+ if (frameLoader && frameLoader->GetRemoteBrowser()) {
+ // This is a remote browser that is going away, notify it that it is now
+ // hidden
+ frameLoader->GetRemoteBrowser()->UpdateEffects(
+ mozilla::dom::EffectsInfo::FullyHidden());
+ }
+ }
+
+ arrayCopy.Clear();
+ sDestroyedFrame = nullptr;
+}
+
+void FrameLayerBuilder::DidBeginRetainedLayerTransaction(
+ LayerManager* aManager) {
+ mRetainingManager = aManager;
+ LayerManagerData* data = static_cast<LayerManagerData*>(
+ aManager->GetUserData(&gLayerManagerUserData));
+ if (data) {
+ mInvalidateAllLayers = data->mInvalidateAllLayers;
+ } else {
+ data = new LayerManagerData(aManager);
+ aManager->SetUserData(&gLayerManagerUserData, data);
+ }
+}
+
+void FrameLayerBuilder::DidEndTransaction() {
+ GetMaskLayerImageCache()->Sweep();
+}
+
+void FrameLayerBuilder::WillEndTransaction() {
+ if (!mRetainingManager) {
+ return;
+ }
+
+ // We need to save the data we'll need to support retaining.
+ LayerManagerData* data = static_cast<LayerManagerData*>(
+ mRetainingManager->GetUserData(&gLayerManagerUserData));
+ NS_ASSERTION(data, "Must have data!");
+
+ // Update all the frames that used to have layers.
+ auto iter = data->mDisplayItems.begin();
+ while (iter != data->mDisplayItems.end()) {
+ DisplayItemData* did = iter->get();
+ if (!did->mUsed) {
+ // This item was visible, but isn't anymore.
+ PaintedLayer* t = did->mLayer->AsPaintedLayer();
+ if (t && did->mGeometry) {
+#ifdef MOZ_DUMP_PAINTING
+ if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) {
+ printf_stderr(
+ "Invalidating unused display item (%i) belonging to "
+ "frame %p from layer %p\n",
+ did->mDisplayItemKey, did->mFrameList[0], t);
+ }
+#endif
+ InvalidatePreTransformRect(
+ t, did->mGeometry->ComputeInvalidationRegion(), did->mClip,
+ GetLastPaintOffset(t), did->mTransform);
+ }
+
+ did->NotifyRemoved();
+
+ // Remove this item. Swapping it with the last element first is
+ // quicker than erasing from the middle.
+ if (iter != data->mDisplayItems.end() - 1) {
+ std::iter_swap(iter, data->mDisplayItems.end() - 1);
+ data->mDisplayItems.pop_back();
+ } else {
+ data->mDisplayItems.pop_back();
+ break;
+ }
+
+ // Don't increment iter because we still need to process the item which
+ // was moved.
+
+ } else {
+ ComputeGeometryChangeForItem(did);
+ iter++;
+ }
+ }
+
+ data->mInvalidateAllLayers = false;
+}
+
+/* static */
+DisplayItemData* FrameLayerBuilder::GetDisplayItemDataForManager(
+ nsPaintedDisplayItem* aItem, LayerManager* aManager) {
+ for (DisplayItemData* did : aItem->Frame()->DisplayItemData()) {
+ DisplayItemData* data = DisplayItemData::AssertDisplayItemData(did);
+ if (data->mDisplayItemKey == aItem->GetPerFrameKey() &&
+ data->mLayer->Manager() == aManager) {
+ return data;
+ }
+ }
+
+ return nullptr;
+}
+
+bool FrameLayerBuilder::HasRetainedDataFor(nsIFrame* aFrame,
+ uint32_t aDisplayItemKey) {
+ const SmallPointerArray<DisplayItemData>& array = aFrame->DisplayItemData();
+ for (uint32_t i = 0; i < array.Length(); i++) {
+ if (DisplayItemData::AssertDisplayItemData(array.ElementAt(i))
+ ->mDisplayItemKey == aDisplayItemKey) {
+ return true;
+ }
+ }
+ if (RefPtr<WebRenderUserData> data =
+ GetWebRenderUserData<WebRenderFallbackData>(aFrame,
+ aDisplayItemKey)) {
+ return true;
+ }
+ return false;
+}
+
+DisplayItemData* FrameLayerBuilder::GetOldLayerForFrame(
+ nsIFrame* aFrame, uint32_t aDisplayItemKey,
+ DisplayItemData* aOldData, /* = nullptr */
+ LayerManager* aOldLayerManager /* = nullptr */) {
+ // If we need to build a new layer tree, then just refuse to recycle
+ // anything.
+ if (!mRetainingManager || mInvalidateAllLayers) {
+ return nullptr;
+ }
+
+ MOZ_ASSERT(!aOldData || aOldLayerManager,
+ "You must provide aOldLayerManager to check aOldData's validity.");
+ MOZ_ASSERT_IF(aOldData, aOldLayerManager == aOldData->mLayer->Manager());
+
+ DisplayItemData* data = aOldData;
+ if (!data || aOldLayerManager != mRetainingManager) {
+ data = GetDisplayItemData(aFrame, aDisplayItemKey);
+ }
+
+ MOZ_ASSERT(data == GetDisplayItemData(aFrame, aDisplayItemKey));
+
+ return data;
+}
+
+Layer* FrameLayerBuilder::GetOldLayerFor(nsDisplayItem* aItem,
+ nsDisplayItemGeometry** aOldGeometry,
+ DisplayItemClip** aOldClip) {
+ uint32_t key = aItem->GetPerFrameKey();
+ nsIFrame* frame = aItem->Frame();
+
+ DisplayItemData* oldData = GetOldLayerForFrame(frame, key);
+ if (oldData) {
+ if (aOldGeometry) {
+ *aOldGeometry = oldData->mGeometry.get();
+ }
+ if (aOldClip) {
+ *aOldClip = &oldData->mClip;
+ }
+ return oldData->mLayer;
+ }
+
+ return nullptr;
+}
+
+/* static */
+DisplayItemData* FrameLayerBuilder::GetOldDataFor(nsDisplayItem* aItem) {
+ const SmallPointerArray<DisplayItemData>& array =
+ aItem->Frame()->DisplayItemData();
+
+ for (uint32_t i = 0; i < array.Length(); i++) {
+ DisplayItemData* data =
+ DisplayItemData::AssertDisplayItemData(array.ElementAt(i));
+
+ if (data->mDisplayItemKey == aItem->GetPerFrameKey()) {
+ return data;
+ }
+ }
+ return nullptr;
+}
+
+// Reset state that should not persist when a layer is recycled.
+static void ResetLayerStateForRecycling(Layer* aLayer) {
+ // Currently, this clears the mask layer and ancestor mask layers.
+ // Other cleanup may be added here.
+ aLayer->SetMaskLayer(nullptr);
+ aLayer->SetAncestorMaskLayers({});
+}
+
+already_AddRefed<ColorLayer> ContainerState::CreateOrRecycleColorLayer(
+ PaintedLayer* aPainted) {
+ auto* data = GetPaintedDisplayItemLayerUserData(aPainted);
+ RefPtr<ColorLayer> layer = data->mColorLayer;
+ if (layer) {
+ ResetLayerStateForRecycling(layer);
+ layer->ClearExtraDumpInfo();
+ } else {
+ // Create a new layer
+ layer = mManager->CreateColorLayer();
+ if (!layer) {
+ return nullptr;
+ }
+ // Mark this layer as being used for painting display items
+ data->mColorLayer = layer;
+ layer->SetUserData(&gColorLayerUserData, nullptr);
+
+ // Remove other layer types we might have stored for this PaintedLayer
+ data->mImageLayer = nullptr;
+ }
+ return layer.forget();
+}
+
+already_AddRefed<ImageLayer> ContainerState::CreateOrRecycleImageLayer(
+ PaintedLayer* aPainted) {
+ auto* data = GetPaintedDisplayItemLayerUserData(aPainted);
+ RefPtr<ImageLayer> layer = data->mImageLayer;
+ if (layer) {
+ ResetLayerStateForRecycling(layer);
+ layer->ClearExtraDumpInfo();
+ } else {
+ // Create a new layer
+ layer = mManager->CreateImageLayer();
+ if (!layer) {
+ return nullptr;
+ }
+ // Mark this layer as being used for painting display items
+ data->mImageLayer = layer;
+ layer->SetUserData(&gImageLayerUserData, nullptr);
+
+ // Remove other layer types we might have stored for this PaintedLayer
+ data->mColorLayer = nullptr;
+ }
+ return layer.forget();
+}
+
+template <typename UserData>
+already_AddRefed<ImageLayer> ContainerState::CreateOrRecycleMaskImageLayerFor(
+ const MaskLayerKey& aKey, UserData* (*aGetUserData)(Layer* aLayer),
+ void (*aSetDefaultUserData)(Layer* aLayer)) {
+ RefPtr<ImageLayer> result = mRecycledMaskImageLayers.Get(aKey);
+
+ if (result && aGetUserData(result.get())) {
+ mRecycledMaskImageLayers.Remove(aKey);
+ aKey.mLayer->ClearExtraDumpInfo();
+ // XXX if we use clip on mask layers, null it out here
+ } else {
+ // Create a new layer
+ result = mManager->CreateImageLayer();
+ if (!result) {
+ return nullptr;
+ }
+ aSetDefaultUserData(result);
+ }
+
+ return result.forget();
+}
+
+static const double SUBPIXEL_OFFSET_EPSILON = 0.02;
+
+/**
+ * This normally computes NSToIntRoundUp(aValue). However, if that would
+ * give a residual near 0.5 while aOldResidual is near -0.5, or
+ * it would give a residual near -0.5 while aOldResidual is near 0.5, then
+ * instead we return the integer in the other direction so that the residual
+ * is close to aOldResidual.
+ */
+static int32_t RoundToMatchResidual(double aValue, double aOldResidual) {
+ int32_t v = NSToIntRoundUp(aValue);
+ double residual = aValue - v;
+ if (aOldResidual < 0) {
+ if (residual > 0 &&
+ fabs(residual - 1.0 - aOldResidual) < SUBPIXEL_OFFSET_EPSILON) {
+ // Round up instead
+ return int32_t(ceil(aValue));
+ }
+ } else if (aOldResidual > 0) {
+ if (residual < 0 &&
+ fabs(residual + 1.0 - aOldResidual) < SUBPIXEL_OFFSET_EPSILON) {
+ // Round down instead
+ return int32_t(floor(aValue));
+ }
+ }
+ return v;
+}
+
+static void ResetScrollPositionForLayerPixelAlignment(
+ AnimatedGeometryRoot* aAnimatedGeometryRoot) {
+ nsIScrollableFrame* sf =
+ nsLayoutUtils::GetScrollableFrameFor(*aAnimatedGeometryRoot);
+ if (sf) {
+ sf->ResetScrollPositionForLayerPixelAlignment();
+ }
+}
+
+static void InvalidateEntirePaintedLayer(
+ PaintedLayer* aLayer, AnimatedGeometryRoot* aAnimatedGeometryRoot,
+ const char* aReason) {
+#ifdef MOZ_DUMP_PAINTING
+ if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) {
+ printf_stderr("Invalidating entire layer %p: %s\n", aLayer, aReason);
+ }
+#endif
+ aLayer->InvalidateWholeLayer();
+ aLayer->SetInvalidRectToVisibleRegion();
+ ResetScrollPositionForLayerPixelAlignment(aAnimatedGeometryRoot);
+}
+
+LayerManager::PaintedLayerCreationHint ContainerState::GetLayerCreationHint(
+ AnimatedGeometryRoot* aAnimatedGeometryRoot) {
+ // Check whether the layer will be scrollable. This is used as a hint to
+ // influence whether tiled layers are used or not.
+
+ // Check creation hint inherited from our parent.
+ if (mParameters.mLayerCreationHint == LayerManager::SCROLLABLE) {
+ return LayerManager::SCROLLABLE;
+ }
+
+ // Check whether there's any active scroll frame on the animated geometry
+ // root chain.
+ for (AnimatedGeometryRoot* agr = aAnimatedGeometryRoot;
+ agr && agr != mContainerAnimatedGeometryRoot; agr = agr->mParentAGR) {
+ nsIFrame* fParent = nsLayoutUtils::GetCrossDocParentFrame(*agr);
+ if (!fParent) {
+ break;
+ }
+ nsIScrollableFrame* scrollable = do_QueryFrame(fParent);
+ if (scrollable) {
+ return LayerManager::SCROLLABLE;
+ }
+ }
+ return LayerManager::NONE;
+}
+
+already_AddRefed<PaintedLayer> ContainerState::AttemptToRecyclePaintedLayer(
+ AnimatedGeometryRoot* aAnimatedGeometryRoot, nsDisplayItem* aItem,
+ const nsPoint& aTopLeft, const nsIFrame* aReferenceFrame) {
+ Layer* oldLayer = mLayerBuilder->GetOldLayerFor(aItem);
+ if (!oldLayer || !oldLayer->AsPaintedLayer()) {
+ return nullptr;
+ }
+
+ if (!mPaintedLayersAvailableForRecycling.EnsureRemoved(
+ oldLayer->AsPaintedLayer())) {
+ // Not found.
+ return nullptr;
+ }
+
+ // Try to recycle the layer.
+ RefPtr<PaintedLayer> layer = oldLayer->AsPaintedLayer();
+
+ // Check if the layer hint has changed and whether or not the layer should
+ // be recreated because of it.
+ if (!layer->IsOptimizedFor(GetLayerCreationHint(aAnimatedGeometryRoot))) {
+ return nullptr;
+ }
+
+ bool didResetScrollPositionForLayerPixelAlignment = false;
+ PaintedDisplayItemLayerUserData* data =
+ RecyclePaintedLayer(layer, aAnimatedGeometryRoot,
+ didResetScrollPositionForLayerPixelAlignment);
+ PreparePaintedLayerForUse(layer, data, aAnimatedGeometryRoot, aReferenceFrame,
+ aTopLeft,
+ didResetScrollPositionForLayerPixelAlignment);
+
+ return layer.forget();
+}
+
+static void ReleaseLayerUserData(void* aData) {
+ PaintedDisplayItemLayerUserData* userData =
+ static_cast<PaintedDisplayItemLayerUserData*>(aData);
+ userData->Release();
+}
+
+already_AddRefed<PaintedLayer> ContainerState::CreatePaintedLayer(
+ PaintedLayerData* aData) {
+ LayerManager::PaintedLayerCreationHint creationHint =
+ GetLayerCreationHint(aData->mAnimatedGeometryRoot);
+
+ // Create a new painted layer
+ RefPtr<PaintedLayer> layer =
+ mManager->CreatePaintedLayerWithHint(creationHint);
+ if (!layer) {
+ return nullptr;
+ }
+
+ // Mark this layer as being used for painting display items
+ RefPtr<PaintedDisplayItemLayerUserData> userData =
+ new PaintedDisplayItemLayerUserData();
+ userData->mDisabledAlpha =
+ mParameters.mDisableSubpixelAntialiasingInDescendants;
+ userData.get()->AddRef();
+ layer->SetUserData(&gPaintedDisplayItemLayerUserData, userData,
+ ReleaseLayerUserData);
+ ResetScrollPositionForLayerPixelAlignment(aData->mAnimatedGeometryRoot);
+
+ PreparePaintedLayerForUse(layer, userData, aData->mAnimatedGeometryRoot,
+ aData->mReferenceFrame,
+ aData->mAnimatedGeometryRootOffset, true);
+
+ return layer.forget();
+}
+
+PaintedDisplayItemLayerUserData* ContainerState::RecyclePaintedLayer(
+ PaintedLayer* aLayer, AnimatedGeometryRoot* aAnimatedGeometryRoot,
+ bool& didResetScrollPositionForLayerPixelAlignment) {
+ // Clear clip rect and mask layer so we don't accidentally stay clipped.
+ // We will reapply any necessary clipping.
+ ResetLayerStateForRecycling(aLayer);
+ aLayer->ClearExtraDumpInfo();
+
+ auto* data = GetPaintedDisplayItemLayerUserData(aLayer);
+ NS_ASSERTION(data, "Recycled PaintedLayers must have user data");
+
+ // This gets called on recycled PaintedLayers that are going to be in the
+ // final layer tree, so it's a convenient time to invalidate the
+ // content that changed where we don't know what PaintedLayer it belonged
+ // to, or if we need to invalidate the entire layer, we can do that.
+ // This needs to be done before we update the PaintedLayer to its new
+ // transform. See nsGfxScrollFrame::InvalidateInternal, where
+ // we ensure that mInvalidPaintedContent is updated according to the
+ // scroll position as of the most recent paint.
+ if (!FuzzyEqual(data->mXScale, mParameters.mXScale, 0.00001f) ||
+ !FuzzyEqual(data->mYScale, mParameters.mYScale, 0.00001f) ||
+ data->mAppUnitsPerDevPixel != mAppUnitsPerDevPixel) {
+#ifdef MOZ_DUMP_PAINTING
+ if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) {
+ printf_stderr("Recycled layer %p changed scale\n", aLayer);
+ }
+#endif
+ InvalidateEntirePaintedLayer(aLayer, aAnimatedGeometryRoot,
+ "recycled layer changed state");
+ didResetScrollPositionForLayerPixelAlignment = true;
+ }
+ if (!data->mRegionToInvalidate.IsEmpty()) {
+#ifdef MOZ_DUMP_PAINTING
+ if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) {
+ printf_stderr("Invalidating deleted frame content from layer %p\n",
+ aLayer);
+ }
+#endif
+ aLayer->InvalidateRegion(data->mRegionToInvalidate);
+#ifdef MOZ_DUMP_PAINTING
+ if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) {
+ nsAutoCString str;
+ AppendToString(str, data->mRegionToInvalidate);
+ printf_stderr("Invalidating layer %p: %s\n", aLayer, str.get());
+ }
+#endif
+ data->mRegionToInvalidate.SetEmpty();
+ }
+ return data;
+}
+
+void ContainerState::PreparePaintedLayerForUse(
+ PaintedLayer* aLayer, PaintedDisplayItemLayerUserData* aData,
+ AnimatedGeometryRoot* aAnimatedGeometryRoot,
+ const nsIFrame* aReferenceFrame, const nsPoint& aTopLeft,
+ bool didResetScrollPositionForLayerPixelAlignment) {
+ aData->mXScale = mParameters.mXScale;
+ aData->mYScale = mParameters.mYScale;
+ aData->mLastAnimatedGeometryRootOrigin = aData->mAnimatedGeometryRootOrigin;
+ aData->mAnimatedGeometryRootOrigin = aTopLeft;
+ aData->mAppUnitsPerDevPixel = mAppUnitsPerDevPixel;
+ aLayer->SetAllowResidualTranslation(mParameters.AllowResidualTranslation());
+
+ // Set up transform so that 0,0 in the PaintedLayer corresponds to the
+ // (pixel-snapped) top-left of the aAnimatedGeometryRoot.
+ nsPoint offset =
+ (*aAnimatedGeometryRoot)->GetOffsetToCrossDoc(aReferenceFrame);
+ nscoord appUnitsPerDevPixel =
+ (*aAnimatedGeometryRoot)->PresContext()->AppUnitsPerDevPixel();
+ gfxPoint scaledOffset(
+ NSAppUnitsToDoublePixels(offset.x, appUnitsPerDevPixel) *
+ mParameters.mXScale,
+ NSAppUnitsToDoublePixels(offset.y, appUnitsPerDevPixel) *
+ mParameters.mYScale);
+ // We call RoundToMatchResidual here so that the residual after rounding
+ // is close to aData->mAnimatedGeometryRootPosition if possible.
+ nsIntPoint pixOffset(
+ RoundToMatchResidual(scaledOffset.x,
+ aData->mAnimatedGeometryRootPosition.x),
+ RoundToMatchResidual(scaledOffset.y,
+ aData->mAnimatedGeometryRootPosition.y));
+ aData->mTranslation = pixOffset;
+ pixOffset += mParameters.mOffset;
+ Matrix matrix = Matrix::Translation(pixOffset.x, pixOffset.y);
+ aLayer->SetBaseTransform(Matrix4x4::From2D(matrix));
+
+ aData->mVisibilityComputedRegion.SetEmpty();
+
+ // Calculate exact position of the top-left of the active scrolled root.
+ // This might not be 0,0 due to the snapping in ScaleToNearestPixels.
+ gfxPoint animatedGeometryRootTopLeft =
+ scaledOffset - ThebesPoint(matrix.GetTranslation()) + mParameters.mOffset;
+ const bool disableAlpha =
+ mParameters.mDisableSubpixelAntialiasingInDescendants;
+ if (aData->mDisabledAlpha != disableAlpha) {
+ aData->mAnimatedGeometryRootPosition = animatedGeometryRootTopLeft;
+ InvalidateEntirePaintedLayer(aLayer, aAnimatedGeometryRoot,
+ "change of subpixel-AA");
+ aData->mDisabledAlpha = disableAlpha;
+ return;
+ }
+
+ // FIXME: Temporary workaround for bug 681192 and bug 724786.
+#ifndef MOZ_WIDGET_ANDROID
+ // If it has changed, then we need to invalidate the entire layer since the
+ // pixels in the layer buffer have the content at a (subpixel) offset
+ // from what we need.
+ if (!animatedGeometryRootTopLeft.WithinEpsilonOf(
+ aData->mAnimatedGeometryRootPosition, SUBPIXEL_OFFSET_EPSILON)) {
+ aData->mAnimatedGeometryRootPosition = animatedGeometryRootTopLeft;
+ InvalidateEntirePaintedLayer(aLayer, aAnimatedGeometryRoot,
+ "subpixel offset");
+ } else if (didResetScrollPositionForLayerPixelAlignment) {
+ aData->mAnimatedGeometryRootPosition = animatedGeometryRootTopLeft;
+ }
+#else
+ Unused << didResetScrollPositionForLayerPixelAlignment;
+#endif
+}
+
+#if defined(DEBUG) || defined(MOZ_DUMP_PAINTING)
+/**
+ * Returns the appunits per dev pixel for the item's frame
+ */
+static int32_t AppUnitsPerDevPixel(nsDisplayItem* aItem) {
+ // The underlying frame for zoom items is the root frame of the subdocument.
+ // But zoom display items report their bounds etc using the parent document's
+ // APD because zoom items act as a conversion layer between the two different
+ // APDs.
+ if (aItem->GetType() == DisplayItemType::TYPE_ZOOM) {
+ return static_cast<nsDisplayZoom*>(aItem)->GetParentAppUnitsPerDevPixel();
+ }
+ return aItem->Frame()->PresContext()->AppUnitsPerDevPixel();
+}
+#endif
+
+/**
+ * Set the visible region for aLayer.
+ * aOuterVisibleRegion is the visible region relative to the parent layer.
+ * aLayerContentsVisibleRect, if non-null, is a rectangle in the layer's
+ * own coordinate system to which the layer's visible region is restricted.
+ * Consumes *aOuterVisibleRegion.
+ */
+static void SetOuterVisibleRegion(
+ Layer* aLayer, nsIntRegion* aOuterVisibleRegion,
+ const nsIntRect* aLayerContentsVisibleRect = nullptr,
+ bool aOuterUntransformed = false) {
+ Matrix4x4 transform = aLayer->GetTransform();
+ Matrix transform2D;
+ if (aOuterUntransformed) {
+ if (aLayerContentsVisibleRect) {
+ aOuterVisibleRegion->And(*aOuterVisibleRegion,
+ *aLayerContentsVisibleRect);
+ }
+ } else if (transform.Is2D(&transform2D) &&
+ !transform2D.HasNonIntegerTranslation()) {
+ aOuterVisibleRegion->MoveBy(-int(transform2D._31), -int(transform2D._32));
+ if (aLayerContentsVisibleRect) {
+ aOuterVisibleRegion->And(*aOuterVisibleRegion,
+ *aLayerContentsVisibleRect);
+ }
+ } else {
+ nsIntRect outerRect = aOuterVisibleRegion->GetBounds();
+ // if 'transform' is not invertible, then nothing will be displayed
+ // for the layer, so it doesn't really matter what we do here
+ Rect outerVisible(outerRect.x, outerRect.y, outerRect.width,
+ outerRect.height);
+ transform.Invert();
+
+ Rect layerContentsVisible = Rect::MaxIntRect();
+
+ if (aLayerContentsVisibleRect) {
+ NS_ASSERTION(aLayerContentsVisibleRect->width >= 0 &&
+ aLayerContentsVisibleRect->height >= 0,
+ "Bad layer contents rectangle");
+ // restrict to aLayerContentsVisibleRect before call GfxRectToIntRect,
+ // in case layerVisible is extremely large (as it can be when
+ // projecting through the inverse of a 3D transform)
+ layerContentsVisible = Rect(
+ aLayerContentsVisibleRect->x, aLayerContentsVisibleRect->y,
+ aLayerContentsVisibleRect->width, aLayerContentsVisibleRect->height);
+ }
+
+ Rect layerVisible =
+ transform.ProjectRectBounds(outerVisible, layerContentsVisible);
+
+ layerVisible.RoundOut();
+
+ IntRect intRect;
+ if (!layerVisible.ToIntRect(&intRect)) {
+ intRect = IntRect::MaxIntRect();
+ }
+
+ *aOuterVisibleRegion = intRect;
+ }
+
+ aLayer->SetVisibleRegion(
+ LayerIntRegion::FromUnknownRegion(*aOuterVisibleRegion));
+}
+
+void ContainerState::SetOuterVisibleRegionForLayer(
+ Layer* aLayer, const nsIntRegion& aOuterVisibleRegion,
+ const nsIntRect* aLayerContentsVisibleRect,
+ bool aOuterUntransformed) const {
+ nsIntRegion visRegion = aOuterVisibleRegion;
+ if (!aOuterUntransformed) {
+ visRegion.MoveBy(mParameters.mOffset);
+ }
+ SetOuterVisibleRegion(aLayer, &visRegion, aLayerContentsVisibleRect,
+ aOuterUntransformed);
+}
+
+nscolor ContainerState::FindOpaqueBackgroundColorInLayer(
+ const PaintedLayerData* aData, const nsIntRect& aRect,
+ bool* aOutIntersectsLayer) const {
+ *aOutIntersectsLayer = true;
+
+ // Scan the candidate's display items.
+ nsIntRect deviceRect = aRect;
+ nsRect appUnitRect = ToAppUnits(deviceRect, mAppUnitsPerDevPixel);
+ appUnitRect.ScaleInverseRoundOut(mParameters.mXScale, mParameters.mYScale);
+
+ for (auto& assignedItem : Reversed(aData->mAssignedDisplayItems)) {
+ if (assignedItem.HasOpacity() || assignedItem.HasTransform()) {
+ // We cannot easily calculate the opaque background color for items inside
+ // a flattened effect.
+ continue;
+ }
+
+ if (IsEffectEndMarker(assignedItem.mType)) {
+ // An optimization: the underlying display item for effect markers is the
+ // same for both start and end markers. Skip the effect end markers.
+ continue;
+ }
+
+ nsDisplayItem* item = assignedItem.mItem;
+ bool snap;
+ nsRect bounds = item->GetBounds(mBuilder, &snap);
+ if (snap && mSnappingEnabled) {
+ nsIntRect snappedBounds = ScaleToNearestPixels(bounds);
+ if (!snappedBounds.Intersects(deviceRect)) continue;
+
+ if (!snappedBounds.Contains(deviceRect)) return NS_RGBA(0, 0, 0, 0);
+
+ } else {
+ // The layer's visible rect is already (close enough to) pixel
+ // aligned, so no need to round out and in here.
+ if (!bounds.Intersects(appUnitRect)) continue;
+
+ if (!bounds.Contains(appUnitRect)) return NS_RGBA(0, 0, 0, 0);
+ }
+
+ if (item->IsInvisibleInRect(appUnitRect)) {
+ continue;
+ }
+
+ if (item->GetClip().IsRectAffectedByClip(deviceRect, mParameters.mXScale,
+ mParameters.mYScale,
+ mAppUnitsPerDevPixel)) {
+ return NS_RGBA(0, 0, 0, 0);
+ }
+
+ MOZ_ASSERT(!assignedItem.HasOpacity() && !assignedItem.HasTransform());
+ Maybe<nscolor> color = item->IsUniform(mBuilder);
+
+ if (color && NS_GET_A(*color) == 255) {
+ return *color;
+ }
+
+ return NS_RGBA(0, 0, 0, 0);
+ }
+
+ *aOutIntersectsLayer = false;
+ return NS_RGBA(0, 0, 0, 0);
+}
+
+nscolor PaintedLayerDataNode::FindOpaqueBackgroundColor(
+ const nsIntRegion& aTargetVisibleRegion, int32_t aUnderIndex) const {
+ if (aUnderIndex == ABOVE_TOP) {
+ aUnderIndex = mPaintedLayerDataStack.Length();
+ }
+ for (int32_t i = aUnderIndex - 1; i >= 0; --i) {
+ const PaintedLayerData* candidate = &mPaintedLayerDataStack[i];
+ if (candidate->VisibleAboveRegionIntersects(aTargetVisibleRegion)) {
+ // Some non-PaintedLayer content between target and candidate; this is
+ // hopeless
+ return NS_RGBA(0, 0, 0, 0);
+ }
+
+ if (!candidate->VisibleRegionIntersects(aTargetVisibleRegion)) {
+ // The layer doesn't intersect our target, ignore it and move on
+ continue;
+ }
+
+ bool intersectsLayer = true;
+ nsIntRect rect = aTargetVisibleRegion.GetBounds();
+ nscolor color = mTree.ContState().FindOpaqueBackgroundColorInLayer(
+ candidate, rect, &intersectsLayer);
+ if (!intersectsLayer) {
+ continue;
+ }
+ return color;
+ }
+ if (mAllDrawingAboveBackground ||
+ !mVisibleAboveBackgroundRegion.Intersect(aTargetVisibleRegion)
+ .IsEmpty()) {
+ // Some non-PaintedLayer content is between this node's background and
+ // target.
+ return NS_RGBA(0, 0, 0, 0);
+ }
+ return FindOpaqueBackgroundColorInParentNode();
+}
+
+nscolor PaintedLayerDataNode::FindOpaqueBackgroundColorCoveringEverything()
+ const {
+ if (!mPaintedLayerDataStack.IsEmpty() || mAllDrawingAboveBackground ||
+ !mVisibleAboveBackgroundRegion.IsEmpty()) {
+ return NS_RGBA(0, 0, 0, 0);
+ }
+ return FindOpaqueBackgroundColorInParentNode();
+}
+
+nscolor PaintedLayerDataNode::FindOpaqueBackgroundColorInParentNode() const {
+ if (mParent) {
+ if (mHasClip) {
+ // Check whether our parent node has uniform content behind our whole
+ // clip.
+ // There's one tricky case here: If our parent node is also a scrollable,
+ // and is currently scrolled in such a way that this inner one is
+ // clipped by it, then it's not really clear how we should determine
+ // whether we have a uniform background in the parent: There might be
+ // non-uniform content in the parts that our scroll port covers in the
+ // parent and that are currently outside the parent's clip.
+ // For now, we'll fail to pull a background color in that case.
+ return mParent->FindOpaqueBackgroundColor(mClipRect);
+ }
+ return mParent->FindOpaqueBackgroundColorCoveringEverything();
+ }
+ // We are the root.
+ return mTree.UniformBackgroundColor();
+}
+
+bool PaintedLayerData::CanOptimizeToImageLayer(nsDisplayListBuilder* aBuilder) {
+ if (!mImage) {
+ return false;
+ }
+
+ return mImage->CanOptimizeToImageLayer(mLayer->Manager(), aBuilder);
+}
+
+already_AddRefed<ImageContainer> PaintedLayerData::GetContainerForImageLayer(
+ nsDisplayListBuilder* aBuilder) {
+ if (!mImage) {
+ return nullptr;
+ }
+
+ return mImage->GetContainer(mLayer->Manager(), aBuilder);
+}
+
+PaintedLayerDataNode::PaintedLayerDataNode(
+ PaintedLayerDataTree& aTree, PaintedLayerDataNode* aParent,
+ AnimatedGeometryRoot* aAnimatedGeometryRoot)
+ : mTree(aTree),
+ mParent(aParent),
+ mAnimatedGeometryRoot(aAnimatedGeometryRoot),
+ mAllDrawingAboveBackground(false) {
+ MOZ_ASSERT(nsLayoutUtils::IsAncestorFrameCrossDoc(
+ mTree.Builder()->RootReferenceFrame(), *mAnimatedGeometryRoot));
+ mHasClip = mTree.IsClippedWithRespectToParentAnimatedGeometryRoot(
+ mAnimatedGeometryRoot, &mClipRect);
+}
+
+PaintedLayerDataNode::~PaintedLayerDataNode() {
+ MOZ_ASSERT(mPaintedLayerDataStack.IsEmpty());
+ MOZ_ASSERT(mChildren.IsEmpty());
+}
+
+PaintedLayerDataNode* PaintedLayerDataNode::AddChildNodeFor(
+ AnimatedGeometryRoot* aAnimatedGeometryRoot) {
+ MOZ_ASSERT(aAnimatedGeometryRoot->mParentAGR == mAnimatedGeometryRoot);
+ UniquePtr<PaintedLayerDataNode> child =
+ MakeUnique<PaintedLayerDataNode>(mTree, this, aAnimatedGeometryRoot);
+ mChildren.AppendElement(std::move(child));
+ return mChildren.LastElement().get();
+}
+
+template <typename NewPaintedLayerCallbackType>
+PaintedLayerData* PaintedLayerDataNode::FindPaintedLayerFor(
+ const nsIntRect& aVisibleRect, const bool aBackfaceHidden,
+ const ActiveScrolledRoot* aASR, const DisplayItemClipChain* aClipChain,
+ NewPaintedLayerCallbackType aNewPaintedLayerCallback) {
+ if (!mPaintedLayerDataStack.IsEmpty()) {
+ PaintedLayerData* lowestUsableLayer = nullptr;
+ for (auto& data : Reversed(mPaintedLayerDataStack)) {
+ if (data.mVisibleAboveRegion.Intersects(aVisibleRect)) {
+ break;
+ }
+ if (data.mBackfaceHidden == aBackfaceHidden && data.mASR == aASR &&
+ data.mClipChain == aClipChain) {
+ lowestUsableLayer = &data;
+ }
+ // Also check whether the event-regions intersect the visible rect,
+ // unless we're in an inactive layer, in which case the event-regions
+ // will be hoisted out into their own layer.
+ // For performance reasons, we check the intersection with the bounds
+ // of the event-regions.
+ if (!mTree.ContState().IsInInactiveLayer() &&
+ (data.mScaledHitRegionBounds.Intersects(aVisibleRect) ||
+ data.mScaledMaybeHitRegionBounds.Intersects(aVisibleRect))) {
+ break;
+ }
+ // If the visible region intersects with the current layer then we
+ // can't possibly use any of the layers below it, so stop the search
+ // now.
+ //
+ // If we're trying to minimize painted layer size and we don't
+ // intersect the current visible region, then make sure we don't
+ // use this painted layer.
+ if (data.mVisibleRegion.Intersects(aVisibleRect)) {
+ break;
+ }
+
+ if (StaticPrefs::layout_smaller_painted_layers()) {
+ lowestUsableLayer = nullptr;
+ }
+ }
+ if (lowestUsableLayer) {
+ return lowestUsableLayer;
+ }
+ }
+ PaintedLayerData* data = mPaintedLayerDataStack.AppendElement();
+ aNewPaintedLayerCallback(data);
+
+ return data;
+}
+
+void PaintedLayerDataNode::FinishChildrenIntersecting(const nsIntRect& aRect) {
+ for (int32_t i = mChildren.Length() - 1; i >= 0; i--) {
+ if (mChildren[i]->Intersects(aRect)) {
+ mChildren[i]->Finish(true);
+ mChildren.RemoveElementAt(i);
+ }
+ }
+}
+
+void PaintedLayerDataNode::FinishAllChildren(
+ bool aThisNodeNeedsAccurateVisibleAboveRegion) {
+ for (int32_t i = mChildren.Length() - 1; i >= 0; i--) {
+ mChildren[i]->Finish(aThisNodeNeedsAccurateVisibleAboveRegion);
+ }
+ mChildren.Clear();
+}
+
+void PaintedLayerDataNode::Finish(bool aParentNeedsAccurateVisibleAboveRegion) {
+ // Skip "visible above region" maintenance, because this node is going away.
+ FinishAllChildren(false);
+
+ PopAllPaintedLayerData();
+
+ if (mParent && aParentNeedsAccurateVisibleAboveRegion) {
+ if (mHasClip) {
+ mParent->AddToVisibleAboveRegion(mClipRect);
+ } else {
+ mParent->SetAllDrawingAbove();
+ }
+ }
+ mTree.NodeWasFinished(mAnimatedGeometryRoot);
+}
+
+void PaintedLayerDataNode::AddToVisibleAboveRegion(const nsIntRect& aRect) {
+ nsIntRegion& visibleAboveRegion =
+ mPaintedLayerDataStack.IsEmpty()
+ ? mVisibleAboveBackgroundRegion
+ : mPaintedLayerDataStack.LastElement().mVisibleAboveRegion;
+ visibleAboveRegion.Or(visibleAboveRegion, aRect);
+ visibleAboveRegion.SimplifyOutward(8);
+}
+
+void PaintedLayerDataNode::SetAllDrawingAbove() {
+ PopAllPaintedLayerData();
+ mAllDrawingAboveBackground = true;
+ mVisibleAboveBackgroundRegion.SetEmpty();
+}
+
+void PaintedLayerDataNode::PopAllPaintedLayerData() {
+ for (int32_t index = mPaintedLayerDataStack.Length() - 1; index >= 0;
+ index--) {
+ PaintedLayerData& data = mPaintedLayerDataStack[index];
+ mTree.ContState().FinishPaintedLayerData(data, [this, &data, index]() {
+ return this->FindOpaqueBackgroundColor(data.mVisibleRegion, index);
+ });
+ }
+ mPaintedLayerDataStack.Clear();
+}
+
+void PaintedLayerDataTree::InitializeForInactiveLayer(
+ AnimatedGeometryRoot* aAnimatedGeometryRoot) {
+ mForInactiveLayer = true;
+ mRoot.emplace(*this, nullptr, aAnimatedGeometryRoot);
+}
+
+nsDisplayListBuilder* PaintedLayerDataTree::Builder() const {
+ return mContainerState.Builder();
+}
+
+void PaintedLayerDataTree::Finish() {
+ if (mRoot) {
+ mRoot->Finish(false);
+ }
+ MOZ_ASSERT(mNodes.Count() == 0);
+ mRoot.reset();
+}
+
+void PaintedLayerDataTree::NodeWasFinished(
+ AnimatedGeometryRoot* aAnimatedGeometryRoot) {
+ mNodes.Remove(aAnimatedGeometryRoot);
+}
+
+void PaintedLayerDataTree::AddingOwnLayer(
+ AnimatedGeometryRoot* aAnimatedGeometryRoot, const nsIntRect* aRect,
+ nscolor* aOutUniformBackgroundColor) {
+ PaintedLayerDataNode* node = nullptr;
+ if (mForInactiveLayer) {
+ node = mRoot.ptr();
+ } else {
+ FinishPotentiallyIntersectingNodes(aAnimatedGeometryRoot, aRect);
+ node = EnsureNodeFor(aAnimatedGeometryRoot);
+ }
+ if (aRect) {
+ if (aOutUniformBackgroundColor) {
+ *aOutUniformBackgroundColor = node->FindOpaqueBackgroundColor(*aRect);
+ }
+ node->AddToVisibleAboveRegion(*aRect);
+ } else {
+ if (aOutUniformBackgroundColor) {
+ *aOutUniformBackgroundColor =
+ node->FindOpaqueBackgroundColorCoveringEverything();
+ }
+ node->SetAllDrawingAbove();
+ }
+}
+
+template <typename NewPaintedLayerCallbackType>
+PaintedLayerData* PaintedLayerDataTree::FindPaintedLayerFor(
+ AnimatedGeometryRoot* aAnimatedGeometryRoot, const ActiveScrolledRoot* aASR,
+ const DisplayItemClipChain* aClipChain, const nsIntRect& aVisibleRect,
+ const bool aBackfaceHidden,
+ NewPaintedLayerCallbackType aNewPaintedLayerCallback) {
+ const nsIntRect* bounds = &aVisibleRect;
+ PaintedLayerDataNode* node = nullptr;
+ if (mForInactiveLayer) {
+ node = mRoot.ptr();
+ } else {
+ FinishPotentiallyIntersectingNodes(aAnimatedGeometryRoot, bounds);
+ node = EnsureNodeFor(aAnimatedGeometryRoot);
+ }
+
+ PaintedLayerData* data =
+ node->FindPaintedLayerFor(aVisibleRect, aBackfaceHidden, aASR, aClipChain,
+ aNewPaintedLayerCallback);
+ return data;
+}
+
+void PaintedLayerDataTree::FinishPotentiallyIntersectingNodes(
+ AnimatedGeometryRoot* aAnimatedGeometryRoot, const nsIntRect* aRect) {
+ AnimatedGeometryRoot* ancestorThatIsChildOfCommonAncestor = nullptr;
+ PaintedLayerDataNode* ancestorNode = FindNodeForAncestorAnimatedGeometryRoot(
+ aAnimatedGeometryRoot, &ancestorThatIsChildOfCommonAncestor);
+ if (!ancestorNode) {
+ // None of our ancestors are in the tree. This should only happen if this
+ // is the very first item we're looking at.
+ MOZ_ASSERT(!mRoot);
+ return;
+ }
+
+ if (ancestorNode->GetAnimatedGeometryRoot() == aAnimatedGeometryRoot) {
+ // aAnimatedGeometryRoot already has a node in the tree.
+ // This is the common case.
+ MOZ_ASSERT(!ancestorThatIsChildOfCommonAncestor);
+ if (aRect) {
+ ancestorNode->FinishChildrenIntersecting(*aRect);
+ } else {
+ ancestorNode->FinishAllChildren();
+ }
+ return;
+ }
+
+ // We have found an existing ancestor, but it's a proper ancestor of our
+ // animated geometry root.
+ // ancestorThatIsChildOfCommonAncestor is the last animated geometry root
+ // encountered on the way up from aAnimatedGeometryRoot to ancestorNode.
+ MOZ_ASSERT(ancestorThatIsChildOfCommonAncestor);
+ MOZ_ASSERT(nsLayoutUtils::IsAncestorFrameCrossDoc(
+ *ancestorThatIsChildOfCommonAncestor, *aAnimatedGeometryRoot));
+ MOZ_ASSERT(ancestorThatIsChildOfCommonAncestor->mParentAGR ==
+ ancestorNode->GetAnimatedGeometryRoot());
+
+ // ancestorThatIsChildOfCommonAncestor is not in the tree yet!
+ MOZ_ASSERT(!mNodes.Get(ancestorThatIsChildOfCommonAncestor));
+
+ // We're about to add a node for ancestorThatIsChildOfCommonAncestor, so we
+ // finish all intersecting siblings.
+ nsIntRect clip;
+ if (IsClippedWithRespectToParentAnimatedGeometryRoot(
+ ancestorThatIsChildOfCommonAncestor, &clip)) {
+ ancestorNode->FinishChildrenIntersecting(clip);
+ } else {
+ ancestorNode->FinishAllChildren();
+ }
+}
+
+PaintedLayerDataNode* PaintedLayerDataTree::EnsureNodeFor(
+ AnimatedGeometryRoot* aAnimatedGeometryRoot) {
+ MOZ_ASSERT(aAnimatedGeometryRoot);
+ PaintedLayerDataNode* node = mNodes.Get(aAnimatedGeometryRoot);
+ if (node) {
+ return node;
+ }
+
+ AnimatedGeometryRoot* parentAnimatedGeometryRoot =
+ aAnimatedGeometryRoot->mParentAGR;
+ if (!parentAnimatedGeometryRoot) {
+ MOZ_ASSERT(!mRoot);
+ MOZ_ASSERT(*aAnimatedGeometryRoot == Builder()->RootReferenceFrame());
+ mRoot.emplace(*this, nullptr, aAnimatedGeometryRoot);
+ node = mRoot.ptr();
+ } else {
+ PaintedLayerDataNode* parentNode =
+ EnsureNodeFor(parentAnimatedGeometryRoot);
+ MOZ_ASSERT(parentNode);
+ node = parentNode->AddChildNodeFor(aAnimatedGeometryRoot);
+ }
+ MOZ_ASSERT(node);
+ mNodes.Put(aAnimatedGeometryRoot, node);
+ return node;
+}
+
+bool PaintedLayerDataTree::IsClippedWithRespectToParentAnimatedGeometryRoot(
+ AnimatedGeometryRoot* aAnimatedGeometryRoot, nsIntRect* aOutClip) {
+ if (mForInactiveLayer) {
+ return false;
+ }
+ nsIScrollableFrame* scrollableFrame =
+ nsLayoutUtils::GetScrollableFrameFor(*aAnimatedGeometryRoot);
+ if (!scrollableFrame) {
+ return false;
+ }
+ nsIFrame* scrollFrame = do_QueryFrame(scrollableFrame);
+ nsRect scrollPort = scrollableFrame->GetScrollPortRect() +
+ Builder()->ToReferenceFrame(scrollFrame);
+ *aOutClip = mContainerState.ScaleToNearestPixels(scrollPort);
+ return true;
+}
+
+PaintedLayerDataNode*
+PaintedLayerDataTree::FindNodeForAncestorAnimatedGeometryRoot(
+ AnimatedGeometryRoot* aAnimatedGeometryRoot,
+ AnimatedGeometryRoot** aOutAncestorChild) {
+ if (!aAnimatedGeometryRoot) {
+ return nullptr;
+ }
+ PaintedLayerDataNode* node = mNodes.Get(aAnimatedGeometryRoot);
+ if (node) {
+ return node;
+ }
+ *aOutAncestorChild = aAnimatedGeometryRoot;
+ return FindNodeForAncestorAnimatedGeometryRoot(
+ aAnimatedGeometryRoot->mParentAGR, aOutAncestorChild);
+}
+
+static bool CanOptimizeAwayPaintedLayer(PaintedLayerData* aData,
+ FrameLayerBuilder* aLayerBuilder) {
+ if (!aLayerBuilder->IsBuildingRetainedLayers()) {
+ return false;
+ }
+
+ // If there's no painted layer with valid content in it that we can reuse,
+ // always create a color or image layer (and potentially throw away an
+ // existing completely invalid painted layer).
+ if (aData->mLayer->GetValidRegion().IsEmpty()) {
+ return true;
+ }
+
+ // There is an existing painted layer we can reuse. Throwing it away can make
+ // compositing cheaper (see bug 946952), but it might cause us to re-allocate
+ // the painted layer frequently due to an animation. So we only discard it if
+ // we're in tree compression mode, which is triggered at a low frequency.
+ return aLayerBuilder->CheckInLayerTreeCompressionMode();
+}
+
+#ifdef DEBUG
+static int32_t FindIndexOfLayerIn(nsTArray<NewLayerEntry>& aArray,
+ Layer* aLayer) {
+ for (uint32_t i = 0; i < aArray.Length(); ++i) {
+ if (aArray[i].mLayer == aLayer) {
+ return i;
+ }
+ }
+ return -1;
+}
+#endif
+
+already_AddRefed<Layer> ContainerState::PrepareImageLayer(
+ PaintedLayerData* aData) {
+ RefPtr<ImageContainer> imageContainer =
+ aData->GetContainerForImageLayer(mBuilder);
+ if (!imageContainer) {
+ return nullptr;
+ }
+
+ RefPtr<ImageLayer> imageLayer = CreateOrRecycleImageLayer(aData->mLayer);
+ imageLayer->SetContainer(imageContainer);
+ aData->mImage->ConfigureLayer(imageLayer, mParameters);
+ imageLayer->SetPostScale(mParameters.mXScale, mParameters.mYScale);
+
+ if (aData->mItemClip->HasClip()) {
+ ParentLayerIntRect clip = ViewAs<ParentLayerPixel>(
+ ScaleToNearestPixels(aData->mItemClip->GetClipRect()));
+ clip.MoveBy(ViewAs<ParentLayerPixel>(mParameters.mOffset));
+ imageLayer->SetClipRect(Some(clip));
+ } else {
+ imageLayer->SetClipRect(Nothing());
+ }
+
+ FLB_LOG_PAINTED_LAYER_DECISION(aData, " Selected image layer=%p\n",
+ imageLayer.get());
+
+ return imageLayer.forget();
+}
+
+already_AddRefed<Layer> ContainerState::PrepareColorLayer(
+ PaintedLayerData* aData) {
+ RefPtr<ColorLayer> colorLayer = CreateOrRecycleColorLayer(aData->mLayer);
+ colorLayer->SetColor(ToDeviceColor(aData->mSolidColor));
+
+ // Copy transform
+ colorLayer->SetBaseTransform(aData->mLayer->GetBaseTransform());
+ colorLayer->SetPostScale(aData->mLayer->GetPostXScale(),
+ aData->mLayer->GetPostYScale());
+
+ nsIntRect visibleRect = aData->mVisibleRegion.GetBounds();
+ visibleRect.MoveBy(-GetTranslationForPaintedLayer(aData->mLayer));
+ colorLayer->SetBounds(visibleRect);
+ colorLayer->SetClipRect(Nothing());
+
+ FLB_LOG_PAINTED_LAYER_DECISION(aData, " Selected color layer=%p\n",
+ colorLayer.get());
+
+ return colorLayer.forget();
+}
+
+static void SetBackfaceHiddenForLayer(bool aBackfaceHidden, Layer* aLayer) {
+ if (aBackfaceHidden) {
+ aLayer->SetContentFlags(aLayer->GetContentFlags() |
+ Layer::CONTENT_BACKFACE_HIDDEN);
+ } else {
+ aLayer->SetContentFlags(aLayer->GetContentFlags() &
+ ~Layer::CONTENT_BACKFACE_HIDDEN);
+ }
+}
+
+template <typename FindOpaqueBackgroundColorCallbackType>
+void ContainerState::FinishPaintedLayerData(
+ PaintedLayerData& aData,
+ FindOpaqueBackgroundColorCallbackType aFindOpaqueBackgroundColor) {
+ PaintedLayerData* data = &aData;
+
+ if (!data->mLayer) {
+ // No layer was recycled, so we create a new one.
+ RefPtr<PaintedLayer> paintedLayer = CreatePaintedLayer(data);
+ data->mLayer = paintedLayer;
+
+ NS_ASSERTION(FindIndexOfLayerIn(mNewChildLayers, paintedLayer) < 0,
+ "Layer already in list???");
+ mNewChildLayers[data->mNewChildLayersIndex].mLayer =
+ std::move(paintedLayer);
+ }
+
+ auto* userData = GetPaintedDisplayItemLayerUserData(data->mLayer);
+ NS_ASSERTION(userData, "where did our user data go?");
+ userData->mLastItemCount = data->mAssignedDisplayItems.size();
+
+ NewLayerEntry* newLayerEntry = &mNewChildLayers[data->mNewChildLayersIndex];
+
+ RefPtr<Layer> layer;
+ bool canOptimizeToImageLayer = data->CanOptimizeToImageLayer(mBuilder);
+
+ FLB_LOG_PAINTED_LAYER_DECISION(data, "Selecting layer for pld=%p\n", data);
+ FLB_LOG_PAINTED_LAYER_DECISION(
+ data, " Solid=%i, hasImage=%c, canOptimizeAwayPaintedLayer=%i\n",
+ data->mIsSolidColorInVisibleRegion, canOptimizeToImageLayer ? 'y' : 'n',
+ CanOptimizeAwayPaintedLayer(data, mLayerBuilder));
+
+ if ((data->mIsSolidColorInVisibleRegion || canOptimizeToImageLayer) &&
+ CanOptimizeAwayPaintedLayer(data, mLayerBuilder)) {
+ NS_ASSERTION(
+ !(data->mIsSolidColorInVisibleRegion && canOptimizeToImageLayer),
+ "Can't be a solid color as well as an image!");
+
+ layer = canOptimizeToImageLayer ? PrepareImageLayer(data)
+ : PrepareColorLayer(data);
+
+ if (layer) {
+ NS_ASSERTION(FindIndexOfLayerIn(mNewChildLayers, layer) < 0,
+ "Layer already in list???");
+ NS_ASSERTION(newLayerEntry->mLayer == data->mLayer,
+ "Painted layer at wrong index");
+ // Store optimized layer in reserved slot
+ NewLayerEntry* paintedLayerEntry = newLayerEntry;
+ newLayerEntry = &mNewChildLayers[data->mNewChildLayersIndex + 1];
+ NS_ASSERTION(!newLayerEntry->mLayer, "Slot already occupied?");
+ newLayerEntry->mLayer = layer;
+ newLayerEntry->mAnimatedGeometryRoot = data->mAnimatedGeometryRoot;
+ newLayerEntry->mASR = paintedLayerEntry->mASR;
+ newLayerEntry->mClipChain = paintedLayerEntry->mClipChain;
+ newLayerEntry->mScrollMetadataASR = paintedLayerEntry->mScrollMetadataASR;
+
+ // Hide the PaintedLayer. We leave it in the layer tree so that we
+ // can find and recycle it later.
+ ParentLayerIntRect emptyRect;
+ data->mLayer->SetClipRect(Some(emptyRect));
+ data->mLayer->SetVisibleRegion(LayerIntRegion());
+ data->mLayer->InvalidateWholeLayer();
+ data->mLayer->SetEventRegions(EventRegions());
+ }
+ }
+
+ if (!layer) {
+ // We couldn't optimize to an image layer or a color layer above.
+ layer = data->mLayer;
+ layer->SetClipRect(Nothing());
+ FLB_LOG_PAINTED_LAYER_DECISION(data, " Selected painted layer=%p\n",
+ layer.get());
+ }
+
+ for (auto& item : data->mAssignedDisplayItems) {
+ MOZ_ASSERT(item.mItem->GetType() !=
+ DisplayItemType::TYPE_COMPOSITOR_HITTEST_INFO);
+
+ if (IsEffectEndMarker(item.mType)) {
+ // Do not invalidate for end markers.
+ continue;
+ }
+
+ InvalidateForLayerChange(item.mItem, data->mLayer, item.mDisplayItemData);
+ mLayerBuilder->AddPaintedDisplayItem(data, item, layer);
+ item.mDisplayItemData = nullptr;
+ }
+
+ if (mLayerBuilder->IsBuildingRetainedLayers()) {
+ newLayerEntry->mVisibleRegion = data->mVisibleRegion;
+ newLayerEntry->mOpaqueRegion = data->mOpaqueRegion;
+ newLayerEntry->mHideAllLayersBelow = data->mHideAllLayersBelow;
+ newLayerEntry->mOpaqueForAnimatedGeometryRootParent =
+ data->mOpaqueForAnimatedGeometryRootParent;
+ } else {
+ SetOuterVisibleRegionForLayer(layer, data->mVisibleRegion);
+ }
+
+#ifdef MOZ_DUMP_PAINTING
+ if (!data->mLog.IsEmpty()) {
+ PaintedLayerData* containingPld =
+ mLayerBuilder->GetContainingPaintedLayerData();
+ if (containingPld && containingPld->mLayer) {
+ containingPld->mLayer->AddExtraDumpInfo(nsCString(data->mLog));
+ } else {
+ layer->AddExtraDumpInfo(nsCString(data->mLog));
+ }
+ }
+#endif
+
+ mLayerBuilder->AddPaintedLayerItemsEntry(userData);
+
+ nsIntRegion transparentRegion;
+ transparentRegion.Sub(data->mVisibleRegion, data->mOpaqueRegion);
+ bool isOpaque = transparentRegion.IsEmpty();
+ // For translucent PaintedLayers, try to find an opaque background
+ // color that covers the entire area beneath it so we can pull that
+ // color into this layer to make it opaque.
+ if (layer == data->mLayer) {
+ nscolor backgroundColor = NS_RGBA(0, 0, 0, 0);
+ if (!isOpaque) {
+ backgroundColor = aFindOpaqueBackgroundColor();
+ if (NS_GET_A(backgroundColor) == 255) {
+ isOpaque = true;
+ }
+ }
+
+ // Store the background color
+ if (userData->mForcedBackgroundColor != backgroundColor) {
+ // Invalidate the entire target PaintedLayer since we're changing
+ // the background color
+#ifdef MOZ_DUMP_PAINTING
+ if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) {
+ printf_stderr(
+ "Forced background color has changed from #%08X to #%08X "
+ "on layer %p\n",
+ userData->mForcedBackgroundColor, backgroundColor, data->mLayer);
+ nsAutoCString str;
+ AppendToString(str, data->mLayer->GetValidRegion());
+ printf_stderr("Invalidating layer %p: %s\n", data->mLayer, str.get());
+ }
+#endif
+ data->mLayer->InvalidateWholeLayer();
+ }
+ userData->mForcedBackgroundColor = backgroundColor;
+ } else {
+ // mask layer for image and color layers
+ SetupMaskLayer(layer, *data->mItemClip);
+ }
+
+ uint32_t flags = 0;
+ nsIWidget* widget = mContainerReferenceFrame->PresContext()->GetRootWidget();
+ // See bug 941095. Not quite ready to disable this.
+ bool hidpi = false && widget && widget->GetDefaultScale().scale >= 2;
+ if (hidpi) {
+ flags |= Layer::CONTENT_DISABLE_SUBPIXEL_AA;
+ }
+ if (isOpaque && !data->mForceTransparentSurface) {
+ flags |= Layer::CONTENT_OPAQUE;
+ } else if (data->mNeedComponentAlpha && !hidpi) {
+ flags |= Layer::CONTENT_COMPONENT_ALPHA;
+ }
+ layer->SetContentFlags(flags);
+
+ userData->mItems = std::move(data->mAssignedDisplayItems);
+ userData->mContainerLayerFrame = GetContainerFrame();
+
+ PaintedLayerData* containingPaintedLayerData =
+ mLayerBuilder->GetContainingPaintedLayerData();
+ // If we're building layers for an inactive layer, the event regions are
+ // clipped to the inactive layer's clip prior to being combined into the
+ // event regions of the containing PLD.
+ // For the dispatch-to-content and maybe-hit regions, rounded corners on
+ // the clip are ignored, since these are approximate regions. For the
+ // remaining regions, rounded corners in the clip cause the region to
+ // be combined into the corresponding "imprecise" region of the
+ // containing's PLD (e.g. the maybe-hit region instead of the hit region).
+ const DisplayItemClip* inactiveLayerClip =
+ mLayerBuilder->GetInactiveLayerClip();
+ if (containingPaintedLayerData) {
+ if (!data->mDispatchToContentHitRegion.GetBounds().IsEmpty()) {
+ nsRect rect = nsLayoutUtils::TransformFrameRectToAncestor(
+ mContainerReferenceFrame,
+ data->mDispatchToContentHitRegion.GetBounds(),
+ containingPaintedLayerData->mReferenceFrame);
+ if (inactiveLayerClip) {
+ rect = inactiveLayerClip->ApplyNonRoundedIntersection(rect);
+ }
+ containingPaintedLayerData->mDispatchToContentHitRegion.Or(
+ containingPaintedLayerData->mDispatchToContentHitRegion, rect);
+ containingPaintedLayerData->mDispatchToContentHitRegion.SimplifyOutward(
+ 8);
+ if (data->mDTCRequiresTargetConfirmation) {
+ containingPaintedLayerData->mDTCRequiresTargetConfirmation = true;
+ }
+ }
+ if (!data->mMaybeHitRegion.GetBounds().IsEmpty()) {
+ nsRect rect = nsLayoutUtils::TransformFrameRectToAncestor(
+ mContainerReferenceFrame, data->mMaybeHitRegion.GetBounds(),
+ containingPaintedLayerData->mReferenceFrame);
+ if (inactiveLayerClip) {
+ rect = inactiveLayerClip->ApplyNonRoundedIntersection(rect);
+ }
+ containingPaintedLayerData->mMaybeHitRegion.Or(
+ containingPaintedLayerData->mMaybeHitRegion, rect);
+ containingPaintedLayerData->mMaybeHitRegion.SimplifyOutward(8);
+ }
+ Maybe<Matrix4x4Flagged> matrixCache;
+ nsLayoutUtils::TransformToAncestorAndCombineRegions(
+ data->mHitRegion, mContainerReferenceFrame,
+ containingPaintedLayerData->mReferenceFrame,
+ &containingPaintedLayerData->mHitRegion,
+ &containingPaintedLayerData->mMaybeHitRegion, &matrixCache,
+ inactiveLayerClip);
+ // See the comment in nsDisplayList::AddFrame, where the touch action
+ // regions are handled. The same thing applies here.
+ bool alreadyHadRegions =
+ !containingPaintedLayerData->mNoActionRegion.IsEmpty() ||
+ !containingPaintedLayerData->mHorizontalPanRegion.IsEmpty() ||
+ !containingPaintedLayerData->mVerticalPanRegion.IsEmpty();
+ nsLayoutUtils::TransformToAncestorAndCombineRegions(
+ data->mNoActionRegion, mContainerReferenceFrame,
+ containingPaintedLayerData->mReferenceFrame,
+ &containingPaintedLayerData->mNoActionRegion,
+ &containingPaintedLayerData->mDispatchToContentHitRegion, &matrixCache,
+ inactiveLayerClip);
+ nsLayoutUtils::TransformToAncestorAndCombineRegions(
+ data->mHorizontalPanRegion, mContainerReferenceFrame,
+ containingPaintedLayerData->mReferenceFrame,
+ &containingPaintedLayerData->mHorizontalPanRegion,
+ &containingPaintedLayerData->mDispatchToContentHitRegion, &matrixCache,
+ inactiveLayerClip);
+ nsLayoutUtils::TransformToAncestorAndCombineRegions(
+ data->mVerticalPanRegion, mContainerReferenceFrame,
+ containingPaintedLayerData->mReferenceFrame,
+ &containingPaintedLayerData->mVerticalPanRegion,
+ &containingPaintedLayerData->mDispatchToContentHitRegion, &matrixCache,
+ inactiveLayerClip);
+ if (alreadyHadRegions) {
+ containingPaintedLayerData->mDispatchToContentHitRegion.OrWith(
+ containingPaintedLayerData->CombinedTouchActionRegion());
+ }
+ containingPaintedLayerData->HitRegionsUpdated();
+ } else {
+ EventRegions regions(
+ ScaleRegionToOutsidePixels(data->mHitRegion),
+ ScaleRegionToOutsidePixels(data->mMaybeHitRegion),
+ ScaleRegionToOutsidePixels(data->mDispatchToContentHitRegion),
+ ScaleRegionToOutsidePixels(data->mNoActionRegion),
+ ScaleRegionToOutsidePixels(data->mHorizontalPanRegion),
+ ScaleRegionToOutsidePixels(data->mVerticalPanRegion),
+ data->mDTCRequiresTargetConfirmation);
+
+ Matrix mat = layer->GetTransform().As2D();
+ mat.Invert();
+ regions.ApplyTranslationAndScale(mat._31, mat._32, mat._11, mat._22);
+
+ layer->SetEventRegions(regions);
+ }
+
+ SetBackfaceHiddenForLayer(data->mBackfaceHidden, data->mLayer);
+ if (layer != data->mLayer) {
+ SetBackfaceHiddenForLayer(data->mBackfaceHidden, layer);
+ }
+}
+
+static bool IsItemAreaInWindowOpaqueRegion(
+ nsDisplayListBuilder* aBuilder, nsDisplayItem* aItem,
+ const nsRect& aComponentAlphaBounds) {
+ if (!aItem->Frame()->PresContext()->IsChrome()) {
+ // Assume that Web content is always in the window opaque region.
+ return true;
+ }
+ if (aItem->ReferenceFrame() != aBuilder->RootReferenceFrame()) {
+ // aItem is probably in some transformed subtree.
+ // We're not going to bother figuring out where this landed, we're just
+ // going to assume it might have landed over a transparent part of
+ // the window.
+ return false;
+ }
+ return aBuilder->GetWindowOpaqueRegion().Contains(aComponentAlphaBounds);
+}
+
+void PaintedLayerData::UpdateEffectStatus(DisplayItemEntryType aType,
+ nsTArray<size_t>& aOpacityIndices) {
+ switch (aType) {
+ case DisplayItemEntryType::PushOpacity:
+ // The index of the new assigned display item in |mAssignedDisplayItems|
+ // array will be the current length of the array.
+ aOpacityIndices.AppendElement(mAssignedDisplayItems.size());
+ break;
+ case DisplayItemEntryType::PopOpacity:
+ MOZ_ASSERT(!aOpacityIndices.IsEmpty());
+ aOpacityIndices.RemoveLastElement();
+ break;
+#ifdef DEBUG
+ case DisplayItemEntryType::PopTransform:
+ MOZ_ASSERT(mTransformLevel >= 0);
+ mTransformLevel--;
+ break;
+ case DisplayItemEntryType::PushTransform:
+ mTransformLevel++;
+ break;
+#endif
+ default:
+ break;
+ }
+}
+
+bool PaintedLayerData::SetupComponentAlpha(
+ ContainerState* aState, nsPaintedDisplayItem* aItem,
+ const nsIntRect& aVisibleRect, const TransformClipNode* aTransform) {
+ nsRect componentAlphaBounds =
+ aItem->GetComponentAlphaBounds(aState->mBuilder);
+
+ if (componentAlphaBounds.IsEmpty()) {
+ // The item does not require component alpha, nothing do do here.
+ return false;
+ }
+
+ if (aTransform) {
+ componentAlphaBounds = aTransform->TransformRect(
+ componentAlphaBounds, aState->mAppUnitsPerDevPixel);
+ }
+
+ const nsIntRect pixelBounds =
+ aState->ScaleToOutsidePixels(componentAlphaBounds, false);
+
+ const nsIntRect visibleRect = pixelBounds.Intersect(aVisibleRect);
+
+ if (!mOpaqueRegion.Contains(visibleRect)) {
+ nsRect buildingRect = aItem->GetBuildingRect();
+
+ if (aTransform) {
+ buildingRect =
+ aTransform->TransformRect(buildingRect, aState->mAppUnitsPerDevPixel);
+ }
+
+ const nsRect tightBounds = componentAlphaBounds.Intersect(buildingRect);
+
+ if (IsItemAreaInWindowOpaqueRegion(aState->mBuilder, aItem, tightBounds)) {
+ mNeedComponentAlpha = true;
+ } else {
+ // There is no opaque background below the item, disable component alpha.
+ aItem->DisableComponentAlpha();
+ return false;
+ }
+ }
+
+ return true;
+}
+
+UniquePtr<InactiveLayerData> PaintedLayerData::CreateInactiveLayerData(
+ ContainerState* aState, nsPaintedDisplayItem* aItem,
+ DisplayItemData* aData) {
+ RefPtr<BasicLayerManager> tempManager;
+ if (aData) {
+ tempManager = aData->InactiveManager();
+ }
+ if (!tempManager) {
+ tempManager = new BasicLayerManager(BasicLayerManager::BLM_INACTIVE);
+ }
+ UniquePtr<InactiveLayerData> data = MakeUnique<InactiveLayerData>();
+ data->mLayerManager = tempManager;
+
+ FrameLayerBuilder* layerBuilder = new FrameLayerBuilder();
+ // Ownership of layerBuilder is passed to tempManager.
+ layerBuilder->Init(aState->Builder(), tempManager, this, true,
+ &aItem->GetClip());
+
+ tempManager->BeginTransaction();
+ if (aState->LayerBuilder()->GetRetainingLayerManager()) {
+ layerBuilder->DidBeginRetainedLayerTransaction(tempManager);
+ }
+
+ data->mProps = LayerProperties::CloneFrom(tempManager->GetRoot());
+ data->mLayer = aItem->BuildLayer(aState->Builder(), tempManager,
+ ContainerLayerParameters());
+ return data;
+}
+
+void PaintedLayerData::Accumulate(
+ ContainerState* aState, nsPaintedDisplayItem* aItem,
+ const nsIntRect& aVisibleRect, const nsRect& aContentRect,
+ const DisplayItemClip& aClip, LayerState aLayerState, nsDisplayList* aList,
+ DisplayItemEntryType aType, nsTArray<size_t>& aOpacityIndices,
+ const RefPtr<TransformClipNode>& aTransform) {
+ // If aItem is nullptr, the cast to nsPaintedDisplayItem failed.
+ MOZ_ASSERT(aItem, "Can only accumulate display items that are painted!");
+
+ FLB_LOG_PAINTED_LAYER_DECISION(
+ this, "Accumulating dp=%s(%p), f=%p against pld=%p\n", aItem->Name(),
+ aItem, aItem->Frame(), this);
+
+ const bool hasOpacity = aOpacityIndices.Length() > 0;
+ UpdateEffectStatus(aType, aOpacityIndices);
+
+ const DisplayItemClip* oldClip = mItemClip;
+ mItemClip = &aClip;
+
+ const bool isMerged = aItem->AsDisplayWrapList() &&
+ aItem->AsDisplayWrapList()->HasMergedFrames();
+
+ if (IsEffectEndMarker(aType)) {
+ mAssignedDisplayItems.emplace_back(aItem, aLayerState, nullptr,
+ aContentRect, aType, hasOpacity,
+ aTransform, isMerged);
+ return;
+ }
+
+ bool clipMatches =
+ (oldClip == mItemClip) || (oldClip && *oldClip == *mItemClip);
+
+ DisplayItemData* currentData =
+ isMerged ? nullptr : aItem->GetDisplayItemData();
+
+ DisplayItemData* oldData = aState->mLayerBuilder->GetOldLayerForFrame(
+ aItem->Frame(), aItem->GetPerFrameKey(), currentData,
+ aItem->GetDisplayItemDataLayerManager());
+
+ mAssignedDisplayItems.emplace_back(aItem, aLayerState, oldData, aContentRect,
+ aType, hasOpacity, aTransform, isMerged);
+
+ if (aLayerState != LayerState::LAYER_NONE) {
+ FLB_LOG_PAINTED_LAYER_DECISION(this, "Creating nested FLB for item %p\n",
+ aItem);
+ mAssignedDisplayItems.back().mInactiveLayerData =
+ CreateInactiveLayerData(aState, aItem, oldData);
+ }
+
+ if (aState->mBuilder->NeedToForceTransparentSurfaceForItem(aItem)) {
+ mForceTransparentSurface = true;
+ }
+
+ if (aState->mParameters.mDisableSubpixelAntialiasingInDescendants) {
+ // Disable component alpha.
+ // Note that the transform (if any) on the PaintedLayer is always an integer
+ // translation so we don't have to factor that in here.
+ aItem->DisableComponentAlpha();
+ } else {
+ const bool needsComponentAlpha =
+ SetupComponentAlpha(aState, aItem, aVisibleRect, aTransform);
+
+ if (needsComponentAlpha) {
+ // This display item needs background copy when pushing opacity group.
+ for (size_t i : aOpacityIndices) {
+ AssignedDisplayItem& item = mAssignedDisplayItems[i];
+ MOZ_ASSERT(item.mType == DisplayItemEntryType::PushOpacity ||
+ item.mType == DisplayItemEntryType::PushOpacityWithBg);
+ item.mType = DisplayItemEntryType::PushOpacityWithBg;
+ }
+ }
+ }
+
+ if (aTransform && aType == DisplayItemEntryType::Item) {
+ // Bounds transformed with axis-aligned transforms could be included in the
+ // opaque region calculations. For simplicity, this is currently not done.
+ return;
+ }
+
+ if (!mIsSolidColorInVisibleRegion && mOpaqueRegion.Contains(aVisibleRect) &&
+ mVisibleRegion.Contains(aVisibleRect) && !mImage) {
+ // A very common case! Most pages have a PaintedLayer with the page
+ // background (opaque) visible and most or all of the page content over the
+ // top of that background.
+ // The rest of this method won't do anything. mVisibleRegion and
+ // mOpaqueRegion don't need updating. mVisibleRegion contains aVisibleRect
+ // already, mOpaqueRegion contains aVisibleRect and therefore whatever the
+ // opaque region of the item is. mVisibleRegion must contain mOpaqueRegion
+ // and therefore aVisibleRect.
+ return;
+ }
+
+ nsIntRegion opaquePixels;
+
+ // Active opacity means no opaque pixels.
+ if (!hasOpacity) {
+ opaquePixels = aState->ComputeOpaqueRect(
+ aItem, mAnimatedGeometryRoot, mASR, aClip, aList, &mHideAllLayersBelow,
+ &mOpaqueForAnimatedGeometryRootParent);
+ opaquePixels.AndWith(aVisibleRect);
+ }
+
+ /* Mark as available for conversion to image layer if this is a nsDisplayImage
+ * and it's the only thing visible in this layer.
+ */
+ if (nsIntRegion(aVisibleRect).Contains(mVisibleRegion) &&
+ opaquePixels.Contains(mVisibleRegion) &&
+ aItem->SupportsOptimizingToImage()) {
+ mImage = static_cast<nsDisplayImageContainer*>(aItem);
+ FLB_LOG_PAINTED_LAYER_DECISION(
+ this, " Tracking image: nsDisplayImageContainer covers the layer\n");
+ } else if (mImage) {
+ FLB_LOG_PAINTED_LAYER_DECISION(this, " No longer tracking image\n");
+ mImage = nullptr;
+ }
+
+ bool isFirstVisibleItem = mVisibleRegion.IsEmpty();
+
+ Maybe<nscolor> uniformColor;
+ if (!hasOpacity) {
+ uniformColor = aItem->IsUniform(aState->mBuilder);
+ }
+
+ // Some display items have to exist (so they can set forceTransparentSurface
+ // below) but don't draw anything. They'll return true for isUniform but
+ // a color with opacity 0.
+ if (!uniformColor || NS_GET_A(*uniformColor) > 0) {
+ // Make sure that the visible area is covered by uniform pixels. In
+ // particular this excludes cases where the edges of the item are not
+ // pixel-aligned (thus the item will not be truly uniform).
+ if (uniformColor) {
+ bool snap;
+ nsRect bounds = aItem->GetBounds(aState->mBuilder, &snap);
+ if (!aState->ScaleToInsidePixels(bounds, snap).Contains(aVisibleRect)) {
+ uniformColor = Nothing();
+ FLB_LOG_PAINTED_LAYER_DECISION(
+ this, " Display item does not cover the visible rect\n");
+ }
+ }
+ if (uniformColor) {
+ if (isFirstVisibleItem) {
+ // This color is all we have
+ mSolidColor = *uniformColor;
+ mIsSolidColorInVisibleRegion = true;
+ } else if (mIsSolidColorInVisibleRegion &&
+ mVisibleRegion.IsEqual(nsIntRegion(aVisibleRect)) &&
+ clipMatches) {
+ // we can just blend the colors together
+ mSolidColor = NS_ComposeColors(mSolidColor, *uniformColor);
+ } else {
+ FLB_LOG_PAINTED_LAYER_DECISION(
+ this, " Layer not a solid color: Can't blend colors togethers\n");
+ mIsSolidColorInVisibleRegion = false;
+ }
+ } else {
+ FLB_LOG_PAINTED_LAYER_DECISION(this,
+ " Layer is not a solid color: Display "
+ "item is not uniform over the visible "
+ "bound\n");
+ mIsSolidColorInVisibleRegion = false;
+ }
+
+ mVisibleRegion.Or(mVisibleRegion, aVisibleRect);
+ mVisibleRegion.SimplifyOutward(4);
+ }
+
+ if (!opaquePixels.IsEmpty()) {
+ for (auto iter = opaquePixels.RectIter(); !iter.Done(); iter.Next()) {
+ // We don't use SimplifyInward here since it's not defined exactly
+ // what it will discard. For our purposes the most important case
+ // is a large opaque background at the bottom of z-order (e.g.,
+ // a canvas background), so we need to make sure that the first rect
+ // we see doesn't get discarded.
+ nsIntRegion tmp;
+ tmp.Or(mOpaqueRegion, iter.Get());
+ // Opaque display items in chrome documents whose window is partially
+ // transparent are always added to the opaque region. This helps ensure
+ // that we get as much subpixel-AA as possible in the chrome.
+ if (tmp.GetNumRects() <= 4 || aItem->Frame()->PresContext()->IsChrome()) {
+ mOpaqueRegion = std::move(tmp);
+ }
+ }
+ }
+}
+
+nsRegion PaintedLayerData::CombinedTouchActionRegion() {
+ nsRegion result;
+ result.Or(mHorizontalPanRegion, mVerticalPanRegion);
+ result.OrWith(mNoActionRegion);
+ return result;
+}
+
+void PaintedLayerData::AccumulateHitTestItem(ContainerState* aState,
+ nsDisplayItem* aItem,
+ const DisplayItemClip& aClip,
+ TransformClipNode* aTransform) {
+ auto* item = static_cast<nsDisplayHitTestInfoBase*>(aItem);
+ const HitTestInfo& info = item->GetHitTestInfo();
+
+ nsRect area = info.mArea;
+ const CompositorHitTestInfo& flags = info.mFlags;
+
+ FLB_LOG_PAINTED_LAYER_DECISION(
+ this,
+ "Accumulating hit test info %p against pld=%p, "
+ "area: [%d, %d, %d, %d], flags: 0x%x]\n",
+ item, this, area.x, area.y, area.width, area.height, flags.serialize());
+
+ area = aClip.ApplyNonRoundedIntersection(area);
+
+ if (aTransform) {
+ area = aTransform->TransformRect(area, aState->mAppUnitsPerDevPixel);
+ }
+
+ if (area.IsEmpty()) {
+ FLB_LOG_PAINTED_LAYER_DECISION(
+ this, "Discarded empty hit test info %p for pld=%p\n", item, this);
+ return;
+ }
+
+ bool hasRoundedCorners = aClip.GetRoundedRectCount() > 0;
+
+ // use the NS_FRAME_SIMPLE_EVENT_REGIONS to avoid calling the slightly
+ // expensive HasNonZeroCorner function if we know from a previous run that
+ // the frame has zero corners.
+ nsIFrame* frame = item->Frame();
+ bool simpleRegions = frame->HasAnyStateBits(NS_FRAME_SIMPLE_EVENT_REGIONS);
+ if (!simpleRegions) {
+ if (nsLayoutUtils::HasNonZeroCorner(frame->StyleBorder()->mBorderRadius)) {
+ hasRoundedCorners = true;
+ } else {
+ frame->AddStateBits(NS_FRAME_SIMPLE_EVENT_REGIONS);
+ }
+ }
+
+ if (hasRoundedCorners || frame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) {
+ mMaybeHitRegion.OrWith(area);
+ } else {
+ mHitRegion.OrWith(area);
+ }
+
+ const auto dtcFlags = flags & CompositorHitTestDispatchToContent;
+ if (!dtcFlags.isEmpty()) {
+ mDispatchToContentHitRegion.OrWith(area);
+
+ if (flags.contains(CompositorHitTestFlags::eRequiresTargetConfirmation)) {
+ mDTCRequiresTargetConfirmation = true;
+ }
+ }
+
+ const auto touchFlags = flags & CompositorHitTestTouchActionMask;
+ if (!touchFlags.isEmpty()) {
+ // If there are multiple touch-action areas, there are multiple elements
+ // with touch-action properties. We don't know what the relationship is
+ // between those elements in terms of DOM ancestry, and so we don't know how
+ // to combine the regions properly. Instead, we just add all the areas to
+ // the dispatch-to-content region, so that the APZ knows to check with the
+ // main thread. See bug 1286957.
+ if (mCollapsedTouchActions) {
+ mDispatchToContentHitRegion.OrWith(area);
+ } else if (touchFlags == CompositorHitTestTouchActionMask) {
+ // everything was disabled, so touch-action:none
+ mNoActionRegion.OrWith(area);
+ } else {
+ // The event regions code does not store enough information to actually
+ // represent all the different states. Prior to the introduction of
+ // CompositorHitTestInfo here in bug 1389149, the following two cases
+ // were effectively getting collapsed:
+ // (1) touch-action: auto
+ // (2) touch-action: manipulation
+ // In both of these cases, none of {mNoActionRegion, mHorizontalPanRegion,
+ // mVerticalPanRegion} were modified, and so the fact that case (2) should
+ // have prevented double-tap-zooming was getting lost.
+ // With CompositorHitTestInfo we can now represent that case correctly,
+ // but only if we use CompositorHitTestInfo all the way to the compositor
+ // (i.e. in the WebRender-enabled case). In the non-WebRender case where
+ // we still use the event regions, we must collapse these two cases back
+ // together. Or add another region to the event regions to fix this
+ // properly.
+ if (touchFlags !=
+ CompositorHitTestFlags::eTouchActionDoubleTapZoomDisabled) {
+ if (!flags.contains(CompositorHitTestFlags::eTouchActionPanXDisabled)) {
+ // pan-x is allowed
+ mHorizontalPanRegion.OrWith(area);
+ }
+ if (!flags.contains(CompositorHitTestFlags::eTouchActionPanYDisabled)) {
+ // pan-y is allowed
+ mVerticalPanRegion.OrWith(area);
+ }
+ } else {
+ // the touch-action: manipulation case described above. To preserve the
+ // existing behaviour, don't touch either mHorizontalPanRegion or
+ // mVerticalPanRegion
+ }
+ }
+ }
+
+ if (!mCollapsedTouchActions) {
+ // If there are multiple touch-action areas, there are multiple elements
+ // with touch-action properties. We don't know what the relationship is
+ // between those elements in terms of DOM ancestry, and so we don't know how
+ // to combine the regions properly. Instead, we just add all the areas to
+ // the dispatch-to-content region, so that the APZ knows to check with the
+ // main thread. See bug 1286957.
+ const int alreadyHadRegions = mNoActionRegion.GetNumRects() +
+ mHorizontalPanRegion.GetNumRects() +
+ mVerticalPanRegion.GetNumRects();
+
+ if (alreadyHadRegions > 1) {
+ mDispatchToContentHitRegion.OrWith(CombinedTouchActionRegion());
+ mNoActionRegion.SetEmpty();
+ mHorizontalPanRegion.SetEmpty();
+ mVerticalPanRegion.SetEmpty();
+ mCollapsedTouchActions = true;
+ }
+ }
+
+ // Avoid quadratic performance as a result of the region growing to include
+ // and arbitrarily large number of rects, which can happen on some pages.
+ mMaybeHitRegion.SimplifyOutward(8);
+ mDispatchToContentHitRegion.SimplifyOutward(8);
+
+ HitRegionsUpdated();
+}
+
+void PaintedLayerData::HitRegionsUpdated() {
+ // Calculate scaled versions of the bounds of mHitRegion and mMaybeHitRegion
+ // for quick access in FindPaintedLayerFor().
+ mScaledHitRegionBounds = mState->ScaleToOutsidePixels(mHitRegion.GetBounds());
+ mScaledMaybeHitRegionBounds =
+ mState->ScaleToOutsidePixels(mMaybeHitRegion.GetBounds());
+}
+
+void ContainerState::NewPaintedLayerData(
+ PaintedLayerData* aData, AnimatedGeometryRoot* aAnimatedGeometryRoot,
+ const ActiveScrolledRoot* aASR, const DisplayItemClipChain* aClipChain,
+ const ActiveScrolledRoot* aScrollMetadataASR, const nsPoint& aTopLeft,
+ const nsIFrame* aReferenceFrame, const bool aBackfaceHidden) {
+ aData->mState = this;
+ aData->mAnimatedGeometryRoot = aAnimatedGeometryRoot;
+ aData->mASR = aASR;
+ aData->mClipChain = aClipChain;
+ aData->mAnimatedGeometryRootOffset = aTopLeft;
+ aData->mReferenceFrame = aReferenceFrame;
+ aData->mBackfaceHidden = aBackfaceHidden;
+
+ aData->mNewChildLayersIndex = mNewChildLayers.Length();
+ NewLayerEntry* newLayerEntry = mNewChildLayers.AppendElement();
+ newLayerEntry->mAnimatedGeometryRoot = aAnimatedGeometryRoot;
+ newLayerEntry->mASR = aASR;
+ newLayerEntry->mScrollMetadataASR = aScrollMetadataASR;
+ newLayerEntry->mClipChain = aClipChain;
+ // newLayerEntry->mOpaqueRegion is filled in later from
+ // paintedLayerData->mOpaqueRegion, if necessary.
+
+ // Allocate another entry for this layer's optimization to
+ // ColorLayer/ImageLayer
+ mNewChildLayers.AppendElement();
+}
+
+#ifdef MOZ_DUMP_PAINTING
+static void DumpPaintedImage(nsDisplayItem* aItem, SourceSurface* aSurface) {
+ nsCString string(aItem->Name());
+ string.Append('-');
+ string.AppendInt((uint64_t)aItem);
+ fprintf_stderr(gfxUtils::sDumpPaintFile, "<script>array[\"%s\"]=\"",
+ string.BeginReading());
+ gfxUtils::DumpAsDataURI(aSurface, gfxUtils::sDumpPaintFile);
+ fprintf_stderr(gfxUtils::sDumpPaintFile, "\";</script>\n");
+}
+#endif
+
+static void PaintInactiveLayer(nsDisplayListBuilder* aBuilder,
+ LayerManager* aManager, nsDisplayItem* aItem,
+ gfxContext* aContext, gfxContext* aCtx) {
+ // This item has an inactive layer. Render it to a PaintedLayer
+ // using a temporary BasicLayerManager.
+ BasicLayerManager* basic = static_cast<BasicLayerManager*>(aManager);
+ RefPtr<gfxContext> context = aContext;
+#ifdef MOZ_DUMP_PAINTING
+ int32_t appUnitsPerDevPixel = AppUnitsPerDevPixel(aItem);
+ nsIntRect itemVisibleRect =
+ aItem->GetPaintRect().ToOutsidePixels(appUnitsPerDevPixel);
+
+ RefPtr<DrawTarget> tempDT;
+ if (gfxEnv::DumpPaint()) {
+ tempDT = gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(
+ itemVisibleRect.Size(), SurfaceFormat::B8G8R8A8);
+ if (tempDT) {
+ context = gfxContext::CreateOrNull(tempDT);
+ if (!context) {
+ // Leave this as crash, it's in the debugging code, we want to know
+ gfxDevCrash(LogReason::InvalidContext)
+ << "PaintInactive context problem " << gfx::hexa(tempDT);
+ return;
+ }
+ context->SetMatrix(
+ Matrix::Translation(-itemVisibleRect.x, -itemVisibleRect.y));
+ }
+ }
+#endif
+ basic->BeginTransaction();
+ basic->SetTarget(context);
+
+ if (aItem->GetType() == DisplayItemType::TYPE_MASK) {
+ static_cast<nsDisplayMasksAndClipPaths*>(aItem)->PaintAsLayer(aBuilder,
+ aCtx, basic);
+ if (basic->InTransaction()) {
+ basic->AbortTransaction();
+ }
+ } else if (aItem->GetType() == DisplayItemType::TYPE_FILTER) {
+ static_cast<nsDisplayFilters*>(aItem)->PaintAsLayer(aBuilder, aCtx, basic);
+ if (basic->InTransaction()) {
+ basic->AbortTransaction();
+ }
+ } else {
+ basic->EndTransaction(FrameLayerBuilder::DrawPaintedLayer, aBuilder);
+ }
+ FrameLayerBuilder* builder = static_cast<FrameLayerBuilder*>(
+ basic->GetUserData(&gLayerManagerLayerBuilder));
+ if (builder) {
+ builder->DidEndTransaction();
+ }
+
+ basic->SetTarget(nullptr);
+
+#ifdef MOZ_DUMP_PAINTING
+ if (gfxEnv::DumpPaint() && tempDT) {
+ RefPtr<SourceSurface> surface = tempDT->Snapshot();
+ DumpPaintedImage(aItem, surface);
+
+ DrawTarget* drawTarget = aContext->GetDrawTarget();
+ Rect rect(itemVisibleRect.x, itemVisibleRect.y, itemVisibleRect.width,
+ itemVisibleRect.height);
+ drawTarget->DrawSurface(surface, rect, Rect(Point(0, 0), rect.Size()));
+
+ aItem->SetPainted();
+ }
+#endif
+}
+
+nsRect ContainerState::GetDisplayPortForAnimatedGeometryRoot(
+ AnimatedGeometryRoot* aAnimatedGeometryRoot) {
+ if (mLastDisplayPortAGR == aAnimatedGeometryRoot) {
+ return mLastDisplayPortRect;
+ }
+
+ mLastDisplayPortAGR = aAnimatedGeometryRoot;
+
+ nsIScrollableFrame* sf =
+ nsLayoutUtils::GetScrollableFrameFor(*aAnimatedGeometryRoot);
+ if (sf == nullptr ||
+ nsLayoutUtils::UsesAsyncScrolling(*aAnimatedGeometryRoot)) {
+ mLastDisplayPortRect = nsRect();
+ return mLastDisplayPortRect;
+ }
+
+ bool usingDisplayport = DisplayPortUtils::GetDisplayPort(
+ (*aAnimatedGeometryRoot)->GetContent(), &mLastDisplayPortRect,
+ DisplayPortOptions().With(DisplayportRelativeTo::ScrollFrame));
+ if (!usingDisplayport) {
+ // No async scrolling, so all that matters is that the layer contents
+ // cover the scrollport.
+ mLastDisplayPortRect = sf->GetScrollPortRect();
+ }
+ nsIFrame* scrollFrame = do_QueryFrame(sf);
+ mLastDisplayPortRect +=
+ scrollFrame->GetOffsetToCrossDoc(mContainerReferenceFrame);
+ return mLastDisplayPortRect;
+}
+
+nsIntRegion ContainerState::ComputeOpaqueRect(
+ nsDisplayItem* aItem, AnimatedGeometryRoot* aAnimatedGeometryRoot,
+ const ActiveScrolledRoot* aASR, const DisplayItemClip& aClip,
+ nsDisplayList* aList, bool* aHideAllLayersBelow,
+ bool* aOpaqueForAnimatedGeometryRootParent) {
+ bool snapOpaque;
+ nsRegion opaque = aItem->GetOpaqueRegion(mBuilder, &snapOpaque);
+ MOZ_ASSERT(!opaque.IsComplex());
+ if (opaque.IsEmpty()) {
+ return nsIntRegion();
+ }
+
+ nsIntRegion opaquePixels;
+ nsRegion opaqueClipped;
+ for (auto iter = opaque.RectIter(); !iter.Done(); iter.Next()) {
+ opaqueClipped.Or(opaqueClipped,
+ aClip.ApproximateIntersectInward(iter.Get()));
+ }
+ if (aAnimatedGeometryRoot == mContainerAnimatedGeometryRoot &&
+ aASR == mContainerASR && opaqueClipped.Contains(mContainerBounds)) {
+ *aHideAllLayersBelow = true;
+ aList->SetIsOpaque();
+ }
+ // Add opaque areas to the "exclude glass" region. Only do this when our
+ // container layer is going to be the rootmost layer, otherwise transforms
+ // etc will mess us up (and opaque contributions from other containers are
+ // not needed).
+ if (!nsLayoutUtils::GetCrossDocParentFrame(mContainerFrame)) {
+ mBuilder->AddWindowOpaqueRegion(aItem->Frame(), opaqueClipped.GetBounds());
+ }
+ opaquePixels = ScaleRegionToInsidePixels(opaqueClipped, snapOpaque);
+
+ if (IsInInactiveLayer()) {
+ return opaquePixels;
+ }
+
+ const nsRect& displayport =
+ GetDisplayPortForAnimatedGeometryRoot(aAnimatedGeometryRoot);
+ if (!displayport.IsEmpty() &&
+ opaquePixels.Contains(ScaleRegionToNearestPixels(displayport))) {
+ *aOpaqueForAnimatedGeometryRootParent = true;
+ }
+ return opaquePixels;
+}
+
+Maybe<size_t> ContainerState::SetupMaskLayerForScrolledClip(
+ Layer* aLayer, const DisplayItemClip& aClip) {
+ if (aClip.GetRoundedRectCount() > 0) {
+ Maybe<size_t> maskLayerIndex = Some(aLayer->GetAncestorMaskLayerCount());
+ if (RefPtr<Layer> maskLayer =
+ CreateMaskLayer(aLayer, aClip, maskLayerIndex)) {
+ aLayer->AddAncestorMaskLayer(maskLayer);
+ return maskLayerIndex;
+ }
+ // Fall through to |return Nothing()|.
+ }
+ return Nothing();
+}
+
+static const ActiveScrolledRoot* GetASRForPerspective(
+ const ActiveScrolledRoot* aASR, nsIFrame* aPerspectiveFrame) {
+ for (const ActiveScrolledRoot* asr = aASR; asr; asr = asr->mParent) {
+ nsIFrame* scrolledFrame = asr->mScrollableFrame->GetScrolledFrame();
+ if (nsLayoutUtils::IsAncestorFrameCrossDoc(scrolledFrame,
+ aPerspectiveFrame)) {
+ return asr;
+ }
+ }
+ return nullptr;
+}
+
+static CSSMaskLayerUserData* GetCSSMaskLayerUserData(Layer* aMaskLayer) {
+ if (!aMaskLayer) {
+ return nullptr;
+ }
+
+ return static_cast<CSSMaskLayerUserData*>(
+ aMaskLayer->GetUserData(&gCSSMaskLayerUserData));
+}
+
+static void SetCSSMaskLayerUserData(Layer* aMaskLayer) {
+ MOZ_ASSERT(aMaskLayer);
+
+ aMaskLayer->SetUserData(&gCSSMaskLayerUserData, new CSSMaskLayerUserData());
+}
+
+void ContainerState::SetupMaskLayerForCSSMask(
+ Layer* aLayer, nsDisplayMasksAndClipPaths* aMaskItem) {
+ RefPtr<ImageLayer> maskLayer = CreateOrRecycleMaskImageLayerFor(
+ MaskLayerKey(aLayer, Nothing()), GetCSSMaskLayerUserData,
+ SetCSSMaskLayerUserData);
+ CSSMaskLayerUserData* oldUserData = GetCSSMaskLayerUserData(maskLayer.get());
+ MOZ_ASSERT(oldUserData);
+
+ bool snap;
+ nsRect bounds = aMaskItem->GetBounds(mBuilder, &snap);
+ nsIntRect itemRect = ScaleToOutsidePixels(bounds, snap);
+
+ // Setup mask layer offset.
+ // We do not repaint mask for mask position change, so update base transform
+ // each time is required.
+ Matrix4x4 matrix;
+ matrix.PreTranslate(itemRect.x, itemRect.y, 0);
+ matrix.PreTranslate(mParameters.mOffset.x, mParameters.mOffset.y, 0);
+ maskLayer->SetBaseTransform(matrix);
+
+ nsPoint maskLayerOffset = aMaskItem->ToReferenceFrame() - bounds.TopLeft();
+
+ CSSMaskLayerUserData newUserData(aMaskItem->Frame(), itemRect,
+ maskLayerOffset);
+ nsRect dirtyRect;
+ if (!aMaskItem->IsInvalid(dirtyRect) && *oldUserData == newUserData) {
+ aLayer->SetMaskLayer(maskLayer);
+ return;
+ }
+
+ int32_t maxSize = mManager->GetMaxTextureSize();
+ IntSize surfaceSize(std::min(itemRect.width, maxSize),
+ std::min(itemRect.height, maxSize));
+
+ if (surfaceSize.IsEmpty()) {
+ // Return early if we know that the size of this mask surface is empty.
+ return;
+ }
+
+ MaskImageData imageData(surfaceSize, mManager);
+ RefPtr<DrawTarget> dt = imageData.CreateDrawTarget();
+ if (!dt || !dt->IsValid()) {
+ NS_WARNING("Could not create DrawTarget for mask layer.");
+ return;
+ }
+
+ RefPtr<gfxContext> maskCtx = gfxContext::CreateOrNull(dt);
+ maskCtx->SetMatrix(Matrix::Translation(-itemRect.TopLeft()));
+ maskCtx->Multiply(
+ gfxMatrix::Scaling(mParameters.mXScale, mParameters.mYScale));
+
+ bool isPaintFinished = aMaskItem->PaintMask(mBuilder, maskCtx);
+
+ RefPtr<ImageContainer> imgContainer =
+ imageData.CreateImageAndImageContainer();
+ if (!imgContainer) {
+ return;
+ }
+ maskLayer->SetContainer(imgContainer);
+
+ if (isPaintFinished) {
+ *oldUserData = std::move(newUserData);
+ }
+ aLayer->SetMaskLayer(maskLayer);
+}
+
+static bool IsScrollThumbLayer(nsDisplayItem* aItem) {
+ return aItem->GetType() == DisplayItemType::TYPE_OWN_LAYER &&
+ static_cast<nsDisplayOwnLayer*>(aItem)->IsScrollThumbLayer();
+}
+
+template <typename ClearFn, typename SelectFn>
+static void ProcessDisplayItemMarker(DisplayItemEntryType aMarker,
+ ClearFn ClearLayerSelectionIfNeeded,
+ SelectFn SelectLayerIfNeeded) {
+ switch (aMarker) {
+ case DisplayItemEntryType::PushTransform:
+ case DisplayItemEntryType::PushOpacity:
+ SelectLayerIfNeeded();
+ break;
+ case DisplayItemEntryType::PopTransform:
+ case DisplayItemEntryType::PopOpacity:
+ ClearLayerSelectionIfNeeded();
+ break;
+ default:
+ break;
+ }
+}
+/*
+ * Iterate through the non-clip items in aList and its descendants.
+ * For each item we compute the effective clip rect. Each item is assigned
+ * to a layer. We invalidate the areas in PaintedLayers where an item
+ * has moved from one PaintedLayer to another. Also,
+ * aState->mInvalidPaintedContent is invalidated in every PaintedLayer.
+ * We set the clip rect for items that generated their own layer, and
+ * create a mask layer to do any rounded rect clipping.
+ * (PaintedLayers don't need a clip rect on the layer, we clip the items
+ * individually when we draw them.)
+ * We set the visible rect for all layers, although the actual setting
+ * of visible rects for some PaintedLayers is deferred until the calling
+ * of ContainerState::Finish.
+ */
+void ContainerState::ProcessDisplayItems(nsDisplayList* aList) {
+ AUTO_PROFILER_LABEL("ContainerState::ProcessDisplayItems",
+ GRAPHICS_LayerBuilding);
+ PerfStats::AutoMetricRecording<PerfStats::Metric::LayerBuilding>
+ autoRecording;
+
+ nsPoint topLeft(0, 0);
+
+ int32_t maxLayers = StaticPrefs::layers_max_active();
+ int layerCount = 0;
+
+ if (!mManager->IsWidgetLayerManager()) {
+ mPaintedLayerDataTree.InitializeForInactiveLayer(
+ mContainerAnimatedGeometryRoot);
+ }
+
+ AnimatedGeometryRoot* lastAnimatedGeometryRoot = nullptr;
+ nsPoint lastTopLeft;
+
+ // Tracks the PaintedLayerData that the item will be accumulated in, if it is
+ // non-null.
+ PaintedLayerData* selectedLayer = nullptr;
+ AutoTArray<size_t, 2> opacityIndices;
+
+ // AGR and ASR for the container item that was flattened.
+ AnimatedGeometryRoot* containerAGR = nullptr;
+ const ActiveScrolledRoot* containerASR = nullptr;
+ nsIFrame* containerReferenceFrame = nullptr;
+ RefPtr<TransformClipNode> transformNode = nullptr;
+
+ const auto InTransform = [&]() { return transformNode; };
+
+ const auto InOpacity = [&]() {
+ return selectedLayer && opacityIndices.Length() > 0;
+ };
+
+ FLBDisplayListIterator iter(mBuilder, aList, this);
+ while (iter.HasNext()) {
+ DisplayItemEntry e = iter.GetNextEntry();
+ DisplayItemEntryType marker = e.mType;
+ nsDisplayItem* item = e.mItem;
+ MOZ_ASSERT(item);
+ DisplayItemType itemType = item->GetType();
+
+ if (itemType == DisplayItemType::TYPE_COMPOSITOR_HITTEST_INFO) {
+ // Override the marker for nsDisplayCompositorHitTestInfo items.
+ marker = DisplayItemEntryType::HitTestInfo;
+ }
+
+ const bool inEffect = InTransform() || InOpacity();
+
+ NS_ASSERTION(mAppUnitsPerDevPixel == AppUnitsPerDevPixel(item),
+ "items in a container layer should all have the same app "
+ "units per dev pixel");
+
+ if (mBuilder->NeedToForceTransparentSurfaceForItem(item)) {
+ aList->SetNeedsTransparentSurface();
+ }
+
+ LayerState layerState = LayerState::LAYER_NONE;
+ if (marker == DisplayItemEntryType::Item) {
+ layerState = item->GetLayerState(mBuilder, mManager, mParameters);
+
+ if (layerState == LayerState::LAYER_INACTIVE &&
+ nsDisplayItem::ForceActiveLayers()) {
+ layerState = LayerState::LAYER_ACTIVE;
+ }
+ }
+
+ AnimatedGeometryRoot* itemAGR = nullptr;
+ const ActiveScrolledRoot* itemASR = nullptr;
+ const DisplayItemClipChain* layerClipChain = nullptr;
+ const DisplayItemClipChain* itemClipChain = nullptr;
+ const DisplayItemClip* itemClipPtr = nullptr;
+
+ bool snap = false;
+ nsRect itemContent;
+
+ if (marker == DisplayItemEntryType::HitTestInfo) {
+ MOZ_ASSERT(item->IsHitTestItem());
+ const auto& hitTestInfo =
+ static_cast<nsDisplayHitTestInfoBase*>(item)->GetHitTestInfo();
+
+ // Override the layer selection hints for items that have hit test
+ // information. This is needed because container items may have different
+ // clipping, AGR, or ASR than the child items in them.
+ itemAGR = hitTestInfo.mAGR;
+ itemASR = hitTestInfo.mASR;
+ itemClipChain = hitTestInfo.mClipChain;
+ itemClipPtr = hitTestInfo.mClip;
+ itemContent = hitTestInfo.mArea;
+ } else {
+ itemAGR = item->GetAnimatedGeometryRoot();
+ itemASR = item->GetActiveScrolledRoot();
+ itemClipChain = item->GetClipChain();
+ itemClipPtr = &item->GetClip();
+ itemContent = item->GetBounds(mBuilder, &snap);
+ }
+
+ if (mManager->IsWidgetLayerManager() && !inEffect) {
+ if (itemClipChain && itemClipChain->mASR == itemASR &&
+ itemType != DisplayItemType::TYPE_STICKY_POSITION) {
+ layerClipChain = itemClipChain->mParent;
+ } else {
+ layerClipChain = itemClipChain;
+ }
+ } else {
+ // Inside a flattened effect or inactive layer, use container AGR and ASR.
+ itemAGR = inEffect ? containerAGR : mContainerAnimatedGeometryRoot;
+ itemASR = inEffect ? containerASR : mContainerASR;
+
+ if (marker == DisplayItemEntryType::HitTestInfo) {
+ // Items with hit test info are processed twice, once with ::HitTestInfo
+ // marker and then with ::Item marker.
+ // With ::HitTestInfo markers, fuse the clip chain of hit test struct,
+ // and with ::Item markers, fuse the clip chain of the actual item.
+ itemClipChain = mBuilder->FuseClipChainUpTo(itemClipChain, itemASR);
+ } else if (!IsEffectEndMarker(marker)) {
+ // No need to fuse clip chain for effect end markers, since it was
+ // already done for effect start markers.
+ item->FuseClipChainUpTo(mBuilder, itemASR);
+ itemClipChain = item->GetClipChain();
+ }
+
+ itemClipPtr = itemClipChain ? &itemClipChain->mClip : nullptr;
+ }
+
+ const DisplayItemClip& itemClip =
+ itemClipPtr ? *itemClipPtr : DisplayItemClip::NoClip();
+
+ if (inEffect && marker == DisplayItemEntryType::HitTestInfo) {
+ // Fast-path for hit test items inside flattened inactive layers.
+ MOZ_ASSERT(selectedLayer);
+ selectedLayer->AccumulateHitTestItem(this, item, itemClip, transformNode);
+ continue;
+ }
+
+ if (inEffect && marker == DisplayItemEntryType::Item) {
+ // Fast-path for items inside flattened inactive layers. This works
+ // because the layer state of the item cannot be active, otherwise the
+ // parent item would not have been flattened.
+ MOZ_ASSERT(selectedLayer);
+ selectedLayer->Accumulate(this, item->AsPaintedDisplayItem(), nsIntRect(),
+ nsRect(), itemClip, layerState, aList, marker,
+ opacityIndices, transformNode);
+ continue;
+ }
+
+ // Items outside of flattened effects and non-item markers inside flattened
+ // effects are processed here.
+ MOZ_ASSERT(!inEffect || (marker != DisplayItemEntryType::Item));
+
+ if (itemAGR == lastAnimatedGeometryRoot) {
+ topLeft = lastTopLeft;
+ } else {
+ lastTopLeft = topLeft =
+ (*itemAGR)->GetOffsetToCrossDoc(mContainerReferenceFrame);
+ lastAnimatedGeometryRoot = itemAGR;
+ }
+
+ const ActiveScrolledRoot* scrollMetadataASR =
+ layerClipChain
+ ? ActiveScrolledRoot::PickDescendant(itemASR, layerClipChain->mASR)
+ : itemASR;
+
+ const bool prerenderedTransform =
+ itemType == DisplayItemType::TYPE_TRANSFORM &&
+ static_cast<nsDisplayTransform*>(item)->MayBeAnimated(mBuilder);
+
+ nsIntRect itemDrawRect = ScaleToOutsidePixels(itemContent, snap);
+ ParentLayerIntRect clipRect;
+ if (itemClip.HasClip()) {
+ const nsRect& itemClipRect = itemClip.GetClipRect();
+ itemContent.IntersectRect(itemContent, itemClipRect);
+ clipRect = ViewAs<ParentLayerPixel>(ScaleToNearestPixels(itemClipRect));
+
+ if (!prerenderedTransform && !IsScrollThumbLayer(item)) {
+ itemDrawRect.IntersectRect(itemDrawRect, clipRect.ToUnknownRect());
+ }
+
+ clipRect.MoveBy(ViewAs<ParentLayerPixel>(mParameters.mOffset));
+ }
+
+ if (marker == DisplayItemEntryType::PopTransform) {
+ MOZ_ASSERT(transformNode);
+ transformNode = transformNode->Parent();
+ }
+
+ nsRect itemVisibleRectAu = itemContent;
+ if (transformNode) {
+ // If we are within transform, transform itemContent and itemDrawRect.
+ MOZ_ASSERT(transformNode);
+
+ itemContent =
+ transformNode->TransformRect(itemContent, mAppUnitsPerDevPixel);
+
+ itemDrawRect = transformNode->TransformRect(itemDrawRect);
+ }
+
+#ifdef DEBUG
+ nsRect bounds = itemContent;
+
+ if (marker == DisplayItemEntryType::HitTestInfo || inEffect) {
+ bounds.SetEmpty();
+ }
+
+ if (!bounds.IsEmpty() && itemASR != mContainerASR) {
+ if (Maybe<nsRect> clip =
+ item->GetClipWithRespectToASR(mBuilder, mContainerASR)) {
+ bounds = clip.ref();
+ }
+ }
+
+ ((nsRect&)mAccumulatedChildBounds)
+ .UnionRect(mAccumulatedChildBounds, bounds);
+#endif
+
+ nsIntRect itemVisibleRect = itemDrawRect;
+
+ // We intersect the building rect with the clipped item bounds to get a
+ // tighter visible rect.
+ if (!prerenderedTransform) {
+ nsRect itemBuildingRect = item->GetBuildingRect();
+
+ if (transformNode) {
+ itemBuildingRect = transformNode->TransformRect(itemBuildingRect,
+ mAppUnitsPerDevPixel);
+ }
+
+ itemVisibleRect = itemVisibleRect.Intersect(
+ ScaleToOutsidePixels(itemBuildingRect, false));
+ }
+
+ const bool forceInactive = maxLayers != -1 && layerCount >= maxLayers;
+
+ // Assign the item to a layer
+ bool treatInactiveItemAsActive =
+ (layerState == LayerState::LAYER_INACTIVE &&
+ mLayerBuilder->GetContainingPaintedLayerData());
+ if (layerState == LayerState::LAYER_ACTIVE_FORCE ||
+ treatInactiveItemAsActive ||
+ (!forceInactive && (layerState == LayerState::LAYER_ACTIVE_EMPTY ||
+ layerState == LayerState::LAYER_ACTIVE))) {
+ layerCount++;
+
+ // Currently we do not support flattening effects within nested inactive
+ // layer trees.
+ MOZ_ASSERT(selectedLayer == nullptr);
+ MOZ_ASSERT(marker == DisplayItemEntryType::Item);
+
+ // LayerState::LAYER_ACTIVE_EMPTY means the layer is created just for its
+ // metadata. We should never see an empty layer with any visible content!
+ NS_ASSERTION(
+ layerState != LayerState::LAYER_ACTIVE_EMPTY ||
+ itemVisibleRect.IsEmpty(),
+ "State is LayerState::LAYER_ACTIVE_EMPTY but visible rect is not.");
+
+ // As long as the new layer isn't going to be a PaintedLayer,
+ // InvalidateForLayerChange doesn't need the new layer pointer.
+ // We also need to check the old data now, because BuildLayer
+ // can overwrite it.
+ DisplayItemData* oldData = mLayerBuilder->GetOldLayerForFrame(
+ item->Frame(), item->GetPerFrameKey());
+ InvalidateForLayerChange(item, nullptr, oldData);
+
+ // 3D-transformed layers don't necessarily draw in the order in which
+ // they're added to their parent container layer.
+ bool mayDrawOutOfOrder = itemType == DisplayItemType::TYPE_TRANSFORM &&
+ (item->Combines3DTransformWithAncestors() ||
+ item->Frame()->Extend3DContext());
+
+ // Let mPaintedLayerDataTree know about this item, so that
+ // FindPaintedLayerFor and FindOpaqueBackgroundColor are aware of this
+ // item, even though it's not in any PaintedLayerDataStack.
+ // Ideally we'd only need the "else" case here and have
+ // mPaintedLayerDataTree figure out the right clip from the animated
+ // geometry root that we give it, but it can't easily figure about
+ // overflow:hidden clips on ancestors just by looking at the frame.
+ // So we'll do a little hand holding and pass the clip instead of the
+ // visible rect for the two important cases.
+ nscolor uniformColor = NS_RGBA(0, 0, 0, 0);
+ nscolor* uniformColorPtr =
+ (mayDrawOutOfOrder || IsInInactiveLayer()) ? nullptr : &uniformColor;
+ nsIntRect clipRectUntyped;
+ nsIntRect* clipPtr = nullptr;
+ if (itemClip.HasClip()) {
+ clipRectUntyped = clipRect.ToUnknownRect();
+ clipPtr = &clipRectUntyped;
+ }
+
+ bool isStickyNotClippedToDisplayPort =
+ itemType == DisplayItemType::TYPE_STICKY_POSITION &&
+ !static_cast<nsDisplayStickyPosition*>(item)
+ ->IsClippedToDisplayPort();
+ bool hasScrolledClip =
+ layerClipChain && layerClipChain->mClip.HasClip() &&
+ (!ActiveScrolledRoot::IsAncestor(layerClipChain->mASR, itemASR) ||
+ isStickyNotClippedToDisplayPort);
+
+ if (hasScrolledClip) {
+ // If the clip is scrolled, reserve just the area of the clip for
+ // layerization, so that elements outside the clip can still merge
+ // into the same layer.
+ const ActiveScrolledRoot* clipASR = layerClipChain->mASR;
+ AnimatedGeometryRoot* clipAGR =
+ mBuilder->AnimatedGeometryRootForASR(clipASR);
+ nsIntRect scrolledClipRect =
+ ScaleToNearestPixels(layerClipChain->mClip.GetClipRect()) +
+ mParameters.mOffset;
+ mPaintedLayerDataTree.AddingOwnLayer(clipAGR, &scrolledClipRect,
+ uniformColorPtr);
+ } else if (item->ShouldFixToViewport(mBuilder) && itemClip.HasClip() &&
+ item->AnimatedGeometryRootForScrollMetadata() != itemAGR &&
+ !nsLayoutUtils::UsesAsyncScrolling(item->Frame())) {
+ // This is basically the same as the case above, but for the non-APZ
+ // case. At the moment, when APZ is off, there is only the root ASR
+ // (because scroll frames without display ports don't create ASRs) and
+ // the whole clip chain is always just one fused clip.
+ // Bug 1336516 aims to change that and to remove this workaround.
+ AnimatedGeometryRoot* clipAGR =
+ item->AnimatedGeometryRootForScrollMetadata();
+ nsIntRect scrolledClipRect =
+ ScaleToNearestPixels(itemClip.GetClipRect()) + mParameters.mOffset;
+ mPaintedLayerDataTree.AddingOwnLayer(clipAGR, &scrolledClipRect,
+ uniformColorPtr);
+ } else if (IsScrollThumbLayer(item) && mManager->IsWidgetLayerManager()) {
+ // For scrollbar thumbs, the clip we care about is the clip added by the
+ // slider frame.
+ mPaintedLayerDataTree.AddingOwnLayer(itemAGR->mParentAGR, clipPtr,
+ uniformColorPtr);
+ } else if (prerenderedTransform && mManager->IsWidgetLayerManager()) {
+ if (itemAGR->mParentAGR) {
+ mPaintedLayerDataTree.AddingOwnLayer(itemAGR->mParentAGR, clipPtr,
+ uniformColorPtr);
+ } else {
+ mPaintedLayerDataTree.AddingOwnLayer(itemAGR, nullptr,
+ uniformColorPtr);
+ }
+ } else {
+ // Using itemVisibleRect here isn't perfect. itemVisibleRect can be
+ // larger or smaller than the potential bounds of item's contents in
+ // itemAGR: It's too large if there's a clipped display
+ // port somewhere among item's contents (see bug 1147673), and it can
+ // be too small if the contents can move, because it only looks at the
+ // contents' current bounds and doesn't anticipate any animations.
+ // Time will tell whether this is good enough, or whether we need to do
+ // something more sophisticated here.
+ mPaintedLayerDataTree.AddingOwnLayer(itemAGR, &itemVisibleRect,
+ uniformColorPtr);
+ }
+
+ ContainerLayerParameters params = mParameters;
+ params.mBackgroundColor = uniformColor;
+ params.mLayerCreationHint = GetLayerCreationHint(itemAGR);
+ if (!transformNode) {
+ params.mItemVisibleRect = &itemVisibleRectAu;
+ } else {
+ // We only use mItemVisibleRect for getting the visible rect for
+ // remote browsers (which should never have inactive transforms), so we
+ // avoid doing transforms on itemVisibleRectAu above and can't report
+ // an accurate bounds here.
+ params.mItemVisibleRect = nullptr;
+ }
+ params.mScrollMetadataASR =
+ ActiveScrolledRoot::IsAncestor(scrollMetadataASR,
+ mContainerScrollMetadataASR)
+ ? mContainerScrollMetadataASR
+ : scrollMetadataASR;
+ params.mCompositorASR =
+ params.mScrollMetadataASR != mContainerScrollMetadataASR
+ ? params.mScrollMetadataASR
+ : mContainerCompositorASR;
+ if (itemType == DisplayItemType::TYPE_FIXED_POSITION) {
+ params.mCompositorASR = itemASR;
+ }
+
+ // Perspective items have a single child item, an nsDisplayTransform.
+ // If the perspective item is scrolled, but the perspective-inducing
+ // frame is outside the scroll frame (indicated by item->Frame()
+ // being outside that scroll frame), we have to take special care to
+ // make APZ scrolling work properly. APZ needs us to put the scroll
+ // frame's FrameMetrics on our child transform ContainerLayer instead.
+ // We make a similar adjustment for OwnLayer items built for frames
+ // with perspective transforms (e.g. when they have rounded corners).
+ // It's worth investigating whether this ASR adjustment can be done at
+ // display item creation time.
+ bool deferASRForPerspective =
+ itemType == DisplayItemType::TYPE_PERSPECTIVE ||
+ (itemType == DisplayItemType::TYPE_OWN_LAYER &&
+ item->Frame()->IsTransformed() && item->Frame()->HasPerspective());
+ if (deferASRForPerspective) {
+ scrollMetadataASR = GetASRForPerspective(
+ scrollMetadataASR,
+ item->Frame()->GetContainingBlock(nsIFrame::SKIP_SCROLLED_FRAME));
+ params.mScrollMetadataASR = scrollMetadataASR;
+ itemASR = scrollMetadataASR;
+ }
+
+ // Just use its layer.
+ // Set layerContentsVisibleRect.width/height to -1 to indicate we
+ // currently don't know. If BuildContainerLayerFor gets called by
+ // item->BuildLayer, this will be set to a proper rect.
+ nsIntRect layerContentsVisibleRect(0, 0, -1, -1);
+ params.mLayerContentsVisibleRect = &layerContentsVisibleRect;
+
+ // If this display item wants to build inactive layers but we are treating
+ // it as active because we are already inside an inactive layer tree,
+ // we need to make sure that the display item's clip is reflected in
+ // FrameLayerBuilder::mInactiveLayerClip (which is normally set in
+ // AddPaintedDisplayItem() when entering an inactive layer tree).
+ // We intersect the display item's clip into any existing inactive layer
+ // clip.
+ const DisplayItemClip* originalInactiveClip = nullptr;
+ DisplayItemClip combinedInactiveClip;
+ if (treatInactiveItemAsActive) {
+ originalInactiveClip = mLayerBuilder->GetInactiveLayerClip();
+ if (originalInactiveClip) {
+ combinedInactiveClip = *originalInactiveClip;
+ }
+ DisplayItemClip nestedClip = item->GetClip();
+ if (nestedClip.HasClip()) {
+ nsRect nestedClipRect = nestedClip.NonRoundedIntersection();
+
+ // Transform the nested clip to be relative to the same reference
+ // frame as the existing mInactiveLayerClip, so that we can intersect
+ // them below.
+ nestedClipRect = nsLayoutUtils::TransformFrameRectToAncestor(
+ item->ReferenceFrame(), nestedClipRect,
+ mLayerBuilder->GetContainingPaintedLayerData()->mReferenceFrame);
+
+ nestedClip.SetTo(nestedClipRect);
+ combinedInactiveClip.IntersectWith(nestedClip);
+ mLayerBuilder->SetInactiveLayerClip(&combinedInactiveClip);
+ }
+ }
+
+ RefPtr<Layer> ownLayer =
+ item->AsPaintedDisplayItem()->BuildLayer(mBuilder, mManager, params);
+
+ // If above we combined a nested clip into mInactiveLayerClip, restore
+ // the original inactive layer clip here.
+ if (treatInactiveItemAsActive) {
+ mLayerBuilder->SetInactiveLayerClip(originalInactiveClip);
+ }
+
+ if (!ownLayer) {
+ continue;
+ }
+
+ NS_ASSERTION(!ownLayer->AsPaintedLayer(),
+ "Should never have created a dedicated Painted layer!");
+
+ SetBackfaceHiddenForLayer(item->BackfaceIsHidden(), ownLayer);
+
+ nsRect invalid;
+ if (item->IsInvalid(invalid)) {
+ ownLayer->SetInvalidRectToVisibleRegion();
+ }
+
+ // If it's not a ContainerLayer, we need to apply the scale transform
+ // ourselves.
+ if (!ownLayer->AsContainerLayer()) {
+ ownLayer->SetPostScale(mParameters.mXScale, mParameters.mYScale);
+ }
+
+ // Update that layer's clip and visible rects.
+ NS_ASSERTION(ownLayer->Manager() == mManager, "Wrong manager");
+ NS_ASSERTION(!ownLayer->HasUserData(&gLayerManagerUserData),
+ "We shouldn't have a FrameLayerBuilder-managed layer here!");
+ NS_ASSERTION(itemClip.HasClip() || itemClip.GetRoundedRectCount() == 0,
+ "If we have rounded rects, we must have a clip rect");
+
+ // It has its own layer. Update that layer's clip and visible rects.
+ ownLayer->SetClipRect(Nothing());
+ ownLayer->SetScrolledClip(Nothing());
+ ownLayer->SetAncestorMaskLayers({});
+ if (itemClip.HasClip()) {
+ ownLayer->SetClipRect(Some(clipRect));
+
+ // rounded rectangle clipping using mask layers
+ // (must be done after visible rect is set on layer)
+ if (itemClip.GetRoundedRectCount() > 0) {
+ SetupMaskLayer(ownLayer, itemClip);
+ }
+ }
+
+ if (hasScrolledClip) {
+ const DisplayItemClip& scrolledClip = layerClipChain->mClip;
+ LayerClip scrolledLayerClip;
+ scrolledLayerClip.SetClipRect(ViewAs<ParentLayerPixel>(
+ ScaleToNearestPixels(scrolledClip.GetClipRect()) +
+ mParameters.mOffset));
+ if (scrolledClip.GetRoundedRectCount() > 0) {
+ scrolledLayerClip.SetMaskLayerIndex(
+ SetupMaskLayerForScrolledClip(ownLayer.get(), scrolledClip));
+ }
+ ownLayer->SetScrolledClip(Some(scrolledLayerClip));
+ }
+
+ if (item->GetType() == DisplayItemType::TYPE_MASK) {
+ MOZ_ASSERT(itemClip.GetRoundedRectCount() == 0);
+
+ nsDisplayMasksAndClipPaths* maskItem =
+ static_cast<nsDisplayMasksAndClipPaths*>(item);
+ SetupMaskLayerForCSSMask(ownLayer, maskItem);
+
+ if (iter.PeekNext() && iter.PeekNext()->GetType() ==
+ DisplayItemType::TYPE_SCROLL_INFO_LAYER) {
+ // Since we do build a layer for mask, there is no need for this
+ // scroll info layer anymore.
+ iter.GetNextItem();
+ }
+ }
+
+ // Convert the visible rect to a region and give the item
+ // a chance to try restrict it further.
+ nsIntRegion itemVisibleRegion = itemVisibleRect;
+ nsRegion tightBounds = item->GetTightBounds(mBuilder, &snap);
+ if (!tightBounds.IsEmpty()) {
+ itemVisibleRegion.AndWith(
+ ScaleRegionToOutsidePixels(tightBounds, snap));
+ }
+
+ ContainerLayer* oldContainer = ownLayer->GetParent();
+ if (oldContainer && oldContainer != mContainerLayer) {
+ oldContainer->RemoveChild(ownLayer);
+ }
+ NS_ASSERTION(FindIndexOfLayerIn(mNewChildLayers, ownLayer) < 0,
+ "Layer already in list???");
+
+ // NewLayerEntry::mClipChain is used by SetupScrollingMetadata() to
+ // populate any scroll clips in the scroll metadata. Perspective layers
+ // have their ASR adjusted such that a scroll metadata that would normally
+ // go on the perspective layer goes on its transform layer child instead.
+ // However, the transform item's clip chain does not contain the
+ // corresponding scroll clip, so we use the perspective item's clip
+ // chain instead.
+ const DisplayItemClipChain* clipChainForScrollClips = layerClipChain;
+ if (itemType == DisplayItemType::TYPE_TRANSFORM && mContainerItem &&
+ mContainerItem->GetType() == DisplayItemType::TYPE_PERSPECTIVE) {
+ clipChainForScrollClips = mContainerItem->GetClipChain();
+ }
+
+ NewLayerEntry* newLayerEntry = mNewChildLayers.AppendElement();
+ newLayerEntry->mLayer = ownLayer;
+ newLayerEntry->mAnimatedGeometryRoot = itemAGR;
+ newLayerEntry->mASR = itemASR;
+ newLayerEntry->mScrollMetadataASR = scrollMetadataASR;
+ newLayerEntry->mClipChain = clipChainForScrollClips;
+ newLayerEntry->mLayerState = layerState;
+ if (itemType == DisplayItemType::TYPE_FIXED_POSITION) {
+ newLayerEntry->mIsFixedToRootScrollFrame =
+ item->Frame()->StyleDisplay()->mPosition ==
+ StylePositionProperty::Fixed &&
+ nsLayoutUtils::IsReallyFixedPos(item->Frame());
+ }
+
+ float contentXScale = 1.0f;
+ float contentYScale = 1.0f;
+ if (ContainerLayer* ownContainer = ownLayer->AsContainerLayer()) {
+ contentXScale = 1 / ownContainer->GetPreXScale();
+ contentYScale = 1 / ownContainer->GetPreYScale();
+ }
+ // nsDisplayTransform::BuildLayer must set layerContentsVisibleRect.
+ // We rely on this to ensure 3D transforms compute a reasonable
+ // layer visible region.
+ NS_ASSERTION(itemType != DisplayItemType::TYPE_TRANSFORM ||
+ layerContentsVisibleRect.width >= 0,
+ "Transform items must set layerContentsVisibleRect!");
+ if (mLayerBuilder->IsBuildingRetainedLayers()) {
+ newLayerEntry->mLayerContentsVisibleRect = layerContentsVisibleRect;
+ if (itemType == DisplayItemType::TYPE_PERSPECTIVE ||
+ (itemType == DisplayItemType::TYPE_TRANSFORM &&
+ (item->Combines3DTransformWithAncestors() ||
+ item->Frame()->Extend3DContext() ||
+ item->Frame()->HasPerspective()))) {
+ // Give untransformed visible region as outer visible region
+ // to avoid failure caused by singular transforms.
+ newLayerEntry->mUntransformedVisibleRegion = true;
+ newLayerEntry->mVisibleRegion =
+ item->GetBuildingRectForChildren().ScaleToOutsidePixels(
+ contentXScale, contentYScale, mAppUnitsPerDevPixel);
+ } else {
+ newLayerEntry->mVisibleRegion = itemVisibleRegion;
+ }
+ newLayerEntry->mOpaqueRegion = ComputeOpaqueRect(
+ item, itemAGR, itemASR, itemClip, aList,
+ &newLayerEntry->mHideAllLayersBelow,
+ &newLayerEntry->mOpaqueForAnimatedGeometryRootParent);
+ } else {
+ bool useChildrenVisible = itemType == DisplayItemType::TYPE_TRANSFORM &&
+ (item->Frame()->IsPreserve3DLeaf() ||
+ item->Frame()->HasPerspective());
+ const nsIntRegion& visible =
+ useChildrenVisible
+ ? item->GetBuildingRectForChildren().ScaleToOutsidePixels(
+ contentXScale, contentYScale, mAppUnitsPerDevPixel)
+ : itemVisibleRegion;
+
+ SetOuterVisibleRegionForLayer(ownLayer, visible,
+ layerContentsVisibleRect.width >= 0
+ ? &layerContentsVisibleRect
+ : nullptr,
+ useChildrenVisible);
+ }
+ if (itemType == DisplayItemType::TYPE_SCROLL_INFO_LAYER) {
+ nsDisplayScrollInfoLayer* scrollItem =
+ static_cast<nsDisplayScrollInfoLayer*>(item);
+ newLayerEntry->mOpaqueForAnimatedGeometryRootParent = false;
+ newLayerEntry->mBaseScrollMetadata = scrollItem->ComputeScrollMetadata(
+ mBuilder, ownLayer->Manager(), mParameters);
+ }
+
+ /**
+ * No need to allocate geometry for items that aren't
+ * part of a PaintedLayer.
+ */
+ if (ownLayer->Manager() == mLayerBuilder->GetRetainingLayerManager()) {
+ oldData = mLayerBuilder->GetOldLayerForFrame(item->Frame(),
+ item->GetPerFrameKey());
+
+ mLayerBuilder->StoreDataForFrame(item->AsPaintedDisplayItem(), ownLayer,
+ layerState, oldData);
+ }
+ } else {
+ const bool backfaceHidden = item->In3DContextAndBackfaceIsHidden();
+
+ // When container item hit test info is processed, we need to use the same
+ // reference frame as the container children.
+ const nsIFrame* referenceFrame = item == mContainerItem
+ ? mContainerReferenceFrame
+ : item->ReferenceFrame();
+
+ MOZ_ASSERT(item != mContainerItem ||
+ marker == DisplayItemEntryType::HitTestInfo);
+
+ PaintedLayerData* paintedLayerData = selectedLayer;
+
+ if (!paintedLayerData) {
+ paintedLayerData = mPaintedLayerDataTree.FindPaintedLayerFor(
+ itemAGR, itemASR, layerClipChain, itemVisibleRect, backfaceHidden,
+ [&](PaintedLayerData* aData) {
+ NewPaintedLayerData(aData, itemAGR, itemASR, layerClipChain,
+ scrollMetadataASR, topLeft, referenceFrame,
+ backfaceHidden);
+ });
+ }
+ MOZ_ASSERT(paintedLayerData);
+
+ if (marker == DisplayItemEntryType::HitTestInfo) {
+ MOZ_ASSERT(!transformNode);
+ paintedLayerData->AccumulateHitTestItem(this, item, itemClip, nullptr);
+ } else {
+ paintedLayerData->Accumulate(
+ this, item->AsPaintedDisplayItem(), itemVisibleRect, itemContent,
+ itemClip, layerState, aList, marker, opacityIndices, transformNode);
+
+ if (!paintedLayerData->mLayer) {
+ // Try to recycle the old layer of this display item.
+ RefPtr<PaintedLayer> layer = AttemptToRecyclePaintedLayer(
+ itemAGR, item, topLeft,
+ inEffect ? containerReferenceFrame : referenceFrame);
+ if (layer) {
+ paintedLayerData->mLayer = layer;
+
+ auto* userData = GetPaintedDisplayItemLayerUserData(layer);
+ paintedLayerData->mAssignedDisplayItems.reserve(
+ userData->mLastItemCount);
+
+ NS_ASSERTION(FindIndexOfLayerIn(mNewChildLayers, layer) < 0,
+ "Layer already in list???");
+ mNewChildLayers[paintedLayerData->mNewChildLayersIndex].mLayer =
+ std::move(layer);
+ }
+ }
+ }
+
+ const auto ClearLayerSelectionIfNeeded = [&]() {
+ if (!InOpacity() && !InTransform()) {
+ selectedLayer = nullptr;
+ containerAGR = nullptr;
+ containerASR = nullptr;
+ containerReferenceFrame = nullptr;
+ }
+ };
+
+ const auto SelectLayerIfNeeded = [&]() {
+ if (!selectedLayer) {
+ selectedLayer = paintedLayerData;
+ containerAGR = itemAGR;
+ containerASR = itemASR;
+ containerReferenceFrame = const_cast<nsIFrame*>(referenceFrame);
+ }
+ };
+
+ if (marker == DisplayItemEntryType::PushTransform) {
+ nsDisplayTransform* transform = static_cast<nsDisplayTransform*>(item);
+
+ const Matrix4x4Flagged& matrix = transform->GetTransformForRendering();
+
+ Maybe<gfx::IntRect> clip;
+ if (itemClip.HasClip()) {
+ const nsRect nonRoundedClip = itemClip.NonRoundedIntersection();
+ clip.emplace(nonRoundedClip.ToNearestPixels(mAppUnitsPerDevPixel));
+ }
+
+ transformNode = new TransformClipNode(transformNode, matrix, clip);
+ }
+
+ ProcessDisplayItemMarker(marker, ClearLayerSelectionIfNeeded,
+ SelectLayerIfNeeded);
+ }
+
+ nsDisplayList* childItems = item->GetSameCoordinateSystemChildren();
+ if (childItems && childItems->NeedsTransparentSurface()) {
+ aList->SetNeedsTransparentSurface();
+ }
+ }
+
+ MOZ_ASSERT(selectedLayer == nullptr);
+}
+
+void ContainerState::InvalidateForLayerChange(nsDisplayItem* aItem,
+ PaintedLayer* aNewLayer,
+ DisplayItemData* aData) {
+ NS_ASSERTION(aItem->GetPerFrameKey(),
+ "Display items that render using Thebes must have a key");
+ Layer* oldLayer = aData ? aData->mLayer.get() : nullptr;
+ if (aNewLayer != oldLayer && oldLayer) {
+ // The item has changed layers.
+ // Invalidate the old bounds in the old layer and new bounds in the new
+ // layer.
+ PaintedLayer* t = oldLayer->AsPaintedLayer();
+ if (t && aData->mGeometry) {
+ // Note that whenever the layer's scale changes, we invalidate the whole
+ // thing, so it doesn't matter whether we are using the old scale at last
+ // paint or a new scale here
+#ifdef MOZ_DUMP_PAINTING
+ if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) {
+ printf_stderr("Display item type %s(%p) changed layers %p to %p!\n",
+ aItem->Name(), aItem->Frame(), t, aNewLayer);
+ }
+#endif
+ InvalidatePreTransformRect(
+ t, aData->mGeometry->ComputeInvalidationRegion(), aData->mClip,
+ GetLastPaintOffset(t), aData->mTransform);
+ }
+ // Clear the old geometry so that invalidation thinks the item has been
+ // added this paint.
+ aData->mGeometry = nullptr;
+ }
+}
+
+static nsRect GetInvalidationRect(nsDisplayItemGeometry* aGeometry,
+ const DisplayItemClip& aClip,
+ TransformClipNode* aTransform,
+ const int32_t aA2D) {
+ const nsRect& rect = aGeometry->ComputeInvalidationRegion();
+ const nsRect clipped = aClip.ApplyNonRoundedIntersection(rect);
+
+ if (aTransform) {
+ return aTransform->TransformRect(clipped, aA2D);
+ }
+
+ return clipped;
+}
+
+void FrameLayerBuilder::ComputeGeometryChangeForItem(DisplayItemData* aData) {
+ nsDisplayItem* item = aData->mItem;
+ PaintedLayer* paintedLayer = aData->mLayer->AsPaintedLayer();
+ // If aData->mOptLayer is presence, means this item has been optimized to the
+ // separate layer. Thus, skip geometry change calculation.
+ if (aData->mOptLayer || !item || !paintedLayer) {
+ aData->EndUpdate();
+ return;
+ }
+
+ // If we're a reused display item, then we can't be invalid, so no need to
+ // do an in-depth comparison. If we haven't previously stored geometry
+ // for this item (if it was an active layer), then we can't skip this
+ // yet.
+ UniquePtr<nsDisplayItemGeometry> geometry;
+ if (aData->mReusedItem && aData->mGeometry) {
+ aData->EndUpdate();
+ return;
+ }
+
+ auto* layerData = GetPaintedDisplayItemLayerUserData(aData->mLayer);
+ nsPoint shift = layerData->mAnimatedGeometryRootOrigin -
+ layerData->mLastAnimatedGeometryRootOrigin;
+
+ const DisplayItemClip& clip = item->GetClip();
+ const int32_t appUnitsPerDevPixel = layerData->mAppUnitsPerDevPixel;
+
+ // If the frame is marked as invalidated, and didn't specify a rect to
+ // invalidate then we want to invalidate both the old and new bounds,
+ // otherwise we only want to invalidate the changed areas. If we do get an
+ // invalid rect, then we want to add this on top of the change areas.
+ nsRect invalid;
+ nsIntRegion invalidPixels;
+
+ if (!aData->mGeometry) {
+ // This item is being added for the first time, invalidate its entire area.
+ geometry = WrapUnique(item->AllocateGeometry(mDisplayListBuilder));
+
+ const nsRect bounds = GetInvalidationRect(
+ geometry.get(), clip, aData->mTransform, appUnitsPerDevPixel);
+
+ invalidPixels = bounds.ScaleToOutsidePixels(
+ layerData->mXScale, layerData->mYScale, appUnitsPerDevPixel);
+#ifdef MOZ_DUMP_PAINTING
+ if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) {
+ printf_stderr("Display item type %s(%p) added to layer %p!\n",
+ item->Name(), item->Frame(), aData->mLayer.get());
+ }
+#endif
+ } else if (aData->mIsInvalid ||
+ (item->IsInvalid(invalid) && invalid.IsEmpty())) {
+ // Layout marked item/frame as needing repainting (without an explicit
+ // rect), invalidate the entire old and new areas.
+ geometry = WrapUnique(item->AllocateGeometry(mDisplayListBuilder));
+
+ nsRect oldArea =
+ GetInvalidationRect(aData->mGeometry.get(), aData->mClip,
+ aData->mOldTransform, appUnitsPerDevPixel);
+ oldArea.MoveBy(shift);
+
+ nsRect newArea = GetInvalidationRect(
+ geometry.get(), clip, aData->mTransform, appUnitsPerDevPixel);
+
+ nsRegion combined;
+ combined.Or(oldArea, newArea);
+ invalidPixels = combined.ScaleToOutsidePixels(
+ layerData->mXScale, layerData->mYScale, appUnitsPerDevPixel);
+#ifdef MOZ_DUMP_PAINTING
+ if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) {
+ printf_stderr(
+ "Display item type %s(%p) (in layer %p) belongs to an "
+ "invalidated frame!\n",
+ item->Name(), item->Frame(), aData->mLayer.get());
+ }
+#endif
+ } else {
+ // Let the display item check for geometry changes and decide what needs to
+ // be repainted.
+ const nsRegion& changedFrameInvalidations =
+ aData->GetChangedFrameInvalidations();
+
+ if (aData->mTransform) {
+ // If this display item is inside a flattened transform the offset is
+ // already included in the root transform, so there is no need to shift.
+ shift = nsPoint();
+ }
+
+ aData->mGeometry->MoveBy(shift);
+
+ nsRegion combined;
+ item->ComputeInvalidationRegion(mDisplayListBuilder, aData->mGeometry.get(),
+ &combined);
+
+ // Only allocate a new geometry object if something actually changed,
+ // otherwise the existing one should be fine. We always reallocate for
+ // inactive layers, since these types don't implement
+ // ComputeInvalidateRegion (and rely on the ComputeDifferences call in
+ // AddPaintedDisplayItem instead).
+ if (!combined.IsEmpty() ||
+ aData->mLayerState == LayerState::LAYER_INACTIVE ||
+ item->NeedsGeometryUpdates()) {
+ geometry = WrapUnique(item->AllocateGeometry(mDisplayListBuilder));
+ }
+
+ aData->mClip.AddOffsetAndComputeDifference(
+ shift, aData->mGeometry->ComputeInvalidationRegion(), clip,
+ geometry ? geometry->ComputeInvalidationRegion()
+ : aData->mGeometry->ComputeInvalidationRegion(),
+ &combined);
+
+ // Add in any rect that the frame specified
+ combined.Or(combined, invalid);
+ combined.Or(combined, changedFrameInvalidations);
+
+ // Restrict invalidation to the clipped region
+ nsRegion clipRegion;
+ if (clip.ComputeRegionInClips(&aData->mClip, shift, &clipRegion)) {
+ combined.And(combined, clipRegion);
+ }
+
+ invalidPixels = combined.ToOutsidePixels(appUnitsPerDevPixel);
+
+ if (aData->mTransform) {
+ invalidPixels = aData->mTransform->TransformRegion(invalidPixels);
+ }
+
+ invalidPixels.ScaleRoundOut(layerData->mXScale, layerData->mYScale);
+
+#ifdef MOZ_DUMP_PAINTING
+ if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) {
+ if (!combined.IsEmpty()) {
+ printf_stderr(
+ "Display item type %s(%p) (in layer %p) changed geometry!\n",
+ item->Name(), item->Frame(), aData->mLayer.get());
+ }
+ }
+#endif
+ }
+
+ if (!invalidPixels.IsEmpty()) {
+ InvalidatePostTransformRegion(paintedLayer, invalidPixels,
+ layerData->mTranslation);
+ }
+
+ aData->EndUpdate(std::move(geometry));
+}
+
+void FrameLayerBuilder::AddPaintedDisplayItem(PaintedLayerData* aLayerData,
+ AssignedDisplayItem& aItem,
+ Layer* aLayer) {
+ PaintedLayer* layer = aLayerData->mLayer;
+ auto* paintedData = GetPaintedDisplayItemLayerUserData(layer);
+
+ if (layer->Manager() == mRetainingManager) {
+ DisplayItemData* data = aItem.mDisplayItemData;
+ if (data && !data->mUsed) {
+ data->BeginUpdate(layer, aItem.mLayerState, aItem.mItem, aItem.mReused,
+ aItem.mMerged);
+ } else {
+ if (data && data->mUsed) {
+ // If the DID has already been used (by a previously merged frame,
+ // which is not merged this paint) we must create a new DID for the
+ // item.
+ aItem.mItem->SetDisplayItemData(nullptr, nullptr);
+ }
+ data = StoreDataForFrame(aItem.mItem, layer, aItem.mLayerState, nullptr);
+ }
+ data->mInactiveManager = aItem.mInactiveLayerData
+ ? aItem.mInactiveLayerData->mLayerManager
+ : nullptr;
+ // We optimized this PaintedLayer into a ColorLayer/ImageLayer. Store the
+ // optimized layer here.
+ if (aLayer != layer) {
+ data->mOptLayer = aLayer;
+ }
+
+ data->mOldTransform = data->mTransform;
+ data->mTransform = aItem.mTransform;
+ }
+
+ if (aItem.mInactiveLayerData) {
+ RefPtr<BasicLayerManager> tempManager =
+ aItem.mInactiveLayerData->mLayerManager;
+ FrameLayerBuilder* layerBuilder = tempManager->GetLayerBuilder();
+ Layer* tmpLayer = aItem.mInactiveLayerData->mLayer;
+
+ // We have no easy way of detecting if this transaction will ever actually
+ // get finished. For now, I've just silenced the warning with nested
+ // transactions in BasicLayers.cpp
+ if (!tmpLayer) {
+ tempManager->EndTransaction(nullptr, nullptr);
+ tempManager->SetUserData(&gLayerManagerLayerBuilder, nullptr);
+ aItem.mItem = nullptr;
+ return;
+ }
+
+ bool snap;
+ nsRect visibleRect = aItem.mItem->GetBuildingRect().Intersect(
+ aItem.mItem->GetBounds(mDisplayListBuilder, &snap));
+ nsIntRegion rgn =
+ visibleRect.ToOutsidePixels(paintedData->mAppUnitsPerDevPixel);
+
+ // Convert the visible rect to a region and give the item
+ // a chance to try restrict it further.
+ nsRegion tightBounds =
+ aItem.mItem->GetTightBounds(mDisplayListBuilder, &snap);
+ if (!tightBounds.IsEmpty()) {
+ rgn.AndWith(
+ tightBounds.ToOutsidePixels(paintedData->mAppUnitsPerDevPixel));
+ }
+ SetOuterVisibleRegion(tmpLayer, &rgn);
+
+ DisplayItemData* data = nullptr;
+ // If BuildLayer didn't call BuildContainerLayerFor, then our new layer
+ // won't have been stored in layerBuilder. Manually add it now.
+ if (mRetainingManager) {
+#ifdef DEBUG_DISPLAY_ITEM_DATA
+ LayerManagerData* parentLmd = static_cast<LayerManagerData*>(
+ layer->Manager()->GetUserData(&gLayerManagerUserData));
+ LayerManagerData* lmd = static_cast<LayerManagerData*>(
+ tempManager->GetUserData(&gLayerManagerUserData));
+ lmd->mParent = parentLmd;
+#endif
+ data =
+ layerBuilder->GetDisplayItemDataForManager(aItem.mItem, tempManager);
+ data = layerBuilder->StoreDataForFrame(aItem.mItem, tmpLayer,
+ LayerState::LAYER_ACTIVE, data);
+ data->mOldTransform = data->mTransform;
+ data->mTransform = aItem.mTransform;
+ }
+
+ tempManager->SetRoot(tmpLayer);
+ layerBuilder->WillEndTransaction();
+ tempManager->AbortTransaction();
+
+ if (gfxUtils::DumpDisplayList() || gfxEnv::DumpPaint()) {
+ fprintf_stderr(
+ gfxUtils::sDumpPaintFile,
+ "Basic layer tree for painting contents of display item %s(%p):\n",
+ aItem.mItem->Name(), aItem.mItem->Frame());
+ std::stringstream stream;
+ tempManager->Dump(stream, "", gfxEnv::DumpPaintToFile());
+ fprint_stderr(gfxUtils::sDumpPaintFile,
+ stream); // not a typo, fprint_stderr declared in nsDebug.h
+ }
+
+ nsIntPoint offset =
+ GetLastPaintOffset(layer) - GetTranslationForPaintedLayer(layer);
+ aItem.mInactiveLayerData->mProps->MoveBy(-offset);
+ // Effective transforms are needed by ComputeDifferences().
+ tmpLayer->ComputeEffectiveTransforms(Matrix4x4());
+ nsIntRegion invalid;
+ if (!aItem.mInactiveLayerData->mProps->ComputeDifferences(tmpLayer, invalid,
+ nullptr)) {
+ nsRect visible = aItem.mItem->Frame()->InkOverflowRect();
+ invalid = visible.ToOutsidePixels(paintedData->mAppUnitsPerDevPixel);
+ }
+ if (aItem.mLayerState == LayerState::LAYER_SVG_EFFECTS) {
+ invalid = SVGIntegrationUtils::AdjustInvalidAreaForSVGEffects(
+ aItem.mItem->Frame(), aItem.mItem->ToReferenceFrame(), invalid);
+ }
+ if (!invalid.IsEmpty()) {
+#ifdef MOZ_DUMP_PAINTING
+ if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) {
+ printf_stderr(
+ "Inactive LayerManager(%p) for display item %s(%p) has "
+ "an invalid region - invalidating layer %p\n",
+ tempManager.get(), aItem.mItem->Name(), aItem.mItem->Frame(),
+ layer);
+ }
+#endif
+
+ if (data && data->mTransform) {
+ invalid = data->mTransform->TransformRegion(invalid);
+ }
+
+ invalid.ScaleRoundOut(paintedData->mXScale, paintedData->mYScale);
+
+ InvalidatePostTransformRegion(layer, invalid,
+ GetTranslationForPaintedLayer(layer));
+ }
+ }
+}
+
+DisplayItemData* FrameLayerBuilder::StoreDataForFrame(
+ nsPaintedDisplayItem* aItem, Layer* aLayer, LayerState aState,
+ DisplayItemData* aData) {
+ MOZ_ASSERT(aItem);
+
+ if (aData) {
+ if (!aData->mUsed) {
+ aData->BeginUpdate(aLayer, aState, false, aItem);
+ }
+ return aData;
+ }
+
+ LayerManagerData* lmd = static_cast<LayerManagerData*>(
+ mRetainingManager->GetUserData(&gLayerManagerUserData));
+
+ RefPtr<DisplayItemData> data = new (aItem->Frame()->PresContext())
+ DisplayItemData(lmd, aItem->GetPerFrameKey(), aLayer);
+
+ data->BeginUpdate(aLayer, aState, true, aItem);
+
+ lmd->mDisplayItems.push_back(data);
+ return data;
+}
+
+void FrameLayerBuilder::StoreDataForFrame(nsIFrame* aFrame,
+ uint32_t aDisplayItemKey,
+ Layer* aLayer, LayerState aState) {
+ DisplayItemData* oldData = GetDisplayItemData(aFrame, aDisplayItemKey);
+ if (oldData && oldData->mFrameList.Length() == 1) {
+ oldData->BeginUpdate(aLayer, aState, false);
+ return;
+ }
+
+ LayerManagerData* lmd = static_cast<LayerManagerData*>(
+ mRetainingManager->GetUserData(&gLayerManagerUserData));
+
+ RefPtr<DisplayItemData> data = new (aFrame->PresContext())
+ DisplayItemData(lmd, aDisplayItemKey, aLayer, aFrame);
+
+ data->BeginUpdate(aLayer, aState, true);
+
+ lmd->mDisplayItems.push_back(data);
+}
+
+AssignedDisplayItem::AssignedDisplayItem(
+ nsPaintedDisplayItem* aItem, LayerState aLayerState, DisplayItemData* aData,
+ const nsRect& aContentRect, DisplayItemEntryType aType,
+ const bool aHasOpacity, const RefPtr<TransformClipNode>& aTransform,
+ const bool aIsMerged)
+ : mItem(aItem),
+ mDisplayItemData(aData),
+ mTransform(aTransform),
+ mContentRect(aContentRect),
+ mLayerState(aLayerState),
+ mType(aType),
+ mReused(aItem->IsReused()),
+ mMerged(aIsMerged),
+ mHasOpacity(aHasOpacity),
+ mHasPaintRect(aItem->HasPaintRect()) {}
+
+InactiveLayerData::~InactiveLayerData() {
+ if (mLayerManager) {
+ mLayerManager->SetUserData(&gLayerManagerLayerBuilder, nullptr);
+ }
+}
+
+bool FrameLayerBuilder::CheckInLayerTreeCompressionMode() {
+ if (mInLayerTreeCompressionMode) {
+ return true;
+ }
+
+ // If we wanted to be in layer tree compression mode, but weren't, then
+ // scheduled a delayed repaint where we will be.
+ mRootPresContext->PresShell()->GetRootFrame()->SchedulePaint(
+ nsIFrame::PAINT_DELAYED_COMPRESS, false);
+
+ return false;
+}
+
+void ContainerState::CollectOldLayers() {
+ for (Layer* layer = mContainerLayer->GetFirstChild(); layer;
+ layer = layer->GetNextSibling()) {
+ NS_ASSERTION(!layer->HasUserData(&gMaskLayerUserData),
+ "Mask layers should not be part of the layer tree.");
+ if (layer->HasUserData(&gPaintedDisplayItemLayerUserData)) {
+ NS_ASSERTION(layer->AsPaintedLayer(), "Wrong layer type");
+ mPaintedLayersAvailableForRecycling.PutEntry(
+ static_cast<PaintedLayer*>(layer));
+ }
+
+ if (Layer* maskLayer = layer->GetMaskLayer()) {
+ NS_ASSERTION(maskLayer->GetType() == Layer::TYPE_IMAGE,
+ "Could not recycle mask layer, unsupported layer type.");
+ mRecycledMaskImageLayers.Put(MaskLayerKey(layer, Nothing()),
+ static_cast<ImageLayer*>(maskLayer));
+ }
+ for (size_t i = 0; i < layer->GetAncestorMaskLayerCount(); i++) {
+ Layer* maskLayer = layer->GetAncestorMaskLayerAt(i);
+
+ NS_ASSERTION(maskLayer->GetType() == Layer::TYPE_IMAGE,
+ "Could not recycle mask layer, unsupported layer type.");
+ mRecycledMaskImageLayers.Put(MaskLayerKey(layer, Some(i)),
+ static_cast<ImageLayer*>(maskLayer));
+ }
+ }
+}
+
+struct OpaqueRegionEntry {
+ AnimatedGeometryRoot* mAnimatedGeometryRoot;
+ const ActiveScrolledRoot* mASR;
+ nsIntRegion mOpaqueRegion;
+};
+
+static OpaqueRegionEntry* FindOpaqueRegionEntry(
+ nsTArray<OpaqueRegionEntry>& aEntries,
+ AnimatedGeometryRoot* aAnimatedGeometryRoot,
+ const ActiveScrolledRoot* aASR) {
+ for (uint32_t i = 0; i < aEntries.Length(); ++i) {
+ OpaqueRegionEntry* d = &aEntries[i];
+ if (d->mAnimatedGeometryRoot == aAnimatedGeometryRoot && d->mASR == aASR) {
+ return d;
+ }
+ }
+ return nullptr;
+}
+
+static const ActiveScrolledRoot* FindDirectChildASR(
+ const ActiveScrolledRoot* aParent, const ActiveScrolledRoot* aDescendant) {
+ MOZ_ASSERT(aDescendant, "can't start at the root when looking for a child");
+ MOZ_ASSERT(ActiveScrolledRoot::IsAncestor(aParent, aDescendant));
+ const ActiveScrolledRoot* directChild = aDescendant;
+ while (directChild->mParent != aParent) {
+ directChild = directChild->mParent;
+ MOZ_RELEASE_ASSERT(directChild, "this must not be null");
+ }
+ return directChild;
+}
+
+static void FixUpFixedPositionLayer(
+ Layer* aLayer, const ActiveScrolledRoot* aTargetASR,
+ const ActiveScrolledRoot* aLeafScrollMetadataASR,
+ const ActiveScrolledRoot* aContainerScrollMetadataASR,
+ const ActiveScrolledRoot* aContainerCompositorASR,
+ bool aIsFixedToRootScrollFrame) {
+ if (!aLayer->GetIsFixedPosition()) {
+ return;
+ }
+
+ // Analyze ASRs to figure out if we need to fix up fixedness annotations on
+ // the layer. Fixed annotations are required in multiple cases:
+ // - Sometimes we set scroll metadata on a layer for a scroll frame that we
+ // don't want the layer to be moved by. (We have to do this if there is a
+ // scrolled clip that is moved by that scroll frame.) So we set the fixed
+ // annotation so that the compositor knows that it should ignore that
+ // scroll metadata when determining the layer's position.
+ // - Sometimes there is a scroll meta data on aLayer's parent layer for a
+ // scroll frame that we don't want aLayer to be moved by. The most common
+ // way for this to happen is with containerful root scrolling, where the
+ // scroll metadata for the root scroll frame is on a container layer that
+ // wraps the whole document's contents.
+ // - Sometimes it's just needed for hit testing, i.e. figuring out what
+ // scroll frame should be scrolled by events over the layer.
+ // A fixed layer needs to be annotated with the scroll ID of the scroll frame
+ // that it is *fixed with respect to*, i.e. the outermost scroll frame which
+ // does not move the layer. nsDisplayFixedPosition only ever annotates layers
+ // with the scroll ID of the presshell's root scroll frame, which is
+ // sometimes the wrong thing to do, so we correct it here. Specifically,
+ // it's the wrong thing to do if the fixed frame's containing block is a
+ // transformed frame - in that case, the fixed frame needs to scroll along
+ // with the transformed frame instead of being fixed with respect to the rsf.
+ // (It would be nice to compute the annotation only in one place and get it
+ // right, instead of fixing it up after the fact like this, but this will
+ // need to do for now.)
+ // compositorASR is the ASR that the layer would move with on the compositor
+ // if there were no fixed annotation on it.
+ const ActiveScrolledRoot* compositorASR =
+ aLeafScrollMetadataASR == aContainerScrollMetadataASR
+ ? aContainerCompositorASR
+ : aLeafScrollMetadataASR;
+
+ // The goal of the annotation is to have the layer move with aTargetASR.
+ if (compositorASR && aTargetASR != compositorASR) {
+ // Mark this layer as fixed with respect to the child scroll frame of
+ // aTargetASR.
+ aLayer->SetFixedPositionData(
+ FindDirectChildASR(aTargetASR, compositorASR)->GetViewId(),
+ aLayer->GetFixedPositionAnchor(), aLayer->GetFixedPositionSides());
+ } else {
+ // Remove the fixed annotation from the layer, unless this layers is fixed
+ // to the document's root scroll frame - in that case, the annotation is
+ // needed for hit testing, because fixed layers in iframes should scroll
+ // the iframe, even though their position is not affected by scrolling in
+ // the iframe. (The APZ hit testing code has a special case for this.)
+ // nsDisplayFixedPosition has annotated this layer with the document's
+ // root scroll frame's scroll id.
+ aLayer->SetIsFixedPosition(aIsFixedToRootScrollFrame);
+ }
+}
+
+void ContainerState::SetupScrollingMetadata(NewLayerEntry* aEntry) {
+ if (!mBuilder->IsPaintingToWindow()) {
+ // async scrolling not possible, and async scrolling info not computed
+ // for this paint.
+ return;
+ }
+
+ const ActiveScrolledRoot* startASR = aEntry->mScrollMetadataASR;
+ const ActiveScrolledRoot* stopASR = mContainerScrollMetadataASR;
+ if (!ActiveScrolledRoot::IsAncestor(stopASR, startASR)) {
+ if (ActiveScrolledRoot::IsAncestor(startASR, stopASR)) {
+ // startASR and stopASR are in the same branch of the ASR tree, but
+ // startASR is closer to the root. Just start at stopASR so that the loop
+ // below doesn't actually do anything.
+ startASR = stopASR;
+ } else {
+ // startASR and stopASR are in different branches of the
+ // ASR tree. Find a common ancestor and make that the stopASR.
+ // This can happen when there's a scrollable frame inside a fixed layer
+ // which has a scrolled clip. As far as scroll metadata is concerned,
+ // the scroll frame's scroll metadata will be a child of the scroll ID
+ // that scrolls the clip on the fixed layer. But as far as ASRs are
+ // concerned, those two ASRs are siblings, parented to the ASR of the
+ // fixed layer.
+ do {
+ stopASR = stopASR->mParent;
+ } while (!ActiveScrolledRoot::IsAncestor(stopASR, startASR));
+ }
+ }
+
+ FixUpFixedPositionLayer(aEntry->mLayer, aEntry->mASR, startASR,
+ mContainerScrollMetadataASR, mContainerCompositorASR,
+ aEntry->mIsFixedToRootScrollFrame);
+
+ AutoTArray<ScrollMetadata, 2> metricsArray;
+ if (aEntry->mBaseScrollMetadata) {
+ metricsArray.AppendElement(*aEntry->mBaseScrollMetadata);
+
+ // The base FrameMetrics was not computed by the nsIScrollableframe, so it
+ // should not have a mask layer.
+ MOZ_ASSERT(!aEntry->mBaseScrollMetadata->HasMaskLayer());
+ }
+
+ // Any extra mask layers we need to attach to ScrollMetadatas.
+ // The list may already contain an entry added for the layer's scrolled clip
+ // so add to it rather than overwriting it (we clear the list when recycling
+ // a layer).
+ nsTArray<RefPtr<Layer>> maskLayers(
+ aEntry->mLayer->GetAllAncestorMaskLayers().Clone());
+
+ // Iterate over the ASR chain and create the corresponding scroll metadatas.
+ // This loop is slightly tricky because the scrollframe-to-clip relationship
+ // is reversed between DisplayItemClipChain and ScrollMetadata:
+ // - DisplayItemClipChain associates the clip with the scroll frame that
+ // this clip is *moved by*, i.e. the clip is moving inside the scroll
+ // frame.
+ // - ScrollMetaData associates the scroll frame with the clip that's
+ // *just outside* the scroll frame, i.e. not moved by the scroll frame
+ // itself.
+ // This discrepancy means that the leaf clip item of the clip chain is never
+ // applied to any scroll meta data. Instead, it was applied earlier as the
+ // layer's clip (or fused with the painted layer contents), or it was applied
+ // as a ScrolledClip on the layer.
+ const DisplayItemClipChain* clipChain = aEntry->mClipChain;
+
+ for (const ActiveScrolledRoot* asr = startASR; asr != stopASR;
+ asr = asr->mParent) {
+ if (!asr) {
+ MOZ_ASSERT_UNREACHABLE("Should have encountered stopASR on the way up.");
+ break;
+ }
+ if (clipChain && clipChain->mASR == asr) {
+ clipChain = clipChain->mParent;
+ }
+
+ nsIScrollableFrame* scrollFrame = asr->mScrollableFrame;
+ const DisplayItemClip* clip = (clipChain && clipChain->mASR == asr->mParent)
+ ? &clipChain->mClip
+ : nullptr;
+
+ scrollFrame->ClipLayerToDisplayPort(aEntry->mLayer, clip, mParameters);
+
+ Maybe<ScrollMetadata> metadata;
+ if (mCachedScrollMetadata.mASR == asr &&
+ mCachedScrollMetadata.mClip == clip) {
+ metadata = mCachedScrollMetadata.mMetadata;
+ } else {
+ metadata = scrollFrame->ComputeScrollMetadata(aEntry->mLayer->Manager(),
+ mContainerReferenceFrame,
+ Some(mParameters), clip);
+ mBuilder->AddScrollFrameToNotify(scrollFrame);
+ mCachedScrollMetadata.mASR = asr;
+ mCachedScrollMetadata.mClip = clip;
+ mCachedScrollMetadata.mMetadata = metadata;
+ }
+
+ if (!metadata) {
+ continue;
+ }
+
+ if (clip && clip->HasClip() && clip->GetRoundedRectCount() > 0) {
+ // The clip in between this scrollframe and its ancestor scrollframe
+ // requires a mask layer. Since this mask layer should not move with
+ // the APZC associated with this FrameMetrics, we attach the mask
+ // layer as an additional, separate clip.
+ Maybe<size_t> nextIndex = Some(maskLayers.Length());
+ RefPtr<Layer> maskLayer =
+ CreateMaskLayer(aEntry->mLayer, *clip, nextIndex);
+ if (maskLayer) {
+ MOZ_ASSERT(metadata->HasScrollClip());
+ metadata->ScrollClip().SetMaskLayerIndex(nextIndex);
+ maskLayers.AppendElement(maskLayer);
+ }
+ }
+
+ metricsArray.AppendElement(*metadata);
+ }
+
+ // Watch out for FrameMetrics copies in profiles
+ aEntry->mLayer->SetScrollMetadata(metricsArray);
+ aEntry->mLayer->SetAncestorMaskLayers(maskLayers);
+}
+
+static inline Maybe<ParentLayerIntRect> GetStationaryClipInContainer(
+ Layer* aLayer) {
+ if (size_t metricsCount = aLayer->GetScrollMetadataCount()) {
+ return aLayer->GetScrollMetadata(metricsCount - 1).GetClipRect();
+ }
+ return aLayer->GetClipRect();
+}
+
+void ContainerState::PostprocessRetainedLayers(
+ nsIntRegion* aOpaqueRegionForContainer) {
+ AutoTArray<OpaqueRegionEntry, 4> opaqueRegions;
+ bool hideAll = false;
+ int32_t opaqueRegionForContainer = -1;
+
+ for (int32_t i = mNewChildLayers.Length() - 1; i >= 0; --i) {
+ NewLayerEntry* e = &mNewChildLayers.ElementAt(i);
+ if (!e->mLayer) {
+ continue;
+ }
+
+ OpaqueRegionEntry* data =
+ FindOpaqueRegionEntry(opaqueRegions, e->mAnimatedGeometryRoot, e->mASR);
+
+ SetupScrollingMetadata(e);
+
+ if (hideAll) {
+ e->mVisibleRegion.SetEmpty();
+ } else if (!e->mLayer->IsScrollbarContainer()) {
+ Maybe<ParentLayerIntRect> clipRect =
+ GetStationaryClipInContainer(e->mLayer);
+ if (clipRect && opaqueRegionForContainer >= 0 &&
+ opaqueRegions[opaqueRegionForContainer].mOpaqueRegion.Contains(
+ clipRect->ToUnknownRect())) {
+ e->mVisibleRegion.SetEmpty();
+ } else if (data) {
+ e->mVisibleRegion.Sub(e->mVisibleRegion, data->mOpaqueRegion);
+ }
+ }
+
+ SetOuterVisibleRegionForLayer(e->mLayer, e->mVisibleRegion,
+ e->mLayerContentsVisibleRect.width >= 0
+ ? &e->mLayerContentsVisibleRect
+ : nullptr,
+ e->mUntransformedVisibleRegion);
+
+ if (!e->mOpaqueRegion.IsEmpty()) {
+ AnimatedGeometryRoot* animatedGeometryRootToCover =
+ e->mAnimatedGeometryRoot;
+ const ActiveScrolledRoot* asrToCover = e->mASR;
+ if (e->mOpaqueForAnimatedGeometryRootParent &&
+ e->mAnimatedGeometryRoot->mParentAGR ==
+ mContainerAnimatedGeometryRoot) {
+ animatedGeometryRootToCover = mContainerAnimatedGeometryRoot;
+ asrToCover = mContainerASR;
+ data = FindOpaqueRegionEntry(opaqueRegions, animatedGeometryRootToCover,
+ asrToCover);
+ }
+
+ if (!data) {
+ if (animatedGeometryRootToCover == mContainerAnimatedGeometryRoot &&
+ asrToCover == mContainerASR) {
+ NS_ASSERTION(opaqueRegionForContainer == -1, "Already found it?");
+ opaqueRegionForContainer = opaqueRegions.Length();
+ }
+ data = opaqueRegions.AppendElement();
+ data->mAnimatedGeometryRoot = animatedGeometryRootToCover;
+ data->mASR = asrToCover;
+ }
+
+ nsIntRegion clippedOpaque = e->mOpaqueRegion;
+ Maybe<ParentLayerIntRect> clipRect = e->mLayer->GetCombinedClipRect();
+ if (clipRect) {
+ clippedOpaque.AndWith(clipRect->ToUnknownRect());
+ }
+ if (e->mLayer->GetScrolledClip()) {
+ // The clip can move asynchronously, so we can't rely on opaque parts
+ // staying visible.
+ clippedOpaque.SetEmpty();
+ } else if (e->mHideAllLayersBelow) {
+ hideAll = true;
+ }
+ data->mOpaqueRegion.Or(data->mOpaqueRegion, clippedOpaque);
+ }
+
+ if (e->mLayer->GetType() == Layer::TYPE_READBACK) {
+ // ReadbackLayers need to accurately read what's behind them. So,
+ // we don't want to do any occlusion culling of layers behind them.
+ // Theoretically we could just punch out the ReadbackLayer's rectangle
+ // from all mOpaqueRegions, but that's probably not worth doing.
+ opaqueRegions.Clear();
+ opaqueRegionForContainer = -1;
+ }
+ }
+
+ if (opaqueRegionForContainer >= 0) {
+ aOpaqueRegionForContainer->Or(
+ *aOpaqueRegionForContainer,
+ opaqueRegions[opaqueRegionForContainer].mOpaqueRegion);
+ }
+}
+
+void ContainerState::Finish(uint32_t* aTextContentFlags,
+ const nsIntRect& aContainerPixelBounds,
+ nsDisplayList* aChildItems) {
+ mPaintedLayerDataTree.Finish();
+
+ NS_ASSERTION(mContainerBounds.IsEqualInterior(mAccumulatedChildBounds),
+ "Bounds computation mismatch");
+
+ if (mLayerBuilder->IsBuildingRetainedLayers()) {
+ nsIntRegion containerOpaqueRegion;
+ PostprocessRetainedLayers(&containerOpaqueRegion);
+ if (containerOpaqueRegion.Contains(aContainerPixelBounds)) {
+ aChildItems->SetIsOpaque();
+ }
+ }
+
+ uint32_t textContentFlags = 0;
+
+ // Make sure that current/existing layers are added to the parent and are
+ // in the correct order.
+ Layer* layer = nullptr;
+ Layer* prevChild = nullptr;
+ for (uint32_t i = 0; i < mNewChildLayers.Length(); ++i, prevChild = layer) {
+ if (!mNewChildLayers[i].mLayer) {
+ continue;
+ }
+
+ layer = mNewChildLayers[i].mLayer;
+
+ if (!layer->GetVisibleRegion().IsEmpty()) {
+ textContentFlags |= layer->GetContentFlags() &
+ (Layer::CONTENT_COMPONENT_ALPHA |
+ Layer::CONTENT_COMPONENT_ALPHA_DESCENDANT |
+ Layer::CONTENT_DISABLE_FLATTENING);
+ }
+
+ if (!layer->GetParent()) {
+ // This is not currently a child of the container, so just add it
+ // now.
+ mContainerLayer->InsertAfter(layer, prevChild);
+ } else {
+ NS_ASSERTION(layer->GetParent() == mContainerLayer,
+ "Layer shouldn't be the child of some other container");
+ if (layer->GetPrevSibling() != prevChild) {
+ mContainerLayer->RepositionChild(layer, prevChild);
+ }
+ }
+ }
+
+ // Remove old layers that have become unused.
+ if (!layer) {
+ layer = mContainerLayer->GetFirstChild();
+ } else {
+ layer = layer->GetNextSibling();
+ }
+ while (layer) {
+ Layer* layerToRemove = layer;
+ layer = layer->GetNextSibling();
+ mContainerLayer->RemoveChild(layerToRemove);
+ }
+
+ *aTextContentFlags = textContentFlags;
+}
+
+static void RestrictScaleToMaxLayerSize(Size& aScale,
+ const nsRect& aVisibleRect,
+ nsIFrame* aContainerFrame,
+ Layer* aContainerLayer) {
+ if (!aContainerLayer->Manager()->IsWidgetLayerManager()) {
+ return;
+ }
+
+ nsIntRect pixelSize = aVisibleRect.ScaleToOutsidePixels(
+ aScale.width, aScale.height,
+ aContainerFrame->PresContext()->AppUnitsPerDevPixel());
+
+ int32_t maxLayerSize = aContainerLayer->GetMaxLayerSize();
+
+ if (pixelSize.width > maxLayerSize) {
+ float scale = (float)pixelSize.width / maxLayerSize;
+ scale = gfxUtils::ClampToScaleFactor(scale);
+ aScale.width /= scale;
+ }
+ if (pixelSize.height > maxLayerSize) {
+ float scale = (float)pixelSize.height / maxLayerSize;
+ scale = gfxUtils::ClampToScaleFactor(scale);
+ aScale.height /= scale;
+ }
+}
+
+static nsSize ComputeDesiredDisplaySizeForAnimation(nsIFrame* aContainerFrame) {
+ // Use the size of the nearest widget as the maximum size. This
+ // is important since it might be a popup that is bigger than the
+ // pres context's size.
+ nsPresContext* presContext = aContainerFrame->PresContext();
+ nsIWidget* widget = aContainerFrame->GetNearestWidget();
+ if (widget) {
+ return LayoutDevicePixel::ToAppUnits(widget->GetClientSize(),
+ presContext->AppUnitsPerDevPixel());
+ }
+
+ return presContext->GetVisibleArea().Size();
+}
+
+/* static */
+Size FrameLayerBuilder::ChooseScale(nsIFrame* aContainerFrame,
+ nsDisplayItem* aContainerItem,
+ const nsRect& aVisibleRect, float aXScale,
+ float aYScale, const Matrix& aTransform2d,
+ bool aCanDraw2D) {
+ Size scale;
+ // XXX Should we do something for 3D transforms?
+ if (aCanDraw2D && !aContainerFrame->Combines3DTransformWithAncestors() &&
+ !aContainerFrame->HasPerspective()) {
+ // If the container's transform is animated off main thread, fix a suitable
+ // scale size for animation
+ if (aContainerItem &&
+ aContainerItem->GetType() == DisplayItemType::TYPE_TRANSFORM &&
+ // FIXME: What we need is only transform, rotate, and scale, not
+ // translate, so it's be better to use a property set, instead of
+ // display item type here.
+ EffectCompositor::HasAnimationsForCompositor(
+ aContainerFrame, DisplayItemType::TYPE_TRANSFORM)) {
+ nsSize displaySize =
+ ComputeDesiredDisplaySizeForAnimation(aContainerFrame);
+ // compute scale using the animation on the container, taking ancestors in
+ // to account
+ nsSize scaledVisibleSize = nsSize(aVisibleRect.Width() * aXScale,
+ aVisibleRect.Height() * aYScale);
+ scale = nsLayoutUtils::ComputeSuitableScaleForAnimation(
+ aContainerFrame, scaledVisibleSize, displaySize);
+ // multiply by the scale inherited from ancestors--we use a uniform
+ // scale factor to prevent blurring when the layer is rotated.
+ float incomingScale = std::max(aXScale, aYScale);
+ scale.width *= incomingScale;
+ scale.height *= incomingScale;
+ } else {
+ // Scale factors are normalized to a power of 2 to reduce the number of
+ // resolution changes
+ scale = aTransform2d.ScaleFactors();
+ // For frames with a changing scale transform round scale factors up to
+ // nearest power-of-2 boundary so that we don't keep having to redraw
+ // the content as it scales up and down. Rounding up to nearest
+ // power-of-2 boundary ensures we never scale up, only down --- avoiding
+ // jaggies. It also ensures we never scale down by more than a factor of
+ // 2, avoiding bad downscaling quality.
+ Matrix frameTransform;
+ if (ActiveLayerTracker::IsScaleSubjectToAnimation(aContainerFrame)) {
+ scale.width = gfxUtils::ClampToScaleFactor(scale.width);
+ scale.height = gfxUtils::ClampToScaleFactor(scale.height);
+
+ // Limit animated scale factors to not grow excessively beyond the
+ // display size.
+ nsSize maxScale(4, 4);
+ if (!aVisibleRect.IsEmpty()) {
+ nsSize displaySize =
+ ComputeDesiredDisplaySizeForAnimation(aContainerFrame);
+ maxScale = Max(maxScale, displaySize / aVisibleRect.Size());
+ }
+ if (scale.width > maxScale.width) {
+ scale.width = gfxUtils::ClampToScaleFactor(maxScale.width, true);
+ }
+ if (scale.height > maxScale.height) {
+ scale.height = gfxUtils::ClampToScaleFactor(maxScale.height, true);
+ }
+ } else {
+ // XXX Do we need to move nearly-integer values to integers here?
+ }
+ }
+ // If the scale factors are too small, just use 1.0. The content is being
+ // scaled out of sight anyway.
+ if (fabs(scale.width) < 1e-8 || fabs(scale.height) < 1e-8) {
+ scale = Size(1.0, 1.0);
+ }
+ } else {
+ scale = Size(1.0, 1.0);
+ }
+
+ // Prevent the scale from getting too large, to avoid excessive memory
+ // allocation. Usually memory allocation is limited by the visible region,
+ // which should be restricted to the display port. But at very large scales
+ // the visible region itself can become excessive due to rounding errors.
+ // Clamping the scale here prevents that.
+ scale =
+ Size(std::min(scale.width, 32768.0f), std::min(scale.height, 32768.0f));
+
+ return scale;
+}
+
+static bool ChooseScaleAndSetTransform(
+ FrameLayerBuilder* aLayerBuilder, nsDisplayListBuilder* aDisplayListBuilder,
+ nsIFrame* aContainerFrame, nsDisplayItem* aContainerItem,
+ const nsRect& aVisibleRect, const Matrix4x4* aTransform,
+ const ContainerLayerParameters& aIncomingScale, ContainerLayer* aLayer,
+ ContainerLayerParameters& aOutgoingScale) {
+ nsIntPoint offset;
+
+ Matrix4x4 transform =
+ Matrix4x4::Scaling(aIncomingScale.mXScale, aIncomingScale.mYScale, 1.0);
+ if (aTransform) {
+ // aTransform is applied first, then the scale is applied to the result
+ transform = (*aTransform) * transform;
+ // Set any matrix entries close to integers to be those exact integers.
+ // This protects against floating-point inaccuracies causing problems
+ // in the checks below.
+ // We use the fixed epsilon version here because we don't want the nudging
+ // to depend on the scroll position.
+ transform.NudgeToIntegersFixedEpsilon();
+ }
+ Matrix transform2d;
+ if (aContainerFrame && aLayerBuilder->GetContainingPaintedLayerData() &&
+ (!aTransform ||
+ (aTransform->Is2D(&transform2d) && !transform2d.HasNonTranslation()))) {
+ // When we have an inactive ContainerLayer, translate the container by the
+ // offset to the reference frame (and offset all child layers by the
+ // reverse) so that the coordinate space of the child layers isn't affected
+ // by scrolling. This gets confusing for complicated transform (since we'd
+ // have to compute the scale factors for the matrix), so we don't bother.
+ // Any frames that are building an nsDisplayTransform for a css transform
+ // would have 0,0 as their offset to the reference frame, so this doesn't
+ // matter.
+ nsPoint appUnitOffset =
+ aDisplayListBuilder->ToReferenceFrame(aContainerFrame);
+ nscoord appUnitsPerDevPixel =
+ aContainerFrame->PresContext()->AppUnitsPerDevPixel();
+ offset = nsIntPoint(NS_lround(NSAppUnitsToDoublePixels(
+ appUnitOffset.x, appUnitsPerDevPixel) *
+ aIncomingScale.mXScale),
+ NS_lround(NSAppUnitsToDoublePixels(
+ appUnitOffset.y, appUnitsPerDevPixel) *
+ aIncomingScale.mYScale));
+ }
+ transform.PostTranslate(offset.x + aIncomingScale.mOffset.x,
+ offset.y + aIncomingScale.mOffset.y, 0);
+
+ if (transform.IsSingular()) {
+ return false;
+ }
+
+ bool canDraw2D = transform.CanDraw2D(&transform2d);
+ Size scale = FrameLayerBuilder::ChooseScale(
+ aContainerFrame, aContainerItem, aVisibleRect, aIncomingScale.mXScale,
+ aIncomingScale.mYScale, transform2d, canDraw2D);
+
+ // If this is a transform container layer, then pre-rendering might
+ // mean we try render a layer bigger than the max texture size. If we have
+ // tiling, that's not a problem, since we'll automatically choose a tiled
+ // layer for layers of that size. If not, we need to apply clamping to
+ // prevent this.
+ if (aTransform && !StaticPrefs::layers_enable_tiles_AtStartup()) {
+ RestrictScaleToMaxLayerSize(scale, aVisibleRect, aContainerFrame, aLayer);
+ }
+
+ // Store the inverse of our resolution-scale on the layer
+ aLayer->SetBaseTransform(transform);
+ aLayer->SetPreScale(1.0f / scale.width, 1.0f / scale.height);
+ aLayer->SetInheritedScale(aIncomingScale.mXScale, aIncomingScale.mYScale);
+
+ aOutgoingScale = ContainerLayerParameters(scale.width, scale.height, -offset,
+ aIncomingScale);
+ if (aTransform) {
+ aOutgoingScale.mInTransformedSubtree = true;
+ if (ActiveLayerTracker::IsTransformAnimated(aDisplayListBuilder,
+ aContainerFrame)) {
+ aOutgoingScale.mInActiveTransformedSubtree = true;
+ }
+ }
+ if ((aLayerBuilder->IsBuildingRetainedLayers() &&
+ (!canDraw2D || transform2d.HasNonIntegerTranslation())) ||
+ aContainerFrame->Extend3DContext() ||
+ aContainerFrame->Combines3DTransformWithAncestors() ||
+ // For async transform animation, the value would be changed at
+ // any time, integer translation is not always true.
+ aContainerFrame->HasAnimationOfTransform()) {
+ aOutgoingScale.mDisableSubpixelAntialiasingInDescendants = true;
+ }
+ return true;
+}
+
+already_AddRefed<ContainerLayer> FrameLayerBuilder::BuildContainerLayerFor(
+ nsDisplayListBuilder* aBuilder, LayerManager* aManager,
+ nsIFrame* aContainerFrame, nsDisplayItem* aContainerItem,
+ nsDisplayList* aChildren, const ContainerLayerParameters& aParameters,
+ const Matrix4x4* aTransform, uint32_t aFlags) {
+ uint32_t containerDisplayItemKey =
+ aContainerItem ? aContainerItem->GetPerFrameKey() : 0;
+ NS_ASSERTION(aContainerFrame,
+ "Container display items here should have a frame");
+ NS_ASSERTION(!aContainerItem || aContainerItem->Frame() == aContainerFrame,
+ "Container display item must match given frame");
+
+ if (!aParameters.mXScale || !aParameters.mYScale) {
+ return nullptr;
+ }
+
+ RefPtr<ContainerLayer> containerLayer;
+ if (aManager == mRetainingManager) {
+ // Using GetOldLayerFor will search merged frames, as well as the underlying
+ // frame. The underlying frame can change when a page scrolls, so this
+ // avoids layer recreation in the situation that a new underlying frame is
+ // picked for a layer.
+ Layer* oldLayer = nullptr;
+ if (aContainerItem) {
+ oldLayer = GetOldLayerFor(aContainerItem);
+ } else {
+ DisplayItemData* data =
+ GetOldLayerForFrame(aContainerFrame, containerDisplayItemKey);
+ if (data) {
+ oldLayer = data->mLayer;
+ }
+ }
+
+ if (oldLayer) {
+ NS_ASSERTION(oldLayer->Manager() == aManager, "Wrong manager");
+ if (oldLayer->HasUserData(&gPaintedDisplayItemLayerUserData)) {
+ // The old layer for this item is actually our PaintedLayer
+ // because we rendered its layer into that PaintedLayer. So we
+ // don't actually have a retained container layer.
+ } else {
+ NS_ASSERTION(oldLayer->GetType() == Layer::TYPE_CONTAINER,
+ "Wrong layer type");
+ containerLayer = static_cast<ContainerLayer*>(oldLayer);
+ ResetLayerStateForRecycling(containerLayer);
+ }
+ }
+ }
+ if (!containerLayer) {
+ // No suitable existing layer was found.
+ containerLayer = aManager->CreateContainerLayer();
+ if (!containerLayer) return nullptr;
+ }
+
+ if (aContainerItem &&
+ aContainerItem->GetType() == DisplayItemType::TYPE_SCROLL_INFO_LAYER) {
+ // Empty layers only have metadata and should never have display items. We
+ // early exit because later, invalidation will walk up the frame tree to
+ // determine which painted layer gets invalidated. Since an empty layer
+ // should never have anything to paint, it should never be invalidated.
+ NS_ASSERTION(aChildren->IsEmpty(), "Should have no children");
+ return containerLayer.forget();
+ }
+
+ const ActiveScrolledRoot* containerASR =
+ aContainerItem ? aContainerItem->GetActiveScrolledRoot() : nullptr;
+ const ActiveScrolledRoot* containerScrollMetadataASR =
+ aParameters.mScrollMetadataASR;
+ const ActiveScrolledRoot* containerCompositorASR = aParameters.mCompositorASR;
+
+ ContainerLayerParameters scaleParameters;
+ nsRect bounds =
+ aChildren->GetClippedBoundsWithRespectToASR(aBuilder, containerASR);
+ nsRect childrenVisible =
+ aContainerItem ? aContainerItem->GetBuildingRectForChildren()
+ : aContainerFrame->InkOverflowRectRelativeToSelf();
+ if (!ChooseScaleAndSetTransform(
+ this, aBuilder, aContainerFrame, aContainerItem,
+ bounds.Intersect(childrenVisible), aTransform, aParameters,
+ containerLayer, scaleParameters)) {
+ return nullptr;
+ }
+
+ if (mRetainingManager) {
+ if (aContainerItem) {
+ nsPaintedDisplayItem* item = aContainerItem->AsPaintedDisplayItem();
+ MOZ_ASSERT(item, "Only painted display items should build layers");
+
+ DisplayItemData* data =
+ GetDisplayItemDataForManager(item, mRetainingManager);
+ StoreDataForFrame(item, containerLayer, LayerState::LAYER_ACTIVE, data);
+ } else {
+ StoreDataForFrame(aContainerFrame, containerDisplayItemKey,
+ containerLayer, LayerState::LAYER_ACTIVE);
+ }
+ }
+
+ nsIntRect pixBounds;
+ nscoord appUnitsPerDevPixel;
+
+ nscolor backgroundColor = NS_RGBA(0, 0, 0, 0);
+ if (aFlags & CONTAINER_ALLOW_PULL_BACKGROUND_COLOR) {
+ backgroundColor = aParameters.mBackgroundColor;
+ }
+
+ uint32_t flags;
+ ContainerState state(aBuilder, aManager, aManager->GetLayerBuilder(),
+ aContainerFrame, aContainerItem, bounds, containerLayer,
+ scaleParameters, backgroundColor, containerASR,
+ containerScrollMetadataASR, containerCompositorASR);
+
+ state.ProcessDisplayItems(aChildren);
+
+ // Set CONTENT_COMPONENT_ALPHA if any of our children have it.
+ // This is suboptimal ... a child could have text that's over transparent
+ // pixels in its own layer, but over opaque parts of previous siblings.
+ pixBounds = state.ScaleToOutsidePixels(bounds, false);
+ appUnitsPerDevPixel = state.GetAppUnitsPerDevPixel();
+ state.Finish(&flags, pixBounds, aChildren);
+
+ // CONTENT_COMPONENT_ALPHA is propogated up to the nearest CONTENT_OPAQUE
+ // ancestor so that BasicLayerManager knows when to copy the background into
+ // pushed groups. Accelerated layers managers can't necessarily do this (only
+ // when the visible region is a simple rect), so we propogate
+ // CONTENT_COMPONENT_ALPHA_DESCENDANT all the way to the root.
+ if (flags & Layer::CONTENT_COMPONENT_ALPHA) {
+ flags |= Layer::CONTENT_COMPONENT_ALPHA_DESCENDANT;
+ }
+
+ // Make sure that rounding the visible region out didn't add any area
+ // we won't paint
+ if (aChildren->IsOpaque() && !aChildren->NeedsTransparentSurface()) {
+ bounds.ScaleRoundIn(scaleParameters.mXScale, scaleParameters.mYScale);
+ if (bounds.Contains(ToAppUnits(pixBounds, appUnitsPerDevPixel))) {
+ // Clear CONTENT_COMPONENT_ALPHA and add CONTENT_OPAQUE instead.
+ flags &= ~Layer::CONTENT_COMPONENT_ALPHA;
+ flags |= Layer::CONTENT_OPAQUE;
+ }
+ }
+ if (nsLayoutUtils::ShouldSnapToGrid(aContainerFrame)) {
+ flags |= Layer::CONTENT_SNAP_TO_GRID;
+ }
+ containerLayer->SetContentFlags(flags);
+ // If aContainerItem is non-null some BuildContainerLayer further up the
+ // call stack is responsible for setting containerLayer's visible region.
+ if (!aContainerItem) {
+ containerLayer->SetVisibleRegion(
+ LayerIntRegion::FromUnknownRegion(pixBounds));
+ }
+ if (aParameters.mLayerContentsVisibleRect) {
+ *aParameters.mLayerContentsVisibleRect =
+ pixBounds + scaleParameters.mOffset;
+ }
+
+ nsPresContext::ClearNotifySubDocInvalidationData(containerLayer);
+
+ return containerLayer.forget();
+}
+
+Layer* FrameLayerBuilder::GetLeafLayerFor(nsDisplayListBuilder* aBuilder,
+ nsDisplayItem* aItem) {
+ Layer* layer = GetOldLayerFor(aItem);
+ if (!layer) {
+ return nullptr;
+ }
+ if (layer->HasUserData(&gPaintedDisplayItemLayerUserData)) {
+ // This layer was created to render Thebes-rendered content for this
+ // display item. The display item should not use it for its own
+ // layer rendering.
+ return nullptr;
+ }
+ ResetLayerStateForRecycling(layer);
+ return layer;
+}
+
+/* static */
+void FrameLayerBuilder::InvalidateAllLayers(LayerManager* aManager) {
+ LayerManagerData* data = static_cast<LayerManagerData*>(
+ aManager->GetUserData(&gLayerManagerUserData));
+ if (data) {
+ data->mInvalidateAllLayers = true;
+ }
+}
+
+/* static */
+void FrameLayerBuilder::InvalidateAllLayersForFrame(nsIFrame* aFrame) {
+ const SmallPointerArray<DisplayItemData>& array = aFrame->DisplayItemData();
+
+ for (uint32_t i = 0; i < array.Length(); i++) {
+ DisplayItemData::AssertDisplayItemData(array.ElementAt(i))
+ ->mParent->mInvalidateAllLayers = true;
+ }
+}
+
+/* static */
+Layer* FrameLayerBuilder::GetDedicatedLayer(nsIFrame* aFrame,
+ DisplayItemType aDisplayItemKey) {
+ // TODO: This isn't completely correct, since a frame could exist as a layer
+ // in the normal widget manager, and as a different layer (or no layer)
+ // in the secondary manager
+
+ const SmallPointerArray<DisplayItemData>& array = aFrame->DisplayItemData();
+ ;
+
+ for (uint32_t i = 0; i < array.Length(); i++) {
+ DisplayItemData* element =
+ DisplayItemData::AssertDisplayItemData(array.ElementAt(i));
+ if (!element->mParent->mLayerManager->IsWidgetLayerManager()) {
+ continue;
+ }
+ if (GetDisplayItemTypeFromKey(element->mDisplayItemKey) ==
+ aDisplayItemKey) {
+ if (element->mOptLayer) {
+ return element->mOptLayer;
+ }
+
+ Layer* layer = element->mLayer;
+ if (!layer->HasUserData(&gColorLayerUserData) &&
+ !layer->HasUserData(&gImageLayerUserData) &&
+ !layer->HasUserData(&gPaintedDisplayItemLayerUserData)) {
+ return layer;
+ }
+ }
+ }
+ return nullptr;
+}
+
+/* static */
+void FrameLayerBuilder::EnumerateGenerationForDedicatedLayers(
+ const nsIFrame* aFrame, AnimationGenerationCallback aCallback) {
+ std::bitset<static_cast<uint32_t>(DisplayItemType::TYPE_MAX)> notFoundTypes;
+ for (auto displayItemType : LayerAnimationInfo::sDisplayItemTypes) {
+ notFoundTypes.set(static_cast<uint32_t>(displayItemType));
+ }
+
+ for (auto displayItemType : LayerAnimationInfo::sDisplayItemTypes) {
+ // For transform animations, the animation is on the primary frame but
+ // |aFrame| is the style frame.
+ const nsIFrame* frameToQuery =
+ displayItemType == DisplayItemType::TYPE_TRANSFORM
+ ? nsLayoutUtils::GetPrimaryFrameFromStyleFrame(aFrame)
+ : aFrame;
+ const nsIFrame::DisplayItemDataArray& displayItemDataArray =
+ frameToQuery->DisplayItemData();
+
+ for (uint32_t i = 0; i < displayItemDataArray.Length(); i++) {
+ DisplayItemData* element = DisplayItemData::AssertDisplayItemData(
+ displayItemDataArray.ElementAt(i));
+ if (!element->mParent->mLayerManager->IsWidgetLayerManager()) {
+ continue;
+ }
+
+ if (GetDisplayItemTypeFromKey(element->mDisplayItemKey) !=
+ displayItemType) {
+ continue;
+ }
+
+ notFoundTypes.reset(static_cast<uint32_t>(displayItemType));
+
+ Maybe<uint64_t> generation;
+ if (element->mOptLayer) {
+ generation = element->mOptLayer->GetAnimationGeneration();
+ } else if (!element->mLayer->HasUserData(&gColorLayerUserData) &&
+ !element->mLayer->HasUserData(&gImageLayerUserData) &&
+ !element->mLayer->HasUserData(
+ &gPaintedDisplayItemLayerUserData)) {
+ generation = element->mLayer->GetAnimationGeneration();
+ }
+
+ if (!aCallback(generation, displayItemType)) {
+ return;
+ }
+
+ break;
+ }
+ }
+
+ // Bail out if we have already enumerated all possible layers for the given
+ // display item types.
+ if (notFoundTypes.none()) {
+ return;
+ }
+
+ // If there are any display item types that the nsIFrame doesn't have, we need
+ // to call the callback function for them respectively.
+ for (auto displayItemType : LayerAnimationInfo::sDisplayItemTypes) {
+ if (notFoundTypes[static_cast<uint32_t>(displayItemType)] &&
+ !aCallback(Nothing(), displayItemType)) {
+ return;
+ }
+ }
+}
+
+gfxSize FrameLayerBuilder::GetPaintedLayerScaleForFrame(nsIFrame* aFrame) {
+ MOZ_ASSERT(aFrame, "need a frame");
+
+ nsPresContext* presCtx = aFrame->PresContext()->GetRootPresContext();
+
+ if (!presCtx) {
+ presCtx = aFrame->PresContext();
+ MOZ_ASSERT(presCtx);
+ }
+
+ nsIFrame* root = presCtx->PresShell()->GetRootFrame();
+
+ MOZ_ASSERT(root);
+
+ float resolution = presCtx->PresShell()->GetResolution();
+
+ Matrix4x4Flagged transform = Matrix4x4::Scaling(resolution, resolution, 1.0);
+ if (aFrame != root) {
+ // aTransform is applied first, then the scale is applied to the result
+ transform = nsLayoutUtils::GetTransformToAncestor(RelativeTo{aFrame},
+ RelativeTo{root}) *
+ transform;
+ }
+
+ Matrix transform2d;
+ if (transform.CanDraw2D(&transform2d)) {
+ return ThebesMatrix(transform2d).ScaleFactors();
+ }
+
+ return gfxSize(1.0, 1.0);
+}
+
+#ifdef MOZ_DUMP_PAINTING
+static void DebugPaintItem(DrawTarget& aDrawTarget, nsPresContext* aPresContext,
+ nsPaintedDisplayItem* aItem,
+ nsDisplayListBuilder* aBuilder) {
+ bool snap;
+ Rect bounds = NSRectToRect(aItem->GetBounds(aBuilder, &snap),
+ aPresContext->AppUnitsPerDevPixel());
+
+ const IntSize size = IntSize::Truncate(bounds.width, bounds.height);
+ if (size.IsEmpty()) {
+ return;
+ }
+
+ RefPtr<DrawTarget> tempDT =
+ aDrawTarget.CreateSimilarDrawTarget(size, SurfaceFormat::B8G8R8A8);
+ RefPtr<gfxContext> context = gfxContext::CreateOrNull(tempDT);
+ if (!context) {
+ // Leave this as crash, it's in the debugging code, we want to know
+ gfxDevCrash(LogReason::InvalidContext)
+ << "DebugPaintItem context problem " << gfx::hexa(tempDT);
+ return;
+ }
+ context->SetMatrix(Matrix::Translation(-bounds.x, -bounds.y));
+
+ aItem->Paint(aBuilder, context);
+ RefPtr<SourceSurface> surface = tempDT->Snapshot();
+ DumpPaintedImage(aItem, surface);
+
+ aDrawTarget.DrawSurface(surface, bounds, Rect(Point(0, 0), bounds.Size()));
+
+ aItem->SetPainted();
+}
+#endif
+
+/* static */
+void FrameLayerBuilder::RecomputeVisibilityForItems(
+ std::vector<AssignedDisplayItem>& aItems, nsDisplayListBuilder* aBuilder,
+ const nsIntRegion& aRegionToDraw, nsRect& aPreviousRectToDraw,
+ const nsIntPoint& aOffset, int32_t aAppUnitsPerDevPixel, float aXScale,
+ float aYScale) {
+ // Update visible regions. We perform visibility analysis to take account
+ // of occlusion culling.
+ nsRegion visible = aRegionToDraw.ToAppUnits(aAppUnitsPerDevPixel);
+ visible.MoveBy(NSIntPixelsToAppUnits(aOffset.x, aAppUnitsPerDevPixel),
+ NSIntPixelsToAppUnits(aOffset.y, aAppUnitsPerDevPixel));
+ visible.ScaleInverseRoundOut(aXScale, aYScale);
+
+ // We're going to read from previousRectToDraw for every iteration, let's do
+ // that on the stack, and just update the heap allocated one now. By the end
+ // of this function {visible} will have been modified by occlusion culling.
+ nsRect previousRectToDraw = aPreviousRectToDraw;
+ aPreviousRectToDraw = visible.GetBounds();
+
+ for (uint32_t i = aItems.size(); i > 0; --i) {
+ AssignedDisplayItem* cdi = &aItems[i - 1];
+ if (!cdi->mItem) {
+ continue;
+ }
+
+ if (cdi->mHasPaintRect &&
+ !cdi->mContentRect.Intersects(visible.GetBounds()) &&
+ !cdi->mContentRect.Intersects(previousRectToDraw)) {
+ continue;
+ }
+
+ if (IsEffectEndMarker(cdi->mType) || cdi->HasOpacity() ||
+ cdi->HasTransform()) {
+ // The visibility calculations are skipped when the item is an effect end
+ // marker, or when the display item is within a flattened effect group.
+ // This is because RecomputeVisibility has already been called for the
+ // group item, and all the children.
+ continue;
+ }
+
+ const DisplayItemClip& clip = cdi->mItem->GetClip();
+
+ NS_ASSERTION(AppUnitsPerDevPixel(cdi->mItem) == aAppUnitsPerDevPixel,
+ "a painted layer should contain items only at the same zoom");
+
+ MOZ_ASSERT(clip.HasClip() || clip.GetRoundedRectCount() == 0,
+ "If we have rounded rects, we must have a clip rect");
+
+ if (!clip.IsRectAffectedByClip(visible.GetBounds())) {
+ cdi->mItem->RecomputeVisibility(aBuilder, &visible);
+ continue;
+ }
+
+ // Do a little dance to account for the fact that we're clipping
+ // to cdi->mClipRect
+ nsRegion clipped;
+ clipped.And(visible, clip.NonRoundedIntersection());
+ nsRegion finalClipped = clipped;
+ cdi->mItem->RecomputeVisibility(aBuilder, &finalClipped);
+ // If we have rounded clip rects, don't subtract from the visible
+ // region since we aren't displaying everything inside the rect.
+ if (clip.GetRoundedRectCount() == 0) {
+ nsRegion removed;
+ removed.Sub(clipped, finalClipped);
+ nsRegion newVisible;
+ newVisible.Sub(visible, removed);
+ // Don't let the visible region get too complex.
+ if (newVisible.GetNumRects() <= 15) {
+ visible = std::move(newVisible);
+ }
+ }
+ }
+}
+
+/**
+ * Tracks and caches the item clip.
+ */
+struct ItemClipTracker {
+ explicit ItemClipTracker(gfxContext* aContext,
+ const int32_t aAppUnitsPerDevPixel)
+ : mContext(aContext),
+ mHasClip(false),
+ mAppUnitsPerDevPixel(aAppUnitsPerDevPixel) {}
+
+ /**
+ * Returns true if a clip is set.
+ */
+ bool HasClip() const { return mHasClip; }
+
+ /**
+ * Returns true if the given |aClip| is set.
+ */
+ bool HasClip(const DisplayItemClip* aClip) const {
+ MOZ_ASSERT(aClip && aClip->HasClip());
+ return mHasClip && mCurrentClip == *aClip;
+ }
+
+ /**
+ * Removes the clip, if there is one.
+ */
+ void Restore() {
+ if (mCurrentClip.HasClip()) {
+ mCurrentClip = DisplayItemClip::NoClip();
+ }
+
+ if (!HasClip()) {
+ return;
+ }
+
+ mContext->Restore();
+ mHasClip = false;
+ };
+
+ /**
+ * Sets the clip to |aClip|, if it is not set already.
+ */
+ void ChangeClipIfNeeded(const DisplayItemClip* aClip) {
+ MOZ_ASSERT(aClip && aClip->HasClip());
+
+ if (HasClip(aClip)) {
+ // Reuse the old clip.
+ return;
+ }
+
+ // Remove the previous clip and save the current state.
+ Restore();
+ mContext->Save();
+
+ // Apply the new clip.
+ mHasClip = true;
+ mCurrentClip = *aClip;
+ mCurrentClip.ApplyTo(mContext, mAppUnitsPerDevPixel);
+ mContext->NewPath();
+ }
+
+ private:
+ gfxContext* mContext;
+ bool mHasClip;
+ const int32_t mAppUnitsPerDevPixel;
+
+ DisplayItemClip mCurrentClip;
+};
+
+/**
+ * Tracks clips managed by |PushClip()| and |PopClip()|.
+ * If allowed by the caller, the top clip may be reused when a new clip that
+ * matches the previous one is pushed to the stack.
+ */
+struct ClipStack {
+ explicit ClipStack(gfxContext* aContext, const int32_t aAppUnitsPerDevPixel)
+ : mContext(aContext),
+ mAppUnitsPerDevPixel(aAppUnitsPerDevPixel),
+ mDeferredPopClip(false) {}
+
+ ~ClipStack() {
+ MOZ_ASSERT(!mDeferredPopClip);
+ MOZ_ASSERT(!HasClips());
+ }
+
+ /**
+ * Returns true if there are clips set.
+ */
+ bool HasClips() const { return mClips.Length() > 0; }
+
+ /**
+ * Returns the clip at the top of the stack.
+ */
+ const DisplayItemClip& TopClip() const {
+ MOZ_ASSERT(HasClips());
+ return mClips.LastElement();
+ }
+
+ /**
+ * Returns true if the top clip matches the given |aClip|.
+ */
+ bool TopClipMatches(const DisplayItemClip& aClip) {
+ return HasClips() && TopClip() == aClip;
+ }
+
+ /**
+ * Pops the current top clip. If |aDeferPopClip| is true, the top clip will
+ * not be popped before the next call to |PopClip(false)|.
+ * This allows the previously set clip to be reused during the next
+ * |PushClip()| call, if the new clip is identical with the top clip.
+ */
+ void PopClip(bool aDeferPopClip) {
+ MOZ_ASSERT(HasClips());
+
+ if (aDeferPopClip) {
+ // Do not allow reusing clip with nested effects.
+ MOZ_ASSERT(!mDeferredPopClip);
+ mDeferredPopClip = true;
+ return;
+ }
+
+ if (TopClip().HasClip()) {
+ mContext->Restore();
+ }
+
+ mClips.RemoveLastElement();
+ mDeferredPopClip = false;
+ }
+
+ /**
+ * Pops the clip, if a call to |PopClip()| has been deferred.
+ */
+ void PopDeferredClip() {
+ if (mDeferredPopClip) {
+ PopClip(false);
+ }
+ }
+
+ /**
+ * Pushes the given |aClip| to the stack.
+ */
+ void PushClip(const DisplayItemClip& aClip) {
+ if (mDeferredPopClip && TopClipMatches(aClip)) {
+ // Reuse this clip. Defer the decision to reuse it again until the next
+ // call to PopClip().
+ mDeferredPopClip = false;
+ return;
+ }
+
+ PopDeferredClip();
+
+ mClips.AppendElement(aClip);
+
+ // Save the current state and apply new clip, if needed.
+ if (aClip.HasClip()) {
+ mContext->Save();
+ aClip.ApplyTo(mContext, mAppUnitsPerDevPixel);
+ mContext->NewPath();
+ }
+ }
+
+ private:
+ gfxContext* mContext;
+ const int32_t mAppUnitsPerDevPixel;
+ AutoTArray<DisplayItemClip, 2> mClips;
+ bool mDeferredPopClip;
+};
+
+/**
+ * Returns a clip for the given |aItem|. If the clip can be simplified to not
+ * include rounded rects, |aOutClip| is used to store the simplified clip.
+ */
+static const DisplayItemClip* GetItemClip(const nsDisplayItem* aItem,
+ DisplayItemClip& aOutClip) {
+ const DisplayItemClip& clip = aItem->GetClip();
+
+ if (!clip.HasClip()) {
+ return nullptr;
+ }
+
+ if (clip.GetRoundedRectCount() > 0 &&
+ !clip.IsRectClippedByRoundedCorner(aItem->GetPaintRect())) {
+ aOutClip.SetTo(clip.GetClipRect());
+ return &aOutClip;
+ }
+
+ return &clip;
+}
+
+/**
+ * Pushes a new opacity group for |aContext| based on |aItem|.
+ */
+static void PushOpacity(gfxContext* aContext, AssignedDisplayItem& aItem) {
+ MOZ_ASSERT(aItem.mType == DisplayItemEntryType::PushOpacity ||
+ aItem.mType == DisplayItemEntryType::PushOpacityWithBg);
+ MOZ_ASSERT(aItem.mItem->GetType() == DisplayItemType::TYPE_OPACITY);
+ nsDisplayOpacity* item = static_cast<nsDisplayOpacity*>(aItem.mItem);
+
+ const float opacity = item->GetOpacity();
+ if (aItem.mType == DisplayItemEntryType::PushOpacityWithBg) {
+ aContext->PushGroupAndCopyBackground(gfxContentType::COLOR_ALPHA, opacity);
+ } else {
+ aContext->PushGroupForBlendBack(gfxContentType::COLOR_ALPHA, opacity);
+ }
+}
+
+/**
+ * Pushes the transformation matrix of |aItem| into |aMatrixStack| and sets the
+ * accumulated transform as the current transformation matrix for |aContext|.
+ */
+static void PushTransform(gfxContext* aContext, AssignedDisplayItem& aItem,
+ nsDisplayListBuilder* aBuilder,
+ MatrixStack4x4& aMatrixStack,
+ const Matrix4x4Flagged& aBaseMatrix) {
+ MOZ_ASSERT(aItem.mType == DisplayItemEntryType::PushTransform);
+ MOZ_ASSERT(aItem.mItem->GetType() == DisplayItemType::TYPE_TRANSFORM);
+
+ nsDisplayTransform* item = static_cast<nsDisplayTransform*>(aItem.mItem);
+ if (item->ShouldSkipTransform(aBuilder)) {
+ aMatrixStack.Push(Matrix4x4Flagged());
+ } else {
+ aMatrixStack.Push(item->GetTransformForRendering());
+ }
+
+ gfx::Matrix4x4Flagged matrix = aMatrixStack.CurrentMatrix() * aBaseMatrix;
+ gfx::Matrix matrix2d;
+ DebugOnly<bool> ok = matrix.CanDraw2D(&matrix2d);
+ MOZ_ASSERT(ok);
+
+ aContext->SetMatrix(matrix2d);
+}
+
+static void UpdateEffectTracking(int& aOpacityLevel, int& aTransformLevel,
+ const DisplayItemEntryType aType) {
+ switch (aType) {
+ case DisplayItemEntryType::PushOpacity:
+ case DisplayItemEntryType::PushOpacityWithBg:
+ aOpacityLevel++;
+ break;
+ case DisplayItemEntryType::PopOpacity:
+ aOpacityLevel--;
+ break;
+ case DisplayItemEntryType::PushTransform:
+ aTransformLevel++;
+ break;
+ case DisplayItemEntryType::PopTransform:
+ aTransformLevel--;
+ break;
+ default:
+ break;
+ }
+
+ MOZ_ASSERT(aOpacityLevel >= 0 && aTransformLevel >= 0);
+}
+
+void FrameLayerBuilder::PaintItems(std::vector<AssignedDisplayItem>& aItems,
+ const nsIntRect& aRect, gfxContext* aContext,
+ nsDisplayListBuilder* aBuilder,
+ nsPresContext* aPresContext,
+ const nsIntPoint& aOffset, float aXScale,
+ float aYScale) {
+ DrawTarget& aDrawTarget = *aContext->GetDrawTarget();
+
+ int32_t appUnitsPerDevPixel = aPresContext->AppUnitsPerDevPixel();
+ nsRect boundRect = ToAppUnits(aRect, appUnitsPerDevPixel);
+ boundRect.MoveBy(NSIntPixelsToAppUnits(aOffset.x, appUnitsPerDevPixel),
+ NSIntPixelsToAppUnits(aOffset.y, appUnitsPerDevPixel));
+ boundRect.ScaleInverseRoundOut(aXScale, aYScale);
+
+ if (boundRect.IsEmpty()) {
+ // Hack! This can happen if the conversion of |aRect| to scaled and offset
+ // app units overflowed. Ideally the conversion would detect this and handle
+ // such situations gracefully. For now, do nothing.
+ return;
+ }
+
+#ifdef DEBUG
+ // Tracks effect nesting level. These are used to track that every effect
+ // start marker has a corresponding end marker.
+ int opacityLevel = 0;
+ int transformLevel = 0;
+#endif
+
+ // Tracks effect nesting level for skipping items between effect markers,
+ // when the effect display item does not intersect with the invalidated area.
+ int emptyEffectLevel = 0;
+
+ // Stores a simplified version of the item clip, if needed.
+ DisplayItemClip temporaryClip;
+
+ // Two types of clips are used during PaintItems(): clips for items and clips
+ // for effects. Item clips are always the most recent clip set, and they are
+ // never nested. The previous item clip is reused, if the next item has the
+ // same clip. Item clips are removed when an effect starts or ends.
+ ItemClipTracker itemClipTracker(aContext, appUnitsPerDevPixel);
+
+ // Since effects can be nested, the effect clips need to be nested as well.
+ // They are pushed for effect start marker, and popped for effect end marker.
+ // Effect clips are tracked by |effectClipStack|. If there are consecutive
+ // effects with the same clip, |effectClipStack| defers popping the clip for
+ // the first end marker, and tries to reuse the previously set clip, when
+ // processing the start marker for the next effect.
+ ClipStack effectClipStack(aContext, appUnitsPerDevPixel);
+
+ MatrixStack4x4 matrixStack;
+ const Matrix4x4Flagged base = Matrix4x4::From2D(aContext->CurrentMatrix());
+
+ for (uint32_t i = 0; i < aItems.size(); ++i) {
+ AssignedDisplayItem& cdi = aItems[i];
+ nsDisplayItem* item = cdi.mItem;
+
+ const auto NextItemStartsEffect = [&]() {
+ const uint32_t next = i + 1;
+ return next < aItems.size() && IsEffectStartMarker(aItems[next].mType);
+ };
+
+ if (!item) {
+ MOZ_ASSERT(cdi.mType == DisplayItemEntryType::Item);
+ continue;
+ }
+
+ nsRect visibleRect = item->GetPaintRect();
+
+ if (matrixStack.HasTransform()) {
+ MOZ_ASSERT(transformLevel > 0);
+
+ if (IsEffectEndMarker(cdi.mType)) {
+ // Always process the effect end markers.
+ visibleRect = boundRect;
+ } else {
+ const Matrix4x4Flagged& matrix = matrixStack.CurrentMatrix();
+ visibleRect = nsLayoutUtils::MatrixTransformRect(visibleRect, matrix,
+ appUnitsPerDevPixel);
+ }
+ }
+
+ const nsRect paintRect = visibleRect.Intersect(boundRect);
+
+ if (paintRect.IsEmpty() || emptyEffectLevel > 0) {
+ // In order for this branch to be hit, either this item has an empty paint
+ // rect and nothing would be drawn, or an effect marker before this
+ // item had an empty paint rect. In the latter case, the items are skipped
+ // until effect POP markers bring |emptyEffectLevel| back to 0.
+ UpdateEffectTracking(emptyEffectLevel, emptyEffectLevel, cdi.mType);
+
+ // Sometimes the item that was going to reuse the previous clip is culled.
+ // Since |PushClip()| is never called for culled items, pop the clip now.
+ effectClipStack.PopDeferredClip();
+ continue;
+ }
+
+#ifdef MOZ_DUMP_PAINTING
+ AUTO_PROFILER_LABEL_DYNAMIC_CSTR_NONSENSITIVE(
+ "FrameLayerBuilder::PaintItems", GRAPHICS_Rasterization, item->Name());
+#else
+ AUTO_PROFILER_LABEL("FrameLayerBuilder::PaintItems",
+ GRAPHICS_Rasterization);
+#endif
+
+ MOZ_ASSERT((opacityLevel == 0 && !cdi.HasOpacity()) ||
+ (opacityLevel > 0 && cdi.HasOpacity()) ||
+ (transformLevel == 0 && !cdi.HasTransform()) ||
+ (transformLevel > 0 && cdi.HasTransform()));
+
+ if (cdi.mType != DisplayItemEntryType::Item) {
+ // If we are processing an effect marker, remove the current item clip, if
+ // there is one.
+ itemClipTracker.Restore();
+ }
+
+ if (cdi.mType == DisplayItemEntryType::PushOpacity ||
+ cdi.mType == DisplayItemEntryType::PushOpacityWithBg) {
+ // To avoid pushing large temporary surfaces, it is important to clip
+ // opacity group with both the paint rect and the actual opacity clip.
+ DisplayItemClip effectClip;
+ effectClip.SetTo(item->GetPaintRect());
+ effectClip.IntersectWith(item->GetClip());
+ effectClipStack.PushClip(effectClip);
+ PushOpacity(aContext, cdi);
+ }
+
+ if (cdi.mType == DisplayItemEntryType::PopOpacity) {
+ MOZ_ASSERT(opacityLevel > 0);
+ aContext->PopGroupAndBlend();
+ }
+
+ if (cdi.mType == DisplayItemEntryType::PushTransform) {
+ effectClipStack.PushClip(item->GetClip());
+ aContext->Save();
+ PushTransform(aContext, cdi, aBuilder, matrixStack, base);
+ }
+
+ if (cdi.mType == DisplayItemEntryType::PopTransform) {
+ MOZ_ASSERT(transformLevel > 0);
+ matrixStack.Pop();
+ aContext->Restore();
+ }
+
+ if (IsEffectEndMarker(cdi.mType)) {
+ // Pop the clip for the effect.
+ MOZ_ASSERT(effectClipStack.HasClips());
+
+ // If the next item starts an effect, defer popping the current clip, and
+ // try to reuse it during the next call to |PushClip()|. Trying to reuse
+ // clips between nested effects would be difficult, for example due to
+ // possibly different coordinate system, so this optimization is limited
+ // to consecutive effects.
+ effectClipStack.PopClip(NextItemStartsEffect());
+ }
+
+ if (cdi.mType != DisplayItemEntryType::Item) {
+#ifdef DEBUG
+ UpdateEffectTracking(opacityLevel, transformLevel, cdi.mType);
+#endif
+ // Nothing more to do with effect markers.
+ continue;
+ }
+
+ const bool paintAsLayer = cdi.mInactiveLayerData.get();
+ nsPaintedDisplayItem* paintedItem = item->AsPaintedDisplayItem();
+ MOZ_ASSERT(paintAsLayer || paintedItem,
+ "The display item does not support painting");
+
+ const DisplayItemClip* itemClip = GetItemClip(item, temporaryClip);
+ bool itemPaintsOwnClip = false;
+
+ if (itemClip && !itemClipTracker.HasClip(itemClip)) {
+ // The clip has changed. Remove the previous clip.
+ itemClipTracker.Restore();
+
+ // Check if the item supports painting with clip.
+ itemPaintsOwnClip =
+ paintAsLayer ? false : paintedItem->CanPaintWithClip(*itemClip);
+
+ if (!itemPaintsOwnClip) {
+ // Item does not support painting with clip, set the clip.
+ itemClipTracker.ChangeClipIfNeeded(itemClip);
+ }
+ }
+
+ if (!itemClip) {
+ // Item does not need clipping, remove the clip if there is one.
+ itemClipTracker.Restore();
+ }
+
+ if (paintAsLayer) {
+ bool saved = aDrawTarget.GetPermitSubpixelAA();
+ PaintInactiveLayer(aBuilder, cdi.mInactiveLayerData->mLayerManager, item,
+ aContext, aContext);
+ aDrawTarget.SetPermitSubpixelAA(saved);
+ continue;
+ }
+
+ nsIFrame* frame = item->Frame();
+ if (aBuilder->IsPaintingToWindow()) {
+ frame->AddStateBits(NS_FRAME_PAINTED_THEBES);
+ }
+
+#ifdef MOZ_DUMP_PAINTING
+ if (gfxEnv::DumpPaintItems()) {
+ DebugPaintItem(aDrawTarget, aPresContext, paintedItem, aBuilder);
+ continue;
+ }
+#endif
+
+ if (itemPaintsOwnClip) {
+ MOZ_ASSERT(itemClip);
+ paintedItem->PaintWithClip(aBuilder, aContext, *itemClip);
+ } else {
+ paintedItem->Paint(aBuilder, aContext);
+ }
+ }
+
+ itemClipTracker.Restore();
+
+ MOZ_ASSERT(opacityLevel == 0);
+ MOZ_ASSERT(transformLevel == 0);
+ MOZ_ASSERT(emptyEffectLevel == 0);
+}
+
+/**
+ * Returns true if it is preferred to draw the list of display
+ * items separately for each rect in the visible region rather
+ * than clipping to a complex region.
+ */
+static bool ShouldDrawRectsSeparately(DrawTarget* aDrawTarget,
+ DrawRegionClip aClip) {
+ if (!StaticPrefs::layout_paint_rects_separately_AtStartup() ||
+ aClip == DrawRegionClip::NONE) {
+ return false;
+ }
+
+ return !aDrawTarget->SupportsRegionClipping();
+}
+
+static void DrawForcedBackgroundColor(DrawTarget& aDrawTarget,
+ const IntRect& aBounds,
+ nscolor aBackgroundColor) {
+ if (NS_GET_A(aBackgroundColor) > 0) {
+ ColorPattern color(ToDeviceColor(aBackgroundColor));
+ aDrawTarget.FillRect(Rect(aBounds), color);
+ }
+}
+
+/*
+ * A note on residual transforms:
+ *
+ * In a transformed subtree we sometimes apply the PaintedLayer's
+ * "residual transform" when drawing content into the PaintedLayer.
+ * This is a translation by components in the range [-0.5,0.5) provided
+ * by the layer system; applying the residual transform followed by the
+ * transforms used by layer compositing ensures that the subpixel alignment
+ * of the content of the PaintedLayer exactly matches what it would be if
+ * we used cairo/Thebes to draw directly to the screen without going through
+ * retained layer buffers.
+ *
+ * The visible and valid regions of the PaintedLayer are computed without
+ * knowing the residual transform (because we don't know what the residual
+ * transform is going to be until we've built the layer tree!). So we have to
+ * consider whether content painted in the range [x, xmost) might be painted
+ * outside the visible region we computed for that content. The visible region
+ * would be [floor(x), ceil(xmost)). The content would be rendered at
+ * [x + r, xmost + r), where -0.5 <= r < 0.5. So some half-rendered pixels could
+ * indeed fall outside the computed visible region, which is not a big deal;
+ * similar issues already arise when we snap cliprects to nearest pixels.
+ * Note that if the rendering of the content is snapped to nearest pixels ---
+ * which it often is --- then the content is actually rendered at
+ * [snap(x + r), snap(xmost + r)). It turns out that floor(x) <= snap(x + r)
+ * and ceil(xmost) >= snap(xmost + r), so the rendering of snapped content
+ * always falls within the visible region we computed.
+ */
+
+/* static */
+void FrameLayerBuilder::DrawPaintedLayer(PaintedLayer* aLayer,
+ gfxContext* aContext,
+ const nsIntRegion& aRegionToDraw,
+ const nsIntRegion& aDirtyRegion,
+ DrawRegionClip aClip,
+ const nsIntRegion& aRegionToInvalidate,
+ void* aCallbackData) {
+ DrawTarget& aDrawTarget = *aContext->GetDrawTarget();
+
+ AUTO_PROFILER_LABEL("FrameLayerBuilder::DrawPaintedLayer",
+ GRAPHICS_Rasterization);
+
+ nsDisplayListBuilder* builder =
+ static_cast<nsDisplayListBuilder*>(aCallbackData);
+
+ FrameLayerBuilder* layerBuilder = aLayer->Manager()->GetLayerBuilder();
+ NS_ASSERTION(layerBuilder, "Unexpectedly null layer builder!");
+
+ auto* userData = GetPaintedDisplayItemLayerUserData(aLayer);
+ NS_ASSERTION(userData, "where did our user data go?");
+ if (!userData->mContainerLayerFrame) {
+ return;
+ }
+
+ bool shouldDrawRectsSeparately =
+ ShouldDrawRectsSeparately(&aDrawTarget, aClip);
+
+ if (!shouldDrawRectsSeparately) {
+ if (aClip == DrawRegionClip::DRAW) {
+ gfxUtils::ClipToRegion(aContext, aRegionToDraw);
+ }
+
+ DrawForcedBackgroundColor(aDrawTarget, aRegionToDraw.GetBounds(),
+ userData->mForcedBackgroundColor);
+ }
+
+ // make the origin of the context coincide with the origin of the
+ // PaintedLayer
+ gfxContextMatrixAutoSaveRestore saveMatrix(aContext);
+ nsIntPoint offset = GetTranslationForPaintedLayer(aLayer);
+ nsPresContext* presContext = userData->mContainerLayerFrame->PresContext();
+
+ if (!userData->mVisibilityComputedRegion.Contains(aDirtyRegion) &&
+ !layerBuilder->GetContainingPaintedLayerData()) {
+ // Recompute visibility of items in our PaintedLayer, if required. Note
+ // that this recomputes visibility for all descendants of our display
+ // items too, so there's no need to do this for the items in inactive
+ // PaintedLayers. If aDirtyRegion has not changed since the previous call
+ // then we can skip this.
+ int32_t appUnitsPerDevPixel = presContext->AppUnitsPerDevPixel();
+ RecomputeVisibilityForItems(userData->mItems, builder, aDirtyRegion,
+ userData->mPreviousRecomputeVisibilityRect,
+ offset, appUnitsPerDevPixel, userData->mXScale,
+ userData->mYScale);
+ userData->mVisibilityComputedRegion = aDirtyRegion;
+ }
+
+ if (shouldDrawRectsSeparately) {
+ for (auto iter = aRegionToDraw.RectIter(); !iter.Done(); iter.Next()) {
+ const nsIntRect& iterRect = iter.Get();
+ gfxContextAutoSaveRestore save(aContext);
+ aContext->NewPath();
+ aContext->Rectangle(ThebesRect(iterRect));
+ aContext->Clip();
+
+ DrawForcedBackgroundColor(aDrawTarget, iterRect,
+ userData->mForcedBackgroundColor);
+
+ // Apply the residual transform if it has been enabled, to ensure that
+ // snapping when we draw into aContext exactly matches the ideal
+ // transform. See above for why this is OK.
+ aContext->SetMatrixDouble(
+ aContext->CurrentMatrixDouble()
+ .PreTranslate(aLayer->GetResidualTranslation() -
+ gfxPoint(offset.x, offset.y))
+ .PreScale(userData->mXScale, userData->mYScale));
+
+ layerBuilder->PaintItems(userData->mItems, iterRect, aContext, builder,
+ presContext, offset, userData->mXScale,
+ userData->mYScale);
+ if (StaticPrefs::gfx_logging_painted_pixel_count_enabled()) {
+ aLayer->Manager()->AddPaintedPixelCount(iterRect.Area());
+ }
+ }
+ } else {
+ // Apply the residual transform if it has been enabled, to ensure that
+ // snapping when we draw into aContext exactly matches the ideal transform.
+ // See above for why this is OK.
+ aContext->SetMatrixDouble(
+ aContext->CurrentMatrixDouble()
+ .PreTranslate(aLayer->GetResidualTranslation() -
+ gfxPoint(offset.x, offset.y))
+ .PreScale(userData->mXScale, userData->mYScale));
+
+ layerBuilder->PaintItems(userData->mItems, aRegionToDraw.GetBounds(),
+ aContext, builder, presContext, offset,
+ userData->mXScale, userData->mYScale);
+ if (StaticPrefs::gfx_logging_painted_pixel_count_enabled()) {
+ aLayer->Manager()->AddPaintedPixelCount(aRegionToDraw.GetBounds().Area());
+ }
+ }
+
+ bool isActiveLayerManager = !aLayer->Manager()->IsInactiveLayerManager();
+
+ if (presContext->GetPaintFlashing() && isActiveLayerManager) {
+ gfxContextAutoSaveRestore save(aContext);
+ if (shouldDrawRectsSeparately) {
+ if (aClip == DrawRegionClip::DRAW) {
+ gfxUtils::ClipToRegion(aContext, aRegionToDraw);
+ }
+ }
+ FlashPaint(aContext);
+ }
+
+ if (presContext->GetDocShell() && isActiveLayerManager) {
+ nsDocShell* docShell = static_cast<nsDocShell*>(presContext->GetDocShell());
+ RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get();
+
+ if (timelines && timelines->HasConsumer(docShell)) {
+ timelines->AddMarkerForDocShell(
+ docShell, MakeUnique<LayerTimelineMarker>(aRegionToDraw));
+ }
+ }
+
+ if (!aRegionToInvalidate.IsEmpty()) {
+ aLayer->AddInvalidRect(aRegionToInvalidate.GetBounds());
+ }
+}
+
+/* static */
+void FrameLayerBuilder::DumpRetainedLayerTree(LayerManager* aManager,
+ std::stringstream& aStream,
+ bool aDumpHtml) {
+ aManager->Dump(aStream, "", aDumpHtml);
+}
+
+nsDisplayItemGeometry* FrameLayerBuilder::GetMostRecentGeometry(
+ nsDisplayItem* aItem) {
+ typedef SmallPointerArray<DisplayItemData> DataArray;
+
+ // Retrieve the array of DisplayItemData associated with our frame.
+ DataArray& dataArray = aItem->Frame()->DisplayItemData();
+
+ // Find our display item data, if it exists, and return its geometry.
+ // We first check for ones with an inactive manager, since items that
+ // create inactive layers will create two DisplayItemData entries,
+ // and we want the outer one.
+ DisplayItemData* firstMatching = nullptr;
+ uint32_t itemPerFrameKey = aItem->GetPerFrameKey();
+ for (DisplayItemData* data : dataArray) {
+ DisplayItemData::AssertDisplayItemData(data);
+ if (data->GetDisplayItemKey() == itemPerFrameKey) {
+ if (data->InactiveManager()) {
+ return data->GetGeometry();
+ }
+ if (!firstMatching) {
+ firstMatching = data;
+ }
+ }
+ }
+ if (firstMatching) {
+ return firstMatching->GetGeometry();
+ }
+ if (RefPtr<WebRenderFallbackData> data =
+ GetWebRenderUserData<WebRenderFallbackData>(aItem->Frame(),
+ itemPerFrameKey)) {
+ return data->GetGeometry();
+ }
+
+ return nullptr;
+}
+
+static gfx::Rect CalculateBounds(
+ const nsTArray<DisplayItemClip::RoundedRect>& aRects,
+ int32_t aAppUnitsPerDevPixel) {
+ nsRect bounds = aRects[0].mRect;
+ for (uint32_t i = 1; i < aRects.Length(); ++i) {
+ bounds.UnionRect(bounds, aRects[i].mRect);
+ }
+
+ return gfx::Rect(bounds.ToNearestPixels(aAppUnitsPerDevPixel));
+}
+
+void ContainerState::SetupMaskLayer(Layer* aLayer,
+ const DisplayItemClip& aClip) {
+ // don't build an unnecessary mask
+ if (aClip.GetRoundedRectCount() == 0) {
+ return;
+ }
+
+ RefPtr<Layer> maskLayer = CreateMaskLayer(aLayer, aClip, Nothing());
+
+ if (!maskLayer) {
+ return;
+ }
+
+ aLayer->SetMaskLayer(maskLayer);
+}
+
+static MaskLayerUserData* GetMaskLayerUserData(Layer* aMaskLayer) {
+ if (!aMaskLayer) {
+ return nullptr;
+ }
+
+ return static_cast<MaskLayerUserData*>(
+ aMaskLayer->GetUserData(&gMaskLayerUserData));
+}
+
+static void SetMaskLayerUserData(Layer* aMaskLayer) {
+ MOZ_ASSERT(aMaskLayer);
+
+ aMaskLayer->SetUserData(&gMaskLayerUserData, new MaskLayerUserData());
+}
+
+already_AddRefed<Layer> ContainerState::CreateMaskLayer(
+ Layer* aLayer, const DisplayItemClip& aClip,
+ const Maybe<size_t>& aForAncestorMaskLayer) {
+ // aLayer will never be the container layer created by an
+ // nsDisplayMasksAndClipPaths because nsDisplayMasksAndClipPaths propagates
+ // the DisplayItemClip to its contents and is not clipped itself.
+ // This assertion will fail if that ever stops being the case.
+ MOZ_ASSERT(!aLayer->GetUserData(&gCSSMaskLayerUserData),
+ "A layer contains round clips should not have css-mask on it.");
+
+ // check if we can re-use the mask layer
+ RefPtr<ImageLayer> maskLayer = CreateOrRecycleMaskImageLayerFor(
+ MaskLayerKey(aLayer, aForAncestorMaskLayer), GetMaskLayerUserData,
+ SetMaskLayerUserData);
+ MaskLayerUserData* userData = GetMaskLayerUserData(maskLayer.get());
+
+ int32_t A2D = mContainerFrame->PresContext()->AppUnitsPerDevPixel();
+ MaskLayerUserData newData(aClip, A2D, mParameters);
+ if (*userData == newData) {
+ return maskLayer.forget();
+ }
+
+ gfx::Rect boundingRect =
+ CalculateBounds(newData.mRoundedClipRects, newData.mAppUnitsPerDevPixel);
+ boundingRect.Scale(mParameters.mXScale, mParameters.mYScale);
+ if (boundingRect.IsEmpty()) {
+ // Return early if we know that there is effectively no visible data.
+ return nullptr;
+ }
+
+ uint32_t maxSize = mManager->GetMaxTextureSize();
+ NS_ASSERTION(maxSize > 0, "Invalid max texture size");
+#ifdef MOZ_GFX_OPTIMIZE_MOBILE
+ // Make mask image width aligned to 4. See Bug 1245552.
+ gfx::Size surfaceSize(
+ std::min<gfx::Float>(
+ GetAlignedStride<4>(NSToIntCeil(boundingRect.Width()), 1), maxSize),
+ std::min<gfx::Float>(boundingRect.Height(), maxSize));
+#else
+ gfx::Size surfaceSize(std::min<gfx::Float>(boundingRect.Width(), maxSize),
+ std::min<gfx::Float>(boundingRect.Height(), maxSize));
+#endif
+
+ // maskTransform is applied to the clip when it is painted into the mask (as a
+ // component of imageTransform), and its inverse used when the mask is used
+ // for masking. It is the transform from the masked layer's space to mask
+ // space
+ gfx::Matrix maskTransform =
+ Matrix::Scaling(surfaceSize.width / boundingRect.Width(),
+ surfaceSize.height / boundingRect.Height());
+ if (surfaceSize.IsEmpty()) {
+ // Return early if we know that the size of this mask surface is empty.
+ return nullptr;
+ }
+
+ gfx::Point p = boundingRect.TopLeft();
+ maskTransform.PreTranslate(-p.x, -p.y);
+ // imageTransform is only used when the clip is painted to the mask
+ gfx::Matrix imageTransform = maskTransform;
+ imageTransform.PreScale(mParameters.mXScale, mParameters.mYScale);
+
+ UniquePtr<MaskLayerImageCache::MaskLayerImageKey> newKey(
+ new MaskLayerImageCache::MaskLayerImageKey());
+
+ // copy and transform the rounded rects
+ for (uint32_t i = 0; i < newData.mRoundedClipRects.Length(); ++i) {
+ newKey->mRoundedClipRects.AppendElement(
+ MaskLayerImageCache::PixelRoundedRect(newData.mRoundedClipRects[i],
+ mContainerFrame->PresContext()));
+ newKey->mRoundedClipRects[i].ScaleAndTranslate(imageTransform);
+ }
+ newKey->mKnowsCompositor = mManager->AsKnowsCompositor();
+
+ const MaskLayerImageCache::MaskLayerImageKey* lookupKey = newKey.get();
+
+ // check to see if we can reuse a mask image
+ RefPtr<ImageContainer> container =
+ GetMaskLayerImageCache()->FindImageFor(&lookupKey);
+
+ if (!container) {
+ IntSize surfaceSizeInt(NSToIntCeil(surfaceSize.width),
+ NSToIntCeil(surfaceSize.height));
+ // no existing mask image, so build a new one
+ MaskImageData imageData(surfaceSizeInt, mManager);
+ RefPtr<DrawTarget> dt = imageData.CreateDrawTarget();
+
+ // fail if we can't get the right surface
+ if (!dt || !dt->IsValid()) {
+ NS_WARNING("Could not create DrawTarget for mask layer.");
+ return nullptr;
+ }
+
+ RefPtr<gfxContext> context = gfxContext::CreateOrNull(dt);
+ MOZ_ASSERT(context); // already checked the draw target above
+ context->Multiply(ThebesMatrix(imageTransform));
+
+ // paint the clipping rects with alpha to create the mask
+ aClip.FillIntersectionOfRoundedRectClips(
+ context, DeviceColor::MaskOpaqueWhite(), newData.mAppUnitsPerDevPixel);
+
+ // build the image and container
+ MOZ_ASSERT(aLayer->Manager() == mManager);
+ container = imageData.CreateImageAndImageContainer();
+ NS_ASSERTION(container, "Could not create image container for mask layer.");
+
+ if (!container) {
+ return nullptr;
+ }
+
+ GetMaskLayerImageCache()->PutImage(newKey.release(), container);
+ }
+
+ maskLayer->SetContainer(container);
+
+ maskTransform.Invert();
+ Matrix4x4 matrix = Matrix4x4::From2D(maskTransform);
+ matrix.PreTranslate(mParameters.mOffset.x, mParameters.mOffset.y, 0);
+ maskLayer->SetBaseTransform(matrix);
+
+ // save the details of the clip in user data
+ *userData = std::move(newData);
+ userData->mImageKey.Reset(lookupKey);
+
+ return maskLayer.forget();
+}
+
+} // namespace mozilla
diff --git a/layout/painting/FrameLayerBuilder.h b/layout/painting/FrameLayerBuilder.h
new file mode 100644
index 0000000000..8446ce634e
--- /dev/null
+++ b/layout/painting/FrameLayerBuilder.h
@@ -0,0 +1,732 @@
+/* -*- 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 FRAMELAYERBUILDER_H_
+#define FRAMELAYERBUILDER_H_
+
+#include <cstddef> // for size_t
+#include <cstdint> // for uint32_t, UINT32_MAX, int32_t, uint64_t, uint8_t
+#include <iosfwd> // for stringstream
+#include <vector> // for vector
+#include "DisplayItemClip.h" // for DisplayItemClip
+#include "LayerState.h" // for LayerState
+#include "LayerUserData.h" // for LayerUserData
+#include "Units.h" // for LayerIntPoint, LayoutDeviceToLayerScale2D
+#include "gfxPoint.h" // for gfxSize
+#include "mozilla/AlreadyAddRefed.h" // for already_AddRefed
+#include "mozilla/Assertions.h" // for AssertionConditionType, MOZ_ASSERT_HELPER2
+#include "mozilla/FunctionRef.h" // for FunctionRef
+#include "mozilla/RefPtr.h" // for RefPtr
+#include "mozilla/UniquePtr.h" // for UniquePtr
+#include "mozilla/gfx/Matrix.h" // for Matrix, Matrix4x4
+#include "mozilla/gfx/Point.h" // for Size
+#include "mozilla/layers/LayerManager.h" // for LayerManager, LayerManager::NONE, LayerManager::PaintedLayerCreationHint, LayerMetricsW...
+#include "mozilla/layers/LayersTypes.h" // for DrawRegionClip, EventRegions
+#include "nsColor.h" // for NS_RGBA, nscolor
+#include "nsDebug.h" // for NS_WARNING
+#include "nsDisplayItemTypes.h" // for DisplayItemType
+#include "nsISupports.h" // for NS_INLINE_DECL_REFCOUNTING, NS_LOG_ADDREF, NS_LOG_RELEASE
+#include "nsPoint.h" // for nsIntPoint
+#include "nsRect.h" // for nsRect (ptr only), nsIntRect
+#include "nsRegion.h" // for nsIntRegion, nsRegion
+#include "nsTArray.h" // for AutoTArray, nsTArray_Impl
+#include "nscore.h" // for nsrefcnt
+
+class gfxContext;
+class nsDisplayItem;
+class nsDisplayItemGeometry;
+class nsDisplayList;
+class nsDisplayListBuilder;
+class nsDisplayMasksAndClipPaths;
+class nsIFrame;
+class nsPaintedDisplayItem;
+class nsPresContext;
+class nsRootPresContext;
+
+namespace mozilla {
+struct ActiveScrolledRoot;
+struct DisplayItemClipChain;
+class TransformClipNode;
+template <class T>
+class Maybe;
+template <typename T>
+class SmallPointerArray;
+
+namespace layers {
+class ContainerLayer;
+class Layer;
+class BasicLayerManager;
+class PaintedLayer;
+class ImageLayer;
+struct LayerProperties;
+} // namespace layers
+
+class FrameLayerBuilder;
+class LayerManagerData;
+class PaintedLayerData;
+class ContainerState;
+class PaintedDisplayItemLayerUserData;
+
+enum class DisplayItemEntryType : uint8_t {
+ Item,
+ PushOpacity,
+ PushOpacityWithBg,
+ PopOpacity,
+ PushTransform,
+ PopTransform,
+ HitTestInfo,
+};
+
+/**
+ * Retained data storage:
+ *
+ * Each layer manager (widget, and inactive) stores a LayerManagerData object
+ * that keeps a hash-set of DisplayItemData items that were drawn into it.
+ * Each frame also keeps a list of DisplayItemData pointers that were
+ * created for that frame. DisplayItemData objects manage these lists
+ * automatically.
+ *
+ * During layer construction we update the data in the LayerManagerData object,
+ * marking items that are modified. At the end we sweep the LayerManagerData
+ * hash-set and remove all items that haven't been modified.
+ */
+
+/**
+ * Retained data for a display item.
+ */
+class DisplayItemData final {
+ public:
+ friend class FrameLayerBuilder;
+ friend class ContainerState;
+
+ uint32_t GetDisplayItemKey() { return mDisplayItemKey; }
+ layers::Layer* GetLayer() const { return mLayer; }
+ nsDisplayItemGeometry* GetGeometry() const { return mGeometry.get(); }
+ const DisplayItemClip& GetClip() const { return mClip; }
+ void Invalidate() { mIsInvalid = true; }
+ void NotifyRemoved();
+ void SetItem(nsPaintedDisplayItem* aItem) { mItem = aItem; }
+ nsPaintedDisplayItem* GetItem() const { return mItem; }
+ nsIFrame* FirstFrame() const { return mFrameList[0]; }
+ layers::BasicLayerManager* InactiveManager() const {
+ return mInactiveManager;
+ }
+
+ bool HasMergedFrames() const { return mFrameList.Length() > 1; }
+
+ static DisplayItemData* AssertDisplayItemData(DisplayItemData* aData);
+
+ void* operator new(size_t sz, nsPresContext* aPresContext);
+
+ nsrefcnt AddRef() {
+ if (mRefCnt == UINT32_MAX) {
+ NS_WARNING("refcount overflow, leaking object");
+ return mRefCnt;
+ }
+ ++mRefCnt;
+ NS_LOG_ADDREF(this, mRefCnt, "DisplayItemData", sizeof(DisplayItemData));
+ return mRefCnt;
+ }
+
+ nsrefcnt Release() {
+ if (mRefCnt == UINT32_MAX) {
+ NS_WARNING("refcount overflow, leaking object");
+ return mRefCnt;
+ }
+ --mRefCnt;
+ NS_LOG_RELEASE(this, mRefCnt, "DisplayItemData");
+ if (mRefCnt == 0) {
+ Destroy();
+ return 0;
+ }
+ return mRefCnt;
+ }
+
+ RefPtr<TransformClipNode> mTransform;
+ RefPtr<TransformClipNode> mOldTransform;
+
+ private:
+ DisplayItemData(LayerManagerData* aParent, uint32_t aKey,
+ layers::Layer* aLayer, nsIFrame* aFrame = nullptr);
+
+ /**
+ * Removes any references to this object from frames
+ * in mFrameList.
+ */
+ ~DisplayItemData();
+
+ void Destroy();
+
+ /**
+ * Associates this DisplayItemData with a frame, and adds it
+ * to the LayerManagerDataProperty list on the frame.
+ */
+ void AddFrame(nsIFrame* aFrame);
+ void RemoveFrame(nsIFrame* aFrame);
+ const nsRegion& GetChangedFrameInvalidations();
+
+ /**
+ * Updates the contents of this item to a new set of data, instead of
+ * allocating a new object. Set the passed in parameters, and clears the opt
+ * layer and inactive manager. Parent, and display item key are assumed to be
+ * the same.
+ *
+ * EndUpdate must be called before the end of the transaction to complete the
+ * update.
+ */
+ void BeginUpdate(layers::Layer* aLayer, LayerState aState, bool aFirstUpdate,
+ nsPaintedDisplayItem* aItem = nullptr);
+ void BeginUpdate(layers::Layer* aLayer, LayerState aState,
+ nsPaintedDisplayItem* aItem, bool aIsReused, bool aIsMerged);
+
+ /**
+ * Completes the update of this, and removes any references to data that won't
+ * live longer than the transaction.
+ *
+ * Updates the geometry, frame list and clip.
+ * For items within a PaintedLayer, a geometry object must be specified to
+ * retain until the next transaction.
+ *
+ */
+ void EndUpdate(mozilla::UniquePtr<nsDisplayItemGeometry>&& aGeometry);
+ void EndUpdate();
+
+ uint32_t mRefCnt;
+ LayerManagerData* mParent;
+ RefPtr<layers::Layer> mLayer;
+ RefPtr<layers::Layer> mOptLayer;
+ RefPtr<layers::BasicLayerManager> mInactiveManager;
+ AutoTArray<nsIFrame*, 1> mFrameList;
+ mozilla::UniquePtr<nsDisplayItemGeometry> mGeometry;
+ DisplayItemClip mClip;
+ uint32_t mDisplayItemKey;
+ LayerState mLayerState;
+
+ /**
+ * Temporary stoarage of the display item being referenced, only valid between
+ * BeginUpdate and EndUpdate.
+ */
+ nsPaintedDisplayItem* mItem;
+ nsRegion mChangedFrameInvalidations;
+
+ /**
+ * Used to track if data currently stored in mFramesWithLayers (from an
+ * existing paint) has been updated in the current paint.
+ */
+ bool mUsed;
+ bool mIsInvalid;
+ bool mReusedItem;
+};
+
+class RefCountedRegion {
+ private:
+ ~RefCountedRegion() = default;
+
+ public:
+ NS_INLINE_DECL_REFCOUNTING(RefCountedRegion)
+
+ RefCountedRegion() : mIsInfinite(false) {}
+ nsRegion mRegion;
+ bool mIsInfinite;
+};
+
+struct AssignedDisplayItem;
+
+struct ContainerLayerParameters {
+ ContainerLayerParameters()
+ : mXScale(1),
+ mYScale(1),
+ mLayerContentsVisibleRect(nullptr),
+ mBackgroundColor(NS_RGBA(0, 0, 0, 0)),
+ mScrollMetadataASR(nullptr),
+ mCompositorASR(nullptr),
+ mInTransformedSubtree(false),
+ mInActiveTransformedSubtree(false),
+ mDisableSubpixelAntialiasingInDescendants(false),
+ mLayerCreationHint(layers::LayerManager::NONE) {}
+ ContainerLayerParameters(float aXScale, float aYScale)
+ : mXScale(aXScale),
+ mYScale(aYScale),
+ mLayerContentsVisibleRect(nullptr),
+ mItemVisibleRect(nullptr),
+ mBackgroundColor(NS_RGBA(0, 0, 0, 0)),
+ mScrollMetadataASR(nullptr),
+ mCompositorASR(nullptr),
+ mInTransformedSubtree(false),
+ mInActiveTransformedSubtree(false),
+ mDisableSubpixelAntialiasingInDescendants(false),
+ mLayerCreationHint(layers::LayerManager::NONE) {}
+ ContainerLayerParameters(float aXScale, float aYScale,
+ const nsIntPoint& aOffset,
+ const ContainerLayerParameters& aParent)
+ : mXScale(aXScale),
+ mYScale(aYScale),
+ mLayerContentsVisibleRect(nullptr),
+ mItemVisibleRect(nullptr),
+ mOffset(aOffset),
+ mBackgroundColor(aParent.mBackgroundColor),
+ mScrollMetadataASR(aParent.mScrollMetadataASR),
+ mCompositorASR(aParent.mCompositorASR),
+ mInTransformedSubtree(aParent.mInTransformedSubtree),
+ mInActiveTransformedSubtree(aParent.mInActiveTransformedSubtree),
+ mDisableSubpixelAntialiasingInDescendants(
+ aParent.mDisableSubpixelAntialiasingInDescendants),
+ mLayerCreationHint(aParent.mLayerCreationHint) {}
+
+ float mXScale, mYScale;
+
+ LayoutDeviceToLayerScale2D Scale() const {
+ return LayoutDeviceToLayerScale2D(mXScale, mYScale);
+ }
+
+ /**
+ * If non-null, the rectangle in which BuildContainerLayerFor stores the
+ * visible rect of the layer, in the coordinate system of the created layer.
+ */
+ nsIntRect* mLayerContentsVisibleRect;
+
+ /**
+ * If non-null, the rectangle which stores the item's visible rect.
+ */
+ nsRect* mItemVisibleRect;
+
+ /**
+ * An offset to apply to all child layers created.
+ */
+ nsIntPoint mOffset;
+
+ LayerIntPoint Offset() const {
+ return LayerIntPoint::FromUnknownPoint(mOffset);
+ }
+
+ nscolor mBackgroundColor;
+ const ActiveScrolledRoot* mScrollMetadataASR;
+ const ActiveScrolledRoot* mCompositorASR;
+
+ bool mInTransformedSubtree;
+ bool mInActiveTransformedSubtree;
+ bool mDisableSubpixelAntialiasingInDescendants;
+ layers::LayerManager::PaintedLayerCreationHint mLayerCreationHint;
+
+ /**
+ * When this is false, PaintedLayer coordinates are drawn to with an integer
+ * translation and the scale in mXScale/mYScale.
+ */
+ bool AllowResidualTranslation() {
+ // If we're in a transformed subtree, but no ancestor transform is actively
+ // changing, we'll use the residual translation when drawing into the
+ // PaintedLayer to ensure that snapping exactly matches the ideal transform.
+ return mInTransformedSubtree && !mInActiveTransformedSubtree;
+ }
+};
+
+/**
+ * The FrameLayerBuilder is responsible for converting display lists
+ * into layer trees. Every LayerManager needs a unique FrameLayerBuilder
+ * to build layers.
+ *
+ * The most important API in this class is BuildContainerLayerFor. This
+ * method takes a display list as input and constructs a ContainerLayer
+ * with child layers that render the contents of the display list. It
+ * records the relationship between frames and layers.
+ *
+ * That data enables us to retain layer trees. When constructing a
+ * ContainerLayer, we first check to see if there's an existing
+ * ContainerLayer for the same frame that can be recycled. If we recycle
+ * it, we also try to reuse its existing PaintedLayer children to render
+ * the display items without layers of their own. The idea is that by
+ * recycling layers deterministically, we can ensure that when nothing
+ * changes in a display list, we will reuse the existing layers without
+ * changes.
+ *
+ * We expose a GetLeafLayerFor method that can be called by display items
+ * that make their own layers (e.g. canvas and video); this method
+ * locates the last layer used to render the display item, if any, and
+ * return it as a candidate for recycling.
+ *
+ * FrameLayerBuilder sets up PaintedLayers so that 0,0 in the Painted layer
+ * corresponds to the (pixel-snapped) top-left of the aAnimatedGeometryRoot.
+ * It sets up ContainerLayers so that 0,0 in the container layer
+ * corresponds to the snapped top-left of the display item reference frame.
+ *
+ * When we construct a container layer, we know the transform that will be
+ * applied to the layer. If the transform scales the content, we can get
+ * better results when intermediate buffers are used by pushing some scale
+ * from the container's transform down to the children. For PaintedLayer
+ * children, the scaling can be achieved by changing the size of the layer
+ * and drawing into it with increased or decreased resolution. By convention,
+ * integer types (nsIntPoint/nsIntSize/nsIntRect/nsIntRegion) are all in layer
+ * coordinates, post-scaling, whereas appunit types are all pre-scaling.
+ */
+class FrameLayerBuilder : public layers::LayerUserData {
+ public:
+ typedef layers::ContainerLayer ContainerLayer;
+ typedef layers::Layer Layer;
+ typedef layers::PaintedLayer PaintedLayer;
+ typedef layers::ImageLayer ImageLayer;
+ typedef layers::LayerManager LayerManager;
+ typedef layers::BasicLayerManager BasicLayerManager;
+ typedef layers::EventRegions EventRegions;
+
+ FrameLayerBuilder();
+ ~FrameLayerBuilder() override;
+
+ static gfx::Size ChooseScale(nsIFrame* aContainerFrame,
+ nsDisplayItem* aContainerItem,
+ const nsRect& aVisibleRect, float aXScale,
+ float aYScale, const gfx::Matrix& aTransform2d,
+ bool aCanDraw2D);
+
+ static void Shutdown();
+
+ void Init(nsDisplayListBuilder* aBuilder, LayerManager* aManager,
+ PaintedLayerData* aLayerData = nullptr,
+ bool aIsInactiveLayerManager = false,
+ const DisplayItemClip* aInactiveLayerClip = nullptr);
+
+ /**
+ * Call this to notify that we have just started a transaction on the
+ * retained layer manager aManager.
+ */
+ void DidBeginRetainedLayerTransaction(LayerManager* aManager);
+
+ /**
+ * Call this just before we end a transaction.
+ */
+ void WillEndTransaction();
+
+ /**
+ * Call this after we end a transaction.
+ */
+ void DidEndTransaction();
+
+ enum {
+ /**
+ * Set this when pulling an opaque background color from behind the
+ * container layer into the container doesn't change the visual results,
+ * given the effects you're going to apply to the container layer.
+ * For example, this is compatible with opacity or clipping/masking, but
+ * not with non-OVER blend modes or filters.
+ */
+ CONTAINER_ALLOW_PULL_BACKGROUND_COLOR = 0x01
+ };
+ /**
+ * Build a container layer for a display item that contains a child
+ * list, either reusing an existing one or creating a new one. It
+ * sets the container layer children to layers which together render
+ * the contents of the display list. It reuses existing layers from
+ * the retained layer manager if possible.
+ * aContainerItem may be null, in which case we construct a root layer.
+ * This gets called by display list code. It calls BuildLayer on the
+ * items in the display list, making items with their own layers
+ * children of the new container, and assigning all other items to
+ * PaintedLayer children created and managed by the FrameLayerBuilder.
+ * Returns a layer with clip rect cleared; it is the
+ * caller's responsibility to add any clip rect. The visible region
+ * is set based on what's in the layer.
+ * The container layer is transformed by aTransform (if non-null), and
+ * the result is transformed by the scale factors in aContainerParameters.
+ * aChildren is modified due to display item merging and flattening.
+ * The visible region of the returned layer is set only if aContainerItem
+ * is null.
+ */
+ already_AddRefed<ContainerLayer> BuildContainerLayerFor(
+ nsDisplayListBuilder* aBuilder, LayerManager* aManager,
+ nsIFrame* aContainerFrame, nsDisplayItem* aContainerItem,
+ nsDisplayList* aChildren,
+ const ContainerLayerParameters& aContainerParameters,
+ const gfx::Matrix4x4* aTransform, uint32_t aFlags = 0);
+
+ /**
+ * Get a retained layer for a display item that needs to create its own
+ * layer for rendering (i.e. under nsDisplayItem::BuildLayer). Returns
+ * null if no retained layer is available, which usually means that this
+ * display item didn't have a layer before so the caller will
+ * need to create one.
+ * Returns a layer with clip rect cleared; it is the
+ * caller's responsibility to add any clip rect and set the visible
+ * region.
+ */
+ Layer* GetLeafLayerFor(nsDisplayListBuilder* aBuilder, nsDisplayItem* aItem);
+
+ /**
+ * Call this to force all retained layers to be discarded and recreated at
+ * the next paint.
+ */
+ static void InvalidateAllLayers(LayerManager* aManager);
+ static void InvalidateAllLayersForFrame(nsIFrame* aFrame);
+
+ /**
+ * Call this to determine if a frame has a dedicated (non-Painted) layer
+ * for the given display item key. If there isn't one, we return null,
+ * otherwise we return the layer.
+ */
+ static Layer* GetDedicatedLayer(nsIFrame* aFrame,
+ DisplayItemType aDisplayItemType);
+
+ using AnimationGenerationCallback = FunctionRef<bool(
+ const Maybe<uint64_t>& aGeneration, DisplayItemType aDisplayItemType)>;
+ /**
+ * Enumerates layers for the all display item types that correspond to
+ * properties we can animate on layers and calls |aCallback|
+ * with the animation generation for the layer. If there is no corresponding
+ * layer for the display item or the layer has no animation, the animation
+ * generation is Nothing().
+ *
+ * The enumeration stops if |aCallback| returns false.
+ */
+ static void EnumerateGenerationForDedicatedLayers(
+ const nsIFrame* aFrame, AnimationGenerationCallback);
+
+ /**
+ * This callback must be provided to EndTransaction. The callback data
+ * must be the nsDisplayListBuilder containing this FrameLayerBuilder.
+ * This function can be called multiple times in a row to draw
+ * different regions. This will occur when, for example, progressive paint is
+ * enabled. In these cases aDirtyRegion can be used to specify a larger region
+ * than aRegionToDraw that will be drawn during the transaction, possibly
+ * allowing the callback to make optimizations.
+ */
+ static void DrawPaintedLayer(PaintedLayer* aLayer, gfxContext* aContext,
+ const nsIntRegion& aRegionToDraw,
+ const nsIntRegion& aDirtyRegion,
+ mozilla::layers::DrawRegionClip aClip,
+ const nsIntRegion& aRegionToInvalidate,
+ void* aCallbackData);
+
+ /**
+ * Dumps this FrameLayerBuilder's retained layer manager's retained
+ * layer tree. Defaults to dumping to stdout in non-HTML format.
+ */
+ static void DumpRetainedLayerTree(LayerManager* aManager,
+ std::stringstream& aStream,
+ bool aDumpHtml = false);
+
+ /**
+ * Returns the most recently allocated geometry item for the given display
+ * item.
+ *
+ * XXX(seth): The current implementation must iterate through all display
+ * items allocated for this display item's frame. This may lead to O(n^2)
+ * behavior in some situations.
+ */
+ static nsDisplayItemGeometry* GetMostRecentGeometry(nsDisplayItem* aItem);
+
+ /******* PRIVATE METHODS to FrameLayerBuilder.cpp ********/
+ /* These are only in the public section because they need
+ * to be called by file-scope helper functions in FrameLayerBuilder.cpp.
+ */
+
+ /**
+ * Record aItem as a display item that is rendered by the PaintedLayer
+ * aLayer, with aClipRect, where aContainerLayerFrame is the frame
+ * for the container layer this ThebesItem belongs to.
+ * aItem must have an underlying frame.
+ * @param aTopLeft offset from active scrolled root to reference frame
+ */
+ void AddPaintedDisplayItem(PaintedLayerData* aLayerData,
+ AssignedDisplayItem& aAssignedDisplayItem,
+ Layer* aLayer);
+
+ /**
+ * Calls GetOldLayerForFrame on the underlying frame of the display item,
+ * and each subsequent merged frame if no layer is found for the underlying
+ * frame.
+ */
+ Layer* GetOldLayerFor(nsDisplayItem* aItem,
+ nsDisplayItemGeometry** aOldGeometry = nullptr,
+ DisplayItemClip** aOldClip = nullptr);
+
+ static DisplayItemData* GetOldDataFor(nsDisplayItem* aItem);
+
+ /**
+ * Destroy any stored LayerManagerDataProperty and the associated data for
+ * aFrame.
+ */
+ static void DestroyDisplayItemDataFor(nsIFrame* aFrame);
+
+ LayerManager* GetRetainingLayerManager() { return mRetainingManager; }
+
+ /**
+ * Returns true if the given display item was rendered during the previous
+ * paint. Returns false otherwise.
+ */
+ static bool HasRetainedDataFor(nsIFrame* aFrame, uint32_t aDisplayItemKey);
+
+ typedef void (*DisplayItemDataCallback)(nsIFrame* aFrame,
+ DisplayItemData* aItem);
+
+ /**
+ * Return the resolution at which we expect to render aFrame's contents,
+ * assuming they are being painted to retained layers. This takes into account
+ * the resolution the contents of the ContainerLayer containing aFrame are
+ * being rendered at, as well as any currently-inactive transforms between
+ * aFrame and that container layer.
+ */
+ static gfxSize GetPaintedLayerScaleForFrame(nsIFrame* aFrame);
+
+ static void RemoveFrameFromLayerManager(
+ const nsIFrame* aFrame, SmallPointerArray<DisplayItemData>& aArray);
+
+ /**
+ * Given a frame and a display item key that uniquely identifies a
+ * display item for the frame, find the layer that was last used to
+ * render that display item. Returns null if there is no such layer.
+ * This could be a dedicated layer for the display item, or a PaintedLayer
+ * that renders many display items.
+ */
+ DisplayItemData* GetOldLayerForFrame(
+ nsIFrame* aFrame, uint32_t aDisplayItemKey,
+ DisplayItemData* aOldData = nullptr,
+ LayerManager* aOldLayerManager = nullptr);
+
+ /**
+ * Stores DisplayItemData associated with aFrame, stores the data in
+ * mNewDisplayItemData.
+ */
+ DisplayItemData* StoreDataForFrame(nsPaintedDisplayItem* aItem, Layer* aLayer,
+ LayerState aState, DisplayItemData* aData);
+ void StoreDataForFrame(nsIFrame* aFrame, uint32_t aDisplayItemKey,
+ Layer* aLayer, LayerState aState);
+
+ protected:
+ friend class LayerManagerData;
+
+ // Flash the area within the context clip if paint flashing is enabled.
+ static void FlashPaint(gfxContext* aContext);
+
+ /*
+ * Get the DisplayItemData array associated with this frame, or null if one
+ * doesn't exist.
+ *
+ * Note that the pointer returned here is only valid so long as you don't
+ * poke the LayerManagerData's mFramesWithLayers hashtable.
+ */
+ DisplayItemData* GetDisplayItemData(nsIFrame* aFrame, uint32_t aKey);
+
+ /*
+ * Get the DisplayItemData associated with this display item,
+ * using the LayerManager instead of FrameLayerBuilder.
+ */
+ static DisplayItemData* GetDisplayItemDataForManager(
+ nsPaintedDisplayItem* aItem, LayerManager* aManager);
+
+ /**
+ * We store one of these for each display item associated with a
+ * PaintedLayer, in a hashtable that maps each PaintedLayer to an array
+ * of ClippedDisplayItems. (PaintedLayerItemsEntry is the hash entry
+ * for that hashtable.)
+ * These are only stored during the paint process, so that the
+ * DrawPaintedLayer callback can figure out which items to draw for the
+ * PaintedLayer.
+ */
+
+ static void RecomputeVisibilityForItems(
+ std::vector<AssignedDisplayItem>& aItems, nsDisplayListBuilder* aBuilder,
+ const nsIntRegion& aRegionToDraw, nsRect& aPreviousRectToDraw,
+ const nsIntPoint& aOffset, int32_t aAppUnitsPerDevPixel, float aXScale,
+ float aYScale);
+
+ void PaintItems(std::vector<AssignedDisplayItem>& aItems,
+ const nsIntRect& aRect, gfxContext* aContext,
+ nsDisplayListBuilder* aBuilder, nsPresContext* aPresContext,
+ const nsIntPoint& aOffset, float aXScale, float aYScale);
+
+ /**
+ * We accumulate ClippedDisplayItem elements in a hashtable during
+ * the paint process. This is the hashentry for that hashtable.
+ */
+ public:
+ /**
+ * Add the PaintedDisplayItemLayerUserData object as being used in this
+ * transaction so that we do some end-of-paint maintenance on it.
+ */
+ void AddPaintedLayerItemsEntry(PaintedDisplayItemLayerUserData* aData);
+
+ PaintedLayerData* GetContainingPaintedLayerData() {
+ return mContainingPaintedLayer;
+ }
+
+ const DisplayItemClip* GetInactiveLayerClip() const {
+ return mInactiveLayerClip;
+ }
+
+ /*
+ * If we're building layers for an item with an inactive layer tree,
+ * this function saves the item's clip, which will later be applied
+ * to the event regions. The clip should be relative to
+ * mContainingPaintedLayer->mReferenceFrame.
+ */
+ void SetInactiveLayerClip(const DisplayItemClip* aClip) {
+ mInactiveLayerClip = aClip;
+ }
+
+ bool IsBuildingRetainedLayers() {
+ return !mIsInactiveLayerManager && mRetainingManager;
+ }
+
+ /**
+ * Attempt to build the most compressed layer tree possible, even if it means
+ * throwing away existing retained buffers.
+ */
+ void SetLayerTreeCompressionMode() { mInLayerTreeCompressionMode = true; }
+ bool CheckInLayerTreeCompressionMode();
+
+ void ComputeGeometryChangeForItem(DisplayItemData* aData);
+
+ // Defined and used only in dom/base/nsDOMWindowUtils.cpp
+ template <class T>
+ static T* GetDebugSingleOldLayerForFrame(nsIFrame* aFrame);
+
+ protected:
+ /**
+ * The layer manager belonging to the widget that is being retained
+ * across paints.
+ */
+ LayerManager* mRetainingManager;
+ /**
+ * The root prescontext for the display list builder reference frame
+ */
+ RefPtr<nsRootPresContext> mRootPresContext;
+
+ /**
+ * The display list builder being used.
+ */
+ nsDisplayListBuilder* mDisplayListBuilder;
+ /**
+ * An array of PaintedLayer user data objects containing the
+ * list of display items (plus clipping data) to be rendered in the
+ * layer. We clean these up at the end of the transaction to
+ * remove references to display items.
+ */
+ AutoTArray<RefPtr<PaintedDisplayItemLayerUserData>, 5> mPaintedLayerItems;
+
+ /**
+ * When building layers for an inactive layer, this is where the
+ * inactive layer will be placed.
+ */
+ PaintedLayerData* mContainingPaintedLayer;
+
+ /**
+ * When building layers for an inactive layer, this stores the clip
+ * of the display item that built the inactive layer.
+ */
+ const DisplayItemClip* mInactiveLayerClip;
+
+ /**
+ * Indicates that the entire layer tree should be rerendered
+ * during this paint.
+ */
+ bool mInvalidateAllLayers;
+
+ bool mInLayerTreeCompressionMode;
+
+ bool mIsInactiveLayerManager;
+};
+
+} // namespace mozilla
+
+#endif /* FRAMELAYERBUILDER_H_ */
diff --git a/layout/painting/LayerState.h b/layout/painting/LayerState.h
new file mode 100644
index 0000000000..d570ae95b0
--- /dev/null
+++ b/layout/painting/LayerState.h
@@ -0,0 +1,29 @@
+/* -*- 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 LAYERSTATE_H_
+#define LAYERSTATE_H_
+
+#include <cstdint>
+
+namespace mozilla {
+
+enum class LayerState : uint8_t {
+ LAYER_NONE,
+ LAYER_INACTIVE,
+ LAYER_ACTIVE,
+ // Force an active layer even if it causes incorrect rendering, e.g.
+ // when the layer has rounded rect clips.
+ LAYER_ACTIVE_FORCE,
+ // Special layer that is metadata only.
+ LAYER_ACTIVE_EMPTY,
+ // Inactive style layer for rendering SVG effects.
+ LAYER_SVG_EFFECTS
+};
+
+} // 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..8a374d9871
--- /dev/null
+++ b/layout/painting/RetainedDisplayListBuilder.cpp
@@ -0,0 +1,1506 @@
+/* -*- 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/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"
+
+/**
+ * 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.
+ */
+
+using namespace mozilla;
+using mozilla::dom::Document;
+
+void RetainedDisplayListData::AddModifiedFrame(nsIFrame* aFrame) {
+ MOZ_ASSERT(!aFrame->IsFrameModified());
+ Flags(aFrame) |= RetainedDisplayListData::FrameFlags::Modified;
+ mModifiedFramesCount++;
+}
+
+RetainedDisplayListData* GetRetainedDisplayListData(nsIFrame* aRootFrame) {
+ RetainedDisplayListData* data =
+ aRootFrame->GetProperty(RetainedDisplayListData::DisplayListData());
+
+ return data;
+}
+
+RetainedDisplayListData* GetOrSetRetainedDisplayListData(nsIFrame* aRootFrame) {
+ RetainedDisplayListData* data = GetRetainedDisplayListData(aRootFrame);
+
+ if (!data) {
+ data = new RetainedDisplayListData();
+ aRootFrame->SetProperty(RetainedDisplayListData::DisplayListData(), data);
+ }
+
+ MOZ_ASSERT(data);
+ return data;
+}
+
+static void MarkFramesWithItemsAndImagesModified(nsDisplayList* aList) {
+ for (nsDisplayItem* i = aList->GetBottom(); i != nullptr; i = i->GetAbove()) {
+ 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.
+ DisplayItemData* data = FrameLayerBuilder::GetOldDataFor(i);
+ // XXX: handle webrender case
+ bool invalidate = false;
+ if (data && data->GetGeometry()) {
+ invalidate = data->GetGeometry()->InvalidateForSyncDecodeImages();
+ } else if (!(i->GetFlags() & TYPE_RENDERS_NO_IMAGES)) {
+ invalidate = true;
+ }
+
+ if (invalidate) {
+ i->FrameForInvalidation()->MarkNeedsDisplayItemRebuild();
+ if (i->GetDependentFrame()) {
+ i->GetDependentFrame()->MarkNeedsDisplayItemRebuild();
+ }
+ }
+ }
+ if (i->GetChildren()) {
+ MarkFramesWithItemsAndImagesModified(i->GetChildren());
+ }
+ }
+}
+
+static AnimatedGeometryRoot* SelectAGRForFrame(
+ nsIFrame* aFrame, AnimatedGeometryRoot* 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.get() : 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, AnimatedGeometryRoot* aAGR,
+ PartialUpdateResult& aUpdated, 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->Count());
+ } else {
+ MOZ_RELEASE_ASSERT(!initializeDAG);
+ }
+
+ MOZ_RELEASE_ASSERT(
+ initializeDAG ||
+ aList->mDAG.Length() ==
+ (initializeOldItems ? aList->Count() : aList->mOldItems.Length()));
+
+ nsDisplayList out;
+
+ 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 (!PreProcessDisplayList(item->GetChildren(),
+ SelectAGRForFrame(f, aAGR), aUpdated,
+ 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 (aAGR && item->GetAnimatedGeometryRoot()->GetAsyncAGR() != aAGR) {
+ mBuilder.MarkFrameForDisplayIfVisible(f, mBuilder.RootReferenceFrame());
+ }
+
+ // TODO: This is here because we sometimes reuse the previous display list
+ // completely. For optimization, we could only restore the state for reused
+ // display items.
+ if (item->RestoreState()) {
+ item->InvalidateItemCacheEntry();
+ }
+
+ // 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());
+ aList->RestoreState();
+
+ if (aKeepLinked) {
+ aList->AppendToTop(&out);
+ }
+ return true;
+}
+
+void RetainedDisplayListBuilder::IncrementSubDocPresShellPaintCount(
+ 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);
+
+ mBuilder.IncrementPresShellPaintCount(presShell);
+}
+
+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) {
+ Maybe<const ActiveScrolledRoot*> asr;
+
+ if (aItem->HasHitTestInfo()) {
+ const HitTestInfo& info =
+ static_cast<nsDisplayHitTestInfoBase*>(aItem)->GetHitTestInfo();
+ asr = SelectContainerASR(info.mClipChain, info.mASR, aContainerASR);
+ } else {
+ asr = aContainerASR;
+ }
+
+ if (!asr) {
+ return;
+ }
+
+ nsDisplayWrapList* wrapList = aItem->AsDisplayWrapList();
+ if (!wrapList) {
+ aItem->SetActiveScrolledRoot(*asr);
+ return;
+ }
+
+ wrapList->SetActiveScrolledRoot(ActiveScrolledRoot::PickAncestor(
+ wrapList->GetFrameActiveScrolledRoot(), *asr));
+
+ wrapList->UpdateHitTestInfoActiveScrolledRoot(*asr);
+}
+
+static void CopyASR(nsDisplayItem* aOld, nsDisplayItem* aNew) {
+ const ActiveScrolledRoot* hitTest = nullptr;
+ if (aOld->HasHitTestInfo()) {
+ MOZ_ASSERT(aNew->HasHitTestInfo());
+ const HitTestInfo& info =
+ static_cast<nsDisplayHitTestInfoBase*>(aOld)->GetHitTestInfo();
+ hitTest = info.mASR;
+ }
+
+ aNew->SetActiveScrolledRoot(aOld->GetActiveScrolledRoot());
+
+ // SetActiveScrolledRoot for most items will also set the hit-test info item's
+ // asr, so we need to manually set that again to what we saved earlier.
+ if (aOld->HasHitTestInfo()) {
+ static_cast<nsDisplayHitTestInfoBase*>(aNew)
+ ->UpdateHitTestInfoActiveScrolledRoot(hitTest);
+ }
+}
+
+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))),
+ 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;
+ 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) {
+ // 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.
+ 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;
+ result.AppendToTop(&mMergedItems);
+ result.mDAG = std::move(mMergedDAG);
+ MOZ_RELEASE_ASSERT(result.mDAG.Length() == result.Count());
+ return result;
+ }
+
+ bool HasMatchingItemInOldList(nsDisplayItem* aItem, OldListIndex* aOutIndex) {
+ nsIFrame::DisplayItemArray* items =
+ aItem->Frame()->GetProperty(nsIFrame::DisplayItems());
+ // 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;
+ for (nsDisplayItemBase* i : *items) {
+ if (i != aItem && i->Frame() == aItem->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
+ nsIFrame::DisplayItemArray* items =
+ aItem->Frame()->GetProperty(nsIFrame::DisplayItems());
+ for (nsDisplayItemBase* i : *items) {
+ 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->GetBottom(); item;
+ item = item->GetAbove()) {
+ 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;
+ while (nsDisplayItem* item = aNewList->RemoveBottom()) {
+ Metrics()->mNewItems++;
+ previousItemIndex = merge.ProcessItemFromNewList(item, previousItemIndex);
+ }
+
+ *aOutList = merge.Finalize();
+ aOutContainerASR = merge.mContainerASR;
+ return merge.mResultIsModified;
+}
+
+static nsIFrame* GetRootFrameForPainting(nsDisplayListBuilder* aBuilder,
+ Document& aDocument) {
+ // Although this is the actual subdocument, it might not be
+ // what painting uses. Walk up to the nsSubDocumentFrame owning
+ // us, and then ask that which subdoc it's going to paint.
+
+ PresShell* presShell = aDocument.GetPresShell();
+ if (!presShell) {
+ return nullptr;
+ }
+ nsView* rootView = presShell->GetViewManager()->GetRootView();
+ if (!rootView) {
+ return nullptr;
+ }
+
+ // There should be an anonymous inner view between the root view
+ // of the subdoc, and the view for the nsSubDocumentFrame.
+ nsView* innerView = rootView->GetParent();
+ if (!innerView) {
+ return nullptr;
+ }
+
+ nsView* subDocView = innerView->GetParent();
+ if (!subDocView) {
+ return nullptr;
+ }
+
+ nsIFrame* subDocFrame = subDocView->GetFrame();
+ if (!subDocFrame) {
+ return nullptr;
+ }
+
+ nsSubDocumentFrame* subdocumentFrame = do_QueryFrame(subDocFrame);
+ MOZ_ASSERT(subdocumentFrame);
+ presShell = subdocumentFrame->GetSubdocumentPresShellForPainting(
+ aBuilder->IsIgnoringPaintSuppression()
+ ? nsSubDocumentFrame::IGNORE_PAINT_SUPPRESSION
+ : 0);
+ return presShell ? presShell->GetRootFrame() : nullptr;
+}
+
+static void TakeAndAddModifiedAndFramesWithPropsFromRootFrame(
+ nsDisplayListBuilder* aBuilder, nsTArray<nsIFrame*>* aModifiedFrames,
+ nsTArray<nsIFrame*>* aFramesWithProps, nsIFrame* aRootFrame,
+ Document& aDoc) {
+ MOZ_ASSERT(aRootFrame);
+
+ if (RetainedDisplayListData* data = GetRetainedDisplayListData(aRootFrame)) {
+ for (auto it = data->Iterator(); !it.Done(); it.Next()) {
+ nsIFrame* frame = it.Key();
+ const RetainedDisplayListData::FrameFlags& flags = it.Data();
+
+ if (flags & RetainedDisplayListData::FrameFlags::Modified) {
+ aModifiedFrames->AppendElement(frame);
+ }
+
+ if (flags & RetainedDisplayListData::FrameFlags::HasProps) {
+ aFramesWithProps->AppendElement(frame);
+ }
+
+ if (flags & RetainedDisplayListData::FrameFlags::HadWillChange) {
+ aBuilder->RemoveFromWillChangeBudgets(frame);
+ }
+ }
+
+ data->Clear();
+ }
+
+ auto recurse = [&](Document& aSubDoc) {
+ if (nsIFrame* rootFrame = GetRootFrameForPainting(aBuilder, aSubDoc)) {
+ TakeAndAddModifiedAndFramesWithPropsFromRootFrame(
+ aBuilder, aModifiedFrames, aFramesWithProps, rootFrame, aSubDoc);
+ }
+ return CallState::Continue;
+ };
+ aDoc.EnumerateSubDocuments(recurse);
+}
+
+static void GetModifiedAndFramesWithProps(
+ nsDisplayListBuilder* aBuilder, nsTArray<nsIFrame*>* aOutModifiedFrames,
+ nsTArray<nsIFrame*>* aOutFramesWithProps) {
+ nsIFrame* rootFrame = aBuilder->RootReferenceFrame();
+ MOZ_ASSERT(rootFrame);
+
+ Document* rootDoc = rootFrame->PresContext()->Document();
+ TakeAndAddModifiedAndFramesWithPropsFromRootFrame(
+ aBuilder, aOutModifiedFrames, aOutFramesWithProps, rootFrame, *rootDoc);
+}
+
+// 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) {
+ nsIFrame::DisplayItemArray* items =
+ aFrame->GetProperty(nsIFrame::DisplayItems());
+ if (!items) {
+ return nullptr;
+ }
+
+ for (nsDisplayItemBase* i : *items) {
+ if (i->HasChildren()) {
+ return static_cast<nsDisplayItem*>(i);
+ }
+ }
+ return nullptr;
+}
+
+static bool IsInPreserve3DContext(const nsIFrame* aFrame) {
+ return aFrame->Extend3DContext() ||
+ aFrame->Combines3DTransformWithAncestors();
+}
+
+static bool ProcessFrameInternal(nsIFrame* aFrame,
+ nsDisplayListBuilder* aBuilder,
+ AnimatedGeometryRoot** 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.
+ AnimatedGeometryRoot* 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,
+ &currentFrame);
+ 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 (currentFrame != aBuilder->RootReferenceFrame() &&
+ currentFrame->IsStackingContext() &&
+ currentFrame->IsFixedPosContainingBlock()) {
+ 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->ReferenceFrame()) {
+ previousVisible -= wrapperItem->ToReferenceFrame();
+ } else {
+ MOZ_ASSERT(wrapperItem->ReferenceFrameForChildren() ==
+ wrapperItem->Frame());
+ }
+
+ 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, AnimatedGeometryRoot** 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.
+ AnimatedGeometryRoot* agr =
+ aBuilder->FindAnimatedGeometryRootFor(aFrame)->GetAsyncAGR();
+
+ 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, &agr, 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", agr);
+ *aOutModifiedAGR = agr;
+ } else if (agr && *aOutModifiedAGR != agr) {
+ 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(nsIFrame::kFloatList),
+ aExtraFrames);
+ AddFramesForContainingBlock(f, f->GetChildList(f->GetAbsoluteListID()),
+ 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,
+ AnimatedGeometryRoot** 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, 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, 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;
+ }
+ }
+
+ 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) {
+ if (f->HasOverrideDirtyRegion()) {
+ f->SetHasOverrideDirtyRegion(false);
+ f->RemoveProperty(nsDisplayListBuilder::DisplayListBuildingRect());
+ f->RemoveProperty(
+ nsDisplayListBuilder::DisplayListBuildingDisplayPortRect());
+ }
+
+ f->SetFrameIsModified(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(&mBuilder, &modifiedFrames.Frames(),
+ &framesWithProps.Frames());
+}
+
+PartialUpdateResult RetainedDisplayListBuilder::AttemptPartialUpdate(
+ nscolor aBackstop) {
+ mBuilder.RemoveModifiedWindowRegions();
+
+ if (mBuilder.ShouldSyncDecodeImages()) {
+ MarkFramesWithItemsAndImagesModified(&mList);
+ }
+
+ InvalidateCaretFramesIfNeeded();
+
+ mBuilder.EnterPresShell(mBuilder.RootReferenceFrame());
+
+ // 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;
+ GetModifiedAndFramesWithProps(&mBuilder, &modifiedFrames.Frames(),
+ &framesWithProps.Frames());
+
+ // Do not allow partial builds if the |ShouldBuildPartial()| heuristic fails.
+ bool shouldBuildPartial = ShouldBuildPartial(modifiedFrames.Frames());
+
+ nsRect modifiedDirty;
+ AnimatedGeometryRoot* modifiedAGR = nullptr;
+ PartialUpdateResult result = PartialUpdateResult::NoChange;
+ if (!shouldBuildPartial ||
+ !ComputeRebuildRegion(modifiedFrames.Frames(), &modifiedDirty,
+ &modifiedAGR, framesWithProps.Frames()) ||
+ !PreProcessDisplayList(&mList, modifiedAGR, result)) {
+ mBuilder.SetPartialBuildFailed(true);
+ mBuilder.LeavePresShell(mBuilder.RootReferenceFrame(), nullptr);
+ mList.DeleteAll(&mBuilder);
+ return PartialUpdateResult::Failed;
+ }
+
+ // This is normally handled by EnterPresShell, but we skipped it so that we
+ // didn't call MarkFrameForDisplayIfVisible before ComputeRebuildRegion.
+ nsIScrollableFrame* sf = mBuilder.RootReferenceFrame()
+ ->PresShell()
+ ->GetRootScrollFrameAsScrollable();
+ if (sf) {
+ nsCanvasFrame* canvasFrame = do_QueryFrame(sf->GetScrolledFrame());
+ if (canvasFrame) {
+ mBuilder.MarkFrameForDisplayIfVisible(canvasFrame,
+ mBuilder.RootReferenceFrame());
+ }
+ }
+
+ modifiedDirty.IntersectRect(
+ modifiedDirty,
+ mBuilder.RootReferenceFrame()->InkOverflowRectRelativeToSelf());
+
+ mBuilder.SetDirtyRect(modifiedDirty);
+ mBuilder.SetPartialUpdate(true);
+ mBuilder.SetPartialBuildFailed(false);
+
+ nsDisplayList modifiedDL;
+ mBuilder.RootReferenceFrame()->BuildDisplayListForStackingContext(
+ &mBuilder, &modifiedDL);
+ if (!modifiedDL.IsEmpty()) {
+ nsLayoutUtils::AddExtraBackgroundItems(
+ &mBuilder, &modifiedDL, mBuilder.RootReferenceFrame(),
+ nsRect(nsPoint(0, 0), mBuilder.RootReferenceFrame()->GetSize()),
+ mBuilder.RootReferenceFrame()->InkOverflowRectRelativeToSelf(),
+ aBackstop);
+ }
+ mBuilder.SetPartialUpdate(false);
+
+ if (mBuilder.PartialBuildFailed()) {
+ mBuilder.LeavePresShell(mBuilder.RootReferenceFrame(), nullptr);
+ 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.
+ Maybe<const ActiveScrolledRoot*> dummy;
+ if (MergeDisplayLists(&modifiedDL, &mList, &mList, dummy)) {
+ result = PartialUpdateResult::Updated;
+ }
+
+ // printf_stderr("Painting --- Merged list:\n");
+ // nsIFrame::PrintDisplayList(&mBuilder, mList);
+
+ mBuilder.LeavePresShell(mBuilder.RootReferenceFrame(), List());
+ return result;
+}
diff --git a/layout/painting/RetainedDisplayListBuilder.h b/layout/painting/RetainedDisplayListBuilder.h
new file mode 100644
index 0000000000..0c4cd73598
--- /dev/null
+++ b/layout/painting/RetainedDisplayListBuilder.h
@@ -0,0 +1,261 @@
+/* -*- 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/TypedEnumBits.h"
+
+class nsWindowSizes;
+
+/**
+ * RetainedDisplayListData contains frame invalidation information. It is stored
+ * in root frames, and used by RetainedDisplayListBuilder.
+ * Currently this is implemented as a map of frame pointers to flags.
+ */
+struct RetainedDisplayListData {
+ NS_DECLARE_FRAME_PROPERTY_DELETABLE(DisplayListData, RetainedDisplayListData)
+
+ enum class FrameFlags : uint8_t {
+ None = 0,
+ Modified = 1 << 0,
+ HasProps = 1 << 1,
+ HadWillChange = 1 << 2
+ };
+
+ RetainedDisplayListData() : mModifiedFramesCount(0) {}
+
+ /**
+ * Adds the frame to modified frames list.
+ */
+ void AddModifiedFrame(nsIFrame* aFrame);
+
+ /**
+ * Removes all the frames from this RetainedDisplayListData.
+ */
+ void Clear() {
+ mFrames.Clear();
+ mModifiedFramesCount = 0;
+ }
+
+ /**
+ * Returns a mutable reference to flags set for the given |aFrame|. If the
+ * frame does not exist in this RetainedDisplayListData, it is added with
+ * default constructible flags FrameFlags::None.
+ */
+ FrameFlags& Flags(nsIFrame* aFrame) { return mFrames.GetOrInsert(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); }
+
+ /**
+ * Returns an iterator to the underlying frame storage.
+ */
+ auto Iterator() { return mFrames.Iter(); }
+
+ /**
+ * Returns the count of modified frames in this RetainedDisplayListData.
+ */
+ uint32_t ModifiedFramesCount() const { return mModifiedFramesCount; }
+
+ /**
+ * Removes the given |aFrame| from this RetainedDisplayListData.
+ */
+ bool Remove(nsIFrame* aFrame) { return mFrames.Remove(aFrame); }
+
+ private:
+ nsDataHashtable<nsPtrHashKey<nsIFrame>, FrameFlags> mFrames;
+ uint32_t mModifiedFramesCount;
+};
+
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(RetainedDisplayListData::FrameFlags)
+
+/**
+ * Returns RetainedDisplayListData property for the given |aRootFrame|, or
+ * nullptr if the property is not set.
+ */
+RetainedDisplayListData* GetRetainedDisplayListData(nsIFrame* aRootFrame);
+
+/**
+ * Returns RetainedDisplayListData property for the given |aRootFrame|. Creates
+ * and sets a new RetainedDisplayListData property if it is not already set.
+ */
+RetainedDisplayListData* GetOrSetRetainedDisplayListData(nsIFrame* aRootFrame);
+
+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;
+};
+
+struct RetainedDisplayListBuilder {
+ RetainedDisplayListBuilder(nsIFrame* aReferenceFrame,
+ nsDisplayListBuilderMode aMode, bool aBuildCaret)
+ : mBuilder(aReferenceFrame, aMode, aBuildCaret, true) {}
+ ~RetainedDisplayListBuilder() { mList.DeleteAll(&mBuilder); }
+
+ nsDisplayListBuilder* Builder() { return &mBuilder; }
+
+ nsDisplayList* List() { return &mList; }
+
+ RetainedDisplayListMetrics* Metrics() { return &mMetrics; }
+
+ PartialUpdateResult AttemptPartialUpdate(nscolor aBackstop);
+
+ /**
+ * Iterates through the display list builder reference frame document and
+ * subdocuments, and clears the modified frame lists from the root frames.
+ * Also clears the frame properties set by RetainedDisplayListBuilder for all
+ * the frames in the modified frame lists.
+ */
+ void ClearFramesWithProps();
+ void AddSizeOfIncludingThis(nsWindowSizes&) const;
+
+ NS_DECLARE_FRAME_PROPERTY_DELETABLE(Cached, RetainedDisplayListBuilder)
+
+ private:
+ 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,
+ AnimatedGeometryRoot* aAGR,
+ PartialUpdateResult& aUpdated,
+ 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,
+ AnimatedGeometryRoot** aOutModifiedAGR,
+ nsTArray<nsIFrame*>& aOutFramesWithProps);
+
+ bool ProcessFrame(nsIFrame* aFrame, nsDisplayListBuilder* aBuilder,
+ nsIFrame* aStopAtFrame,
+ nsTArray<nsIFrame*>& aOutFramesWithProps,
+ const bool aStopAtStackingContext, nsRect* aOutDirty,
+ AnimatedGeometryRoot** aOutModifiedAGR);
+
+ friend class MergeState;
+
+ nsDisplayListBuilder mBuilder;
+ RetainedDisplayList mList;
+ nsRect mPreviousVisibleRect;
+ WeakFrame mPreviousCaret;
+ RetainedDisplayListMetrics mMetrics;
+};
+
+#endif // RETAINEDDISPLAYLISTBUILDER_H_
diff --git a/layout/painting/RetainedDisplayListHelpers.h b/layout/painting/RetainedDisplayListHelpers.h
new file mode 100644
index 0000000000..f2f29661cc
--- /dev/null
+++ b/layout/painting/RetainedDisplayListHelpers.h
@@ -0,0 +1,181 @@
+/* -*- 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"
+
+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;
+};
+
+struct 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);
+
+#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/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/crashtests.list b/layout/painting/crashtests/crashtests.list
new file mode 100644
index 0000000000..aea54e7537
--- /dev/null
+++ b/layout/painting/crashtests/crashtests.list
@@ -0,0 +1,26 @@
+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
+load 1430589-1.html
+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) load 1547420-1.html
+load 1549909.html
+asserts(6) load 1551389-1.html # bug 847368
+asserts(0-2) load 1555819-1.html
+load 1574392.html
+load 1589800-1.html
diff --git a/layout/painting/moz.build b/layout/painting/moz.build
new file mode 100644
index 0000000000..4a1aad01ee
--- /dev/null
+++ b/layout/painting/moz.build
@@ -0,0 +1,74 @@
+# -*- 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",
+ "FrameLayerBuilder.h",
+ "LayerState.h",
+ "MatrixStack.h",
+ "nsCSSRenderingBorders.h",
+ "nsCSSRenderingGradients.h",
+ "nsDisplayItemTypes.h",
+ "nsDisplayItemTypesList.h",
+ "nsDisplayList.h",
+ "nsDisplayListArenaTypes.h",
+ "nsDisplayListInvalidation.h",
+ "nsImageRenderer.h",
+ "RetainedDisplayListBuilder.h",
+ "RetainedDisplayListHelpers.h",
+ "TransformClipNode.h",
+]
+
+EXPORTS.mozilla += [
+ "PaintTracker.h",
+]
+
+UNIFIED_SOURCES += [
+ "ActiveLayerTracker.cpp",
+ "DashedCornerFinder.cpp",
+ "DisplayItemClip.cpp",
+ "DisplayItemClipChain.cpp",
+ "DisplayListClipState.cpp",
+ "DottedCornerFinder.cpp",
+ "FrameLayerBuilder.cpp",
+ "MaskLayerImageCache.cpp",
+ "nsCSSRendering.cpp",
+ "nsCSSRenderingBorders.cpp",
+ "nsCSSRenderingGradients.cpp",
+ "nsDisplayList.cpp",
+ "nsDisplayListInvalidation.cpp",
+ "nsImageRenderer.cpp",
+ "PaintTracker.cpp",
+ "RetainedDisplayListBuilder.cpp",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+LOCAL_INCLUDES += [
+ "/docshell/base",
+ "/dom/base",
+ "/gfx/2d",
+ "/layout/base",
+ "/layout/generic",
+ "/layout/style",
+ "/layout/tables",
+ "/layout/xul",
+]
+
+LOCAL_INCLUDES += CONFIG["SKIA_INCLUDES"]
+
+FINAL_LIBRARY = "xul"
+
+CXXFLAGS += CONFIG["MOZ_CAIRO_CFLAGS"]
+
+if CONFIG["CC_TYPE"] in ("clang", "gcc"):
+ CXXFLAGS += ["-Wno-error=shadow"]
diff --git a/layout/painting/nsCSSRendering.cpp b/layout/painting/nsCSSRendering.cpp
new file mode 100644
index 0000000000..7fb4fbda4c
--- /dev/null
+++ b/layout/painting/nsCSSRendering.cpp
@@ -0,0 +1,4903 @@
+/* -*- 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 <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/PathHelpers.h"
+#include "mozilla/HashFunctions.h"
+#include "mozilla/MathAlgorithms.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/SVGImageContext.h"
+#include "gfxFont.h"
+#include "ScaledFontBase.h"
+#include "skia/include/core/SkTextBlob.h"
+
+#include "BorderConsts.h"
+#include "nsStyleConsts.h"
+#include "nsPresContext.h"
+#include "nsIFrame.h"
+#include "nsIFrameInlines.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 "nsCSSRendering.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 "GeckoProfiler.h"
+#include "nsCSSRenderingBorders.h"
+#include "mozilla/css/ImageLoader.h"
+#include "ImageContainer.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 InlineBackgroundData* gInlineBGData = nullptr;
+
+// 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() {
+ delete gInlineBGData;
+ 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;
+ }
+
+ image::ImgDrawResult result;
+ Maybe<nsCSSBorderImageRenderer> bir =
+ nsCSSBorderImageRenderer::CreateBorderImageRenderer(
+ aForFrame->PresContext(), aForFrame, aBorderArea, aStyleBorder,
+ aItem->GetPaintRect(), 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]));
+
+ Document* document = nullptr;
+ nsIContent* content = aForFrame->GetContent();
+ if (content) {
+ document = content->OwnerDoc();
+ }
+
+ return nsCSSBorderRenderer(
+ aPresContext, document, 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)) {
+ 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);
+}
+
+static nsRect GetOutlineInnerRect(nsIFrame* aFrame) {
+ nsRect* savedOutlineInnerRect =
+ aFrame->GetProperty(nsIFrame::OutlineInnerRectProperty());
+ if (savedOutlineInnerRect) {
+ return *savedOutlineInnerRect;
+ }
+
+ // FIXME bug 1221888
+ NS_ERROR("we should have saved a frame property");
+ return nsRect(nsPoint(0, 0), aFrame->GetSize());
+}
+
+Maybe<nsCSSBorderRenderer> nsCSSRendering::CreateBorderRendererForOutline(
+ nsPresContext* aPresContext, gfxContext* aRenderingContext,
+ nsIFrame* aForFrame, const nsRect& aDirtyRect, const nsRect& aBorderArea,
+ ComputedStyle* aStyle) {
+ nscoord twipsRadii[8];
+
+ // Get our ComputedStyle's color struct.
+ const nsStyleOutline* ourOutline = aStyle->StyleOutline();
+
+ if (!ourOutline->ShouldPaintOutline()) {
+ // Empty outline
+ return Nothing();
+ }
+
+ nsRect innerRect;
+ if (
+#ifdef MOZ_XUL
+ aStyle->GetPseudoType() == PseudoStyleType::XULTree
+#else
+ false
+#endif
+ ) {
+ innerRect = aBorderArea;
+ } else {
+ innerRect = GetOutlineInnerRect(aForFrame) + aBorderArea.TopLeft();
+ }
+ nscoord offset = ourOutline->mOutlineOffset.ToAppUnits();
+ 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();
+
+ nsRect outerRect = innerRect;
+ outerRect.Inflate(width);
+
+ // get the radius for our outline
+ nsIFrame::ComputeBorderRadii(ourOutline->mOutlineRadius, aBorderArea.Size(),
+ outerRect.Size(), Sides(), twipsRadii);
+
+ // Get our conversion values
+ nscoord oneDevPixel = aPresContext->DevPixelsToAppUnits(1);
+
+ // get the outer rectangles
+ Rect oRect(NSRectToRect(outerRect, oneDevPixel));
+
+ // convert the radii
+ nsMargin outlineMargin(width, width, width, width);
+ RectCornerRadii outlineRadii;
+ ComputePixelRadii(twipsRadii, oneDevPixel, &outlineRadii);
+
+ StyleBorderStyle outlineStyle;
+ if (ourOutline->mOutlineStyle.IsAuto()) {
+ if (StaticPrefs::layout_css_outline_style_auto_enabled()) {
+ nsITheme* theme = aPresContext->Theme();
+ if (theme->ThemeSupportsWidget(aPresContext, aForFrame,
+ StyleAppearance::FocusOutline)) {
+ theme->DrawWidgetBackground(aRenderingContext, aForFrame,
+ StyleAppearance::FocusOutline, innerRect,
+ aDirtyRect);
+ return Nothing();
+ }
+ }
+ 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();
+ }
+
+ 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};
+
+ // convert the border widths
+ Float outlineWidths[4] = {
+ Float(width) / oneDevPixel, Float(width) / oneDevPixel,
+ Float(width) / oneDevPixel, Float(width) / oneDevPixel};
+ Rect dirtyRect = NSRectToRect(aDirtyRect, oneDevPixel);
+
+ Document* document = nullptr;
+ nsIContent* content = aForFrame->GetContent();
+ if (content) {
+ document = content->OwnerDoc();
+ }
+
+ DrawTarget* dt =
+ aRenderingContext ? aRenderingContext->GetDrawTarget() : nullptr;
+ nsCSSBorderRenderer br(aPresContext, document, dt, dirtyRect, oRect,
+ outlineStyles, outlineWidths, outlineRadii,
+ outlineColors, !aForFrame->BackfaceIsHidden(),
+ Nothing());
+
+ return Some(br);
+}
+
+void nsCSSRendering::PaintOutline(nsPresContext* aPresContext,
+ gfxContext& aRenderingContext,
+ nsIFrame* aForFrame, const nsRect& aDirtyRect,
+ const nsRect& aBorderArea,
+ ComputedStyle* aStyle) {
+ Maybe<nsCSSBorderRenderer> br = CreateBorderRendererForOutline(
+ aPresContext, &aRenderingContext, aForFrame, aDirtyRect, aBorderArea,
+ 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, nullptr, 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, NSToCoordRoundWithClamp);
+ // Adjust aTopLeftCoord by the specified % of the extra space.
+ *aTopLeftCoord = aCoord.Resolve(extraSpace, 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);
+}
+
+nsIFrame* nsCSSRendering::FindNonTransparentBackgroundFrame(
+ nsIFrame* aFrame, bool aStartAtParent /*= false*/) {
+ NS_ASSERTION(aFrame,
+ "Cannot find NonTransparentBackgroundFrame in a null frame");
+
+ nsIFrame* frame = nullptr;
+ if (aStartAtParent) {
+ frame = nsLayoutUtils::GetParentOrPlaceholderFor(aFrame);
+ }
+ if (!frame) {
+ frame = aFrame;
+ }
+
+ while (frame) {
+ // No need to call GetVisitedDependentColor because it always uses
+ // this alpha component anyway.
+ if (NS_GET_A(frame->StyleBackground()->BackgroundColor(frame)) > 0) {
+ break;
+ }
+
+ if (frame->IsThemed()) {
+ break;
+ }
+
+ nsIFrame* parent = nsLayoutUtils::GetParentOrPlaceholderFor(frame);
+ if (!parent) {
+ break;
+ }
+
+ frame = parent;
+ }
+ return frame;
+}
+
+// Returns true if aFrame is a canvas frame.
+// We need to treat the viewport as canvas because, even though
+// it does not actually paint a background, we need to get the right
+// background style so we correctly detect transparent documents.
+bool nsCSSRendering::IsCanvasFrame(const nsIFrame* aFrame) {
+ LayoutFrameType frameType = aFrame->Type();
+ return frameType == LayoutFrameType::Canvas ||
+ frameType == LayoutFrameType::XULRoot ||
+ frameType == LayoutFrameType::PageContent ||
+ frameType == LayoutFrameType::Viewport;
+}
+
+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) {
+ return aForFrame;
+ }
+
+ nsIFrame* bodyFrame = bodyContent->GetPrimaryFrame();
+ if (!bodyFrame) {
+ 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|,
+ * |nsRootBoxFrame|, 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| returns true if a background should be painted, and
+ * the resulting ComputedStyle to use for the background information
+ * will be filled in to |aBackground|.
+ */
+ComputedStyle* nsCSSRendering::FindRootFrameBackground(nsIFrame* aForFrame) {
+ return FindBackgroundStyleFrame(aForFrame)->Style();
+}
+
+inline bool FindElementBackground(const nsIFrame* aForFrame,
+ nsIFrame* aRootElementFrame) {
+ 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) {
+ return true; // A pseudo-element frame.
+ }
+
+ // 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) {
+ return true;
+ }
+
+ const nsStyleBackground* htmlBG = aRootElementFrame->StyleBackground();
+ return !htmlBG->IsTransparent(aRootElementFrame);
+}
+
+bool nsCSSRendering::FindBackgroundFrame(const nsIFrame* aForFrame,
+ nsIFrame** aBackgroundFrame) {
+ nsIFrame* rootElementFrame =
+ aForFrame->PresShell()->FrameConstructor()->GetRootElementStyleFrame();
+ if (IsCanvasFrame(aForFrame)) {
+ *aBackgroundFrame = FindCanvasBackgroundFrame(aForFrame, rootElementFrame);
+ return true;
+ }
+
+ *aBackgroundFrame = const_cast<nsIFrame*>(aForFrame);
+ return FindElementBackground(aForFrame, rootElementFrame);
+}
+
+bool nsCSSRendering::FindBackground(const nsIFrame* aForFrame,
+ ComputedStyle** aBackgroundSC) {
+ nsIFrame* backgroundFrame = nullptr;
+ if (FindBackgroundFrame(aForFrame, &backgroundFrame)) {
+ *aBackgroundSC = backgroundFrame->Style();
+ return true;
+ }
+ return false;
+}
+
+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);
+
+ 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");
+
+ ComputedStyle* sc;
+ if (!FindBackground(aParams.frame, &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(
+ LayerManager* 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;
+ if (!FindBackground(aParams.frame, &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->IsFrameOfType(nsIFrame::eSVG) &&
+ !aForFrame->IsSVGOuterSVGFrame());
+
+ // 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->IsFrameOfType(nsIFrame::eSVG) ||
+ aForFrame->IsSVGOuterSVGFrame());
+
+ // 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::InsetBorderRadii(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, 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,
+ 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,
+ 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 = IsCanvasFrame(aParams.frame);
+ 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];
+
+ if (!aParams.bgClipRect) {
+ bool isBottomLayer = (i == layers.mImageCount - 1);
+ if (currentBackgroundClip != layer.mClip || isBottomLayer) {
+ currentBackgroundClip = layer.mClip;
+ ImageLayerClipState currentLayerClipState;
+ if (isBottomLayer) {
+ currentLayerClipState = clipState;
+ } else {
+ // 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, &currentLayerClipState);
+ }
+ 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,
+ clipState.mBGClipArea, layer, nullptr);
+ result &= state.mImageRenderer.PrepareResult();
+
+ // Skip the layer painting code if we found the dirty region is empty.
+ if (clipState.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(),
+ clipState.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()) {
+ return 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->IsFrameOfType(nsIFrame::eSVG) &&
+ !aForFrame->IsSVGOuterSVGFrame());
+ *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->IsFrameOfType(nsIFrame::eSVG) ||
+ aForFrame->IsSVGOuterSVGFrame());
+
+ 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);
+ }
+ }
+ }
+ }
+ *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;
+ }
+
+ 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 uint8_t aStyle, const Rect& aClippedRect,
+ const Float aICoordInFrame, const Float aCycleLength, bool aVertical) {
+ switch (aStyle) {
+ case NS_STYLE_TEXT_DECORATION_STYLE_DOTTED:
+ case NS_STYLE_TEXT_DECORATION_STYLE_DASHED:
+ case NS_STYLE_TEXT_DECORATION_STYLE_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) {
+ // https://skia.org/user/api/SkTextBlob_Reference#Text_Blob_Text_Intercepts
+ int count = aBlob->getIntercepts(aBounds, nullptr);
+ if (count < 2) {
+ return;
+ }
+ aBlob->getIntercepts(aBounds, aIntercepts.AppendElements(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 != NS_STYLE_TEXT_DECORATION_STYLE_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.GetStringStart(), iter.GetStringEnd()),
+ aParams.provider) /
+ appUnitsPerDevPixel;
+ };
+
+ while (iter.NextRun()) {
+ if (iter.GetGlyphRun()->mOrientation ==
+ mozilla::gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT ||
+ (iter.GetGlyphRun()->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.GetGlyphRun()->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.GetStringStart(), iter.GetStringEnd(),
+ (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 NS_STYLE_TEXT_DECORATION_STYLE_SOLID:
+ case NS_STYLE_TEXT_DECORATION_STYLE_DOUBLE:
+ break;
+ case NS_STYLE_TEXT_DECORATION_STYLE_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 NS_STYLE_TEXT_DECORATION_STYLE_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 NS_STYLE_TEXT_DECORATION_STYLE_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 NS_STYLE_TEXT_DECORATION_STYLE_SOLID:
+ case NS_STYLE_TEXT_DECORATION_STYLE_DOTTED:
+ case NS_STYLE_TEXT_DECORATION_STYLE_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 NS_STYLE_TEXT_DECORATION_STYLE_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,
+ NS_STYLE_TEXT_DECORATION_STYLE_SOLID);
+ textDrawer->AppendDecoration(p1b, p2b, lineThickness, aParams.vertical,
+ color,
+ NS_STYLE_TEXT_DECORATION_STYLE_SOLID);
+ } else {
+ aDrawTarget.StrokeLine(p1a, p2a, colorPat, strokeOptions, drawOptions);
+ aDrawTarget.StrokeLine(p1b, p2b, colorPat, strokeOptions, drawOptions);
+ }
+ return;
+ }
+ case NS_STYLE_TEXT_DECORATION_STYLE_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 : pt.x;
+ Float& ptBCoord = aParams.vertical ? pt.x : pt.y;
+ 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 != NS_STYLE_TEXT_DECORATION_STYLE_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 != NS_STYLE_TEXT_DECORATION_STYLE_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 != NS_STYLE_TEXT_DECORATION_STYLE_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 <= NS_STYLE_TEXT_DECORATION_STYLE_WAVY,
+ "Invalid aStyle value");
+
+ if (aParams.style == NS_STYLE_TEXT_DECORATION_STYLE_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 == NS_STYLE_TEXT_DECORATION_STYLE_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 == NS_STYLE_TEXT_DECORATION_STYLE_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);
+ mContext =
+ mAlphaBoxBlur.Init(aDestinationCtx, rect, spreadRadius, blurRadius,
+ &dirtyRect, &skipRect, useHardwareAccel);
+ } else {
+ mContext =
+ mAlphaBoxBlur.Init(aDestinationCtx, rect, spreadRadius, blurRadius,
+ &dirtyRect, nullptr, useHardwareAccel);
+ }
+
+ 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.
+ gfx::Size 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.width * aInnerClipRectRadii[i].width);
+ aInnerClipRectRadii[i].height =
+ std::floor(scale.height * 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..262ecdc239
--- /dev/null
+++ b/layout/painting/nsCSSRendering.h
@@ -0,0 +1,938 @@
+/* -*- 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 LayerManager;
+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::LayerManager LayerManager;
+ 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> CreateBorderRendererForOutline(
+ nsPresContext* aPresContext, gfxContext* aRenderingContext,
+ nsIFrame* aForFrame, const nsRect& aDirtyRect, const nsRect& aBorderArea,
+ mozilla::ComputedStyle* aStyle);
+
+ static ImgDrawResult 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);
+
+ static void CreateWebRenderCommandsForNullBorder(
+ 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(
+ 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);
+
+ /**
+ * Render the outline for an element using css rendering rules
+ * for borders.
+ */
+ static void PaintOutline(nsPresContext* aPresContext,
+ gfxContext& aRenderingContext, nsIFrame* aForFrame,
+ const nsRect& aDirtyRect, const nsRect& aBorderArea,
+ 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);
+
+ /**
+ * @return true if |aFrame| is a canvas frame, in the CSS sense.
+ */
+ static bool IsCanvasFrame(const nsIFrame* aFrame);
+
+ /**
+ * Fill in an aBackgroundSC to be used to paint the background
+ * for an element. This applies the rules for propagating
+ * backgrounds between BODY, the root element, and the canvas.
+ * @return true if there is some meaningful background.
+ */
+ static bool FindBackground(const nsIFrame* aForFrame,
+ mozilla::ComputedStyle** aBackgroundSC);
+ static bool FindBackgroundFrame(const nsIFrame* aForFrame,
+ nsIFrame** aBackgroundFrame);
+
+ /**
+ * 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);
+
+ /**
+ * Returns background style information for the canvas.
+ *
+ * @param aForFrame
+ * the frame used to represent the canvas, in the CSS sense (i.e.
+ * nsCSSRendering::IsCanvasFrame(aForFrame) must be true)
+ * @param aRootElementFrame
+ * the frame representing the root element of the document
+ * @param aBackground
+ * contains background style information for the canvas on return
+ */
+
+ static nsIFrame* FindCanvasBackgroundFrame(const nsIFrame* aForFrame,
+ nsIFrame* aRootElementFrame) {
+ MOZ_ASSERT(IsCanvasFrame(aForFrame), "not a canvas frame");
+ if (aRootElementFrame) {
+ return 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);
+ }
+
+ static mozilla::ComputedStyle* FindCanvasBackground(
+ nsIFrame* aForFrame, nsIFrame* aRootElementFrame) {
+ return FindCanvasBackgroundFrame(aForFrame, aRootElementFrame)->Style();
+ }
+
+ /**
+ * Find a frame which draws a non-transparent background,
+ * for various table-related and HR-related backwards-compatibility hacks.
+ * This function will also stop if it finds themed frame which might draw
+ * background.
+ *
+ * Be very hesitant if you're considering calling this function -- it's
+ * usually not what you want.
+ */
+ static nsIFrame* FindNonTransparentBackgroundFrame(
+ nsIFrame* aFrame, bool aStartAtParent = false);
+
+ /**
+ * Determine the background color to draw taking into account print settings.
+ */
+ static nscolor DetermineBackgroundColor(nsPresContext* aPresContext,
+ 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 = 0x16,
+ };
+
+ 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,
+ mozilla::ComputedStyle* mBackgroundSC, const nsStyleBorder& aBorder);
+
+ static bool CanBuildWebRenderDisplayItemsForStyleImageLayer(
+ LayerManager* 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, 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, 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 such as
+ // NS_STYLE_TEXT_DECORATION_STYLE_*.
+ uint8_t style = NS_STYLE_TEXT_DECORATION_STYLE_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 mBlendMode) {
+ switch (mBlendMode) {
+ 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;
+ 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
+ * NS_STYLE_TEXT_DECORATION_STYLE_DOTTED or
+ * NS_STYLE_TEXT_DECORATION_STYLE_DASHED or
+ * NS_STYLE_TEXT_DECORATION_STYLE_WAVY.
+ * @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 uint8_t 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;
+ RefPtr<gfxContext> mContext;
+ 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..5ab531f244
--- /dev/null
+++ b/layout/painting/nsCSSRenderingBorders.cpp
@@ -0,0 +1,3899 @@
+/* -*- 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 "nsLayoutUtils.h"
+#include "nsStyleConsts.h"
+#include "nsContentUtils.h"
+#include "nsCSSColorUtils.h"
+#include "nsCSSRendering.h"
+#include "nsCSSRenderingGradients.h"
+#include "nsDisplayList.h"
+#include "GeckoProfiler.h"
+#include "nsExpirationTracker.h"
+#include "nsIScriptError.h"
+#include "nsClassHashtable.h"
+#include "nsPresContext.h"
+#include "nsStyleStruct.h"
+#include "gfx2DGlue.h"
+#include "gfxGradientCache.h"
+#include "mozilla/layers/StackingContextHelper.h"
+#include "mozilla/layers/RenderRootStateManager.h"
+#include "mozilla/layers/WebRenderLayerManager.h"
+#include "mozilla/Range.h"
+#include <algorithm>
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+using namespace mozilla::image;
+using mozilla::dom::Document;
+
+#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, const Document* aDocument,
+ 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),
+ mDocument(aDocument),
+ 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;
+
+ 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 (mDocument) {
+ if (!mPresContext->HasWarnedAboutTooLargeDashedOrDottedRadius()) {
+ mPresContext->SetHasWarnedAboutTooLargeDashedOrDottedRadius();
+ nsContentUtils::ReportToConsole(
+ nsIScriptError::warningFlag, "CSS"_ns, mDocument,
+ 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
+ Float startAngle = (c * M_PI) / 2.0f - M_PI,
+ endAngle = startAngle + M_PI / 2.0f, 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 (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);
+
+ 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(LayoutDeviceSize::FromUnknownSize(mBorderRadii[0]),
+ LayoutDeviceSize::FromUnknownSize(mBorderRadii[1]),
+ LayoutDeviceSize::FromUnknownSize(mBorderRadii[3]),
+ LayoutDeviceSize::FromUnknownSize(mBorderRadii[2]));
+
+ 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();
+ mImageRenderer.PurgeCacheForViewportChange(svgViewportSize,
+ hasIntrinsicRatio);
+
+ // 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];
+ float outset[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;
+
+ // The outset is already taken into account by the adjustments to mArea
+ // in our constructor. We use mArea as our dest rect so we can just supply
+ // zero outsets to WebRender.
+ outset[i] = 0.0f;
+ }
+
+ 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);
+
+ Maybe<SVGImageContext> svgContext;
+ gfx::IntSize decodeSize =
+ nsLayoutUtils::ComputeImageContainerDrawingParameters(
+ img, aForFrame, imageRect, aSc, flags, svgContext);
+
+ RefPtr<layers::ImageContainer> container;
+ drawResult = img->GetImageContainerAtSize(aManager->LayerManager(),
+ decodeSize, svgContext, flags,
+ getter_AddRefs(container));
+ if (!container) {
+ break;
+ }
+
+ mozilla::wr::ImageRendering rendering = wr::ToImageRendering(
+ nsLayoutUtils::GetSamplingFilterForFrame(aItem->Frame()));
+ gfx::IntSize size;
+ Maybe<wr::ImageKey> key = aManager->CommandBuilder().CreateImageKey(
+ aItem, container, aBuilder, aResources, rendering, aSc, size,
+ Nothing());
+ if (key.isNothing()) {
+ break;
+ }
+
+ 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(), 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(),
+ mImageSize.width / appUnitsPerDevPixel,
+ mImageSize.height / appUnitsPerDevPixel,
+ mFill,
+ wr::ToDeviceIntSideOffsets(slice[0], slice[1], slice[2], slice[3]),
+ wr::ToLayoutSideOffsets(outset[0], outset[1], outset[2], outset[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.origin.x, dest.origin.y) + lineStart;
+ LayoutDevicePoint endPoint =
+ LayoutDevicePoint(dest.origin.x, dest.origin.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,
+ wr::ToLayoutSideOffsets(outset[0], outset[1], outset[2],
+ outset[3]));
+ } 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,
+ wr::ToLayoutSideOffsets(outset[0], outset[1], outset[2],
+ outset[3]));
+ } 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,
+ wr::ToLayoutSideOffsets(outset[0], outset[1], outset[2],
+ outset[3]));
+ }
+ 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) {
+ 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..35801a1773
--- /dev/null
+++ b/layout/painting/nsCSSRenderingBorders.h
@@ -0,0 +1,355 @@
+/* -*- 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;
+class nsDisplayBorder;
+
+namespace mozilla {
+
+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 nsDisplayBorder;
+ friend class nsDisplayOutline;
+ friend class nsDisplayButtonBorder;
+ friend class nsDisplayButtonForeground;
+
+ public:
+ nsCSSBorderRenderer(nsPresContext* aPresContext,
+ const mozilla::dom::Document* aDocument,
+ 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(
+ 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;
+
+ // Target document to report warning
+ nsPresContext* mPresContext;
+ const mozilla::dom::Document* mDocument;
+
+ // 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(
+ nsDisplayItem* aItem, nsIFrame* aForFrame,
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const mozilla::layers::StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager,
+ 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 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..7deee60fce
--- /dev/null
+++ b/layout/painting/nsCSSRenderingGradients.cpp
@@ -0,0 +1,1297 @@
+/* -*- 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 "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);
+}
+
+// Returns aFrac*aC2 + (1 - aFrac)*C1. The interpolation is done
+// in unpremultiplied space, which is what SVG gradients and cairo
+// gradients expect.
+static sRGBColor InterpolateColor(const sRGBColor& aC1, const sRGBColor& aC2,
+ float aFrac) {
+ double other = 1 - aFrac;
+ return sRGBColor(aC2.r * aFrac + aC1.r * other, aC2.g * aFrac + aC1.g * other,
+ aC2.b * aFrac + aC1.b * other,
+ aC2.a * aFrac + aC1.a * other);
+}
+
+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 * p.x + d.y * p.y;
+ double denominator = d.x * d.x + d.y * d.y;
+ return numerator / denominator;
+}
+
+static bool RectIsBeyondLinearGradientEdge(const gfxRect& aRect,
+ const gfxMatrix& aPatternMatrix,
+ const nsTArray<ColorStop>& aStops,
+ const gfxPoint& aGradientStart,
+ const gfxPoint& aGradientEnd,
+ sRGBColor* 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;
+ }
+
+ sRGBColor color1 = stops[x - 1].mColor;
+ sRGBColor 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.
+ float relativeOffset =
+ (newStop.mPosition - offset1) / (offset2 - offset1);
+ float multiplier = powf(relativeOffset, logf(.5f) / logf(midpoint));
+
+ gfx::Float red = color1.r + multiplier * (color2.r - color1.r);
+ gfx::Float green = color1.g + multiplier * (color2.g - color1.g);
+ gfx::Float blue = color1.b + multiplier * (color2.b - color1.b);
+ gfx::Float alpha = color1.a + multiplier * (color2.a - color1.a);
+
+ newStop.mColor = sRGBColor(red, green, blue, alpha);
+ }
+
+ stops.ReplaceElementsAt(x, 1, newStops, 9);
+ x += 9;
+ }
+}
+
+static sRGBColor Premultiply(const sRGBColor& aColor) {
+ gfx::Float a = aColor.a;
+ return sRGBColor(aColor.r * a, aColor.g * a, aColor.b * a, a);
+}
+
+static sRGBColor Unpremultiply(const sRGBColor& aColor) {
+ gfx::Float a = aColor.a;
+ return (a > 0.f) ? sRGBColor(aColor.r / a, aColor.g / a, aColor.b / a, a)
+ : aColor;
+}
+
+static sRGBColor TransparentColor(sRGBColor aColor) {
+ aColor.a = 0;
+ return aColor;
+}
+
+// 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.a == rightStop.mColor.a ||
+ 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.a == 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.a == 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.a != 1.0f || rightStop.mColor.a != 1.0f) {
+ sRGBColor premulLeftColor = Premultiply(leftStop.mColor);
+ sRGBColor premulRightColor = Premultiply(rightStop.mColor);
+ // Calculate how many extra steps. We do a step per 10% transparency.
+ size_t stepCount =
+ NSToIntFloor(fabsf(leftStop.mColor.a - rightStop.mColor.a) /
+ 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,
+ Unpremultiply(
+ InterpolateColor(premulLeftColor, premulRightColor, frac)));
+ aStops.InsertElementAt(x, newStop);
+ x++;
+ }
+ }
+ }
+}
+
+static ColorStop InterpolateColorStop(const ColorStop& aFirst,
+ const ColorStop& aSecond,
+ double aPosition,
+ const sRGBColor& 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,
+ Unpremultiply(InterpolateColor(
+ Premultiply(aFirst.mColor), Premultiply(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) {
+ sRGBColor 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 sRGBColor GetSpecifiedColor(
+ const StyleGenericGradientItem<StyleColor, T>& aItem,
+ const ComputedStyle& aStyle) {
+ if (aItem.IsInterpolationHint()) {
+ return sRGBColor();
+ }
+ const StyleColor& color = aItem.IsSimpleColorStop()
+ ? aItem.AsSimpleColorStop()
+ : aItem.AsComplexColorStop().color;
+ return sRGBColor::FromABGR(color.CalcColor(aStyle));
+}
+
+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 = InterpolateColor(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).
+ sRGBColor firstColor(mStops[0].mColor);
+ sRGBColor 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();
+ sRGBColor edgeColor;
+ if (mGradient->IsLinear() && !isRepeat &&
+ RectIsBeyondLinearGradientEdge(fillRectRelativeToTile, matrix, mStops,
+ gradientStart, gradientEnd,
+ &edgeColor)) {
+ edgeColor.a *= aOpacity;
+ aContext.SetColor(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;
+ }
+
+ RefPtr<gfxContext> tileContext = gfxContext::CreateOrNull(tileTarget);
+
+ tileContext->SetPattern(aGradientPattern);
+ tileContext->Paint();
+
+ tileContext = nullptr;
+ 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..bde769e5e5
--- /dev/null
+++ b/layout/painting/nsCSSRenderingGradients.h
@@ -0,0 +1,121 @@
+/* -*- 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 "nsStyleStruct.h"
+#include "Units.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/webrender/webrender_ffi.h"
+
+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 gfx::sRGBColor& aColor)
+ : mPosition(aPosition), mIsMidpoint(aIsMidPoint), mColor(aColor) {}
+ double mPosition; // along the gradient line; 0=start, 1=end
+ bool mIsMidpoint;
+ gfx::sRGBColor 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..74c3197804
--- /dev/null
+++ b/layout/painting/nsDisplayItemTypesList.h
@@ -0,0 +1,133 @@
+/* -*- 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(BULLET, TYPE_IS_CONTENTFUL)
+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(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(HEADER_FOOTER, TYPE_RENDERS_NO_IMAGES)
+DECLARE_DISPLAY_ITEM_TYPE(IMAGE, TYPE_IS_CONTENTFUL)
+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(PLUGIN, 0)
+DECLARE_DISPLAY_ITEM_TYPE(PLUGIN_READBACK, 0)
+DECLARE_DISPLAY_ITEM_TYPE(PRINT_PLUGIN, 0)
+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(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_OUTER_SVG, TYPE_RENDERS_NO_IMAGES)
+DECLARE_DISPLAY_ITEM_TYPE(SVG_GEOMETRY, 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
+
+#ifdef MOZ_XUL
+DECLARE_DISPLAY_ITEM_TYPE(XUL_EVENT_REDIRECTOR,
+ TYPE_RENDERS_NO_IMAGES | TYPE_IS_CONTAINER)
+DECLARE_DISPLAY_ITEM_TYPE(XUL_GROUP_BACKGROUND, 0)
+DECLARE_DISPLAY_ITEM_TYPE(XUL_IMAGE, 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)
+# ifdef DEBUG_LAYOUT
+DECLARE_DISPLAY_ITEM_TYPE(XUL_DEBUG, TYPE_RENDERS_NO_IMAGES)
+# endif
+#endif
+
+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)
+
+DECLARE_DISPLAY_ITEM_TYPE(DEBUG_BORDER, TYPE_RENDERS_NO_IMAGES)
+DECLARE_DISPLAY_ITEM_TYPE(DEBUG_IMAGE_MAP, TYPE_RENDERS_NO_IMAGES)
+DECLARE_DISPLAY_ITEM_TYPE(DEBUG_PLACEHOLDER, TYPE_RENDERS_NO_IMAGES)
+DECLARE_DISPLAY_ITEM_TYPE(EVENT_TARGET_BORDER, TYPE_RENDERS_NO_IMAGES)
+#endif
diff --git a/layout/painting/nsDisplayList.cpp b/layout/painting/nsDisplayList.cpp
new file mode 100644
index 0000000000..dfe894b670
--- /dev/null
+++ b/layout/painting/nsDisplayList.cpp
@@ -0,0 +1,10324 @@
+/* -*- 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/dom/BrowserChild.h"
+#include "mozilla/dom/HTMLCanvasElement.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/layers/PLayerTransaction.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/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 "LayerTreeInvalidation.h"
+#include "mozilla/MathAlgorithms.h"
+
+#include "imgIContainer.h"
+#include "BasicLayers.h"
+#include "nsBoxFrame.h"
+#include "nsImageFrame.h"
+#include "nsSubDocumentFrame.h"
+#include "GeckoProfiler.h"
+#include "nsViewManager.h"
+#include "ImageLayers.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/EventStates.h"
+#include "mozilla/HashTable.h"
+#include "mozilla/LookAndFeel.h"
+#include "mozilla/OperatorNewExtensions.h"
+#include "mozilla/PendingAnimationTracker.h"
+#include "mozilla/Preferences.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 "nsPrintfCString.h"
+#include "UnitTransforms.h"
+#include "LayerAnimationInfo.h"
+#include "FrameLayerBuilder.h"
+#include "mozilla/EventStateManager.h"
+#include "nsCaret.h"
+#include "nsDOMTokenList.h"
+#include "nsCSSProps.h"
+#include "nsTableCellFrame.h"
+#include "nsTableColFrame.h"
+#include "nsTextFrame.h"
+#include "nsSliderFrame.h"
+#include "nsFocusManager.h"
+#include "ClientLayerManager.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"
+
+using namespace mozilla;
+using namespace mozilla::layers;
+using namespace mozilla::dom;
+using namespace mozilla::layout;
+using namespace mozilla::gfx;
+
+typedef ScrollableLayerGuid::ViewID ViewID;
+typedef nsStyleTransformMatrix::TransformReferenceBox TransformReferenceBox;
+
+#ifdef DEBUG
+static bool SpammyLayoutWarningsEnabled() {
+ static bool sValue = false;
+ static bool sValueInitialized = false;
+
+ if (!sValueInitialized) {
+ Preferences::GetBool("layout.spammy_warnings.enabled", &sValue);
+ sValueInitialized = true;
+ }
+
+ return sValue;
+}
+#endif
+
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+void AssertUniqueItem(nsDisplayItem* aItem) {
+ nsIFrame::DisplayItemArray* items =
+ aItem->Frame()->GetProperty(nsIFrame::DisplayItems());
+ if (!items) {
+ return;
+ }
+ for (nsDisplayItemBase* i : *items) {
+ if (i != aItem && !i->HasDeletedFrame() && i->Frame() == aItem->Frame() &&
+ i->GetPerFrameKey() == aItem->GetPerFrameKey()) {
+ if (i->IsPreProcessedItem()) {
+ continue;
+ }
+ MOZ_DIAGNOSTIC_ASSERT(false, "Duplicate display item!");
+ }
+ }
+}
+#endif
+
+bool ShouldBuildItemForEventsOrPlugins(const DisplayItemType aType) {
+ return aType == DisplayItemType::TYPE_COMPOSITOR_HITTEST_INFO ||
+ aType == DisplayItemType::TYPE_PLUGIN ||
+ (GetDisplayItemFlagsForType(aType) & TYPE_IS_CONTAINER);
+}
+
+void UpdateDisplayItemData(nsPaintedDisplayItem* aItem) {
+ for (mozilla::DisplayItemData* did : aItem->Frame()->DisplayItemData()) {
+ if (did->GetDisplayItemKey() == aItem->GetPerFrameKey() &&
+ did->GetLayer()->AsPaintedLayer()) {
+ if (!did->HasMergedFrames()) {
+ aItem->SetDisplayItemData(did, did->GetLayer()->Manager());
+ }
+
+ return;
+ }
+ }
+}
+
+/* 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 */
+nsCString ActiveScrolledRoot::ToString(
+ const ActiveScrolledRoot* aActiveScrolledRoot) {
+ nsAutoCString str;
+ for (auto* asr = aActiveScrolledRoot; asr; asr = asr->mParent) {
+ str.AppendPrintf("<0x%p>", asr->mScrollableFrame);
+ if (asr->mParent) {
+ str.AppendLiteral(", ");
+ }
+ }
+ return std::move(str);
+}
+
+mozilla::layers::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, mozilla::layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder,
+ const Maybe<LayoutDevicePoint>& aPosition = Nothing()) {
+ EffectSet* effects =
+ EffectSet::GetEffectSetForFrame(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;
+ }
+ RefPtr<gfxContext> maskCtx =
+ gfxContext::CreatePreservingTransformOrNull(maskDT);
+ MOZ_ASSERT(maskCtx);
+ 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;
+}
+
+/* static */
+void nsDisplayListBuilder::AddAnimationsAndTransitionsToLayer(
+ Layer* aLayer, nsDisplayListBuilder* aBuilder, nsDisplayItem* aItem,
+ nsIFrame* aFrame, DisplayItemType aType) {
+ // This function can be called in two ways: from
+ // nsDisplay*::BuildLayer while constructing a layer (with all
+ // pointers non-null), or from RestyleManager's handling of
+ // UpdateOpacityLayer/UpdateTransformLayer hints.
+ MOZ_ASSERT(!aBuilder == !aItem,
+ "should only be called in two configurations, with both "
+ "aBuilder and aItem, or with neither");
+ MOZ_ASSERT(!aItem || aFrame == aItem->Frame(), "frame mismatch");
+
+ // Only send animations to a layer that is actually using
+ // off-main-thread compositing.
+ LayersBackend backend = aLayer->Manager()->GetBackendType();
+ if (!(backend == layers::LayersBackend::LAYERS_CLIENT ||
+ backend == layers::LayersBackend::LAYERS_WR)) {
+ return;
+ }
+
+ AnimationInfo& animationInfo = aLayer->GetAnimationInfo();
+ animationInfo.AddAnimationsForDisplayItem(aFrame, aBuilder, aItem, aType,
+ aLayer->Manager());
+ animationInfo.TransferMutatedFlagToLayer(aLayer);
+}
+
+nsDisplayWrapList* nsDisplayListBuilder::MergeItems(
+ nsTArray<nsDisplayWrapList*>& 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* merged = nullptr;
+
+ for (nsDisplayWrapList* item : Reversed(aItems)) {
+ MOZ_ASSERT(item);
+
+ if (!merged) {
+ // Create the temporary item.
+ merged = item->Clone(this);
+ MOZ_ASSERT(merged);
+
+ AddTemporaryItem(merged);
+ } else {
+ // Merge the item properties (frame/bounds/etc) with the previously
+ // created temporary item.
+ MOZ_ASSERT(merged->CanMerge(item));
+ merged->Merge(item);
+ }
+
+ // Create nsDisplayWrapList that points to the internal display list of the
+ // item we are merging. This nsDisplayWrapList is added to the display list
+ // of the temporary item.
+ merged->MergeDisplayListFromItem(this, item);
+ }
+
+ 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;
+}
+
+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::GetHighResolutionDisplayPort(
+ 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() &&
+ mozilla::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::nsDisplayListBuilder(nsIFrame* aReferenceFrame,
+ nsDisplayListBuilderMode aMode,
+ bool aBuildCaret,
+ bool aRetainingDisplayList)
+ : mReferenceFrame(aReferenceFrame),
+ mIgnoreScrollFrame(nullptr),
+ mCurrentActiveScrolledRoot(nullptr),
+ mCurrentContainerASR(nullptr),
+ mCurrentFrame(aReferenceFrame),
+ mCurrentReferenceFrame(aReferenceFrame),
+ mRootAGR(AnimatedGeometryRoot::CreateAGRForFrame(
+ aReferenceFrame, nullptr, true, aRetainingDisplayList)),
+ mCurrentAGR(mRootAGR),
+ mBuildingExtraPagesForPageNum(0),
+ mUsedAGRBudget(0),
+ mDirtyRect(-1, -1, -1, -1),
+ mGlassDisplayItem(nullptr),
+ mCaretFrame(nullptr),
+ mScrollInfoItemsForHoisting(nullptr),
+ mFirstClipChainToDestroy(nullptr),
+ mMode(aMode),
+ mTableBackgroundSet(nullptr),
+ mCurrentScrollParentId(ScrollableLayerGuid::NULL_SCROLL_ID),
+ mCurrentScrollbarTarget(ScrollableLayerGuid::NULL_SCROLL_ID),
+ mFilterASR(nullptr),
+ mContainsBlendMode(false),
+ mIsBuildingScrollbar(false),
+ mCurrentScrollbarWillHaveLayer(false),
+ mBuildCaret(aBuildCaret),
+ mRetainingDisplayList(aRetainingDisplayList),
+ mPartialUpdate(false),
+ mIgnoreSuppression(false),
+ mIncludeAllOutOfFlows(false),
+ mDescendIntoSubdocuments(true),
+ mSelectedFramesOnly(false),
+ mAllowMergingAndFlattening(true),
+ mWillComputePluginGeometry(false),
+ mInTransform(false),
+ mInEventsAndPluginsOnly(false),
+ mInFilter(false),
+ mInPageSequence(false),
+ mIsInChromePresContext(false),
+ mSyncDecodeImages(false),
+ mIsPaintingToWindow(false),
+ mUseHighQualityScaling(false),
+ mIsPaintingForWebRender(false),
+ mIsCompositingCheap(false),
+ mContainsPluginItem(false),
+ mAncestorHasApzAwareEventHandler(false),
+ mHaveScrollableDisplayPort(false),
+ mWindowDraggingAllowed(false),
+ mIsBuildingForPopup(nsLayoutUtils::IsPopup(aReferenceFrame)),
+ mForceLayerForScrollParent(false),
+ mAsyncPanZoomEnabled(nsLayoutUtils::AsyncPanZoomEnabled(aReferenceFrame)),
+ mBuildingInvisibleItems(false),
+ mIsBuilding(false),
+ mInInvalidSubtree(false),
+ mDisablePartialUpdates(false),
+ mPartialBuildFailed(false),
+ mIsInActiveDocShell(false),
+ mBuildAsyncZoomContainer(false),
+ mContainsBackdropFilter(false),
+ mIsRelativeToLayoutViewport(false),
+ mUseOverlayScrollbars(false),
+ mHitTestArea(),
+ mHitTestInfo(CompositorHitTestInvisibleToHit) {
+ MOZ_COUNT_CTOR(nsDisplayListBuilder);
+
+ mBuildCompositorHitTestInfo = mAsyncPanZoomEnabled && IsForPainting();
+
+ ShouldRebuildDisplayListDueToPrefChange();
+
+ mUseOverlayScrollbars =
+ (LookAndFeel::GetInt(LookAndFeel::IntID::UseOverlayScrollbars) != 0);
+
+ static_assert(
+ static_cast<uint32_t>(DisplayItemType::TYPE_MAX) < (1 << TYPE_BITS),
+ "Check TYPE_MAX should not overflow");
+}
+
+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();
+ mCurrentAGR = mRootAGR;
+ mFrameToAnimatedGeometryRootMap.Put(mReferenceFrame, mRootAGR);
+
+ 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::EndFrame() {
+ NS_ASSERTION(!mInInvalidSubtree,
+ "Someone forgot to cleanup mInInvalidSubtree!");
+ mFrameToAnimatedGeometryRootMap.Clear();
+ mAGRBudgetSet.Clear();
+ 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);
+}
+
+void nsDisplayListBuilder::MarkFrameForDisplayIfVisible(
+ nsIFrame* aFrame, const nsIFrame* aStopAtFrame) {
+ AddFrameMarkedForDisplayIfVisible(aFrame);
+ for (nsIFrame* f = aFrame; f; f = nsLayoutUtils::GetDisplayListParent(f)) {
+ if (f->ForceDescendIntoIfVisible()) {
+ return;
+ }
+ f->SetForceDescendIntoIfVisible(true);
+ if (f == aStopAtFrame) {
+ // we've reached a frame that we know will be painted, so we can stop.
+ break;
+ }
+ }
+}
+
+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 DocElementBoxFrames, which is rare as
+ // each xul doc only gets one near the root). We only care about first one,
+ // since that will be the background of the root window.
+
+ if (IsPartialUpdate()) {
+ if (aItem->Frame()->IsDocElementBoxFrame()) {
+#ifdef DEBUG
+ if (mHasGlassItemDuringPartial) {
+ NS_WARNING("Multiple glass backgrounds found?");
+ } else
+#endif
+ if (!mHasGlassItemDuringPartial) {
+ mHasGlassItemDuringPartial = true;
+ aItem->SetIsGlassItem();
+ }
+ }
+ return;
+ }
+
+ if (aItem->Frame()->IsDocElementBoxFrame()) {
+#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;
+}
+
+AnimatedGeometryRoot* nsDisplayListBuilder::WrapAGRForFrame(
+ nsIFrame* aAnimatedGeometryRoot, bool aIsAsync,
+ AnimatedGeometryRoot* aParent /* = nullptr */) {
+ DebugOnly<bool> dummy;
+ MOZ_ASSERT(IsAnimatedGeometryRoot(aAnimatedGeometryRoot, dummy) == AGR_YES);
+
+ RefPtr<AnimatedGeometryRoot> result;
+ if (!mFrameToAnimatedGeometryRootMap.Get(aAnimatedGeometryRoot, &result)) {
+ MOZ_ASSERT(nsLayoutUtils::IsAncestorFrameCrossDoc(RootReferenceFrame(),
+ aAnimatedGeometryRoot));
+ RefPtr<AnimatedGeometryRoot> parent = aParent;
+ if (!parent) {
+ nsIFrame* parentFrame =
+ nsLayoutUtils::GetCrossDocParentFrame(aAnimatedGeometryRoot);
+ if (parentFrame) {
+ bool isAsync;
+ nsIFrame* parentAGRFrame =
+ FindAnimatedGeometryRootFrameFor(parentFrame, isAsync);
+ parent = WrapAGRForFrame(parentAGRFrame, isAsync);
+ }
+ }
+ result = AnimatedGeometryRoot::CreateAGRForFrame(
+ aAnimatedGeometryRoot, parent, aIsAsync, IsRetainingDisplayList());
+ mFrameToAnimatedGeometryRootMap.Put(aAnimatedGeometryRoot, result);
+ }
+ MOZ_ASSERT(!aParent || result->mParentAGR == aParent);
+ return result;
+}
+
+AnimatedGeometryRoot* nsDisplayListBuilder::AnimatedGeometryRootForASR(
+ const ActiveScrolledRoot* aASR) {
+ if (!aASR) {
+ return GetRootAnimatedGeometryRoot();
+ }
+ nsIFrame* scrolledFrame = aASR->mScrollableFrame->GetScrolledFrame();
+ return FindAnimatedGeometryRootFor(scrolledFrame);
+}
+
+AnimatedGeometryRoot* nsDisplayListBuilder::FindAnimatedGeometryRootFor(
+ nsIFrame* aFrame) {
+ if (!IsPaintingToWindow()) {
+ return mRootAGR;
+ }
+ if (aFrame == mCurrentFrame) {
+ return mCurrentAGR;
+ }
+ RefPtr<AnimatedGeometryRoot> result;
+ if (mFrameToAnimatedGeometryRootMap.Get(aFrame, &result)) {
+ return result;
+ }
+
+ bool isAsync;
+ nsIFrame* agrFrame = FindAnimatedGeometryRootFrameFor(aFrame, isAsync);
+ result = WrapAGRForFrame(agrFrame, isAsync);
+ mFrameToAnimatedGeometryRootMap.Put(aFrame, result);
+ return result;
+}
+
+AnimatedGeometryRoot* nsDisplayListBuilder::FindAnimatedGeometryRootFor(
+ nsDisplayItem* aItem) {
+ if (aItem->ShouldFixToViewport(this)) {
+ // Make its active scrolled root be the active scrolled root of
+ // the enclosing viewport, since it shouldn't be scrolled by scrolled
+ // frames in its document. InvalidateFixedBackgroundFramesFromList in
+ // nsGfxScrollFrame will not repaint this item when scrolling occurs.
+ nsIFrame* viewportFrame = nsLayoutUtils::GetClosestFrameOfType(
+ aItem->Frame(), LayoutFrameType::Viewport, RootReferenceFrame());
+ if (viewportFrame) {
+ return FindAnimatedGeometryRootFor(viewportFrame);
+ }
+ }
+ return FindAnimatedGeometryRootFor(aItem->Frame());
+}
+
+void nsDisplayListBuilder::SetIsRelativeToLayoutViewport() {
+ mIsRelativeToLayoutViewport = true;
+ UpdateShouldBuildAsyncZoomContainer();
+}
+
+void nsDisplayListBuilder::UpdateShouldBuildAsyncZoomContainer() {
+ Document* document = mReferenceFrame->PresContext()->Document();
+ // On desktop, we want to disable zooming in fullscreen mode (bug 1650488).
+ // On mobile (and RDM), we need zooming even in fullscreen mode to respect
+ // mobile viewport sizing (bug 1659761).
+ bool disableZoomingForFullscreen =
+ document->Fullscreen() &&
+ !document->GetPresShell()->UsesMobileViewportSizing();
+ mBuildAsyncZoomContainer = !mIsRelativeToLayoutViewport &&
+ !disableZoomingForFullscreen &&
+ 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) != 0);
+
+ if (didBuildAsyncZoomContainer != mBuildAsyncZoomContainer) {
+ return true;
+ }
+
+ if (hadOverlayScrollbarsLastTime != mUseOverlayScrollbars) {
+ 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);
+ }
+}
+
+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;
+}
+
+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 (GetAccurateVisibleRegions() || 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) {
+ mReferenceFrame->AddPaintedPresShell(aPresShell);
+ 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->HadContentfulPaint() && rootPresContext &&
+ rootPresContext->RefreshDriver()->IsInRefresh()) {
+ 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 {
+ mCurrentAGR = mRootAGR;
+
+ 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, 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, 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;
+};
+
+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::CopyWholeChain(
+ const DisplayItemClipChain* aClipChain) {
+ return CreateClipChainIntersection(nullptr, aClipChain, nullptr);
+}
+
+const DisplayItemClipChain* nsDisplayListBuilder::FuseClipChainUpTo(
+ const DisplayItemClipChain* aClipChain, const ActiveScrolledRoot* aASR) {
+ if (!aClipChain) {
+ return nullptr;
+ }
+
+ const DisplayItemClipChain* sc = aClipChain;
+ DisplayItemClip mergedClip;
+ while (sc && ActiveScrolledRoot::PickDescendant(aASR, sc->mASR) == sc->mASR) {
+ mergedClip.IntersectWith(sc->mClip);
+ sc = sc->mParent;
+ }
+
+ if (!mergedClip.HasClip()) {
+ return nullptr;
+ }
+
+ return AllocateDisplayItemClipChain(mergedClip, aASR, sc);
+}
+
+const nsIFrame* nsDisplayListBuilder::FindReferenceFrameFor(
+ const nsIFrame* aFrame, nsPoint* aOffset) const {
+ auto MaybeApplyAdditionalOffset = [&]() {
+ if (AdditionalOffset()) {
+ // The additional reference frame offset should only affect descendants
+ // of |mAdditionalOffsetFrame|.
+ MOZ_ASSERT(nsLayoutUtils::IsAncestorFrameCrossDoc(mAdditionalOffsetFrame,
+ aFrame));
+ *aOffset += *AdditionalOffset();
+ }
+ };
+
+ if (aFrame == mCurrentFrame) {
+ if (aOffset) {
+ *aOffset = mCurrentOffsetToReferenceFrame;
+ }
+ return mCurrentReferenceFrame;
+ }
+
+ for (const nsIFrame* f = aFrame; f;
+ f = nsLayoutUtils::GetCrossDocParentFrame(f)) {
+ if (f == mReferenceFrame || f->IsTransformed()) {
+ if (aOffset) {
+ *aOffset = aFrame->GetOffsetToCrossDoc(f);
+ MaybeApplyAdditionalOffset();
+ }
+ return f;
+ }
+ }
+
+ if (aOffset) {
+ *aOffset = aFrame->GetOffsetToCrossDoc(mReferenceFrame);
+ }
+
+ return mReferenceFrame;
+}
+
+// Sticky frames are active if their nearest scrollable frame is also active.
+static bool IsStickyFrameActive(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame, nsIFrame* aParent) {
+ MOZ_ASSERT(aFrame->StyleDisplay()->mPosition ==
+ StylePositionProperty::Sticky);
+
+ // Find the nearest scrollframe.
+ nsIScrollableFrame* sf = nsLayoutUtils::GetNearestScrollableFrame(
+ aFrame->GetParent(), nsLayoutUtils::SCROLLABLE_SAME_DOC |
+ nsLayoutUtils::SCROLLABLE_INCLUDE_HIDDEN);
+ if (!sf) {
+ return false;
+ }
+
+ return sf->IsScrollingActive(aBuilder);
+}
+
+nsDisplayListBuilder::AGRState nsDisplayListBuilder::IsAnimatedGeometryRoot(
+ nsIFrame* aFrame, bool& aIsAsync, nsIFrame** aParent) {
+ // We can return once we know that this frame is an AGR, and we're either
+ // async, or sure that none of the later conditions might make us async.
+ // The exception to this is when IsPaintingToWindow() == false.
+ aIsAsync = false;
+ if (aFrame == mReferenceFrame) {
+ aIsAsync = true;
+ return AGR_YES;
+ }
+
+ if (!IsPaintingToWindow()) {
+ if (aParent) {
+ *aParent = nsLayoutUtils::GetCrossDocParentFrame(aFrame);
+ }
+ return AGR_NO;
+ }
+
+ nsIFrame* parent = nsLayoutUtils::GetCrossDocParentFrame(aFrame);
+ if (!parent) {
+ aIsAsync = true;
+ return AGR_YES;
+ }
+
+ if (aFrame->StyleDisplay()->mPosition == StylePositionProperty::Sticky &&
+ IsStickyFrameActive(this, aFrame, parent)) {
+ aIsAsync = true;
+ return AGR_YES;
+ }
+
+ if (aFrame->IsTransformed()) {
+ aIsAsync = EffectCompositor::HasAnimationsForCompositor(
+ aFrame, DisplayItemType::TYPE_TRANSFORM);
+ return AGR_YES;
+ }
+
+ LayoutFrameType parentType = parent->Type();
+ if (parentType == LayoutFrameType::Scroll ||
+ parentType == LayoutFrameType::ListControl) {
+ nsIScrollableFrame* sf = do_QueryFrame(parent);
+ if (sf->GetScrolledFrame() == aFrame && sf->IsScrollingActive(this)) {
+ MOZ_ASSERT(!aFrame->IsTransformed());
+ aIsAsync = sf->IsMaybeAsynchronouslyScrolled();
+ return AGR_YES;
+ }
+ }
+
+ // Treat the slider thumb as being as an active scrolled root when it wants
+ // its own layer so that it can move without repainting.
+ if (parentType == LayoutFrameType::Slider) {
+ auto* sf = static_cast<nsSliderFrame*>(parent)->GetScrollFrame();
+ // The word "Maybe" in IsMaybeScrollingActive might be confusing but we do
+ // indeed need to always consider scroll thumbs as AGRs if
+ // IsMaybeScrollingActive is true because that is the same condition we use
+ // in ScrollFrameHelper::AppendScrollPartsTo to layerize scroll thumbs.
+ if (sf && sf->IsMaybeScrollingActive()) {
+ return AGR_YES;
+ }
+ }
+
+ if (nsLayoutUtils::IsPopup(aFrame)) {
+ return AGR_YES;
+ }
+
+ if (ActiveLayerTracker::IsOffsetStyleAnimated(aFrame)) {
+ const bool inBudget = AddToAGRBudget(aFrame);
+ if (inBudget) {
+ return AGR_YES;
+ }
+ }
+
+ if (!aFrame->GetParent() &&
+ DisplayPortUtils::ViewportHasDisplayPort(aFrame->PresContext())) {
+ // Viewport frames in a display port need to be animated geometry roots
+ // for background-attachment:fixed elements.
+ return AGR_YES;
+ }
+
+ // Fixed-pos frames are parented by the viewport frame, which has no parent.
+ if (DisplayPortUtils::IsFixedPosFrameInDisplayPort(aFrame)) {
+ return AGR_YES;
+ }
+
+ if (aParent) {
+ *aParent = parent;
+ }
+
+ return AGR_NO;
+}
+
+nsIFrame* nsDisplayListBuilder::FindAnimatedGeometryRootFrameFor(
+ nsIFrame* aFrame, bool& aIsAsync) {
+ MOZ_ASSERT(
+ nsLayoutUtils::IsAncestorFrameCrossDoc(RootReferenceFrame(), aFrame));
+ nsIFrame* cursor = aFrame;
+ while (cursor != RootReferenceFrame()) {
+ nsIFrame* next;
+ if (IsAnimatedGeometryRoot(cursor, aIsAsync, &next) == AGR_YES) {
+ return cursor;
+ }
+ cursor = next;
+ }
+ // Root frame is always an async agr.
+ aIsAsync = true;
+ return cursor;
+}
+
+void nsDisplayListBuilder::RecomputeCurrentAnimatedGeometryRoot() {
+ bool isAsync;
+ if (*mCurrentAGR != mCurrentFrame &&
+ IsAnimatedGeometryRoot(const_cast<nsIFrame*>(mCurrentFrame), isAsync) ==
+ AGR_YES) {
+ AnimatedGeometryRoot* oldAGR = mCurrentAGR;
+ mCurrentAGR = WrapAGRForFrame(const_cast<nsIFrame*>(mCurrentFrame), isAsync,
+ mCurrentAGR);
+
+ // Iterate the AGR cache and look for any objects that reference the old AGR
+ // and check to see if they need to be updated. AGRs can be in the cache
+ // multiple times, so we may end up doing the work multiple times for AGRs
+ // that don't change.
+ for (auto iter = mFrameToAnimatedGeometryRootMap.Iter(); !iter.Done();
+ iter.Next()) {
+ RefPtr<AnimatedGeometryRoot> cached = iter.UserData();
+ if (cached->mParentAGR == oldAGR && cached != mCurrentAGR) {
+ // It's possible that this cached AGR struct that has the old AGR as a
+ // parent should instead have mCurrentFrame has a parent.
+ nsIFrame* parent = FindAnimatedGeometryRootFrameFor(*cached, isAsync);
+ MOZ_ASSERT(parent == mCurrentFrame || parent == *oldAGR);
+ if (parent == mCurrentFrame) {
+ cached->mParentAGR = mCurrentAGR;
+ }
+ }
+ }
+ }
+}
+
+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;
+ }
+
+ mozilla::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 nsDisplayHitTestInfoBase::AddSizeOfExcludingThis(
+ nsWindowSizes& aSizes) const {
+ nsPaintedDisplayItem::AddSizeOfExcludingThis(aSizes);
+ aSizes.mLayoutRetainedDisplayListSize +=
+ aSizes.mState.mMallocSizeOf(mHitTestInfo.get());
+}
+
+void nsDisplayTransform::AddSizeOfExcludingThis(nsWindowSizes& aSizes) const {
+ nsDisplayHitTestInfoBase::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 += mAGRBudgetSet.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 (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.RemoveEntry(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.GetOrInsert(presContext);
+
+ const bool onBudget =
+ (documentBudget + cost) / gWillChangeAreaMultiplier < budgetLimit;
+
+ if (onBudget) {
+ documentBudget += cost;
+ mFrameWillChangeBudgets.Put(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();
+
+ DocumentWillChangeBudget* documentBudget =
+ mDocumentWillChangeBudgets.GetValue(frameBudget.mPresContext);
+
+ if (documentBudget) {
+ *documentBudget -= frameBudget.mUsage;
+ }
+
+ entry.Remove();
+ }
+}
+
+void nsDisplayListBuilder::ClearWillChangeBudgets() {
+ mFrameWillChangeBudgets.Clear();
+ mDocumentWillChangeBudgets.Clear();
+}
+
+#ifdef MOZ_GFX_OPTIMIZE_MOBILE
+const float gAGRBudgetAreaMultiplier = 0.3;
+#else
+const float gAGRBudgetAreaMultiplier = 3.0;
+#endif
+
+bool nsDisplayListBuilder::AddToAGRBudget(nsIFrame* aFrame) {
+ if (mAGRBudgetSet.Contains(aFrame)) {
+ return true;
+ }
+
+ const nsPresContext* presContext =
+ aFrame->PresContext()->GetRootPresContext();
+ if (!presContext) {
+ return false;
+ }
+
+ const nsRect area = presContext->GetVisibleArea();
+ const uint32_t budgetLimit =
+ gAGRBudgetAreaMultiplier *
+ nsPresContext::AppUnitsToIntCSSPixels(area.width) *
+ nsPresContext::AppUnitsToIntCSSPixels(area.height);
+
+ const uint32_t cost = GetLayerizationCost(aFrame->GetSize());
+ const bool onBudget = mUsedAGRBudget + cost < budgetLimit;
+
+ if (onBudget) {
+ mUsedAGRBudget += cost;
+ mAGRBudgetSet.PutEntry(aFrame);
+ }
+
+ return onBudget;
+}
+
+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, const bool aBuildNew) {
+ MOZ_ASSERT(aFrame);
+ MOZ_ASSERT(aList);
+
+ if (!BuildCompositorHitTestInfo()) {
+ return;
+ }
+
+ const CompositorHitTestInfo info = aFrame->GetCompositorHitTestInfo(this);
+ if (info == CompositorHitTestInvisibleToHit) {
+ return;
+ }
+
+ const nsRect area = aFrame->GetCompositorHitTestArea(this);
+ if (!aBuildNew && GetHitTestInfo() == info &&
+ GetHitTestArea().Contains(area)) {
+ return;
+ }
+
+ auto* item = MakeDisplayItem<nsDisplayCompositorHitTestInfo>(
+ this, aFrame, info, Some(area));
+ MOZ_ASSERT(item);
+
+ SetCompositorHitTestInfo(area, info);
+ aList->AppendToTop(item);
+}
+
+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());
+}
+
+static void MoveListTo(nsDisplayList* aList,
+ nsTArray<nsDisplayItem*>* aElements) {
+ nsDisplayItem* item;
+ while ((item = aList->RemoveBottom()) != nullptr) {
+ aElements->AppendElement(item);
+ }
+}
+
+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;
+}
+
+bool nsDisplayList::ComputeVisibilityForRoot(nsDisplayListBuilder* aBuilder,
+ nsRegion* aVisibleRegion) {
+ AUTO_PROFILER_LABEL("nsDisplayList::ComputeVisibilityForRoot", GRAPHICS);
+
+ nsRegion r;
+ const ActiveScrolledRoot* rootASR = nullptr;
+ r.And(*aVisibleRegion, GetClippedBoundsWithRespectToASR(aBuilder, rootASR));
+ return ComputeVisibilityForSublist(aBuilder, aVisibleRegion, r.GetBounds());
+}
+
+static nsRegion TreatAsOpaque(nsDisplayItem* aItem,
+ nsDisplayListBuilder* aBuilder) {
+ bool snap;
+ nsRegion opaque = aItem->GetOpaqueRegion(aBuilder, &snap);
+ MOZ_ASSERT(
+ (aBuilder->IsForEventDelivery() && aBuilder->HitTestIsForVisibility()) ||
+ !opaque.IsComplex());
+ if (aBuilder->IsForPluginGeometry() &&
+ aItem->GetType() != DisplayItemType::TYPE_COMPOSITOR_HITTEST_INFO) {
+ // Treat all leaf chrome items as opaque, unless their frames are opacity:0.
+ // Since opacity:0 frames generate an nsDisplayOpacity, that item will
+ // not be treated as opaque here, so opacity:0 chrome content will be
+ // effectively ignored, as it should be.
+ // We treat leaf chrome items as opaque to ensure that they cover
+ // content plugins, for security reasons.
+ // Non-leaf chrome items don't render contents of their own so shouldn't
+ // be treated as opaque (and their bounds is just the union of their
+ // children, which might be a large area their contents don't really cover).
+ nsIFrame* f = aItem->Frame();
+ if (f->PresContext()->IsChrome() && !aItem->GetChildren() &&
+ f->StyleEffects()->mOpacity != 0.0) {
+ opaque = aItem->GetBounds(aBuilder, &snap);
+ }
+ }
+ if (opaque.IsEmpty()) {
+ return opaque;
+ }
+ nsRegion opaqueClipped;
+ for (auto iter = opaque.RectIter(); !iter.Done(); iter.Next()) {
+ opaqueClipped.Or(opaqueClipped,
+ aItem->GetClip().ApproximateIntersectInward(iter.Get()));
+ }
+ return opaqueClipped;
+}
+
+bool nsDisplayList::ComputeVisibilityForSublist(
+ nsDisplayListBuilder* aBuilder, nsRegion* aVisibleRegion,
+ const nsRect& aListVisibleBounds) {
+#ifdef DEBUG
+ nsRegion r;
+ r.And(*aVisibleRegion, GetClippedBounds(aBuilder));
+ // XXX this fails sometimes:
+ NS_WARNING_ASSERTION(r.GetBounds().IsEqualInterior(aListVisibleBounds),
+ "bad aListVisibleBounds");
+#endif
+
+ bool anyVisible = false;
+
+ AutoTArray<nsDisplayItem*, 512> elements;
+ MoveListTo(this, &elements);
+
+ for (int32_t i = elements.Length() - 1; i >= 0; --i) {
+ nsDisplayItem* item = elements[i];
+
+ if (item->ForceNotVisible() && !item->GetSameCoordinateSystemChildren()) {
+ NS_ASSERTION(item->GetBuildingRect().IsEmpty(),
+ "invisible items should have empty vis rect");
+ item->SetPaintRect(nsRect());
+ } else {
+ nsRect bounds = item->GetClippedBounds(aBuilder);
+
+ nsRegion itemVisible;
+ itemVisible.And(*aVisibleRegion, bounds);
+ item->SetPaintRect(itemVisible.GetBounds());
+ }
+
+ if (item->ComputeVisibility(aBuilder, aVisibleRegion)) {
+ anyVisible = true;
+
+ nsRegion opaque = TreatAsOpaque(item, aBuilder);
+ // Subtract opaque item from the visible region
+ aBuilder->SubtractFromVisibleRegion(aVisibleRegion, opaque);
+ }
+ AppendToBottom(item);
+ }
+
+ mIsOpaque = !aVisibleRegion->Intersects(aListVisibleBounds);
+ return anyVisible;
+}
+
+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);
+}
+
+LayerManager* nsDisplayListBuilder::GetWidgetLayerManager(nsView** aView) {
+ if (aView) {
+ *aView = RootReferenceFrame()->GetView();
+ }
+ if (RootReferenceFrame() !=
+ nsLayoutUtils::GetDisplayRootFrame(RootReferenceFrame())) {
+ return nullptr;
+ }
+ nsIWidget* window = RootReferenceFrame()->GetNearestWidget();
+ if (window) {
+ return window->GetLayerManager();
+ }
+ return nullptr;
+}
+
+// Find the layer which should house the root scroll metadata for a given
+// layer tree. This is the async zoom container layer if there is one,
+// otherwise it's the root layer.
+Layer* GetLayerForRootMetadata(Layer* aRootLayer, ViewID aRootScrollId) {
+ Layer* asyncZoomContainer = DepthFirstSearch<ForwardIterator>(
+ aRootLayer, [aRootScrollId](Layer* aLayer) {
+ if (auto id = aLayer->IsAsyncZoomContainer()) {
+ return *id == aRootScrollId;
+ }
+ return false;
+ });
+ return asyncZoomContainer ? asyncZoomContainer : aRootLayer;
+}
+
+FrameLayerBuilder* nsDisplayList::BuildLayers(nsDisplayListBuilder* aBuilder,
+ LayerManager* aLayerManager,
+ uint32_t aFlags,
+ bool aIsWidgetTransaction) {
+ nsIFrame* frame = aBuilder->RootReferenceFrame();
+ nsPresContext* presContext = frame->PresContext();
+ PresShell* presShell = presContext->PresShell();
+
+ FrameLayerBuilder* layerBuilder = new FrameLayerBuilder();
+ layerBuilder->Init(aBuilder, aLayerManager);
+
+ if (aFlags & PAINT_COMPRESSED) {
+ layerBuilder->SetLayerTreeCompressionMode();
+ }
+
+ RefPtr<ContainerLayer> root;
+ {
+ AUTO_PROFILER_LABEL_CATEGORY_PAIR(GRAPHICS_LayerBuilding);
+#ifdef MOZ_GECKO_PROFILER
+ nsCOMPtr<nsIDocShell> docShell = presContext->GetDocShell();
+ AUTO_PROFILER_TRACING_MARKER_DOCSHELL("Paint", "LayerBuilding", GRAPHICS,
+ docShell);
+#endif
+
+ if (XRE_IsContentProcess() && StaticPrefs::gfx_content_always_paint()) {
+ FrameLayerBuilder::InvalidateAllLayers(aLayerManager);
+ }
+
+ if (aIsWidgetTransaction) {
+ layerBuilder->DidBeginRetainedLayerTransaction(aLayerManager);
+ }
+
+ // Clear any ScrollMetadata that may have been set on the root layer on a
+ // previous paint. This paint will set new metrics if necessary, and if we
+ // don't clear the old one here, we may be left with extra metrics.
+ if (Layer* rootLayer = aLayerManager->GetRoot()) {
+ rootLayer->SetScrollMetadata(nsTArray<ScrollMetadata>());
+ }
+
+ float resolutionUniform = 1.0f;
+ float resolutionX = resolutionUniform;
+ float resolutionY = resolutionUniform;
+
+ // If we are in a remote browser, then apply scaling from ancestor browsers
+ if (BrowserChild* browserChild = BrowserChild::GetFrom(presShell)) {
+ if (!browserChild->IsTopLevel()) {
+ resolutionX *= browserChild->GetEffectsInfo().mScaleX;
+ resolutionY *= browserChild->GetEffectsInfo().mScaleY;
+ }
+ }
+
+ ContainerLayerParameters containerParameters(resolutionX, resolutionY);
+
+ {
+ PaintTelemetry::AutoRecord record(PaintTelemetry::Metric::Layerization);
+
+ root = layerBuilder->BuildContainerLayerFor(aBuilder, aLayerManager,
+ frame, nullptr, this,
+ containerParameters, nullptr);
+
+ aBuilder->NotifyAndClearScrollFrames();
+
+ if (!record.GetStart().IsNull() &&
+ StaticPrefs::layers_acceleration_draw_fps()) {
+ if (PaintTiming* pt =
+ ClientLayerManager::MaybeGetPaintTiming(aLayerManager)) {
+ pt->flbMs() = (TimeStamp::Now() - record.GetStart()).ToMilliseconds();
+ }
+ }
+ }
+
+ if (!root) {
+ return nullptr;
+ }
+ // Root is being scaled up by the X/Y resolution. Scale it back down.
+ root->SetPostScale(1.0f / resolutionX, 1.0f / resolutionY);
+
+ auto callback = [root](ScrollableLayerGuid::ViewID aScrollId) -> bool {
+ return nsLayoutUtils::ContainsMetricsWithId(root, aScrollId);
+ };
+ if (Maybe<ScrollMetadata> rootMetadata = nsLayoutUtils::GetRootMetadata(
+ aBuilder, root->Manager(), containerParameters, callback)) {
+ GetLayerForRootMetadata(root, rootMetadata->GetMetrics().GetScrollId())
+ ->SetScrollMetadata(rootMetadata.value());
+ }
+
+ // NS_WARNING is debug-only, so don't even bother checking the conditions
+ // in a release build.
+#ifdef DEBUG
+ bool usingDisplayport = false;
+ if (nsIFrame* rootScrollFrame = presShell->GetRootScrollFrame()) {
+ nsIContent* content = rootScrollFrame->GetContent();
+ if (content) {
+ usingDisplayport = DisplayPortUtils::HasDisplayPort(content);
+ }
+ }
+ if (usingDisplayport &&
+ !(root->GetContentFlags() & Layer::CONTENT_OPAQUE) &&
+ SpammyLayoutWarningsEnabled()) {
+ // See bug 693938, attachment 567017
+ NS_WARNING("Transparent content with displayports can be expensive.");
+ }
+#endif
+
+ aLayerManager->SetRoot(root);
+ layerBuilder->WillEndTransaction();
+ }
+ return layerBuilder;
+}
+
+/**
+ * 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.
+ */
+already_AddRefed<LayerManager> nsDisplayList::PaintRoot(
+ nsDisplayListBuilder* aBuilder, gfxContext* aCtx, uint32_t aFlags) {
+ AUTO_PROFILER_LABEL("nsDisplayList::PaintRoot", GRAPHICS);
+
+ RefPtr<LayerManager> layerManager;
+ bool widgetTransaction = false;
+ bool doBeginTransaction = true;
+ nsView* view = nullptr;
+ if (aFlags & PAINT_USE_WIDGET_LAYERS) {
+ layerManager = aBuilder->GetWidgetLayerManager(&view);
+ if (layerManager) {
+ layerManager->SetContainsSVG(false);
+
+ doBeginTransaction = !(aFlags & PAINT_EXISTING_TRANSACTION);
+ widgetTransaction = true;
+ }
+ }
+ if (!layerManager) {
+ if (!aCtx) {
+ NS_WARNING("Nowhere to paint into");
+ return nullptr;
+ }
+ layerManager = new BasicLayerManager(BasicLayerManager::BLM_OFFSCREEN);
+ }
+
+ nsIFrame* frame = aBuilder->RootReferenceFrame();
+ nsPresContext* presContext = frame->PresContext();
+ PresShell* presShell = presContext->PresShell();
+ Document* document = presShell->GetDocument();
+
+ if (layerManager->GetBackendType() == layers::LayersBackend::LAYERS_WR) {
+ if (doBeginTransaction) {
+ if (aCtx) {
+ if (!layerManager->BeginTransactionWithTarget(aCtx)) {
+ return nullptr;
+ }
+ } else {
+ if (!layerManager->BeginTransaction()) {
+ return nullptr;
+ }
+ }
+ }
+
+ bool prevIsCompositingCheap =
+ aBuilder->SetIsCompositingCheap(layerManager->IsCompositingCheap());
+ MaybeSetupTransactionIdAllocator(layerManager, presContext);
+
+ bool sent = false;
+ if (aFlags & PAINT_IDENTICAL_DISPLAY_LIST) {
+ sent = layerManager->EndEmptyTransaction();
+ }
+
+ if (!sent) {
+ // Windowed plugins are not supported with WebRender enabled.
+ // But PluginGeometry needs to be updated to show plugin.
+ // Windowed plugins are going to be removed by Bug 1296400.
+ nsRootPresContext* rootPresContext = presContext->GetRootPresContext();
+ if (rootPresContext && XRE_IsContentProcess()) {
+ if (aBuilder->WillComputePluginGeometry()) {
+ rootPresContext->ComputePluginGeometryUpdates(
+ aBuilder->RootReferenceFrame(), aBuilder, this);
+ }
+ // This must be called even if PluginGeometryUpdates were not computed.
+ rootPresContext->CollectPluginGeometryUpdates(layerManager);
+ }
+
+ 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));
+ }
+
+ // 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())) {
+ frame->ClearInvalidationStateBits();
+ }
+
+ aBuilder->SetIsCompositingCheap(prevIsCompositingCheap);
+ if (document && widgetTransaction) {
+ TriggerPendingAnimations(*document,
+ layerManager->GetAnimationReadyTime());
+ }
+
+ if (presContext->RefreshDriver()->HasScheduleFlush()) {
+ presContext->NotifyInvalidation(layerManager->GetLastTransactionId(),
+ frame->GetRect());
+ }
+
+ return layerManager.forget();
+ }
+
+ NotifySubDocInvalidationFunc computeInvalidFunc =
+ presContext->MayHavePaintEventListenerInSubDocument()
+ ? nsPresContext::NotifySubDocInvalidation
+ : nullptr;
+
+ UniquePtr<LayerProperties> props;
+
+ bool computeInvalidRect =
+ (computeInvalidFunc || (!layerManager->IsCompositingCheap() &&
+ layerManager->NeedsWidgetInvalidation())) &&
+ widgetTransaction;
+
+ if (computeInvalidRect) {
+ props = LayerProperties::CloneFrom(layerManager->GetRoot());
+ }
+
+ if (doBeginTransaction) {
+ if (aCtx) {
+ if (!layerManager->BeginTransactionWithTarget(aCtx)) {
+ return nullptr;
+ }
+ } else {
+ if (!layerManager->BeginTransaction()) {
+ return nullptr;
+ }
+ }
+ }
+
+ bool temp =
+ aBuilder->SetIsCompositingCheap(layerManager->IsCompositingCheap());
+ LayerManager::EndTransactionFlags flags = LayerManager::END_DEFAULT;
+ if (layerManager->NeedsWidgetInvalidation()) {
+ if (aFlags & PAINT_NO_COMPOSITE) {
+ flags = LayerManager::END_NO_COMPOSITE;
+ }
+ } else {
+ // Client layer managers never composite directly, so
+ // we don't need to worry about END_NO_COMPOSITE.
+ if (aBuilder->WillComputePluginGeometry()) {
+ flags = LayerManager::END_NO_REMOTE_COMPOSITE;
+ }
+ }
+
+ MaybeSetupTransactionIdAllocator(layerManager, presContext);
+
+ bool sent = false;
+ if (aFlags & PAINT_IDENTICAL_DISPLAY_LIST) {
+ sent = layerManager->EndEmptyTransaction(flags);
+ }
+
+ if (!sent) {
+ FrameLayerBuilder* layerBuilder =
+ BuildLayers(aBuilder, layerManager, aFlags, widgetTransaction);
+
+ if (!layerBuilder) {
+ layerManager->SetUserData(&gLayerManagerLayerBuilder, nullptr);
+ return nullptr;
+ }
+
+ // If this is the content process, we ship plugin geometry updates over with
+ // layer updates, so calculate that now before we call EndTransaction.
+ nsRootPresContext* rootPresContext = presContext->GetRootPresContext();
+ if (rootPresContext && XRE_IsContentProcess()) {
+ if (aBuilder->WillComputePluginGeometry()) {
+ rootPresContext->ComputePluginGeometryUpdates(
+ aBuilder->RootReferenceFrame(), aBuilder, this);
+ }
+ // The layer system caches plugin configuration information for forwarding
+ // with layer updates which needs to get set during reflow. This must be
+ // called even if there are no windowed plugins in the page.
+ rootPresContext->CollectPluginGeometryUpdates(layerManager);
+ }
+
+ layerManager->EndTransaction(FrameLayerBuilder::DrawPaintedLayer, aBuilder,
+ flags);
+ layerBuilder->DidEndTransaction();
+ }
+
+ 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())) {
+ frame->ClearInvalidationStateBits();
+ }
+
+ aBuilder->SetIsCompositingCheap(temp);
+
+ if (document && widgetTransaction) {
+ TriggerPendingAnimations(*document, layerManager->GetAnimationReadyTime());
+ }
+
+ nsIntRegion invalid;
+ if (props) {
+ if (!props->ComputeDifferences(layerManager->GetRoot(), invalid,
+ computeInvalidFunc)) {
+ invalid = nsIntRect::MaxIntRect();
+ }
+ } else if (widgetTransaction) {
+ LayerProperties::ClearInvalidations(layerManager->GetRoot());
+ }
+
+ bool shouldInvalidate = layerManager->NeedsWidgetInvalidation();
+
+ if (view) {
+ if (props) {
+ if (!invalid.IsEmpty()) {
+ nsIntRect bounds = invalid.GetBounds();
+ nsRect rect(presContext->DevPixelsToAppUnits(bounds.x),
+ presContext->DevPixelsToAppUnits(bounds.y),
+ presContext->DevPixelsToAppUnits(bounds.width),
+ presContext->DevPixelsToAppUnits(bounds.height));
+ if (shouldInvalidate) {
+ view->GetViewManager()->InvalidateViewNoSuppression(view, rect);
+ }
+ presContext->NotifyInvalidation(layerManager->GetLastTransactionId(),
+ bounds);
+ }
+ } else if (shouldInvalidate) {
+ view->GetViewManager()->InvalidateView(view);
+ }
+ }
+
+ layerManager->SetUserData(&gLayerManagerLayerBuilder, nullptr);
+ return layerManager.forget();
+}
+
+nsDisplayItem* nsDisplayList::RemoveBottom() {
+ nsDisplayItem* item = mSentinel.mAbove;
+ if (!item) {
+ return nullptr;
+ }
+ mSentinel.mAbove = item->mAbove;
+ if (item == mTop) {
+ // must have been the only item
+ mTop = &mSentinel;
+ }
+ item->mAbove = nullptr;
+ mLength--;
+ return item;
+}
+
+void nsDisplayList::DeleteAll(nsDisplayListBuilder* aBuilder) {
+ nsDisplayItem* item;
+ while ((item = RemoveBottom()) != nullptr) {
+ item->Destroy(aBuilder);
+ }
+}
+
+static bool IsFrameReceivingPointerEvents(nsIFrame* aFrame) {
+ return StylePointerEvents::None !=
+ aFrame->StyleUI()->GetEffectivePointerEvents(aFrame);
+}
+
+// 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::GetCrossDocParentFrame(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));
+}
+
+bool nsDisplayItemBase::HasModifiedFrame() const {
+ return mItemFlags.contains(ItemBaseFlag::ModifiedFrame);
+}
+
+void nsDisplayItemBase::SetModifiedFrame(bool aModified) {
+ if (aModified) {
+ mItemFlags += ItemBaseFlag::ModifiedFrame;
+ } else {
+ mItemFlags -= ItemBaseFlag::ModifiedFrame;
+ }
+}
+
+void nsDisplayItemBase::SetDeletedFrame() {
+ mItemFlags += ItemBaseFlag::DeletedFrame;
+}
+
+bool nsDisplayItemBase::HasDeletedFrame() const {
+ bool retval = mItemFlags.contains(ItemBaseFlag::DeletedFrame) ||
+ (GetType() == DisplayItemType::TYPE_REMOTE &&
+ !static_cast<const nsDisplayRemote*>(this)->GetFrameLoader());
+ MOZ_ASSERT(retval || mFrame);
+ return retval;
+}
+
+#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)
+ : nsDisplayItemBase(aBuilder, aFrame),
+ mActiveScrolledRoot(aActiveScrolledRoot),
+ mAnimatedGeometryRoot(nullptr) {
+ MOZ_COUNT_CTOR(nsDisplayItem);
+
+ mReferenceFrame = aBuilder->FindReferenceFrameFor(aFrame, &mToReferenceFrame);
+ // This can return the wrong result if the item override
+ // ShouldFixToViewport(), the item needs to set it again in its constructor.
+ mAnimatedGeometryRoot = aBuilder->FindAnimatedGeometryRootFor(aFrame);
+ MOZ_ASSERT(nsLayoutUtils::IsAncestorFrameCrossDoc(
+ aBuilder->RootReferenceFrame(), *mAnimatedGeometryRoot),
+ "Bad");
+ NS_ASSERTION(
+ aBuilder->GetVisibleRect().width >= 0 || !aBuilder->IsForPainting(),
+ "visible rect not set");
+
+ nsDisplayItem::SetClipChain(
+ aBuilder->ClipState().GetCurrentCombinedClipChain(aBuilder), true);
+
+ // 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(disp)) {
+ mItemFlags += ItemFlag::Combines3DTransformWithAncestors;
+ }
+}
+
+/* static */
+bool nsDisplayItem::ForceActiveLayers() {
+ return StaticPrefs::layers_force_active();
+}
+
+int32_t nsDisplayItem::ZIndex() const { return mFrame->ZIndex().valueOr(0); }
+
+bool nsDisplayItem::ComputeVisibility(nsDisplayListBuilder* aBuilder,
+ nsRegion* aVisibleRegion) {
+ return !GetPaintRect().IsEmpty() &&
+ !IsInvisibleInRect(aVisibleRegion->GetBounds());
+}
+
+bool nsDisplayItem::RecomputeVisibility(nsDisplayListBuilder* aBuilder,
+ nsRegion* aVisibleRegion) {
+ if (ForceNotVisible() && !GetSameCoordinateSystemChildren()) {
+ // mForceNotVisible wants to ensure that this display item doesn't render
+ // anything itself. If this item has contents, then we obviously want to
+ // render those, so we don't need this check in that case.
+ NS_ASSERTION(GetBuildingRect().IsEmpty(),
+ "invisible items without children should have empty vis rect");
+ SetPaintRect(nsRect());
+ } else {
+ bool snap;
+ nsRect bounds = GetBounds(aBuilder, &snap);
+
+ nsRegion itemVisible;
+ itemVisible.And(*aVisibleRegion, bounds);
+ SetPaintRect(itemVisible.GetBounds());
+ }
+
+ // When we recompute visibility within layers we don't need to
+ // expand the visible region for content behind plugins (the plugin
+ // is not in the layer).
+ if (!ComputeVisibility(aBuilder, aVisibleRegion)) {
+ SetPaintRect(nsRect());
+ return false;
+ }
+
+ nsRegion opaque = TreatAsOpaque(this, aBuilder);
+ aBuilder->SubtractFromVisibleRegion(aVisibleRegion, opaque);
+ return true;
+}
+
+void nsDisplayItem::SetClipChain(const DisplayItemClipChain* aClipChain,
+ bool aStore) {
+ mClipChain = aClipChain;
+ mClip = DisplayItemClipChain::ClipForASR(aClipChain, mActiveScrolledRoot);
+
+ if (aStore) {
+ mState.mClipChain = mClipChain;
+ mState.mClip = mClip;
+ }
+}
+
+Maybe<nsRect> nsDisplayItem::GetClipWithRespectToASR(
+ nsDisplayListBuilder* aBuilder, const ActiveScrolledRoot* aASR) const {
+ if (const DisplayItemClip* clip =
+ DisplayItemClipChain::ClipForASR(GetClipChain(), aASR)) {
+ return Some(clip->GetClipRect());
+ }
+#ifdef DEBUG
+ MOZ_ASSERT(false, "item should have finite clip with respect to aASR");
+#endif
+ return Nothing();
+}
+
+void nsDisplayItem::FuseClipChainUpTo(nsDisplayListBuilder* aBuilder,
+ const ActiveScrolledRoot* aASR) {
+ mClipChain = aBuilder->FuseClipChainUpTo(mClipChain, aASR);
+
+ if (mClipChain) {
+ mClip = &mClipChain->mClip;
+ } else {
+ mClip = nullptr;
+ }
+}
+
+bool nsDisplayItem::ShouldUseAdvancedLayer(LayerManager* aManager,
+ PrefFunc aFunc) const {
+ return CanUseAdvancedLayer(aManager) ? aFunc() : false;
+}
+
+bool nsDisplayItem::CanUseAdvancedLayer(LayerManager* aManager) const {
+ return StaticPrefs::layers_advanced_basic_layer_enabled() || !aManager ||
+ aManager->GetBackendType() == layers::LayersBackend::LAYERS_WR;
+}
+
+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;
+}
+
+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) {
+ MOZ_COUNT_CTOR(nsDisplayContainer);
+ mChildren.AppendToTop(aList);
+ UpdateBounds(aBuilder);
+
+ // Clear and store the clip chain set by nsDisplayItem constructor.
+ nsDisplayItem::SetClipChain(nullptr, true);
+}
+
+bool nsDisplayContainer::CreateWebRenderCommands(
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) {
+ aManager->CommandBuilder().CreateWebRenderCommandsFromDisplayList(
+ GetChildren(), this, aDisplayListBuilder, aSc, aBuilder, aResources);
+ return true;
+}
+
+/**
+ * Like |nsDisplayList::ComputeVisibilityForSublist()|, but restricts
+ * |aVisibleRegion| to given |aBounds| of the list.
+ */
+static bool ComputeClippedVisibilityForSubList(nsDisplayListBuilder* aBuilder,
+ nsRegion* aVisibleRegion,
+ nsDisplayList* aList,
+ const nsRect& aBounds) {
+ nsRegion visibleRegion;
+ visibleRegion.And(*aVisibleRegion, aBounds);
+ nsRegion originalVisibleRegion = visibleRegion;
+
+ const bool anyItemVisible =
+ aList->ComputeVisibilityForSublist(aBuilder, &visibleRegion, aBounds);
+ nsRegion removed;
+ // removed = originalVisibleRegion - visibleRegion
+ removed.Sub(originalVisibleRegion, visibleRegion);
+ // aVisibleRegion = aVisibleRegion - removed (modulo any simplifications
+ // SubtractFromVisibleRegion does)
+ aBuilder->SubtractFromVisibleRegion(aVisibleRegion, removed);
+
+ return anyItemVisible;
+}
+
+bool nsDisplayContainer::ComputeVisibility(nsDisplayListBuilder* aBuilder,
+ nsRegion* aVisibleRegion) {
+ return ::ComputeClippedVisibilityForSubList(aBuilder, aVisibleRegion,
+ GetChildren(), GetPaintRect());
+}
+
+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) {
+ if (aList->IsOpaque()) {
+ // Everything within list bounds that's visible is opaque. This is an
+ // optimization to avoid calculating the opaque region.
+ return aListBounds;
+ }
+
+ if (aBuilder->HitTestIsForVisibility()) {
+ // If we care about an accurate opaque region, iterate the display list
+ // and build up a region of opaque bounds.
+ return aList->GetOpaqueRegion(aBuilder);
+ }
+
+ return nsRegion();
+}
+
+nsRegion nsDisplayContainer::GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
+ bool* aSnap) const {
+ return ::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;
+}
+
+LayerState nsDisplaySolidColor::GetLayerState(
+ nsDisplayListBuilder* aBuilder, LayerManager* aManager,
+ const ContainerLayerParameters& aParameters) {
+ if (ForceActiveLayers()) {
+ return LayerState::LAYER_ACTIVE;
+ }
+
+ return LayerState::LAYER_NONE;
+}
+
+already_AddRefed<Layer> nsDisplaySolidColor::BuildLayer(
+ nsDisplayListBuilder* aBuilder, LayerManager* aManager,
+ const ContainerLayerParameters& aContainerParameters) {
+ RefPtr<ColorLayer> layer = static_cast<ColorLayer*>(
+ aManager->GetLayerBuilder()->GetLeafLayerFor(aBuilder, this));
+ if (!layer) {
+ layer = aManager->CreateColorLayer();
+ if (!layer) {
+ return nullptr;
+ }
+ }
+ layer->SetColor(ToDeviceColor(mColor));
+
+ const int32_t appUnitsPerDevPixel =
+ mFrame->PresContext()->AppUnitsPerDevPixel();
+ layer->SetBounds(mBounds.ToNearestPixels(appUnitsPerDevPixel));
+ layer->SetBaseTransform(gfx::Matrix4x4::Translation(
+ aContainerParameters.mOffset.x, aContainerParameters.mOffset.y, 0));
+
+ return layer.forget();
+}
+
+void nsDisplaySolidColor::Paint(nsDisplayListBuilder* aBuilder,
+ gfxContext* aCtx) {
+ int32_t appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel();
+ DrawTarget* drawTarget = aCtx->GetDrawTarget();
+ Rect rect =
+ NSRectToSnappedRect(GetPaintRect(), 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(
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) {
+ LayoutDeviceRect bounds = LayoutDeviceRect::FromAppUnits(
+ mBounds, mFrame->PresContext()->AppUnitsPerDevPixel());
+ wr::LayoutRect r = wr::ToLayoutRect(bounds);
+ aBuilder.PushRect(r, r, !BackfaceIsHidden(),
+ 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(
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ mozilla::layers::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(),
+ wr::ToColorF(ToDeviceColor(mColor)));
+ }
+
+ return true;
+}
+
+static void RegisterThemeGeometry(nsDisplayListBuilder* aBuilder,
+ nsDisplayItem* aItem, nsIFrame* aFrame,
+ nsITheme::ThemeGeometryType aType) {
+ if (aBuilder->IsInChromeDocumentOrPopup() && !aBuilder->IsInTransform()) {
+ nsIFrame* displayRoot = nsLayoutUtils::GetDisplayRootFrame(aFrame);
+ nsPoint offset = aBuilder->IsInSubdocument()
+ ? aBuilder->ToReferenceFrame(aFrame)
+ : aFrame->GetOffsetTo(displayRoot);
+ nsRect borderBox = nsRect(offset, aFrame->GetSize());
+ 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,
+ 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)
+ : nsDisplayImageContainer(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),
+ mImageFlags(0) {
+ 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) {
+ mAnimatedGeometryRoot =
+ aInitData.builder->FindAnimatedGeometryRootFor(this);
+
+ // 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 nsIFrame* GetBackgroundComputedStyleFrame(nsIFrame* aFrame) {
+ nsIFrame* f;
+ if (!nsCSSRendering::FindBackgroundFrame(aFrame, &f)) {
+ // 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, other wise 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 (!aFrame->StyleDisplay()->HasAppearance()) {
+ return nullptr;
+ }
+
+ nsIContent* content = aFrame->GetContent();
+ if (!content || content->GetParent()) {
+ return nullptr;
+ }
+
+ f = aFrame;
+ }
+ return f;
+}
+
+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,
+ 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, 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*/
+bool nsDisplayBackgroundImage::AppendBackgroundItemsToTop(
+ nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ const nsRect& aBackgroundRect, nsDisplayList* aList,
+ bool aAllowWillPaintBorderOptimization, ComputedStyle* aComputedStyle,
+ const nsRect& aBackgroundOriginRect, nsIFrame* aSecondaryReferenceFrame,
+ Maybe<nsDisplayListBuilder::AutoBuildingDisplayList>*
+ aAutoBuildingDisplayList) {
+ ComputedStyle* bgSC = aComputedStyle;
+ const nsStyleBackground* bg = nullptr;
+ nsRect bgRect = aBackgroundRect;
+ nsRect bgOriginRect = bgRect;
+ if (!aBackgroundOriginRect.IsEmpty()) {
+ bgOriginRect = aBackgroundOriginRect;
+ }
+ nsPresContext* presContext = aFrame->PresContext();
+ bool isThemed = aFrame->IsThemed();
+ nsIFrame* dependentFrame = nullptr;
+ if (!isThemed) {
+ if (!bgSC) {
+ dependentFrame = GetBackgroundComputedStyleFrame(aFrame);
+ if (dependentFrame) {
+ bgSC = dependentFrame->Style();
+ if (dependentFrame == aFrame) {
+ dependentFrame = nullptr;
+ }
+ }
+ }
+ if (bgSC) {
+ bg = bgSC->StyleBackground();
+ }
+ }
+
+ bool drawBackgroundColor = false;
+ // XUL root frames need special handling for now even though they return true
+ // from nsCSSRendering::IsCanvasFrame they rely on us painting the background
+ // image from here, see bug 1665476.
+ bool drawBackgroundImage =
+ aFrame->IsXULRootFrame() && aFrame->ComputeShouldPaintBackground().mImage;
+ nscolor color = NS_RGBA(0, 0, 0, 0);
+ if (!nsCSSRendering::IsCanvasFrame(aFrame) && bg) {
+ color = nsCSSRendering::DetermineBackgroundColor(
+ presContext, bgSC, aFrame, drawBackgroundImage, drawBackgroundColor);
+ }
+
+ if (SpecialCutoutRegionCase(aBuilder, aFrame, aBackgroundRect, aList,
+ color)) {
+ return false;
+ }
+
+ const nsStyleBorder* borderStyle = aFrame->StyleBorder();
+ const nsStyleEffects* effectsStyle = aFrame->StyleEffects();
+ bool hasInsetShadow = effectsStyle->HasBoxShadowWithInset(true);
+ bool willPaintBorder = aAllowWillPaintBorderOptimization && !isThemed &&
+ !hasInsetShadow && borderStyle->HasBorder();
+
+ // 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;
+ // Even if we don't actually have a background color to paint, we may still
+ // need to create an item for hit testing.
+ if ((drawBackgroundColor && color != NS_RGBA(0, 0, 0, 0)) ||
+ aBuilder->IsForEventDelivery()) {
+ if (aAutoBuildingDisplayList && !*aAutoBuildingDisplayList) {
+ aAutoBuildingDisplayList->emplace(aBuilder, aFrame);
+ }
+ Maybe<DisplayListClipState::AutoSaveRestore> clipState;
+ nsRect bgColorRect = bgRect;
+ if (bg && !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.
+ bool useWillPaintBorderOptimization =
+ willPaintBorder &&
+ nsLayoutUtils::HasNonZeroCorner(borderStyle->mBorderRadius);
+
+ nsCSSRendering::ImageLayerClipState clip;
+ nsCSSRendering::GetImageLayerClip(
+ bg->BottomLayer(), aFrame, *aFrame->StyleBorder(), bgRect, bgRect,
+ 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) {
+ bgItem->SetDependentFrame(aBuilder, dependentFrame);
+ bgItemList.AppendToTop(bgItem);
+ }
+ }
+
+ if (isThemed) {
+ nsDisplayThemedBackground* bgItem = CreateThemedBackground(
+ aBuilder, aFrame, aSecondaryReferenceFrame, bgRect);
+
+ if (bgItem) {
+ bgItem->Init(aBuilder);
+ bgItemList.AppendToTop(bgItem);
+ }
+
+ aList->AppendToTop(&bgItemList);
+ return true;
+ }
+
+ if (!bg || !drawBackgroundImage) {
+ aList->AppendToTop(&bgItemList);
+ return false;
+ }
+
+ const ActiveScrolledRoot* asr = aBuilder->CurrentActiveScrolledRoot();
+
+ bool needBlendContainer = false;
+
+ // 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;
+ }
+
+ if (aAutoBuildingDisplayList && !*aAutoBuildingDisplayList) {
+ aAutoBuildingDisplayList->emplace(aBuilder, aFrame);
+ }
+
+ 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, bgRect,
+ willPaintBorder);
+ }
+
+ nsDisplayList thisItemList;
+ 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);
+ 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) {
+ bgItem->SetDependentFrame(aBuilder, dependentFrame);
+
+ thisItemList.AppendToTop(
+ nsDisplayFixedPosition::CreateForFixedBackground(
+ aBuilder, aFrame, aSecondaryReferenceFrame, bgItem, i));
+ }
+ } else { // bgData.shouldFixToViewport == false
+ nsDisplayBackgroundImage* bgItem = CreateBackgroundImage(
+ aBuilder, aFrame, aSecondaryReferenceFrame, bgData);
+ if (bgItem) {
+ bgItem->SetDependentFrame(aBuilder, dependentFrame);
+ thisItemList.AppendToTop(bgItem);
+ }
+ }
+
+ if (bg->mImage.mLayers[i].mBlendMode != StyleBlend::Normal) {
+ DisplayListClipState::AutoSaveRestore blendClip(aBuilder);
+ // 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) {
+ DisplayListClipState::AutoSaveRestore blendContainerClip(aBuilder);
+
+ bgItemList.AppendToTop(
+ nsDisplayBlendContainer::CreateForBackgroundBlendMode(
+ aBuilder, aFrame, aSecondaryReferenceFrame, &bgItemList, asr));
+ }
+
+ aList->AppendToTop(&bgItemList);
+ return false;
+}
+
+// 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::CanOptimizeToImageLayer(
+ LayerManager* aManager, nsDisplayListBuilder* aBuilder) {
+ if (!mBackgroundStyle) {
+ return false;
+ }
+
+ // We currently can't handle tiled backgrounds.
+ if (!mDestRect.Contains(mFillRect)) {
+ return false;
+ }
+
+ // For 'contain' and 'cover', we allow any pixel of the image to be sampled
+ // because there isn't going to be any spriting/atlasing going on.
+ const nsStyleImageLayers::Layer& layer =
+ mBackgroundStyle->StyleBackground()->mImage.mLayers[mLayer];
+ bool allowPartialImages = layer.mSize.IsContain() || layer.mSize.IsCover();
+ if (!allowPartialImages && !mFillRect.Contains(mDestRect)) {
+ return false;
+ }
+
+ return nsDisplayImageContainer::CanOptimizeToImageLayer(aManager, aBuilder);
+}
+
+nsRect nsDisplayBackgroundImage::GetDestRect() const { return mDestRect; }
+
+already_AddRefed<imgIContainer> nsDisplayBackgroundImage::GetImage() {
+ nsCOMPtr<imgIContainer> image = mImage;
+ return image.forget();
+}
+
+nsDisplayBackgroundImage::ImageLayerization
+nsDisplayBackgroundImage::ShouldCreateOwnLayer(nsDisplayListBuilder* aBuilder,
+ LayerManager* aManager) {
+ if (ForceActiveLayers()) {
+ return WHENEVER_POSSIBLE;
+ }
+
+ nsIFrame* backgroundStyleFrame =
+ nsCSSRendering::FindBackgroundStyleFrame(StyleFrame());
+ if (ActiveLayerTracker::IsBackgroundPositionAnimated(aBuilder,
+ backgroundStyleFrame)) {
+ return WHENEVER_POSSIBLE;
+ }
+
+ if (StaticPrefs::layout_animated_image_layers_enabled() && mBackgroundStyle) {
+ const nsStyleImageLayers::Layer& layer =
+ mBackgroundStyle->StyleBackground()->mImage.mLayers[mLayer];
+ const auto* image = &layer.mImage;
+ if (auto* request = image->GetImageRequest()) {
+ nsCOMPtr<imgIContainer> image;
+ if (NS_SUCCEEDED(request->GetImage(getter_AddRefs(image))) && image) {
+ bool animated = false;
+ if (NS_SUCCEEDED(image->GetAnimated(&animated)) && animated) {
+ return WHENEVER_POSSIBLE;
+ }
+ }
+ }
+ }
+
+ if (nsLayoutUtils::GPUImageScalingEnabled() &&
+ aManager->IsCompositingCheap()) {
+ return ONLY_FOR_SCALING;
+ }
+
+ return NO_LAYER_NEEDED;
+}
+
+static void CheckForBorderItem(nsDisplayItem* aItem, uint32_t& aFlags) {
+ nsDisplayItem* nextItem = aItem->GetAbove();
+ while (nextItem && nextItem->GetType() == DisplayItemType::TYPE_BACKGROUND) {
+ nextItem = nextItem->GetAbove();
+ }
+ if (nextItem && nextItem->Frame() == aItem->Frame() &&
+ nextItem->GetType() == DisplayItemType::TYPE_BORDER) {
+ aFlags |= nsCSSRendering::PAINTBG_WILL_PAINT_BORDER;
+ }
+}
+
+LayerState nsDisplayBackgroundImage::GetLayerState(
+ nsDisplayListBuilder* aBuilder, LayerManager* aManager,
+ const ContainerLayerParameters& aParameters) {
+ mImageFlags = aBuilder->GetBackgroundPaintFlags();
+ CheckForBorderItem(this, mImageFlags);
+
+ ImageLayerization shouldLayerize = ShouldCreateOwnLayer(aBuilder, aManager);
+ if (shouldLayerize == NO_LAYER_NEEDED) {
+ // We can skip the call to CanOptimizeToImageLayer if we don't want a
+ // layer anyway.
+ return LayerState::LAYER_NONE;
+ }
+
+ if (CanOptimizeToImageLayer(aManager, aBuilder)) {
+ if (shouldLayerize == WHENEVER_POSSIBLE) {
+ return LayerState::LAYER_ACTIVE;
+ }
+
+ MOZ_ASSERT(shouldLayerize == ONLY_FOR_SCALING,
+ "unhandled ImageLayerization value?");
+
+ MOZ_ASSERT(mImage);
+ int32_t imageWidth;
+ int32_t imageHeight;
+ mImage->GetWidth(&imageWidth);
+ mImage->GetHeight(&imageHeight);
+ NS_ASSERTION(imageWidth != 0 && imageHeight != 0, "Invalid image size!");
+
+ int32_t appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel();
+ LayoutDeviceRect destRect =
+ LayoutDeviceRect::FromAppUnits(GetDestRect(), appUnitsPerDevPixel);
+
+ const LayerRect destLayerRect = destRect * aParameters.Scale();
+
+ // Calculate the scaling factor for the frame.
+ const gfxSize scale = gfxSize(destLayerRect.width / imageWidth,
+ destLayerRect.height / imageHeight);
+
+ if ((scale.width != 1.0f || scale.height != 1.0f) &&
+ (destLayerRect.width * destLayerRect.height >= 64 * 64)) {
+ // Separate this image into a layer.
+ // There's no point in doing this if we are not scaling at all or if the
+ // target size is pretty small.
+ return LayerState::LAYER_ACTIVE;
+ }
+ }
+
+ return LayerState::LAYER_NONE;
+}
+
+already_AddRefed<Layer> nsDisplayBackgroundImage::BuildLayer(
+ nsDisplayListBuilder* aBuilder, LayerManager* aManager,
+ const ContainerLayerParameters& aParameters) {
+ RefPtr<ImageLayer> layer = static_cast<ImageLayer*>(
+ aManager->GetLayerBuilder()->GetLeafLayerFor(aBuilder, this));
+ if (!layer) {
+ layer = aManager->CreateImageLayer();
+ if (!layer) {
+ return nullptr;
+ }
+ }
+ RefPtr<ImageContainer> imageContainer = GetContainer(aManager, aBuilder);
+ layer->SetContainer(imageContainer);
+ ConfigureLayer(layer, aParameters);
+ return layer.forget();
+}
+
+bool nsDisplayBackgroundImage::CanBuildWebRenderDisplayItems(
+ LayerManager* aManager, nsDisplayListBuilder* aDisplayListBuilder) {
+ if (aDisplayListBuilder) {
+ mImageFlags = aDisplayListBuilder->GetBackgroundPaintFlags();
+ }
+
+ return mBackgroundStyle->StyleBackground()->mImage.mLayers[mLayer].mClip !=
+ StyleGeometryBox::Text &&
+ nsCSSRendering::CanBuildWebRenderDisplayItemsForStyleImageLayer(
+ aManager, *StyleFrame()->PresContext(), StyleFrame(),
+ mBackgroundStyle->StyleBackground(), mLayer, mImageFlags);
+}
+
+bool nsDisplayBackgroundImage::CreateWebRenderCommands(
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc, RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) {
+ if (!CanBuildWebRenderDisplayItems(aManager->LayerManager(),
+ aDisplayListBuilder)) {
+ return false;
+ }
+
+ CheckForBorderItem(this, mImageFlags);
+ nsCSSRendering::PaintBGParams params =
+ nsCSSRendering::PaintBGParams::ForSingleLayer(
+ *StyleFrame()->PresContext(), GetPaintRect(), mBackgroundRect,
+ StyleFrame(), mImageFlags, mLayer, CompositionOp::OP_OVER);
+ params.bgClipRect = &mBounds;
+ ImgDrawResult result =
+ nsCSSRendering::BuildWebRenderDisplayItemsForStyleImageLayer(
+ params, aBuilder, aResources, aSc, aManager, this);
+ if (result == ImgDrawResult::NOT_SUPPORTED) {
+ return false;
+ }
+
+ nsDisplayBackgroundGeometry::UpdateDrawResult(this, result);
+ return true;
+}
+
+void nsDisplayBackgroundImage::HitTest(nsDisplayListBuilder* aBuilder,
+ const nsRect& aRect,
+ HitTestState* aState,
+ nsTArray<nsIFrame*>* aOutFrames) {
+ if (RoundedBorderIntersectsRect(mFrame, ToReferenceFrame(), aRect)) {
+ aOutFrames->AppendElement(mFrame);
+ }
+}
+
+bool nsDisplayBackgroundImage::ComputeVisibility(nsDisplayListBuilder* aBuilder,
+ nsRegion* aVisibleRegion) {
+ if (!nsDisplayImageContainer::ComputeVisibility(aBuilder, aVisibleRegion)) {
+ return false;
+ }
+
+ // Return false if the background was propagated away from this
+ // frame. We don't want this display item to show up and confuse
+ // anything.
+ return mBackgroundStyle;
+}
+
+/* static */
+nsRegion nsDisplayBackgroundImage::GetInsideClipRegion(
+ const nsDisplayItem* aItem, StyleGeometryBox aClip, const nsRect& aRect,
+ const nsRect& aBackgroundRect) {
+ nsRegion result;
+ if (aRect.IsEmpty()) {
+ return result;
+ }
+
+ 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 = GetInsideClipRegion(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];
+ if (layer.RenderingMightDependOnPositioningAreaSizeChange()) {
+ return true;
+ }
+ return false;
+}
+
+void nsDisplayBackgroundImage::Paint(nsDisplayListBuilder* aBuilder,
+ gfxContext* aCtx) {
+ PaintInternal(aBuilder, aCtx, GetPaintRect(), &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(),
+ mImageFlags, mLayer, CompositionOp::OP_OVER);
+ params.bgClipRect = aClipRect;
+ ImgDrawResult result = nsCSSRendering::PaintStyleImageLayer(params, *aCtx);
+
+ if (clip == StyleGeometryBox::Text) {
+ ctx->PopGroupAndBlend();
+ }
+
+ nsDisplayBackgroundGeometry::UpdateDrawResult(this, result);
+}
+
+void nsDisplayBackgroundImage::ComputeInvalidationRegion(
+ nsDisplayListBuilder* aBuilder, const nsDisplayItemGeometry* aGeometry,
+ nsRegion* aInvalidRegion) const {
+ if (!mBackgroundStyle) {
+ return;
+ }
+
+ 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 (aBuilder->ShouldSyncDecodeImages()) {
+ const auto& image =
+ mBackgroundStyle->StyleBackground()->mImage.mLayers[mLayer].mImage;
+ if (image.IsImageRequestType() &&
+ geometry->ShouldInvalidateToSyncDecodeImages()) {
+ aInvalidRegion->Or(*aInvalidRegion, bounds);
+ }
+ }
+ 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 ||
+ mAppearance == StyleAppearance::MozWinGlass) {
+ 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 ||
+ mAppearance == StyleAppearance::MozWinGlass) {
+ 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(), 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(
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) {
+ nsITheme* theme = StyleFrame()->PresContext()->Theme();
+ return theme->CreateWebRenderCommandsForWidget(aBuilder, aResources, aSc,
+ aManager, StyleFrame(),
+ mAppearance, mBackgroundRect);
+}
+
+bool nsDisplayThemedBackground::IsWindowActive() const {
+ EventStates docState = mFrame->GetContent()->OwnerDoc()->GetDocumentState();
+ return !docState.HasState(NS_DOCUMENT_STATE_WINDOW_INACTIVE);
+}
+
+void nsDisplayThemedBackground::ComputeInvalidationRegion(
+ nsDisplayListBuilder* aBuilder, const nsDisplayItemGeometry* aGeometry,
+ nsRegion* aInvalidRegion) 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();
+}
+
+void nsDisplayImageContainer::ConfigureLayer(
+ ImageLayer* aLayer, const ContainerLayerParameters& aParameters) {
+ aLayer->SetSamplingFilter(nsLayoutUtils::GetSamplingFilterForFrame(mFrame));
+
+ nsCOMPtr<imgIContainer> image = GetImage();
+ MOZ_ASSERT(image);
+ int32_t imageWidth;
+ int32_t imageHeight;
+ image->GetWidth(&imageWidth);
+ image->GetHeight(&imageHeight);
+ NS_ASSERTION(imageWidth != 0 && imageHeight != 0, "Invalid image size!");
+
+ if (imageWidth > 0 && imageHeight > 0) {
+ // We're actually using the ImageContainer. Let our frame know that it
+ // should consider itself to have painted successfully.
+ UpdateDrawResult(ImgDrawResult::SUCCESS);
+ }
+
+ // It's possible (for example, due to downscale-during-decode) that the
+ // ImageContainer this ImageLayer is holding has a different size from the
+ // intrinsic size of the image. For this reason we compute the transform using
+ // the ImageContainer's size rather than the image's intrinsic size.
+ // XXX(seth): In reality, since the size of the ImageContainer may change
+ // asynchronously, this is not enough. Bug 1183378 will provide a more
+ // complete fix, but this solution is safe in more cases than simply relying
+ // on the intrinsic size.
+ IntSize containerSize = aLayer->GetContainer()
+ ? aLayer->GetContainer()->GetCurrentSize()
+ : IntSize(imageWidth, imageHeight);
+
+ const int32_t factor = mFrame->PresContext()->AppUnitsPerDevPixel();
+ const LayoutDeviceRect destRect(
+ LayoutDeviceIntRect::FromAppUnitsToNearest(GetDestRect(), factor));
+
+ const LayoutDevicePoint p = destRect.TopLeft();
+ Matrix transform = Matrix::Translation(p.x + aParameters.mOffset.x,
+ p.y + aParameters.mOffset.y);
+ transform.PreScale(destRect.width / containerSize.width,
+ destRect.height / containerSize.height);
+ aLayer->SetBaseTransform(gfx::Matrix4x4::From2D(transform));
+}
+
+already_AddRefed<ImageContainer> nsDisplayImageContainer::GetContainer(
+ LayerManager* aManager, nsDisplayListBuilder* aBuilder) {
+ nsCOMPtr<imgIContainer> image = GetImage();
+ if (!image) {
+ MOZ_ASSERT_UNREACHABLE(
+ "Must call CanOptimizeToImage() and get true "
+ "before calling GetContainer()");
+ return nullptr;
+ }
+
+ uint32_t flags = imgIContainer::FLAG_ASYNC_NOTIFY;
+ if (aBuilder->ShouldSyncDecodeImages()) {
+ flags |= imgIContainer::FLAG_SYNC_DECODE;
+ }
+
+ RefPtr<ImageContainer> container = image->GetImageContainer(aManager, flags);
+ if (!container || !container->HasCurrentImage()) {
+ return nullptr;
+ }
+
+ return container.forget();
+}
+
+bool nsDisplayImageContainer::CanOptimizeToImageLayer(
+ LayerManager* aManager, nsDisplayListBuilder* aBuilder) {
+ uint32_t flags = aBuilder->ShouldSyncDecodeImages()
+ ? imgIContainer::FLAG_SYNC_DECODE
+ : imgIContainer::FLAG_NONE;
+
+ nsCOMPtr<imgIContainer> image = GetImage();
+ if (!image) {
+ return false;
+ }
+
+ if (!image->IsImageContainerAvailable(aManager, flags)) {
+ return false;
+ }
+
+ int32_t imageWidth;
+ int32_t imageHeight;
+ image->GetWidth(&imageWidth);
+ image->GetHeight(&imageHeight);
+
+ if (imageWidth == 0 || imageHeight == 0) {
+ NS_ASSERTION(false, "invalid image size");
+ return false;
+ }
+
+ const int32_t factor = mFrame->PresContext()->AppUnitsPerDevPixel();
+ const LayoutDeviceRect destRect(
+ LayoutDeviceIntRect::FromAppUnitsToNearest(GetDestRect(), factor));
+
+ // Calculate the scaling factor for the frame.
+ const gfxSize scale =
+ gfxSize(destRect.width / imageWidth, destRect.height / imageHeight);
+
+ if (scale.width < 0.34 || scale.height < 0.34) {
+ // This would look awful as long as we can't use high-quality downscaling
+ // for image layers (bug 803703), so don't turn this into an image layer.
+ return false;
+ }
+
+ if (mFrame->IsImageFrame() || mFrame->IsImageControlFrame()) {
+ // Image layer doesn't support draw focus ring for image map.
+ nsImageFrame* f = static_cast<nsImageFrame*>(mFrame);
+ if (f->HasImageMap()) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+#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
+
+void nsDisplayBackgroundColor::ApplyOpacity(nsDisplayListBuilder* aBuilder,
+ float aOpacity,
+ const DisplayItemClipChain* aClip) {
+ NS_ASSERTION(CanApplyOpacity(), "ApplyOpacity should be allowed");
+ mColor.a = mColor.a * aOpacity;
+ IntersectClip(aBuilder, aClip, false);
+}
+
+bool nsDisplayBackgroundColor::CanApplyOpacity() 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);
+}
+
+LayerState nsDisplayBackgroundColor::GetLayerState(
+ nsDisplayListBuilder* aBuilder, LayerManager* aManager,
+ const ContainerLayerParameters& aParameters) {
+ if (ForceActiveLayers() && !HasBackgroundClipText()) {
+ return LayerState::LAYER_ACTIVE;
+ }
+
+ if (EffectCompositor::HasAnimationsForCompositor(
+ mFrame, DisplayItemType::TYPE_BACKGROUND_COLOR)) {
+ return LayerState::LAYER_ACTIVE_FORCE;
+ }
+
+ return LayerState::LAYER_NONE;
+}
+
+already_AddRefed<Layer> nsDisplayBackgroundColor::BuildLayer(
+ nsDisplayListBuilder* aBuilder, LayerManager* aManager,
+ const ContainerLayerParameters& aContainerParameters) {
+ if (mColor == sRGBColor()) {
+ return nullptr;
+ }
+
+ RefPtr<ColorLayer> layer = static_cast<ColorLayer*>(
+ aManager->GetLayerBuilder()->GetLeafLayerFor(aBuilder, this));
+ if (!layer) {
+ layer = aManager->CreateColorLayer();
+ if (!layer) {
+ return nullptr;
+ }
+ }
+ layer->SetColor(ToDeviceColor(mColor));
+
+ int32_t appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel();
+ layer->SetBounds(mBackgroundRect.ToNearestPixels(appUnitsPerDevPixel));
+ layer->SetBaseTransform(gfx::Matrix4x4::Translation(
+ aContainerParameters.mOffset.x, aContainerParameters.mOffset.y, 0));
+
+ // Both nsDisplayBackgroundColor and nsDisplayTableBackgroundColor use this
+ // function, but only nsDisplayBackgroundColor supports compositor animations.
+ if (GetType() == DisplayItemType::TYPE_BACKGROUND_COLOR) {
+ nsDisplayListBuilder::AddAnimationsAndTransitionsToLayer(
+ layer, aBuilder, this, mFrame, GetType());
+ }
+ return layer.forget();
+}
+
+bool nsDisplayBackgroundColor::CreateWebRenderCommands(
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) {
+ if (mColor == sRGBColor()) {
+ 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(mColor)), &prop);
+ } else {
+ aBuilder.StartGroup(this);
+ aBuilder.PushRect(r, r, !BackfaceIsHidden(),
+ wr::ToColorF(ToDeviceColor(mColor)));
+ 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 nsDisplayBackgroundImage::GetInsideClipRegion(
+ 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();
+}
+
+void nsDisplayOutline::Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) {
+ // TODO join outlines together
+ MOZ_ASSERT(mFrame->StyleOutline()->ShouldPaintOutline(),
+ "Should have not created a nsDisplayOutline!");
+
+ nsPoint offset = ToReferenceFrame();
+ nsCSSRendering::PaintOutline(
+ mFrame->PresContext(), *aCtx, mFrame, GetPaintRect(),
+ nsRect(offset, mFrame->GetSize()), mFrame->Style());
+}
+
+bool nsDisplayOutline::IsThemedOutline() const {
+ const auto& outlineStyle = mFrame->StyleOutline()->mOutlineStyle;
+ if (!outlineStyle.IsAuto() ||
+ !StaticPrefs::layout_css_outline_style_auto_enabled()) {
+ return false;
+ }
+
+ nsPresContext* pc = mFrame->PresContext();
+ nsITheme* theme = pc->Theme();
+ return theme->ThemeSupportsWidget(pc, mFrame, StyleAppearance::FocusOutline);
+}
+
+bool nsDisplayOutline::CreateWebRenderCommands(
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) {
+ if (IsThemedOutline()) {
+ return false;
+ }
+
+ nsPoint offset = ToReferenceFrame();
+
+ mozilla::Maybe<nsCSSBorderRenderer> borderRenderer =
+ nsCSSRendering::CreateBorderRendererForOutline(
+ mFrame->PresContext(), nullptr, mFrame, GetPaintRect(),
+ nsRect(offset, mFrame->GetSize()), 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::IsInvisibleInRect(const nsRect& aRect) const {
+ const nsStyleOutline* outline = mFrame->StyleOutline();
+ nsRect borderBox(ToReferenceFrame(), mFrame->GetSize());
+ if (borderBox.Contains(aRect) &&
+ !nsLayoutUtils::HasNonZeroCorner(outline->mOutlineRadius)) {
+ if (outline->mOutlineOffset._0 >= 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);
+}
+
+nsDisplayCompositorHitTestInfo::nsDisplayCompositorHitTestInfo(
+ nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ const mozilla::gfx::CompositorHitTestInfo& aHitTestFlags,
+ const mozilla::Maybe<nsRect>& aArea)
+ : nsDisplayHitTestInfoBase(aBuilder, aFrame),
+ mAppUnitsPerDevPixel(mFrame->PresContext()->AppUnitsPerDevPixel()) {
+ MOZ_COUNT_CTOR(nsDisplayCompositorHitTestInfo);
+ // We should never even create this display item if we're not building
+ // compositor hit-test info or if the computed hit info indicated the
+ // frame is invisible to hit-testing
+ MOZ_ASSERT(aBuilder->BuildCompositorHitTestInfo());
+ MOZ_ASSERT(aHitTestFlags != CompositorHitTestInvisibleToHit);
+
+ const nsRect& area =
+ aArea.isSome() ? *aArea : aFrame->GetCompositorHitTestArea(aBuilder);
+
+ SetHitTestInfo(area, aHitTestFlags);
+ InitializeScrollTarget(aBuilder);
+}
+
+nsDisplayCompositorHitTestInfo::nsDisplayCompositorHitTestInfo(
+ nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ mozilla::UniquePtr<HitTestInfo>&& aHitTestInfo)
+ : nsDisplayHitTestInfoBase(aBuilder, aFrame),
+ mAppUnitsPerDevPixel(mFrame->PresContext()->AppUnitsPerDevPixel()) {
+ MOZ_COUNT_CTOR(nsDisplayCompositorHitTestInfo);
+ SetHitTestInfo(std::move(aHitTestInfo));
+ InitializeScrollTarget(aBuilder);
+}
+
+void nsDisplayCompositorHitTestInfo::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(HitTestFlags().contains(CompositorHitTestFlags::eScrollbar));
+ mScrollTarget = mozilla::Some(aBuilder->GetCurrentScrollbarTarget());
+ }
+}
+
+bool nsDisplayCompositorHitTestInfo::CreateWebRenderCommands(
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) {
+ if (HitTestArea().IsEmpty()) {
+ return true;
+ }
+
+ // XXX: eventually this scrollId computation and the SetHitTestInfo
+ // call will get moved out into the WR display item iteration code so that
+ // we don't need to do it as often, and so that we can do it for other
+ // display item types as well (reducing the need for as many instances of
+ // this display item).
+ ScrollableLayerGuid::ViewID scrollId =
+ mScrollTarget.valueOrFrom([&]() -> ScrollableLayerGuid::ViewID {
+ const ActiveScrolledRoot* asr = GetActiveScrolledRoot();
+ Maybe<ScrollableLayerGuid::ViewID> fixedTarget =
+ aBuilder.GetContainingFixedPosScrollTarget(asr);
+ if (fixedTarget) {
+ return *fixedTarget;
+ }
+ if (asr) {
+ return asr->GetViewId();
+ }
+ return ScrollableLayerGuid::NULL_SCROLL_ID;
+ });
+
+ Maybe<SideBits> sideBits =
+ aBuilder.GetContainingFixedPosSideBits(GetActiveScrolledRoot());
+
+ // Insert a transparent rectangle with the hit-test info
+ const LayoutDeviceRect devRect =
+ LayoutDeviceRect::FromAppUnits(HitTestArea(), mAppUnitsPerDevPixel);
+
+ const wr::LayoutRect rect = wr::ToLayoutRect(devRect);
+
+ aBuilder.StartGroup(this);
+ aBuilder.PushHitTest(rect, rect, !BackfaceIsHidden(), scrollId,
+ HitTestFlags(), sideBits.valueOr(SideBits::eNone));
+ aBuilder.FinishGroup();
+
+ return true;
+}
+
+int32_t nsDisplayCompositorHitTestInfo::ZIndex() const {
+ return mOverrideZIndex ? *mOverrideZIndex
+ : nsDisplayHitTestInfoBase::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);
+}
+
+#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(
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) {
+ using namespace mozilla::layers;
+ int32_t contentOffset;
+ nsIFrame* frame = mCaret->GetFrame(&contentOffset);
+ if (!frame) {
+ return true;
+ }
+ NS_ASSERTION(frame == mFrame, "We're referring different frame");
+
+ int32_t appUnitsPerDevPixel = frame->PresContext()->AppUnitsPerDevPixel();
+
+ nsRect caretRect;
+ nsRect hookRect;
+ mCaret->ComputeCaretRects(frame, contentOffset, &caretRect, &hookRect);
+
+ gfx::DeviceColor color = ToDeviceColor(frame->GetCaretColorAt(contentOffset));
+ 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(), wr::ToColorF(color));
+
+ if (!devHookRect.IsEmpty()) {
+ aBuilder.PushRect(hook, hook, !BackfaceIsHidden(), 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 {
+ 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);
+ }
+
+ if (aBuilder->ShouldSyncDecodeImages() &&
+ geometry->ShouldInvalidateToSyncDecodeImages()) {
+ aInvalidRegion->Or(*aInvalidRegion, GetBounds(aBuilder, &snap));
+ }
+}
+
+LayerState nsDisplayBorder::GetLayerState(
+ nsDisplayListBuilder* aBuilder, LayerManager* aManager,
+ const ContainerLayerParameters& aParameters) {
+ return LayerState::LAYER_NONE;
+}
+
+bool nsDisplayBorder::CreateWebRenderCommands(
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) {
+ nsRect rect = nsRect(ToReferenceFrame(), mFrame->GetSize());
+
+ aBuilder.StartGroup(this);
+ ImgDrawResult drawResult = nsCSSRendering::CreateWebRenderCommandsForBorder(
+ this, mFrame, rect, aBuilder, aResources, aSc, aManager,
+ aDisplayListBuilder);
+
+ if (drawResult == ImgDrawResult::NOT_SUPPORTED) {
+ aBuilder.CancelGroup(true);
+ return false;
+ }
+
+ aBuilder.FinishGroup();
+
+ nsDisplayBorderGeometry::UpdateDrawResult(this, drawResult);
+ return true;
+};
+
+void nsDisplayBorder::Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) {
+ nsPoint offset = ToReferenceFrame();
+
+ PaintBorderFlags flags = aBuilder->ShouldSyncDecodeImages()
+ ? PaintBorderFlags::SyncDecodeImages
+ : PaintBorderFlags();
+
+ ImgDrawResult result = nsCSSRendering::PaintBorder(
+ mFrame->PresContext(), *aCtx, mFrame, GetPaintRect(),
+ nsRect(offset, mFrame->GetSize()), mFrame->Style(), flags,
+ mFrame->GetSkipSides());
+
+ nsDisplayBorderGeometry::UpdateDrawResult(this, result);
+}
+
+nsRect nsDisplayBorder::GetBounds(nsDisplayListBuilder* aBuilder,
+ bool* aSnap) const {
+ *aSnap = true;
+ return mBounds;
+}
+
+// Given a region, compute a conservative approximation to it as a list
+// of rectangles that aren't vertically adjacent (i.e., vertically
+// adjacent or overlapping rectangles are combined).
+// Right now this is only approximate, some vertically overlapping rectangles
+// aren't guaranteed to be combined.
+static void ComputeDisjointRectangles(const nsRegion& aRegion,
+ nsTArray<nsRect>* aRects) {
+ nscoord accumulationMargin = nsPresContext::CSSPixelsToAppUnits(25);
+ nsRect accumulated;
+
+ for (auto iter = aRegion.RectIter(); !iter.Done(); iter.Next()) {
+ const nsRect& r = iter.Get();
+ if (accumulated.IsEmpty()) {
+ accumulated = r;
+ continue;
+ }
+
+ if (accumulated.YMost() >= r.y - accumulationMargin) {
+ accumulated.UnionRect(accumulated, r);
+ } else {
+ aRects->AppendElement(accumulated);
+ accumulated = r;
+ }
+ }
+
+ // Finish the in-flight rectangle, if there is one.
+ if (!accumulated.IsEmpty()) {
+ aRects->AppendElement(accumulated);
+ }
+}
+
+void nsDisplayBoxShadowOuter::Paint(nsDisplayListBuilder* aBuilder,
+ gfxContext* aCtx) {
+ nsPoint offset = ToReferenceFrame();
+ nsRect borderRect = mFrame->VisualBorderRectRelativeToSelf() + offset;
+ nsPresContext* presContext = mFrame->PresContext();
+ AutoTArray<nsRect, 10> rects;
+ ComputeDisjointRectangles(mVisibleRegion, &rects);
+
+ AUTO_PROFILER_LABEL("nsDisplayBoxShadowOuter::Paint", GRAPHICS);
+
+ for (uint32_t i = 0; i < rects.Length(); ++i) {
+ nsCSSRendering::PaintBoxShadowOuter(presContext, *aCtx, mFrame, borderRect,
+ rects[i], mOpacity);
+ }
+}
+
+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::ComputeVisibility(nsDisplayListBuilder* aBuilder,
+ nsRegion* aVisibleRegion) {
+ if (!nsPaintedDisplayItem::ComputeVisibility(aBuilder, aVisibleRegion)) {
+ return false;
+ }
+
+ mVisibleRegion.And(*aVisibleRegion, GetPaintRect());
+ return true;
+}
+
+bool nsDisplayBoxShadowOuter::CanBuildWebRenderDisplayItems() {
+ auto shadows = mFrame->StyleEffects()->mBoxShadow.AsSpan();
+ if (shadows.IsEmpty()) {
+ return false;
+ }
+
+ bool hasBorderRadius;
+ bool nativeTheme =
+ nsCSSRendering::HasBoxShadowNativeTheme(mFrame, hasBorderRadius);
+
+ // We don't support native themed things yet like box shadows around
+ // input buttons.
+ if (nativeTheme) {
+ return false;
+ }
+
+ return true;
+}
+
+bool nsDisplayBoxShadowOuter::CreateWebRenderCommands(
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) {
+ if (!CanBuildWebRenderDisplayItems()) {
+ return false;
+ }
+
+ int32_t appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel();
+ nsPoint offset = ToReferenceFrame();
+ nsRect borderRect = mFrame->VisualBorderRectRelativeToSelf() + offset;
+ AutoTArray<nsRect, 10> rects;
+ bool snap;
+ nsRect bounds = GetBounds(aDisplayListBuilder, &snap);
+ ComputeDisjointRectangles(bounds, &rects);
+
+ 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.
+ for (uint32_t i = 0; i < rects.Length(); ++i) {
+ LayoutDeviceRect clipRect =
+ LayoutDeviceRect::FromAppUnits(rects[i], appUnitsPerDevPixel);
+ auto shadows = mFrame->StyleEffects()->mBoxShadow.AsSpan();
+ MOZ_ASSERT(!shadows.IsEmpty());
+
+ for (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, mOpacity);
+
+ // 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 {
+ auto* geometry =
+ static_cast<const nsDisplayBoxShadowOuterGeometry*>(aGeometry);
+ bool snap;
+ if (!geometry->mBounds.IsEqualInterior(GetBounds(aBuilder, &snap)) ||
+ !geometry->mBorderRect.IsEqualInterior(GetBorderRect()) ||
+ mOpacity != geometry->mOpacity) {
+ 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();
+ AutoTArray<nsRect, 10> rects;
+ ComputeDisjointRectangles(mVisibleRegion, &rects);
+
+ AUTO_PROFILER_LABEL("nsDisplayBoxShadowInner::Paint", GRAPHICS);
+
+ DrawTarget* drawTarget = aCtx->GetDrawTarget();
+ gfxContext* gfx = aCtx;
+ int32_t appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel();
+
+ for (uint32_t i = 0; i < rects.Length(); ++i) {
+ gfx->Save();
+ gfx->Clip(NSRectToSnappedRect(rects[i], appUnitsPerDevPixel, *drawTarget));
+ nsCSSRendering::PaintBoxShadowInner(presContext, *aCtx, mFrame, borderRect);
+ gfx->Restore();
+ }
+}
+
+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.
+ if (nativeTheme) {
+ return false;
+ }
+
+ return true;
+}
+
+/* static */
+void nsDisplayBoxShadowInner::CreateInsetBoxShadowWebRenderCommands(
+ mozilla::wr::DisplayListBuilder& aBuilder, const StackingContextHelper& aSc,
+ nsRegion& aVisibleRegion, nsIFrame* aFrame, const nsRect& aBorderRect) {
+ if (!nsCSSRendering::ShouldPaintBoxShadowInner(aFrame)) {
+ return;
+ }
+
+ int32_t appUnitsPerDevPixel = aFrame->PresContext()->AppUnitsPerDevPixel();
+
+ AutoTArray<nsRect, 10> rects;
+ ComputeDisjointRectangles(aVisibleRegion, &rects);
+
+ auto shadows = aFrame->StyleEffects()->mBoxShadow.AsSpan();
+
+ for (uint32_t i = 0; i < rects.Length(); ++i) {
+ LayoutDeviceRect clipRect =
+ LayoutDeviceRect::FromAppUnits(rects[i], appUnitsPerDevPixel);
+
+ for (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(
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) {
+ if (!CanCreateWebRenderCommands(aDisplayListBuilder, mFrame,
+ ToReferenceFrame())) {
+ return false;
+ }
+
+ bool snap;
+ nsRegion visible = GetBounds(aDisplayListBuilder, &snap);
+ nsPoint offset = ToReferenceFrame();
+ nsRect borderRect = nsRect(offset, mFrame->GetSize());
+ nsDisplayBoxShadowInner::CreateInsetBoxShadowWebRenderCommands(
+ aBuilder, aSc, visible, mFrame, borderRect);
+
+ return true;
+}
+
+bool nsDisplayBoxShadowInner::ComputeVisibility(nsDisplayListBuilder* aBuilder,
+ nsRegion* aVisibleRegion) {
+ if (!nsPaintedDisplayItem::ComputeVisibility(aBuilder, aVisibleRegion)) {
+ return false;
+ }
+
+ mVisibleRegion.And(*aVisibleRegion, GetPaintRect());
+ 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)
+ : nsDisplayHitTestInfoBase(aBuilder, aFrame, aActiveScrolledRoot),
+ mFrameActiveScrolledRoot(aBuilder->CurrentActiveScrolledRoot()),
+ mOverrideZIndex(0),
+ mHasZIndexOverride(false),
+ mClearingClipChain(aClearClipChain) {
+ MOZ_COUNT_CTOR(nsDisplayWrapList);
+
+ mBaseBuildingRect = GetBuildingRect();
+
+ mListPtr = &mList;
+ mListPtr->AppendToTop(aList);
+ nsDisplayWrapList::UpdateBounds(aBuilder);
+
+ if (!aFrame || !aFrame->IsTransformed()) {
+ return;
+ }
+
+ // If we're a transformed frame, then we need to find out if we're inside
+ // the nsDisplayTransform or outside of it. Frames inside the transform
+ // need mReferenceFrame == mFrame, outside needs the next ancestor
+ // reference frame.
+ // If we're inside the transform, then the nsDisplayItem constructor
+ // will have done the right thing.
+ // If we're outside the transform, then we should have only one child
+ // (since nsDisplayTransform wraps all actual content), and that child
+ // will have the correct reference frame set (since nsDisplayTransform
+ // handles this explictly).
+ nsDisplayItem* i = mListPtr->GetBottom();
+ if (i &&
+ (!i->GetAbove() || i->GetType() == DisplayItemType::TYPE_TRANSFORM) &&
+ i->Frame() == mFrame) {
+ mReferenceFrame = i->ReferenceFrame();
+ mToReferenceFrame = i->ToReferenceFrame();
+ }
+
+ nsRect visible = aBuilder->GetVisibleRect() +
+ aBuilder->GetCurrentFrameOffsetToReferenceFrame();
+
+ SetBuildingRect(visible);
+}
+
+nsDisplayWrapList::nsDisplayWrapList(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame, nsDisplayItem* aItem)
+ : nsDisplayHitTestInfoBase(aBuilder, aFrame,
+ aBuilder->CurrentActiveScrolledRoot()),
+ mOverrideZIndex(0),
+ mHasZIndexOverride(false) {
+ MOZ_COUNT_CTOR(nsDisplayWrapList);
+
+ mBaseBuildingRect = GetBuildingRect();
+
+ mListPtr = &mList;
+ mListPtr->AppendToTop(aItem);
+ nsDisplayWrapList::UpdateBounds(aBuilder);
+
+ if (!aFrame || !aFrame->IsTransformed()) {
+ return;
+ }
+
+ // See the previous nsDisplayWrapList constructor
+ if (aItem->Frame() == aFrame) {
+ mReferenceFrame = aItem->ReferenceFrame();
+ mToReferenceFrame = aItem->ToReferenceFrame();
+ }
+
+ nsRect visible = aBuilder->GetVisibleRect() +
+ aBuilder->GetCurrentFrameOffsetToReferenceFrame();
+
+ SetBuildingRect(visible);
+}
+
+nsDisplayWrapList::~nsDisplayWrapList() { MOZ_COUNT_DTOR(nsDisplayWrapList); }
+
+void nsDisplayWrapList::MergeDisplayListFromItem(
+ nsDisplayListBuilder* aBuilder, const nsDisplayWrapList* aItem) {
+ const nsDisplayWrapList* wrappedItem = aItem->AsDisplayWrapList();
+ MOZ_ASSERT(wrappedItem);
+
+ // Create a new nsDisplayWrapList using a copy-constructor. This is done
+ // to preserve the information about bounds.
+ nsDisplayWrapList* wrapper =
+ MakeClone<nsDisplayWrapList>(aBuilder, wrappedItem);
+ 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;
+
+ mListPtr->AppendToBottom(wrapper);
+}
+
+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;
+}
+
+bool nsDisplayWrapList::ComputeVisibility(nsDisplayListBuilder* aBuilder,
+ nsRegion* aVisibleRegion) {
+ return ::ComputeClippedVisibilityForSubList(aBuilder, aVisibleRegion,
+ GetChildren(), GetPaintRect());
+}
+
+nsRegion nsDisplayWrapList::GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
+ bool* aSnap) const {
+ *aSnap = false;
+ bool snap;
+ return ::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 nsDisplayWrapList::Paint(nsDisplayListBuilder* aBuilder,
+ gfxContext* aCtx) {
+ NS_ERROR("nsDisplayWrapList should have been flattened away for painting");
+}
+
+/**
+ * Returns true if all descendant display items can be placed in the same
+ * PaintedLayer --- GetLayerState returns LayerState::LAYER_INACTIVE or
+ * LayerState::LAYER_NONE, and they all have the expected animated geometry
+ * root.
+ */
+static LayerState RequiredLayerStateForChildren(
+ nsDisplayListBuilder* aBuilder, LayerManager* aManager,
+ const ContainerLayerParameters& aParameters, const nsDisplayList& aList,
+ const AnimatedGeometryRoot* aExpectedAGRForChildren,
+ const ActiveScrolledRoot* aExpectedASRForChildren) {
+ LayerState result = LayerState::LAYER_INACTIVE;
+ for (nsDisplayItem* i : aList) {
+ if (result == LayerState::LAYER_INACTIVE &&
+ (i->GetAnimatedGeometryRoot() != aExpectedAGRForChildren ||
+ i->GetActiveScrolledRoot() != aExpectedASRForChildren)) {
+ result = LayerState::LAYER_ACTIVE;
+ }
+
+ LayerState state = i->GetLayerState(aBuilder, aManager, aParameters);
+ if (state == LayerState::LAYER_ACTIVE &&
+ (i->GetType() == DisplayItemType::TYPE_BLEND_MODE ||
+ i->GetType() == DisplayItemType::TYPE_TABLE_BLEND_MODE)) {
+ // nsDisplayBlendMode always returns LayerState::LAYER_ACTIVE to ensure
+ // that the blending operation happens in the intermediate surface of its
+ // parent display item (usually an nsDisplayBlendContainer). But this does
+ // not mean that it needs all its ancestor display items to become active.
+ // So we ignore its layer state and look at its children instead.
+ state = RequiredLayerStateForChildren(
+ aBuilder, aManager, aParameters,
+ *i->GetSameCoordinateSystemChildren(), i->GetAnimatedGeometryRoot(),
+ i->GetActiveScrolledRoot());
+ }
+ if ((state == LayerState::LAYER_ACTIVE ||
+ state == LayerState::LAYER_ACTIVE_FORCE) &&
+ state > result) {
+ result = state;
+ }
+ if (state == LayerState::LAYER_ACTIVE_EMPTY && state > result) {
+ result = LayerState::LAYER_ACTIVE_FORCE;
+ }
+ if (state == LayerState::LAYER_NONE) {
+ nsDisplayList* list = i->GetSameCoordinateSystemChildren();
+ if (list) {
+ LayerState childState = RequiredLayerStateForChildren(
+ aBuilder, aManager, aParameters, *list, aExpectedAGRForChildren,
+ aExpectedASRForChildren);
+ if (childState > result) {
+ result = childState;
+ }
+ }
+ }
+ }
+ return result;
+}
+
+nsRect nsDisplayWrapList::GetComponentAlphaBounds(
+ nsDisplayListBuilder* aBuilder) const {
+ return mListPtr->GetComponentAlphaBounds(aBuilder);
+}
+
+bool nsDisplayWrapList::CreateWebRenderCommands(
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) {
+ aManager->CommandBuilder().CreateWebRenderCommandsFromDisplayList(
+ GetChildren(), this, aDisplayListBuilder, aSc, aBuilder, aResources);
+ return true;
+}
+
+static nsresult WrapDisplayList(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame, nsDisplayList* aList,
+ nsDisplayWrapper* 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,
+ nsDisplayWrapper* aWrapper) {
+ nsDisplayList newList;
+ nsDisplayItem* item;
+ while ((item = aList->RemoveBottom())) {
+ item = aWrapper->WrapItem(aBuilder, item);
+ if (!item) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ newList.AppendToTop(item);
+ }
+ // aList was emptied
+ aList->AppendToTop(&newList);
+ return NS_OK;
+}
+
+nsresult nsDisplayWrapper::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 nsDisplayWrapper::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 aForEventsAndPluginsOnly, bool aNeedsActiveLayer)
+ : nsDisplayWrapList(aBuilder, aFrame, aList, aActiveScrolledRoot, true),
+ mOpacity(aFrame->StyleEffects()->mOpacity),
+ mForEventsAndPluginsOnly(aForEventsAndPluginsOnly),
+ mNeedsActiveLayer(aNeedsActiveLayer),
+ mChildOpacityState(ChildOpacityState::Unknown) {
+ MOZ_COUNT_CTOR(nsDisplayOpacity);
+ mState.mOpacity = mOpacity;
+}
+
+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();
+}
+
+// nsDisplayOpacity uses layers for rendering
+already_AddRefed<Layer> nsDisplayOpacity::BuildLayer(
+ nsDisplayListBuilder* aBuilder, LayerManager* aManager,
+ const ContainerLayerParameters& aContainerParameters) {
+ MOZ_ASSERT(mChildOpacityState != ChildOpacityState::Applied);
+
+ ContainerLayerParameters params = aContainerParameters;
+ RefPtr<Layer> container = aManager->GetLayerBuilder()->BuildContainerLayerFor(
+ aBuilder, aManager, mFrame, this, &mList, params, nullptr,
+ FrameLayerBuilder::CONTAINER_ALLOW_PULL_BACKGROUND_COLOR);
+ if (!container) {
+ return nullptr;
+ }
+
+ container->SetOpacity(mOpacity);
+ nsDisplayListBuilder::AddAnimationsAndTransitionsToLayer(
+ container, aBuilder, this, mFrame, GetType());
+ return container.forget();
+}
+
+/**
+ * This doesn't take into account layer scaling --- the layer may be
+ * rendered at a higher (or lower) resolution, affecting the retained layer
+ * size --- but this should be good enough.
+ */
+static bool IsItemTooSmallForActiveLayer(nsIFrame* aFrame) {
+ nsIntRect visibleDevPixels =
+ aFrame->InkOverflowRectRelativeToSelf().ToOutsidePixels(
+ aFrame->PresContext()->AppUnitsPerDevPixel());
+ return visibleDevPixels.Size() <
+ nsIntSize(StaticPrefs::layout_min_active_layer_size(),
+ StaticPrefs::layout_min_active_layer_size());
+}
+
+/* static */
+bool nsDisplayOpacity::NeedsActiveLayer(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame,
+ bool aEnforceMinimumSize) {
+ if (EffectCompositor::HasAnimationsForCompositor(
+ aFrame, DisplayItemType::TYPE_OPACITY) ||
+ (ActiveLayerTracker::IsStyleAnimated(
+ aBuilder, aFrame, nsCSSPropertyIDSet::OpacityProperties()) &&
+ !(aEnforceMinimumSize && IsItemTooSmallForActiveLayer(aFrame)))) {
+ return true;
+ }
+ return false;
+}
+
+void nsDisplayOpacity::ApplyOpacity(nsDisplayListBuilder* aBuilder,
+ float aOpacity,
+ const DisplayItemClipChain* aClip) {
+ NS_ASSERTION(CanApplyOpacity(), "ApplyOpacity should be allowed");
+ mOpacity = mOpacity * aOpacity;
+ IntersectClip(aBuilder, aClip, false);
+}
+
+bool nsDisplayOpacity::CanApplyOpacity() 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(nsDisplayList* aList,
+ nsTArray<nsPaintedDisplayItem*>& aArray) {
+ if (aList->Count() > 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(i->GetChildren(), aArray)) {
+ return false;
+ }
+
+ continue;
+ }
+
+ if (aArray.Length() == kOpacityMaxChildCount) {
+ return false;
+ }
+
+ auto* item = i->AsPaintedDisplayItem();
+ if (!item || !item->CanApplyOpacity()) {
+ return false;
+ }
+
+ aArray.AppendElement(item);
+ }
+
+ return true;
+}
+
+bool nsDisplayOpacity::ApplyToChildren(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(&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;
+ }
+ }
+ }
+
+ for (uint32_t i = 0; i < childCount; i++) {
+ children[i].item->ApplyOpacity(aBuilder, mOpacity, mClipChain);
+ }
+
+ 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::ApplyToFilterOrMask(const bool aUsingLayers) {
+ if (mList.Count() != 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 ||
+ type == DisplayItemType::TYPE_FILTER) {
+ auto* filterOrMaskItem = static_cast<nsDisplayEffectsBase*>(item);
+ filterOrMaskItem->SelectOpacityOptimization(aUsingLayers);
+ return true;
+ }
+
+ return false;
+}
+
+bool nsDisplayOpacity::ShouldFlattenAway(nsDisplayListBuilder* aBuilder) {
+ 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;
+ }
+
+ const bool usingLayers = !aBuilder->IsPaintingForWebRender();
+
+ if (ApplyToFilterOrMask(usingLayers)) {
+ MOZ_ASSERT(SVGIntegrationUtils::UsingEffectsForFrame(mFrame));
+ mChildOpacityState = ChildOpacityState::Applied;
+ return true;
+ }
+
+ // Return true if we successfully applied opacity to child items, or if
+ // WebRender is not in use. In the latter case, the opacity gets flattened and
+ // applied during layer building.
+ return ApplyToChildren(aBuilder) || usingLayers;
+}
+
+nsDisplayItem::LayerState nsDisplayOpacity::GetLayerState(
+ nsDisplayListBuilder* aBuilder, LayerManager* aManager,
+ const ContainerLayerParameters& aParameters) {
+ // If we only created this item so that we'd get correct nsDisplayEventRegions
+ // for child frames, then force us to inactive to avoid unnecessary
+ // layerization changes for content that won't ever be painted.
+ if (mForEventsAndPluginsOnly) {
+ MOZ_ASSERT(mOpacity == 0);
+ return LayerState::LAYER_INACTIVE;
+ }
+
+ if (mNeedsActiveLayer) {
+ // Returns LayerState::LAYER_ACTIVE_FORCE to avoid flatterning the layer for
+ // async animations.
+ return LayerState::LAYER_ACTIVE_FORCE;
+ }
+
+ return RequiredLayerStateForChildren(aBuilder, aManager, aParameters, mList,
+ GetAnimatedGeometryRoot(),
+ GetActiveScrolledRoot());
+}
+
+bool nsDisplayOpacity::ComputeVisibility(nsDisplayListBuilder* aBuilder,
+ nsRegion* aVisibleRegion) {
+ // Our children are translucent so we should not allow them to subtract
+ // area from aVisibleRegion. We do need to find out what is visible under
+ // our children in the temporary compositing buffer, because if our children
+ // paint our entire bounds opaquely then we don't need an alpha channel in
+ // the temporary compositing buffer.
+ nsRect bounds = GetClippedBounds(aBuilder);
+ nsRegion visibleUnderChildren;
+ visibleUnderChildren.And(*aVisibleRegion, bounds);
+ return nsDisplayWrapList::ComputeVisibility(aBuilder, &visibleUnderChildren);
+}
+
+void nsDisplayOpacity::ComputeInvalidationRegion(
+ nsDisplayListBuilder* aBuilder, const nsDisplayItemGeometry* aGeometry,
+ nsRegion* aInvalidRegion) 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(
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) {
+ MOZ_ASSERT(mChildOpacityState != ChildOpacityState::Applied);
+ float* opacityForSC = &mOpacity;
+
+ 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());
+ StackingContextHelper sc(aSc, GetActiveScrolledRoot(), mFrame, this, aBuilder,
+ params);
+
+ aManager->CommandBuilder().CreateWebRenderCommandsFromDisplayList(
+ &mList, this, aDisplayListBuilder, sc, aBuilder, aResources);
+ return true;
+}
+
+nsDisplayBlendMode::nsDisplayBlendMode(
+ nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayList* aList,
+ mozilla::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();
+}
+
+LayerState nsDisplayBlendMode::GetLayerState(
+ nsDisplayListBuilder* aBuilder, LayerManager* aManager,
+ const ContainerLayerParameters& aParameters) {
+ return LayerState::LAYER_ACTIVE;
+}
+
+bool nsDisplayBlendMode::CreateWebRenderCommands(
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ mozilla::layers::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);
+}
+
+// nsDisplayBlendMode uses layers for rendering
+already_AddRefed<Layer> nsDisplayBlendMode::BuildLayer(
+ nsDisplayListBuilder* aBuilder, LayerManager* aManager,
+ const ContainerLayerParameters& aContainerParameters) {
+ ContainerLayerParameters newContainerParameters = aContainerParameters;
+ newContainerParameters.mDisableSubpixelAntialiasingInDescendants = true;
+
+ RefPtr<Layer> container = aManager->GetLayerBuilder()->BuildContainerLayerFor(
+ aBuilder, aManager, mFrame, this, &mList, newContainerParameters,
+ nullptr);
+ if (!container) {
+ return nullptr;
+ }
+
+ container->SetMixBlendMode(nsCSSRendering::GetGFXBlendMode(mBlendMode));
+
+ return container.forget();
+}
+
+mozilla::gfx::CompositionOp nsDisplayBlendMode::BlendMode() {
+ return nsCSSRendering::GetGFXBlendMode(mBlendMode);
+}
+
+bool nsDisplayBlendMode::ComputeVisibility(nsDisplayListBuilder* aBuilder,
+ nsRegion* aVisibleRegion) {
+ // Our children are need their backdrop so we should not allow them to
+ // subtract area from aVisibleRegion. We do need to find out what is visible
+ // under our children in the temporary compositing buffer, because if our
+ // children paint our entire bounds opaquely then we don't need an alpha
+ // channel in the temporary compositing buffer.
+ nsRect bounds = GetClippedBounds(aBuilder);
+ nsRegion visibleUnderChildren;
+ visibleUnderChildren.And(*aVisibleRegion, bounds);
+ return nsDisplayWrapList::ComputeVisibility(aBuilder, &visibleUnderChildren);
+}
+
+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);
+}
+
+// nsDisplayBlendContainer uses layers for rendering
+already_AddRefed<Layer> nsDisplayBlendContainer::BuildLayer(
+ nsDisplayListBuilder* aBuilder, LayerManager* aManager,
+ const ContainerLayerParameters& aContainerParameters) {
+ // turn off anti-aliasing in the parent stacking context because it changes
+ // how the group is initialized.
+ ContainerLayerParameters newContainerParameters = aContainerParameters;
+ newContainerParameters.mDisableSubpixelAntialiasingInDescendants = true;
+
+ RefPtr<Layer> container = aManager->GetLayerBuilder()->BuildContainerLayerFor(
+ aBuilder, aManager, mFrame, this, &mList, newContainerParameters,
+ nullptr);
+ if (!container) {
+ return nullptr;
+ }
+
+ container->SetForceIsolatedGroup(true);
+ return container.forget();
+}
+
+LayerState nsDisplayBlendContainer::GetLayerState(
+ nsDisplayListBuilder* aBuilder, LayerManager* aManager,
+ const ContainerLayerParameters& aParameters) {
+ return RequiredLayerStateForChildren(aBuilder, aManager, aParameters, mList,
+ GetAnimatedGeometryRoot(),
+ GetActiveScrolledRoot());
+}
+
+bool nsDisplayBlendContainer::CreateWebRenderCommands(
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ mozilla::layers::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);
+
+ // For scroll thumb layers, override the AGR to be the thumb's AGR rather
+ // than the AGR for mFrame (which is the slider frame).
+ if (IsScrollThumbLayer()) {
+ if (nsIFrame* thumbFrame = nsIFrame::GetChildXULBox(mFrame)) {
+ mAnimatedGeometryRoot = aBuilder->FindAnimatedGeometryRootFor(thumbFrame);
+ }
+ }
+}
+
+LayerState nsDisplayOwnLayer::GetLayerState(
+ nsDisplayListBuilder* aBuilder, LayerManager* aManager,
+ const ContainerLayerParameters& aParameters) {
+ if (mForceActive) {
+ return mozilla::LayerState::LAYER_ACTIVE_FORCE;
+ }
+
+ return RequiredLayerStateForChildren(aBuilder, aManager, aParameters, mList,
+ GetAnimatedGeometryRoot(),
+ GetActiveScrolledRoot());
+}
+
+bool nsDisplayOwnLayer::IsScrollThumbLayer() const {
+ return mScrollbarData.mScrollbarLayerType ==
+ layers::ScrollbarLayerType::Thumb;
+}
+
+bool nsDisplayOwnLayer::IsScrollbarContainer() const {
+ return mScrollbarData.mScrollbarLayerType ==
+ layers::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();
+}
+
+// nsDisplayOpacity uses layers for rendering
+already_AddRefed<Layer> nsDisplayOwnLayer::BuildLayer(
+ nsDisplayListBuilder* aBuilder, LayerManager* aManager,
+ const ContainerLayerParameters& aContainerParameters) {
+ RefPtr<ContainerLayer> layer =
+ aManager->GetLayerBuilder()->BuildContainerLayerFor(
+ aBuilder, aManager, mFrame, this, &mList, aContainerParameters,
+ nullptr, FrameLayerBuilder::CONTAINER_ALLOW_PULL_BACKGROUND_COLOR);
+
+ if (IsScrollThumbLayer() || IsScrollbarContainer()) {
+ layer->SetScrollbarData(mScrollbarData);
+ }
+
+ if (mFlags & nsDisplayOwnLayerFlags::GenerateSubdocInvalidations) {
+ mFrame->PresContext()->SetNotifySubDocInvalidationData(layer);
+ }
+ return layer.forget();
+}
+
+bool nsDisplayOwnLayer::CreateWebRenderCommands(
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc, RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) {
+ Maybe<wr::WrAnimationProperty> prop;
+ bool needsProp = aManager->LayerManager()->AsyncPanZoomEnabled() &&
+ (IsScrollThumbLayer() || IsZoomingLayer() ||
+ (IsFixedPositionLayer() && HasDynamicToolbar()) ||
+ (IsStickyPositionLayer() && HasDynamicToolbar()) ||
+ (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->effect_type = wr::WrAnimationType::Transform;
+ }
+
+ wr::StackingContextParams params;
+ params.animation = prop.ptrOr(nullptr);
+ params.clip =
+ wr::WrStackingContextClip::ClipChain(aBuilder.CurrentClipChainId());
+ if (IsScrollbarContainer()) {
+ params.prim_flags |= wr::PrimitiveFlags::IS_SCROLLBAR_CONTAINER;
+ }
+ if (IsScrollThumbLayer()) {
+ params.prim_flags |= wr::PrimitiveFlags::IS_SCROLLBAR_THUMB;
+ }
+ if (IsZoomingLayer()) {
+ params.reference_frame_kind = wr::WrReferenceFrameKind::Zoom;
+ }
+ StackingContextHelper sc(aSc, GetActiveScrolledRoot(), mFrame, this, aBuilder,
+ params);
+
+ nsDisplayWrapList::CreateWebRenderCommands(aBuilder, aResources, sc, aManager,
+ aDisplayListBuilder);
+ return true;
+}
+
+bool nsDisplayOwnLayer::UpdateScrollData(
+ mozilla::layers::WebRenderScrollData* aData,
+ mozilla::layers::WebRenderLayerScrollData* aLayerData) {
+ bool isRelevantToApz =
+ (IsScrollThumbLayer() || IsScrollbarContainer() || IsZoomingLayer() ||
+ (IsFixedPositionLayer() && HasDynamicToolbar()) ||
+ (IsStickyPositionLayer() && HasDynamicToolbar()));
+
+ if (!isRelevantToApz) {
+ return false;
+ }
+
+ if (!aLayerData) {
+ return true;
+ }
+
+ if (IsZoomingLayer()) {
+ aLayerData->SetZoomAnimationId(mWrAnimationId);
+ return true;
+ }
+
+ if (IsFixedPositionLayer() && HasDynamicToolbar()) {
+ aLayerData->SetFixedPositionAnimationId(mWrAnimationId);
+ return true;
+ }
+
+ if (IsStickyPositionLayer() && HasDynamicToolbar()) {
+ 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);
+
+ // The SubDocument display item is conceptually outside the viewport frame,
+ // so in cases where the viewport frame is an AGR, the SubDocument's AGR
+ // should be not the viewport frame itself, but its parent AGR.
+ if (*mAnimatedGeometryRoot == mFrame && mAnimatedGeometryRoot->mParentAGR) {
+ mAnimatedGeometryRoot = mAnimatedGeometryRoot->mParentAGR;
+ }
+
+ 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);
+}
+
+void nsDisplaySubDocument::Disown() {
+ if (mFrame) {
+ mFrame->RemoveDisplayItem(this);
+ RemoveFrame(mFrame);
+ }
+ if (mSubDocFrame) {
+ mSubDocFrame->RemoveDisplayItem(this);
+ RemoveFrame(mSubDocFrame);
+ }
+}
+
+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);
+}
+
+bool nsDisplaySubDocument::ComputeVisibility(nsDisplayListBuilder* aBuilder,
+ nsRegion* aVisibleRegion) {
+ bool usingDisplayPort = UseDisplayPortForViewport(aBuilder, mFrame);
+
+ if (!(mFlags & nsDisplayOwnLayerFlags::GenerateScrollableLayer) ||
+ !usingDisplayPort) {
+ return nsDisplayWrapList::ComputeVisibility(aBuilder, aVisibleRegion);
+ }
+
+ nsRect displayport;
+ nsIFrame* rootScrollFrame = mFrame->PresShell()->GetRootScrollFrame();
+ MOZ_ASSERT(rootScrollFrame);
+ Unused << DisplayPortUtils::GetDisplayPort(
+ rootScrollFrame->GetContent(), &displayport,
+ DisplayPortOptions().With(DisplayportRelativeTo::ScrollFrame));
+
+ nsRegion childVisibleRegion;
+ // The visible region for the children may be much bigger than the hole we
+ // are viewing the children from, so that the compositor process has enough
+ // content to asynchronously pan while content is being refreshed.
+ childVisibleRegion =
+ displayport + mFrame->GetOffsetToCrossDoc(ReferenceFrame());
+
+ nsRect boundedRect = childVisibleRegion.GetBounds().Intersect(
+ mList.GetClippedBoundsWithRespectToASR(aBuilder, mActiveScrolledRoot));
+ bool visible = mList.ComputeVisibilityForSublist(
+ aBuilder, &childVisibleRegion, boundedRect);
+
+ // If APZ is enabled then don't allow this computation to influence
+ // aVisibleRegion, on the assumption that the layer can be asynchronously
+ // scrolled so we'll definitely need all the content under it.
+ if (!nsLayoutUtils::UsesAsyncScrolling(mFrame)) {
+ bool snap;
+ nsRect bounds = GetBounds(aBuilder, &snap);
+ nsRegion removed;
+ removed.Sub(bounds, childVisibleRegion);
+
+ aBuilder->SubtractFromVisibleRegion(aVisibleRegion, removed);
+ }
+
+ return visible;
+}
+
+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) {
+ nsDisplayList temp;
+ 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);
+ }
+
+ return MakeDisplayItemWithIndex<nsDisplayFixedPosition>(aBuilder, aFrame,
+ aIndex + 1, &temp);
+}
+
+nsDisplayFixedPosition::nsDisplayFixedPosition(
+ nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayList* aList,
+ const ActiveScrolledRoot* aActiveScrolledRoot,
+ const ActiveScrolledRoot* aContainerASR)
+ : nsDisplayOwnLayer(aBuilder, aFrame, aList, aActiveScrolledRoot),
+ mContainerASR(aContainerASR),
+ mIsFixedBackground(false) {
+ MOZ_COUNT_CTOR(nsDisplayFixedPosition);
+ Init(aBuilder);
+}
+
+nsDisplayFixedPosition::nsDisplayFixedPosition(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame,
+ nsDisplayList* aList)
+ : nsDisplayOwnLayer(aBuilder, aFrame, aList,
+ aBuilder->CurrentActiveScrolledRoot()),
+ mContainerASR(nullptr), // XXX maybe this should be something?
+ mIsFixedBackground(true) {
+ MOZ_COUNT_CTOR(nsDisplayFixedPosition);
+ Init(aBuilder);
+}
+
+void nsDisplayFixedPosition::Init(nsDisplayListBuilder* aBuilder) {
+ mAnimatedGeometryRootForScrollMetadata = mAnimatedGeometryRoot;
+ if (ShouldFixToViewport(aBuilder)) {
+ mAnimatedGeometryRoot = aBuilder->FindAnimatedGeometryRootFor(this);
+ }
+}
+
+already_AddRefed<Layer> nsDisplayFixedPosition::BuildLayer(
+ nsDisplayListBuilder* aBuilder, LayerManager* aManager,
+ const ContainerLayerParameters& aContainerParameters) {
+ RefPtr<Layer> layer =
+ nsDisplayOwnLayer::BuildLayer(aBuilder, aManager, aContainerParameters);
+
+ layer->SetIsFixedPosition(true);
+
+ nsPresContext* presContext = mFrame->PresContext();
+ nsIFrame* fixedFrame =
+ mIsFixedBackground ? presContext->PresShell()->GetRootFrame() : mFrame;
+
+ const nsIFrame* viewportFrame = fixedFrame->GetParent();
+ // anchorRect will be in the container's coordinate system (aLayer's parent
+ // layer). This is the same as the display items' reference frame.
+ nsRect anchorRect;
+ if (viewportFrame) {
+ anchorRect.SizeTo(viewportFrame->GetSize());
+ // Fixed position frames are reflowed into the scroll-port size if one has
+ // been set.
+ if (const ViewportFrame* viewport = do_QueryFrame(viewportFrame)) {
+ anchorRect.SizeTo(
+ viewport->AdjustViewportSizeForFixedPosition(anchorRect));
+ }
+ } else {
+ // A display item directly attached to the viewport.
+ // For background-attachment:fixed items, the anchor point is always the
+ // top-left of the viewport currently.
+ viewportFrame = fixedFrame;
+ }
+ // The anchorRect top-left is always the viewport top-left.
+ anchorRect.MoveTo(viewportFrame->GetOffsetToCrossDoc(ReferenceFrame()));
+
+ nsLayoutUtils::SetFixedPositionLayerData(layer, viewportFrame, anchorRect,
+ fixedFrame, presContext,
+ aContainerParameters);
+
+ return layer.forget();
+}
+
+ViewID nsDisplayFixedPosition::GetScrollTargetId() {
+ if (mContainerASR && !nsLayoutUtils::IsReallyFixedPos(mFrame)) {
+ return mContainerASR->GetViewId();
+ }
+ return nsLayoutUtils::ScrollIdForRootScrollFrame(mFrame->PresContext());
+}
+
+bool nsDisplayFixedPosition::CreateWebRenderCommands(
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ mozilla::layers::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.
+ mozilla::wr::DisplayListBuilder::FixedPosScrollTargetTracker tracker(
+ aBuilder, GetActiveScrolledRoot(), GetScrollTargetId(), sides);
+ return nsDisplayOwnLayer::CreateWebRenderCommands(
+ aBuilder, aResources, aSc, aManager, aDisplayListBuilder);
+}
+
+bool nsDisplayFixedPosition::UpdateScrollData(
+ mozilla::layers::WebRenderScrollData* aData,
+ mozilla::layers::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(mContainerASR).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)
+ : nsDisplayFixedPosition(aBuilder, aFrame, aList),
+ 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) {
+ MOZ_COUNT_CTOR(nsDisplayStickyPosition);
+}
+
+void nsDisplayStickyPosition::SetClipChain(
+ const DisplayItemClipChain* aClipChain, bool aStore) {
+ mClipChain = aClipChain;
+ mClip = nullptr;
+
+ MOZ_ASSERT(!mClip,
+ "There should never be a clip on this item because no clip moves "
+ "with it.");
+
+ if (aStore) {
+ mState.mClipChain = aClipChain;
+ mState.mClip = mClip;
+ }
+}
+
+already_AddRefed<Layer> nsDisplayStickyPosition::BuildLayer(
+ nsDisplayListBuilder* aBuilder, LayerManager* aManager,
+ const ContainerLayerParameters& aContainerParameters) {
+ RefPtr<Layer> layer =
+ nsDisplayOwnLayer::BuildLayer(aBuilder, aManager, aContainerParameters);
+
+ StickyScrollContainer* stickyScrollContainer =
+ StickyScrollContainer::GetStickyScrollContainerForFrame(mFrame);
+ if (!stickyScrollContainer) {
+ return layer.forget();
+ }
+
+ nsIFrame* scrollFrame = do_QueryFrame(stickyScrollContainer->ScrollFrame());
+ nsPresContext* presContext = scrollFrame->PresContext();
+
+ // Sticky position frames whose scroll frame is the root scroll frame are
+ // reflowed into the scroll-port size if one has been set.
+ nsSize scrollFrameSize = scrollFrame->GetSize();
+ if (scrollFrame == presContext->PresShell()->GetRootScrollFrame() &&
+ presContext->PresShell()->IsVisualViewportSizeSet()) {
+ scrollFrameSize = presContext->PresShell()->GetVisualViewportSize();
+ }
+
+ nsLayoutUtils::SetFixedPositionLayerData(
+ layer, scrollFrame,
+ nsRect(scrollFrame->GetOffsetToCrossDoc(ReferenceFrame()),
+ scrollFrameSize),
+ mFrame, presContext, aContainerParameters);
+
+ ViewID scrollId = nsLayoutUtils::FindOrCreateIDFor(
+ stickyScrollContainer->ScrollFrame()->GetScrolledFrame()->GetContent());
+
+ float factor = presContext->AppUnitsPerDevPixel();
+ LayerRectAbsolute stickyOuter;
+ LayerRectAbsolute stickyInner;
+ CalculateLayerScrollRanges(
+ stickyScrollContainer, factor, aContainerParameters.mXScale,
+ aContainerParameters.mYScale, stickyOuter, stickyInner);
+ layer->SetStickyPositionData(scrollId, stickyOuter, stickyInner);
+
+ return layer.forget();
+}
+
+// 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.
+ if (!stickyScrollContainer->ScrollFrame()
+ ->IsMaybeAsynchronouslyScrolled()) {
+ stickyScrollContainer = nullptr;
+ }
+ }
+ return stickyScrollContainer;
+}
+
+bool nsDisplayStickyPosition::CreateWebRenderCommands(
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::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(ReferenceFrame());
+
+ // 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);
+
+ 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(
+ mozilla::layers::WebRenderScrollData* aData,
+ mozilla::layers::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);
+
+ 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
+}
+
+already_AddRefed<Layer> nsDisplayScrollInfoLayer::BuildLayer(
+ nsDisplayListBuilder* aBuilder, LayerManager* aManager,
+ const ContainerLayerParameters& aContainerParameters) {
+ // In general for APZ with event-regions we no longer have a need for
+ // scrollinfo layers. However, in some cases, there might be content that
+ // cannot be layerized, and so needs to scroll synchronously. To handle those
+ // cases, we still want to generate scrollinfo layers.
+
+ return aManager->GetLayerBuilder()->BuildContainerLayerFor(
+ aBuilder, aManager, mFrame, this, &mList, aContainerParameters, nullptr,
+ FrameLayerBuilder::CONTAINER_ALLOW_PULL_BACKGROUND_COLOR);
+}
+
+LayerState nsDisplayScrollInfoLayer::GetLayerState(
+ nsDisplayListBuilder* aBuilder, LayerManager* aManager,
+ const ContainerLayerParameters& aParameters) {
+ return LayerState::LAYER_ACTIVE_EMPTY;
+}
+
+UniquePtr<ScrollMetadata> nsDisplayScrollInfoLayer::ComputeScrollMetadata(
+ nsDisplayListBuilder* aBuilder, LayerManager* aLayerManager,
+ const ContainerLayerParameters& aContainerParameters) {
+ ScrollMetadata metadata = nsLayoutUtils::ComputeScrollMetadata(
+ mScrolledFrame, mScrollFrame, mScrollFrame->GetContent(),
+ ReferenceFrame(), aLayerManager, mScrollParentId, mScrollFrame->GetSize(),
+ Nothing(), false, Some(aContainerParameters));
+ metadata.GetMetrics().SetIsScrollInfoLayer(true);
+ nsIScrollableFrame* scrollableFrame = mScrollFrame->GetScrollTargetFrame();
+ if (scrollableFrame) {
+ aBuilder->AddScrollFrameToNotify(scrollableFrame);
+ }
+
+ return UniquePtr<ScrollMetadata>(new ScrollMetadata(metadata));
+}
+
+bool nsDisplayScrollInfoLayer::UpdateScrollData(
+ mozilla::layers::WebRenderScrollData* aData,
+ mozilla::layers::WebRenderLayerScrollData* aLayerData) {
+ if (aLayerData) {
+ UniquePtr<ScrollMetadata> metadata = ComputeScrollMetadata(
+ aData->GetBuilder(), aData->GetManager(), ContainerLayerParameters());
+ MOZ_ASSERT(aData);
+ MOZ_ASSERT(metadata);
+ aLayerData->AppendScrollMetadata(*aData, *metadata);
+ }
+ return true;
+}
+
+bool nsDisplayScrollInfoLayer::CreateWebRenderCommands(
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ mozilla::layers::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);
+}
+
+bool nsDisplayZoom::ComputeVisibility(nsDisplayListBuilder* aBuilder,
+ nsRegion* aVisibleRegion) {
+ // Convert the passed in visible region to our appunits.
+ nsRegion visibleRegion;
+ // mVisibleRect has been clipped to GetClippedBounds
+ visibleRegion.And(*aVisibleRegion, GetPaintRect());
+ visibleRegion = visibleRegion.ScaleToOtherAppUnitsRoundOut(mParentAPD, mAPD);
+ nsRegion originalVisibleRegion = visibleRegion;
+
+ nsRect transformedVisibleRect =
+ GetPaintRect().ScaleToOtherAppUnitsRoundOut(mParentAPD, mAPD);
+ bool retval;
+ // If we are to generate a scrollable layer we call
+ // nsDisplaySubDocument::ComputeVisibility to make the necessary adjustments
+ // for ComputeVisibility, it does all it's calculations in the child APD.
+ bool usingDisplayPort = UseDisplayPortForViewport(aBuilder, mFrame);
+ if (!(mFlags & nsDisplayOwnLayerFlags::GenerateScrollableLayer) ||
+ !usingDisplayPort) {
+ retval = mList.ComputeVisibilityForSublist(aBuilder, &visibleRegion,
+ transformedVisibleRect);
+ } else {
+ retval = nsDisplaySubDocument::ComputeVisibility(aBuilder, &visibleRegion);
+ }
+
+ nsRegion removed;
+ // removed = originalVisibleRegion - visibleRegion
+ removed.Sub(originalVisibleRegion, visibleRegion);
+ // Convert removed region to parent appunits.
+ removed = removed.ScaleToOtherAppUnitsRoundIn(mAPD, mParentAPD);
+ // aVisibleRegion = aVisibleRegion - removed (modulo any simplifications
+ // SubtractFromVisibleRegion does)
+ aBuilder->SubtractFromVisibleRegion(aVisibleRegion, removed);
+
+ return retval;
+}
+
+nsDisplayAsyncZoom::nsDisplayAsyncZoom(
+ nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayList* aList,
+ const ActiveScrolledRoot* aActiveScrolledRoot,
+ mozilla::layers::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);
+}
+
+already_AddRefed<Layer> nsDisplayAsyncZoom::BuildLayer(
+ nsDisplayListBuilder* aBuilder, LayerManager* aManager,
+ const ContainerLayerParameters& aContainerParameters) {
+ PresShell* presShell = mFrame->PresShell();
+ ContainerLayerParameters containerParameters(
+ presShell->GetResolution(), presShell->GetResolution(), nsIntPoint(),
+ aContainerParameters);
+
+ RefPtr<Layer> layer =
+ nsDisplayOwnLayer::BuildLayer(aBuilder, aManager, containerParameters);
+
+ layer->SetIsAsyncZoomContainer(Some(mViewID));
+
+ layer->SetPostScale(1.0f / presShell->GetResolution(),
+ 1.0f / presShell->GetResolution());
+ layer->AsContainerLayer()->SetScaleToResolution(presShell->GetResolution());
+
+ return layer.forget();
+}
+
+bool nsDisplayAsyncZoom::UpdateScrollData(
+ mozilla::layers::WebRenderScrollData* aData,
+ mozilla::layers::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)
+ : nsDisplayHitTestInfoBase(aBuilder, aFrame),
+ mTransform(Some(Matrix4x4())),
+ mTransformGetter(nullptr),
+ mAnimatedGeometryRootForChildren(mAnimatedGeometryRoot),
+ mAnimatedGeometryRootForScrollMetadata(mAnimatedGeometryRoot),
+ mChildrenBuildingRect(aChildrenBuildingRect),
+ mPrerenderDecision(PrerenderDecision::No),
+ mIsTransformSeparator(true) {
+ 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)
+ : nsDisplayHitTestInfoBase(aBuilder, aFrame),
+ mTransformGetter(nullptr),
+ mAnimatedGeometryRootForChildren(mAnimatedGeometryRoot),
+ mAnimatedGeometryRootForScrollMetadata(mAnimatedGeometryRoot),
+ mChildrenBuildingRect(aChildrenBuildingRect),
+ mPrerenderDecision(aPrerenderDecision),
+ mIsTransformSeparator(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,
+ ComputeTransformFunction aTransformGetter)
+ : nsDisplayHitTestInfoBase(aBuilder, aFrame),
+ mTransformGetter(aTransformGetter),
+ mAnimatedGeometryRootForChildren(mAnimatedGeometryRoot),
+ mAnimatedGeometryRootForScrollMetadata(mAnimatedGeometryRoot),
+ mChildrenBuildingRect(aChildrenBuildingRect),
+ mPrerenderDecision(PrerenderDecision::No),
+ mIsTransformSeparator(false) {
+ MOZ_COUNT_CTOR(nsDisplayTransform);
+ MOZ_ASSERT(aFrame, "Must have a frame!");
+ Init(aBuilder, aList);
+}
+
+void nsDisplayTransform::SetReferenceFrameToAncestor(
+ nsDisplayListBuilder* aBuilder) {
+ if (mFrame == aBuilder->RootReferenceFrame()) {
+ return;
+ }
+ nsIFrame* outerFrame = nsLayoutUtils::GetCrossDocParentFrame(mFrame);
+ mReferenceFrame = aBuilder->FindReferenceFrameFor(outerFrame);
+ mToReferenceFrame = mFrame->GetOffsetToCrossDoc(mReferenceFrame);
+ if (DisplayPortUtils::IsFixedPosFrameInDisplayPort(mFrame)) {
+ // This is an odd special case. If we are both IsFixedPosFrameInDisplayPort
+ // and transformed that we are our own AGR parent.
+ // We want our frame to be our AGR because FrameLayerBuilder uses our AGR to
+ // determine if we are inside a fixed pos subtree. If we use the outer AGR
+ // from outside the fixed pos subtree FLB can't tell that we are fixed pos.
+ mAnimatedGeometryRoot = mAnimatedGeometryRootForChildren;
+ } else if (mFrame->StyleDisplay()->mPosition ==
+ StylePositionProperty::Sticky &&
+ IsStickyFrameActive(aBuilder, mFrame, nullptr)) {
+ // Similar to the IsFixedPosFrameInDisplayPort case we are our own AGR.
+ // We are inside the sticky position, so our AGR is the sticky positioned
+ // frame, which is our AGR, not the parent AGR.
+ mAnimatedGeometryRoot = mAnimatedGeometryRootForChildren;
+ } else if (mAnimatedGeometryRoot->mParentAGR) {
+ mAnimatedGeometryRootForScrollMetadata = mAnimatedGeometryRoot->mParentAGR;
+ if (!MayBeAnimated(aBuilder)) {
+ // If we're an animated transform then we want the same AGR as our
+ // children so that FrameLayerBuilder knows that this layer moves with the
+ // transform and won't compute occlusions. If we're not animated then use
+ // our parent AGR so that inactive transform layers can go in the same
+ // PaintedLayer as surrounding content.
+ mAnimatedGeometryRoot = mAnimatedGeometryRoot->mParentAGR;
+ }
+ }
+
+ SetBuildingRect(aBuilder->GetVisibleRect() + mToReferenceFrame);
+}
+
+void nsDisplayTransform::Init(nsDisplayListBuilder* aBuilder,
+ nsDisplayList* aChildren) {
+ mShouldFlatten = false;
+ mChildren.AppendToTop(aChildren);
+ UpdateBounds(aBuilder);
+}
+
+bool nsDisplayTransform::ShouldFlattenAway(nsDisplayListBuilder* aBuilder) {
+ if (gfxVars::UseWebRender() ||
+ !StaticPrefs::layout_display_list_flatten_transform()) {
+ return false;
+ }
+
+ MOZ_ASSERT(!mShouldFlatten);
+ mShouldFlatten = GetTransform().Is2D();
+ return mShouldFlatten;
+}
+
+/* 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 = mozilla::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;
+ }
+
+ /* Find our containing block, which is the element that provides the
+ * value for perspective we need to use
+ */
+
+ // TODO: Is it possible that the cbFrame's bounds haven't been set correctly
+ // yet
+ // (similar to the aBoundsOverride case for GetResultingTransformMatrix)?
+ nsIFrame* cbFrame = aFrame->GetContainingBlock(nsIFrame::SKIP_SCROLLED_FRAME);
+ if (!cbFrame) {
+ return false;
+ }
+
+ /* Grab the values for perspective and perspective-origin (if present) */
+ const nsStyleDisplay* cbDisplay = cbFrame->StyleDisplay();
+ if (cbDisplay->mChildPerspective.IsNone()) {
+ return false;
+ }
+
+ MOZ_ASSERT(cbDisplay->mChildPerspective.IsLength());
+ // TODO(emilio): Seems quite silly to go through app units just to convert to
+ // float pixels below.
+ nscoord perspective = cbDisplay->mChildPerspective.length._0.ToAppUnits();
+ if (perspective < std::numeric_limits<Float>::epsilon()) {
+ return true;
+ }
+
+ TransformReferenceBox refBox(cbFrame);
+
+ Point perspectiveOrigin = nsStyleTransformMatrix::Convert2DPosition(
+ cbDisplay->mPerspectiveOrigin.horizontal,
+ cbDisplay->mPerspectiveOrigin.vertical, refBox, aAppUnitsPerPixel);
+
+ /* GetOffsetTo computes the offset required to move from 0,0 in cbFrame to 0,0
+ * in aFrame. Although we actually want the inverse of this, it's faster to
+ * compute this way.
+ */
+ nsPoint frameToCbOffset = -aFrame->GetOffsetTo(cbFrame);
+ Point frameToCbGfxOffset(
+ NSAppUnitsToFloatPixels(frameToCbOffset.x, aAppUnitsPerPixel),
+ NSAppUnitsToFloatPixels(frameToCbOffset.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 += frameToCbGfxOffset;
+
+ aOutMatrix._34 =
+ -1.0 / NSAppUnitsToFloatPixels(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
+ // disp->mSpecifiedTransform, since we still need any
+ // parentsChildrenOnlyTransform.
+ Matrix svgTransform, parentsChildrenOnlyTransform;
+ bool hasSVGTransforms =
+ frame &&
+ 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.
+ 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::GetCrossDocParentFrame(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::GetCrossDocParentFrame(aFrame);
+ container;
+ container = nsLayoutUtils::GetCrossDocParentFrame(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 (mTransformGetter) {
+ mTransform.emplace(mTransformGetter(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() || mTransformGetter || mIsTransformSeparator) {
+ if (!mTransformGetter && !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(!mTransformGetter);
+
+ 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();
+ }
+
+ // XXX: should go back to fix mTransformGetter.
+ 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::GetCrossDocParentFrame(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(
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::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();
+ }
+ }
+
+ // 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,
+ };
+
+ Maybe<nsDisplayTransform*> deferredTransformItem;
+ 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 = Some(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;
+ params.mTransformPtr = transformForSC;
+ params.prim_flags = !BackfaceIsHidden()
+ ? wr::PrimitiveFlags::IS_BACKFACE_VISIBLE
+ : wr::PrimitiveFlags{0};
+ 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(
+ mozilla::layers::WebRenderScrollData* aData,
+ mozilla::layers::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();
+}
+
+already_AddRefed<Layer> nsDisplayTransform::BuildLayer(
+ nsDisplayListBuilder* aBuilder, LayerManager* aManager,
+ const ContainerLayerParameters& aContainerParameters) {
+ // While generating a glyph mask, the transform vector of the root frame had
+ // been applied into the target context, so stop applying it again here.
+ const bool shouldSkipTransform = ShouldSkipTransform(aBuilder);
+
+ /* For frames without transform, it would not be removed for
+ * backface hidden here. But, it would be removed by the init
+ * function of nsDisplayTransform.
+ */
+ const Matrix4x4 newTransformMatrix =
+ shouldSkipTransform ? Matrix4x4() : GetTransformForRendering();
+
+ uint32_t flags = FrameLayerBuilder::CONTAINER_ALLOW_PULL_BACKGROUND_COLOR;
+ RefPtr<ContainerLayer> container =
+ aManager->GetLayerBuilder()->BuildContainerLayerFor(
+ aBuilder, aManager, mFrame, this, GetChildren(), aContainerParameters,
+ &newTransformMatrix, flags);
+
+ if (!container) {
+ return nullptr;
+ }
+
+ // Add the preserve-3d flag for this layer, BuildContainerLayerFor clears all
+ // flags, so we never need to explicitly unset this flag.
+ if (mFrame->Extend3DContext() && !mIsTransformSeparator) {
+ container->SetContentFlags(container->GetContentFlags() |
+ Layer::CONTENT_EXTEND_3D_CONTEXT);
+ } else {
+ container->SetContentFlags(container->GetContentFlags() &
+ ~Layer::CONTENT_EXTEND_3D_CONTEXT);
+ }
+
+ if (CanUseAsyncAnimations(aBuilder)) {
+ mFrame->SetProperty(nsIFrame::RefusedAsyncAnimationProperty(), false);
+ }
+
+ // We don't send animations for transform separator display items.
+ if (!mIsTransformSeparator) {
+ nsDisplayListBuilder::AddAnimationsAndTransitionsToLayer(
+ container, aBuilder, this, mFrame, GetType());
+ }
+
+ if (CanUseAsyncAnimations(aBuilder) && MayBeAnimated(aBuilder)) {
+ // Only allow async updates to the transform if we're an animated layer,
+ // since that's what triggers us to set the correct AGR in the constructor
+ // and makes sure FrameLayerBuilder won't compute occlusions for this layer.
+ container->SetUserData(nsIFrame::LayerIsPrerenderedDataKey(),
+ /*the value is irrelevant*/ nullptr);
+ container->SetContentFlags(container->GetContentFlags() |
+ Layer::CONTENT_MAY_CHANGE_TRANSFORM);
+ } else {
+ container->RemoveUserData(nsIFrame::LayerIsPrerenderedDataKey());
+ container->SetContentFlags(container->GetContentFlags() &
+ ~Layer::CONTENT_MAY_CHANGE_TRANSFORM);
+ }
+ return container.forget();
+}
+
+bool nsDisplayTransform::MayBeAnimated(nsDisplayListBuilder* aBuilder,
+ bool aEnforceMinimumSize) 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.
+ if (EffectCompositor::HasAnimationsForCompositor(
+ mFrame, DisplayItemType::TYPE_TRANSFORM) ||
+ (ActiveLayerTracker::IsTransformAnimated(aBuilder, mFrame) &&
+ !(aEnforceMinimumSize && IsItemTooSmallForActiveLayer(mFrame)))) {
+ return true;
+ }
+ return false;
+}
+
+nsDisplayItem::LayerState nsDisplayTransform::GetLayerState(
+ nsDisplayListBuilder* aBuilder, LayerManager* aManager,
+ const ContainerLayerParameters& aParameters) {
+ // If the transform is 3d, the layer takes part in preserve-3d
+ // sorting, or the layer is a separator then we *always* want this
+ // to be an active layer.
+ // Checking HasPerspective() is needed to handle perspective value 0 when
+ // the transform is 2D.
+ if (!GetTransform().Is2D() || Combines3DTransformWithAncestors() ||
+ mIsTransformSeparator || mFrame->HasPerspective()) {
+ return LayerState::LAYER_ACTIVE_FORCE;
+ }
+
+ if (MayBeAnimated(aBuilder)) {
+ // Returns LayerState::LAYER_ACTIVE_FORCE to avoid flatterning the layer for
+ // async animations.
+ return LayerState::LAYER_ACTIVE_FORCE;
+ }
+
+ // Expect the child display items to have this frame as their animated
+ // geometry root (since it will be their reference frame). If they have a
+ // different animated geometry root, we'll make this an active layer so the
+ // animation can be accelerated.
+ return RequiredLayerStateForChildren(
+ aBuilder, aManager, aParameters, *GetChildren(),
+ mAnimatedGeometryRootForChildren, GetActiveScrolledRoot());
+}
+
+bool nsDisplayTransform::ComputeVisibility(nsDisplayListBuilder* aBuilder,
+ nsRegion* aVisibleRegion) {
+ // nsDisplayTransform::GetBounds() returns an empty rect in nested 3d context.
+ // Calling mStoredList.RecomputeVisibility below for such transform causes the
+ // child display items to end up with empty visible rect.
+ // We avoid this by bailing out always if we are dealing with a 3d context.
+ if (mFrame->Extend3DContext() || Combines3DTransformWithAncestors()) {
+ return true;
+ }
+
+ /* As we do this, we need to be sure to
+ * untransform the visible rect, since we want everything that's painting to
+ * think that it's painting in its original rectangular coordinate space.
+ * If we can't untransform, take the entire overflow rect */
+ nsRect untransformedVisibleRect;
+ if (!UntransformPaintRect(aBuilder, &untransformedVisibleRect)) {
+ untransformedVisibleRect = mFrame->InkOverflowRectRelativeToSelf();
+ }
+
+ bool snap;
+ const nsRect bounds = GetUntransformedBounds(aBuilder, &snap);
+ nsRegion visibleRegion;
+ visibleRegion.And(bounds, untransformedVisibleRect);
+ GetChildren()->ComputeVisibilityForSublist(aBuilder, &visibleRegion,
+ visibleRegion.GetBounds());
+
+ return true;
+}
+
+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;
+ }
+
+ /* 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;
+ if (aRect.width == 1 && aRect.height == 1) {
+ // Magic width/height indicating we're hit testing a point, not a rect
+ 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));
+
+ bool snap;
+ nsRect childBounds = GetUntransformedBounds(aBuilder, &snap);
+ Rect childGfxBounds(NSAppUnitsToFloatPixels(childBounds.x, factor),
+ NSAppUnitsToFloatPixels(childBounds.y, factor),
+ NSAppUnitsToFloatPixels(childBounds.width, factor),
+ NSAppUnitsToFloatPixels(childBounds.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);
+
+#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 = ::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();
+
+ uint32_t flags =
+ INCLUDE_PERSPECTIVE | OFFSET_BY_ORIGIN | INCLUDE_PRESERVE3D_ANCESTORS;
+ FrameTransformProperties props(aFrame, aRefBox, factor);
+ return nsLayoutUtils::MatrixTransformRect(
+ aUntransformedBounds,
+ GetResultingTransformMatrixInternal(props, aRefBox, nsPoint(0, 0), factor,
+ flags),
+ 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();
+
+ uint32_t flags =
+ INCLUDE_PERSPECTIVE | OFFSET_BY_ORIGIN | INCLUDE_PRESERVE3D_ANCESTORS;
+
+ Matrix4x4 transform =
+ GetResultingTransformMatrix(aFrame, nsPoint(0, 0), factor, flags);
+ if (transform.IsSingular()) {
+ return false;
+ }
+
+ RectDouble result(NSAppUnitsToFloatPixels(aTransformedBounds.x, factor),
+ NSAppUnitsToFloatPixels(aTransformedBounds.y, factor),
+ NSAppUnitsToFloatPixels(aTransformedBounds.width, factor),
+ NSAppUnitsToFloatPixels(aTransformedBounds.height, factor));
+
+ RectDouble childGfxBounds(
+ NSAppUnitsToFloatPixels(aChildBounds.x, factor),
+ NSAppUnitsToFloatPixels(aChildBounds.y, factor),
+ NSAppUnitsToFloatPixels(aChildBounds.width, factor),
+ NSAppUnitsToFloatPixels(aChildBounds.height, factor));
+
+ result = transform.Inverse().ProjectRectBounds(result, childGfxBounds);
+ *aOutRect = nsLayoutUtils::RoundGfxRectToAppRect(ThebesRect(result), factor);
+ 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)
+ : nsDisplayHitTestInfoBase(aBuilder, aFrame) {
+ mList.AppendToTop(aList);
+ MOZ_ASSERT(mList.Count() == 1);
+ MOZ_ASSERT(mList.GetTop()->GetType() == DisplayItemType::TYPE_TRANSFORM);
+ mAnimatedGeometryRoot = aBuilder->FindAnimatedGeometryRootFor(
+ mFrame->GetContainingBlock(nsIFrame::SKIP_SCROLLED_FRAME));
+}
+
+already_AddRefed<Layer> nsDisplayPerspective::BuildLayer(
+ nsDisplayListBuilder* aBuilder, LayerManager* aManager,
+ const ContainerLayerParameters& aContainerParameters) {
+ 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 nullptr;
+ }
+
+ /*
+ * 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);
+
+ RefPtr<ContainerLayer> container =
+ aManager->GetLayerBuilder()->BuildContainerLayerFor(
+ aBuilder, aManager, mFrame, this, GetChildren(), aContainerParameters,
+ &perspectiveMatrix, 0);
+
+ if (!container) {
+ return nullptr;
+ }
+
+ // Sort of a lie, but we want to pretend that the perspective layer extends a
+ // 3d context so that it gets its transform combined with children. Might need
+ // a better name that reflects this use case and isn't specific to
+ // preserve-3d.
+ container->SetContentFlags(container->GetContentFlags() |
+ Layer::CONTENT_EXTEND_3D_CONTEXT);
+ container->SetTransformIsPerspective(true);
+
+ return container.forget();
+}
+
+LayerState nsDisplayPerspective::GetLayerState(
+ nsDisplayListBuilder* aBuilder, LayerManager* aManager,
+ const ContainerLayerParameters& aParameters) {
+ return LayerState::LAYER_ACTIVE_FORCE;
+}
+
+nsRegion nsDisplayPerspective::GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
+ bool* aSnap) const {
+ if (!GetChildren()->GetTop()) {
+ *aSnap = false;
+ return nsRegion();
+ }
+
+ return GetChildren()->GetTop()->GetOpaqueRegion(aBuilder, aSnap);
+}
+
+bool nsDisplayPerspective::CreateWebRenderCommands(
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::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->GetContainingBlock(nsIFrame::SKIP_SCROLLED_FRAME);
+
+ // 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;
+ params.mTransformPtr = &perspectiveMatrix;
+ 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 (auto* asr = GetActiveScrolledRoot(); asr; asr = asr->mParent) {
+ if (nsLayoutUtils::IsAncestorFrameCrossDoc(
+ 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;
+}
+
+bool nsDisplayPerspective::ComputeVisibility(nsDisplayListBuilder* aBuilder,
+ nsRegion* aVisibleRegion) {
+ return mList.ComputeVisibilityForSublist(aBuilder, aVisibleRegion,
+ GetPaintRect());
+}
+
+nsDisplayText::nsDisplayText(nsDisplayListBuilder* aBuilder,
+ nsTextFrame* aFrame,
+ const Maybe<bool>& aIsSelected)
+ : nsPaintedDisplayItem(aBuilder, aFrame),
+ mOpacity(1.0f),
+ mVisIStartEdge(0),
+ mVisIEndEdge(0) {
+ MOZ_COUNT_CTOR(nsDisplayText);
+ mIsFrameSelected = aIsSelected;
+ mBounds = mFrame->InkOverflowRectRelativeToSelf() + ToReferenceFrame();
+ // Bug 748228
+ mBounds.Inflate(mFrame->PresContext()->AppUnitsPerDevPixel());
+}
+
+bool nsDisplayText::CanApplyOpacity() const {
+ if (IsSelected()) {
+ return false;
+ }
+
+ nsTextFrame* f = static_cast<nsTextFrame*>(mFrame);
+ const nsStyleText* textStyle = f->StyleText();
+ if (textStyle->HasTextShadow()) {
+ return false;
+ }
+
+ nsTextFrame::TextDecorations decorations;
+ f->GetTextDecorations(f->PresContext(), nsTextFrame::eResolvedColors,
+ decorations);
+ if (decorations.HasDecorationLines()) {
+ return false;
+ }
+
+ return true;
+}
+
+void nsDisplayText::Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) {
+ AUTO_PROFILER_LABEL("nsDisplayText::Paint", GRAPHICS);
+
+ DrawTargetAutoDisableSubpixelAntialiasing disable(aCtx->GetDrawTarget(),
+ IsSubpixelAADisabled());
+ RenderToContext(aCtx, aBuilder);
+}
+
+bool nsDisplayText::CreateWebRenderCommands(
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::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) let's us early reject a bunch of things, but it can produce
+ // incorrect results for shadows, because they can translate things back into
+ // view. Also if we're selected we might have some shadows from the
+ // ::selected and ::inctive-selected pseudo-selectors. So don't do this
+ // optimization if we have shadows or a selection.
+ if (!(IsSelected() || f->StyleText()->HasTextShadow())) {
+ nsRect visible = GetPaintRect();
+ visible.Inflate(3 * appUnitsPerDevPixel);
+ bounds = bounds.Intersect(visible);
+ }
+
+ RefPtr<gfxContext> textDrawer = aBuilder.GetTextContext(
+ aResources, aSc, aManager, this, bounds, deviceOffset);
+
+ aBuilder.StartGroup(this);
+
+ RenderToContext(textDrawer, aDisplayListBuilder, true);
+ const bool result = textDrawer->GetTextDrawer()->Finish();
+
+ if (result) {
+ aBuilder.FinishGroup();
+ } else {
+ aBuilder.CancelGroup(true);
+ }
+
+ return result;
+}
+
+void nsDisplayText::RenderToContext(gfxContext* aCtx,
+ nsDisplayListBuilder* aBuilder,
+ 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(GetPaintRect(), A2D);
+ extraVisible.Inflate(1);
+
+ gfxRect pixelVisible(extraVisible.x, extraVisible.y, extraVisible.width,
+ extraVisible.height);
+ pixelVisible.Inflate(2);
+ pixelVisible.RoundOut();
+
+ bool willClip = !aBuilder->IsForGenerateGlyphMask() && !aIsRecording;
+ if (willClip) {
+ aCtx->NewPath();
+ aCtx->Rectangle(pixelVisible);
+ aCtx->Clip();
+ }
+
+ 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(),
+ IsSelected(), mOpacity);
+
+ if (willClip) {
+ aCtx->PopClip();
+ }
+}
+
+bool nsDisplayText::IsSelected() const {
+ if (mIsFrameSelected.isNothing()) {
+ MOZ_ASSERT((nsTextFrame*)do_QueryFrame(mFrame));
+ auto* f = static_cast<nsTextFrame*>(mFrame);
+ mIsFrameSelected.emplace(f->IsSelected());
+ }
+
+ return mIsFrameSelected.value();
+}
+
+class nsDisplayTextGeometry : public nsDisplayItemGenericGeometry {
+ public:
+ nsDisplayTextGeometry(nsDisplayText* aItem, nsDisplayListBuilder* aBuilder)
+ : nsDisplayItemGenericGeometry(aItem, aBuilder),
+ mOpacity(aItem->Opacity()),
+ 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;
+ float mOpacity;
+ 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()) ||
+ mOpacity != geometry->mOpacity) {
+ aInvalidRegion->Or(oldRect, newRect);
+ }
+}
+
+void nsDisplayText::WriteDebugInfo(std::stringstream& aStream) {
+#ifdef DEBUG
+ aStream << " (\"";
+
+ nsTextFrame* f = static_cast<nsTextFrame*>(mFrame);
+ nsCString buf;
+ int32_t totalContentLength;
+ f->ToCString(buf, &totalContentLength);
+
+ aStream << buf.get() << "\")";
+#endif
+}
+
+nsDisplayEffectsBase::nsDisplayEffectsBase(
+ nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayList* aList,
+ const ActiveScrolledRoot* aActiveScrolledRoot, bool aClearClipChain)
+ : nsDisplayWrapList(aBuilder, aFrame, aList, aActiveScrolledRoot,
+ aClearClipChain),
+ mHandleOpacity(false) {
+ MOZ_COUNT_CTOR(nsDisplayEffectsBase);
+}
+
+nsDisplayEffectsBase::nsDisplayEffectsBase(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame,
+ nsDisplayList* aList)
+ : nsDisplayWrapList(aBuilder, aFrame, aList), mHandleOpacity(false) {
+ 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 {
+ 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()) ||
+ geometry->mOpacity != mFrame->StyleEffects()->mOpacity ||
+ geometry->mHandleOpacity != ShouldHandleOpacity()) {
+ // 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() {
+ const nsIContent* content = mFrame->GetContent();
+ bool hasSVGLayout = mFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT);
+ if (hasSVGLayout) {
+ ISVGDisplayableFrame* svgFrame = do_QueryFrame(mFrame);
+ if (!svgFrame || !mFrame->GetContent()->IsSVGElement()) {
+ NS_ASSERTION(false, "why?");
+ return false;
+ }
+ if (!static_cast<const SVGElement*>(content)->HasValidDimensions()) {
+ return false; // The SVG spec says not to draw filters for this
+ }
+ }
+
+ return true;
+}
+
+typedef SVGIntegrationUtils::PaintFramesParams 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);
+
+ gfxPoint devPixelOffsetToUserSpace = nsLayoutUtils::PointToGfxPoint(
+ offsetToUserSpace, frame->PresContext()->AppUnitsPerDevPixel());
+
+ gfxContextMatrixAutoSaveRestore matSR(&ctx);
+ ctx.SetMatrixDouble(
+ ctx.CurrentMatrixDouble().PreTranslate(devPixelOffsetToUserSpace));
+
+ // Convert boaderArea and dirtyRect to user space.
+ int32_t appUnitsPerDevPixel = frame->PresContext()->AppUnitsPerDevPixel();
+ nsRect userSpaceBorderArea = aParams.borderArea - offsetToUserSpace;
+ nsRect userSpaceDirtyRect = aParams.dirtyRect - offsetToUserSpace;
+
+ // Union all mask layer rectangles in user space.
+ gfxRect maskInUserSpace;
+ for (size_t i = 0; i < maskFrames.Length(); i++) {
+ SVGMaskFrame* maskFrame = maskFrames[i];
+ gfxRect currentMaskSurfaceRect;
+
+ if (maskFrame) {
+ currentMaskSurfaceRect = maskFrame->GetMaskArea(aParams.frame);
+ } else {
+ nsCSSRendering::ImageLayerClipState clipState;
+ nsCSSRendering::GetImageLayerClip(
+ svgReset->mMask.mLayers[i], frame, *frame->StyleBorder(),
+ userSpaceBorderArea, userSpaceDirtyRect, false, /* aWillPaintBorder */
+ appUnitsPerDevPixel, &clipState);
+ currentMaskSurfaceRect = clipState.mDirtyRectInDevPx;
+ }
+
+ maskInUserSpace = maskInUserSpace.Union(currentMaskSurfaceRect);
+ }
+
+ if (!maskInUserSpace.IsEmpty()) {
+ aParams.maskRect = Some(ToRect(maskInUserSpace));
+ } else {
+ aParams.maskRect = Nothing();
+ }
+}
+
+nsDisplayMasksAndClipPaths::nsDisplayMasksAndClipPaths(
+ nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayList* aList,
+ const ActiveScrolledRoot* aActiveScrolledRoot)
+ : nsDisplayEffectsBase(aBuilder, aFrame, aList, aActiveScrolledRoot, true),
+ mApplyOpacityWithSimpleClipPath(false) {
+ 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) {
+ if (!svgReset->mMask.mLayers[i].mImage.IsResolved()) {
+ continue;
+ }
+ bool isTransformedFixed;
+ nsBackgroundLayerState state = nsCSSRendering::PrepareImageLayer(
+ presContext, aFrame, flags, mFrame->GetRectRelativeToSelf(),
+ mFrame->GetRectRelativeToSelf(), svgReset->mMask.mLayers[i],
+ &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 ==
+ mozilla::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;
+ }
+
+ if (mFrame->StyleEffects()->mOpacity == 0.0f && mHandleOpacity) {
+ return false;
+ }
+
+ nsIFrame* firstFrame =
+ nsLayoutUtils::FirstContinuationOrIBSplitSibling(mFrame);
+
+ if (SVGObserverUtils::GetAndObserveClipPath(firstFrame, nullptr) ==
+ SVGObserverUtils::eHasRefsSomeInvalid ||
+ SVGObserverUtils::GetAndObserveMasks(firstFrame, nullptr) ==
+ SVGObserverUtils::eHasRefsSomeInvalid) {
+ return false;
+ }
+
+ return true;
+}
+
+already_AddRefed<Layer> nsDisplayMasksAndClipPaths::BuildLayer(
+ nsDisplayListBuilder* aBuilder, LayerManager* aManager,
+ const ContainerLayerParameters& aContainerParameters) {
+ if (!IsValidMask()) {
+ return nullptr;
+ }
+
+ RefPtr<ContainerLayer> container =
+ aManager->GetLayerBuilder()->BuildContainerLayerFor(
+ aBuilder, aManager, mFrame, this, &mList, aContainerParameters,
+ nullptr);
+
+ return container.forget();
+}
+
+bool nsDisplayMasksAndClipPaths::PaintMask(nsDisplayListBuilder* aBuilder,
+ gfxContext* aMaskContext,
+ bool* aMaskPainted) {
+ MOZ_ASSERT(aMaskContext->GetDrawTarget()->GetFormat() == SurfaceFormat::A8);
+
+ imgDrawingParams imgParams(aBuilder->GetImageDecodeFlags());
+ nsRect borderArea = nsRect(ToReferenceFrame(), mFrame->GetSize());
+ SVGIntegrationUtils::PaintFramesParams params(*aMaskContext, mFrame, mBounds,
+ borderArea, aBuilder, nullptr,
+ mHandleOpacity, imgParams);
+ ComputeMaskGeometry(params);
+ bool maskIsComplete = false;
+ bool painted = SVGIntegrationUtils::PaintMask(params, maskIsComplete);
+ if (aMaskPainted) {
+ *aMaskPainted = painted;
+ }
+
+ nsDisplayMasksAndClipPathsGeometry::UpdateDrawResult(this, imgParams.result);
+
+ return maskIsComplete &&
+ (imgParams.result == ImgDrawResult::SUCCESS ||
+ imgParams.result == ImgDrawResult::SUCCESS_NOT_COMPLETE ||
+ imgParams.result == ImgDrawResult::WRONG_SIZE);
+}
+
+LayerState nsDisplayMasksAndClipPaths::GetLayerState(
+ nsDisplayListBuilder* aBuilder, LayerManager* aManager,
+ const ContainerLayerParameters& aParameters) {
+ if (CanPaintOnMaskLayer(aManager)) {
+ LayerState result = RequiredLayerStateForChildren(
+ aBuilder, aManager, aParameters, mList, GetAnimatedGeometryRoot(),
+ GetActiveScrolledRoot());
+ // When we're not active, FrameLayerBuilder will call PaintAsLayer()
+ // on us during painting. In that case we don't want a mask layer to
+ // be created, because PaintAsLayer() takes care of applying the mask.
+ // So we return LayerState::LAYER_SVG_EFFECTS instead of
+ // LayerState::LAYER_INACTIVE so that FrameLayerBuilder doesn't set a mask
+ // layer on our layer.
+ return result == LayerState::LAYER_INACTIVE ? LayerState::LAYER_SVG_EFFECTS
+ : result;
+ }
+
+ return LayerState::LAYER_SVG_EFFECTS;
+}
+
+bool nsDisplayMasksAndClipPaths::CanPaintOnMaskLayer(LayerManager* aManager) {
+ if (!aManager->IsWidgetLayerManager()) {
+ return false;
+ }
+
+ if (!SVGIntegrationUtils::IsMaskResourceReady(mFrame)) {
+ return false;
+ }
+
+ if (StaticPrefs::layers_draw_mask_debug()) {
+ return false;
+ }
+
+ // We don't currently support this item creating a mask
+ // for both the clip-path, and rounded rect clipping.
+ if (GetClip().GetRoundedRectCount() != 0) {
+ return false;
+ }
+
+ return true;
+}
+
+bool nsDisplayMasksAndClipPaths::ComputeVisibility(
+ nsDisplayListBuilder* aBuilder, nsRegion* aVisibleRegion) {
+ // Our children may be made translucent or arbitrarily deformed so we should
+ // not allow them to subtract area from aVisibleRegion.
+ nsRegion childrenVisible(GetPaintRect());
+ nsRect r = GetPaintRect().Intersect(mList.GetClippedBounds(aBuilder));
+ mList.ComputeVisibilityForSublist(aBuilder, &childrenVisible, r);
+ return true;
+}
+
+void nsDisplayMasksAndClipPaths::ComputeInvalidationRegion(
+ nsDisplayListBuilder* aBuilder, const nsDisplayItemGeometry* aGeometry,
+ nsRegion* aInvalidRegion) const {
+ nsDisplayEffectsBase::ComputeInvalidationRegion(aBuilder, aGeometry,
+ aInvalidRegion);
+
+ 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;
+ }
+ }
+ }
+
+ if (aBuilder->ShouldSyncDecodeImages() &&
+ geometry->ShouldInvalidateToSyncDecodeImages()) {
+ const nsStyleSVGReset* svgReset = mFrame->StyleSVGReset();
+ NS_FOR_VISIBLE_IMAGE_LAYERS_BACK_TO_FRONT(i, svgReset->mMask) {
+ const auto& image = svgReset->mMask.mLayers[i].mImage;
+ if (image.IsImageRequestType()) {
+ aInvalidRegion->Or(*aInvalidRegion, bounds);
+ break;
+ }
+ }
+ }
+}
+
+void nsDisplayMasksAndClipPaths::PaintAsLayer(nsDisplayListBuilder* aBuilder,
+ gfxContext* aCtx,
+ LayerManager* aManager) {
+ // Clip the drawing target by mVisibleRect, which contains the visible
+ // region of the target frame and its out-of-flow and inflow descendants.
+ gfxContext* context = aCtx;
+
+ Rect bounds = NSRectToRect(GetPaintRect(),
+ mFrame->PresContext()->AppUnitsPerDevPixel());
+ bounds.RoundOut();
+ context->Clip(bounds);
+
+ imgDrawingParams imgParams(aBuilder->GetImageDecodeFlags());
+ nsRect borderArea = nsRect(ToReferenceFrame(), mFrame->GetSize());
+ SVGIntegrationUtils::PaintFramesParams params(*aCtx, mFrame, GetPaintRect(),
+ borderArea, aBuilder, aManager,
+ mHandleOpacity, imgParams);
+
+ ComputeMaskGeometry(params);
+
+ SVGIntegrationUtils::PaintMaskAndClipPath(params);
+
+ context->PopClip();
+
+ nsDisplayMasksAndClipPathsGeometry::UpdateDrawResult(this, imgParams.result);
+}
+
+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.
+ gfxContext* context = aCtx;
+
+ Rect bounds = NSRectToRect(GetPaintRect(),
+ mFrame->PresContext()->AppUnitsPerDevPixel());
+ bounds.RoundOut();
+ context->Clip(bounds);
+
+ imgDrawingParams imgParams(aBuilder->GetImageDecodeFlags());
+ nsRect borderArea = nsRect(ToReferenceFrame(), mFrame->GetSize());
+ SVGIntegrationUtils::PaintFramesParams params(*aCtx, mFrame, GetPaintRect(),
+ borderArea, aBuilder, nullptr,
+ mHandleOpacity, imgParams);
+
+ ComputeMaskGeometry(params);
+
+ SVGIntegrationUtils::PaintMaskAndClipPath(params, aPaintChildren);
+
+ context->PopClip();
+
+ nsDisplayMasksAndClipPathsGeometry::UpdateDrawResult(this, imgParams.result);
+}
+
+static Maybe<wr::WrClipId> CreateSimpleClipRegion(
+ const nsDisplayMasksAndClipPaths& aDisplayItem,
+ wr::DisplayListBuilder& aBuilder) {
+ nsIFrame* frame = aDisplayItem.Frame();
+ 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);
+
+ AutoTArray<wr::ComplexClipRegion, 1> clipRegions;
+
+ wr::LayoutRect rect;
+ switch (shape.tag) {
+ case StyleBasicShape::Tag::Inset: {
+ const nsRect insetRect = ShapeUtils::ComputeInsetRect(shape, refBox) +
+ aDisplayItem.ToReferenceFrame();
+
+ nscoord radii[8] = {0};
+
+ if (ShapeUtils::ComputeInsetRadii(shape, insetRect, refBox, radii)) {
+ clipRegions.AppendElement(
+ wr::ToComplexClipRegion(insetRect, radii, appUnitsPerDevPixel));
+ }
+
+ rect = 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 : mozilla::AllPhysicalHalfCorners()) {
+ ellipseRadii[corner] =
+ HalfCornerIsX(corner) ? radii.width : radii.height;
+ }
+
+ clipRegions.AppendElement(wr::ToComplexClipRegion(
+ ellipseRect, ellipseRadii, appUnitsPerDevPixel));
+
+ rect = wr::ToLayoutRect(
+ LayoutDeviceRect::FromAppUnits(ellipseRect, 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::WrClipId clipId = aBuilder.DefineClip(Nothing(), rect, &clipRegions);
+ return Some(clipId);
+}
+
+static Maybe<wr::WrClipId> CreateWRClipPathAndMasks(
+ nsDisplayMasksAndClipPaths* aDisplayItem, const LayoutDeviceRect& aBounds,
+ wr::IpcResourceUpdateQueue& aResources, wr::DisplayListBuilder& aBuilder,
+ const StackingContextHelper& aSc, layers::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();
+ }
+
+ wr::WrClipId clipId = aBuilder.DefineImageMaskClip(mask.ref());
+
+ return Some(clipId);
+}
+
+bool nsDisplayMasksAndClipPaths::CreateWebRenderCommands(
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) {
+ bool snap;
+ auto appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel();
+ nsRect displayBounds = GetBounds(aDisplayListBuilder, &snap);
+ LayoutDeviceRect bounds =
+ LayoutDeviceRect::FromAppUnits(displayBounds, appUnitsPerDevPixel);
+
+ Maybe<wr::WrClipId> clip = CreateWRClipPathAndMasks(
+ this, bounds, aResources, aBuilder, aSc, aManager, aDisplayListBuilder);
+
+ 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 = mApplyOpacityWithSimpleClipPath
+ ? Some(mFrame->StyleEffects()->mOpacity)
+ : Nothing();
+
+ wr::StackingContextParams params;
+ params.clip = wr::WrStackingContextClip::ClipId(*clip);
+ params.opacity = opacity.ptrOr(nullptr);
+ layer.emplace(aSc, GetActiveScrolledRoot(), mFrame, this, aBuilder, params,
+ bounds);
+ sc = layer.ptr();
+ }
+
+ nsDisplayEffectsBase::CreateWebRenderCommands(aBuilder, aResources, *sc,
+ aManager, aDisplayListBuilder);
+
+ return true;
+}
+
+void nsDisplayMasksAndClipPaths::SelectOpacityOptimization(
+ const bool aUsingLayers) {
+ if (aUsingLayers ||
+ !SVGIntegrationUtils::UsingSimpleClipPathForFrame(mFrame)) {
+ // Handle opacity in mask and clip-path drawing code.
+ SetHandleOpacity();
+ MOZ_ASSERT(!mApplyOpacityWithSimpleClipPath);
+ } else {
+ // Allow WebRender simple clip paths to also handle opacity.
+ mApplyOpacityWithSimpleClipPath = 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
+ MOZ_ASSERT(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=(";
+ if (mHandleOpacity) {
+ first = false;
+ aTo += nsPrintfCString("opacity(%f)", mFrame->StyleEffects()->mOpacity);
+ }
+ 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
+
+already_AddRefed<Layer> nsDisplayBackdropRootContainer::BuildLayer(
+ nsDisplayListBuilder* aBuilder, LayerManager* aManager,
+ const ContainerLayerParameters& aContainerParameters) {
+ RefPtr<Layer> container = aManager->GetLayerBuilder()->BuildContainerLayerFor(
+ aBuilder, aManager, mFrame, this, &mList, aContainerParameters, nullptr);
+ if (!container) {
+ return nullptr;
+ }
+
+ return container.forget();
+}
+
+LayerState nsDisplayBackdropRootContainer::GetLayerState(
+ nsDisplayListBuilder* aBuilder, LayerManager* aManager,
+ const ContainerLayerParameters& aParameters) {
+ return RequiredLayerStateForChildren(aBuilder, aManager, aParameters, mList,
+ GetAnimatedGeometryRoot(),
+ GetActiveScrolledRoot());
+}
+
+bool nsDisplayBackdropRootContainer::CreateWebRenderCommands(
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) {
+ wr::StackingContextParams params;
+ params.flags |= wr::StackingContextFlags::IS_BACKDROP_ROOT;
+ params.clip =
+ wr::WrStackingContextClip::ClipChain(aBuilder.CurrentClipChainId());
+ StackingContextHelper sc(aSc, GetActiveScrolledRoot(), mFrame, this, aBuilder,
+ params);
+
+ nsDisplayWrapList::CreateWebRenderCommands(aBuilder, aResources, sc, aManager,
+ aDisplayListBuilder);
+ return true;
+}
+
+/* static */
+bool nsDisplayBackdropFilters::CanCreateWebRenderCommands(
+ nsDisplayListBuilder* aBuilder, nsIFrame* aFrame) {
+ return SVGIntegrationUtils::CanCreateWebRenderFiltersForFrame(aFrame);
+}
+
+bool nsDisplayBackdropFilters::CreateWebRenderCommands(
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) {
+ WrFiltersHolder wrFilters;
+ Maybe<nsRect> filterClip;
+ auto filterChain = mFrame->StyleEffects()->mBackdropFilters.AsSpan();
+ if (!SVGIntegrationUtils::CreateWebRenderCSSFilters(filterChain, mFrame,
+ wrFilters) &&
+ !SVGIntegrationUtils::BuildWebRenderFilters(mFrame, filterChain,
+ wrFilters, filterClip)) {
+ return false;
+ }
+
+ nsCSSRendering::ImageLayerClipState clip;
+ nsCSSRendering::GetImageLayerClip(
+ mFrame->StyleBackground()->BottomLayer(), mFrame, *mFrame->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;
+}
+
+/* static */
+nsDisplayFilters::nsDisplayFilters(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame, nsDisplayList* aList)
+ : nsDisplayEffectsBase(aBuilder, aFrame, aList),
+ mEffectsBounds(aFrame->InkOverflowRectRelativeToSelf()) {
+ MOZ_COUNT_CTOR(nsDisplayFilters);
+}
+
+already_AddRefed<Layer> nsDisplayFilters::BuildLayer(
+ nsDisplayListBuilder* aBuilder, LayerManager* aManager,
+ const ContainerLayerParameters& aContainerParameters) {
+ if (!ValidateSVGFrame()) {
+ return nullptr;
+ }
+
+ if (mFrame->StyleEffects()->mOpacity == 0.0f && mHandleOpacity) {
+ return nullptr;
+ }
+
+ nsIFrame* firstFrame =
+ nsLayoutUtils::FirstContinuationOrIBSplitSibling(mFrame);
+
+ // 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, so no need for a layer.
+ if (SVGObserverUtils::GetAndObserveFilters(firstFrame, nullptr) ==
+ SVGObserverUtils::eHasRefsSomeInvalid) {
+ return nullptr;
+ }
+
+ ContainerLayerParameters newContainerParameters = aContainerParameters;
+ newContainerParameters.mDisableSubpixelAntialiasingInDescendants = true;
+
+ RefPtr<ContainerLayer> container =
+ aManager->GetLayerBuilder()->BuildContainerLayerFor(
+ aBuilder, aManager, mFrame, this, &mList, newContainerParameters,
+ nullptr);
+ return container.forget();
+}
+
+LayerState nsDisplayFilters::GetLayerState(
+ nsDisplayListBuilder* aBuilder, LayerManager* aManager,
+ const ContainerLayerParameters& aParameters) {
+ return LayerState::LAYER_SVG_EFFECTS;
+}
+
+bool nsDisplayFilters::ComputeVisibility(nsDisplayListBuilder* aBuilder,
+ nsRegion* aVisibleRegion) {
+ nsPoint offset = ToReferenceFrame();
+ nsRect dirtyRect = SVGIntegrationUtils::GetRequiredSourceForInvalidArea(
+ mFrame, GetPaintRect() - offset) +
+ offset;
+
+ // Our children may be made translucent or arbitrarily deformed so we should
+ // not allow them to subtract area from aVisibleRegion.
+ nsRegion childrenVisible(dirtyRect);
+ nsRect r = dirtyRect.Intersect(
+ mList.GetClippedBoundsWithRespectToASR(aBuilder, mActiveScrolledRoot));
+ mList.ComputeVisibilityForSublist(aBuilder, &childrenVisible, r);
+ return true;
+}
+
+void nsDisplayFilters::ComputeInvalidationRegion(
+ nsDisplayListBuilder* aBuilder, const nsDisplayItemGeometry* aGeometry,
+ nsRegion* aInvalidRegion) const {
+ nsDisplayEffectsBase::ComputeInvalidationRegion(aBuilder, aGeometry,
+ aInvalidRegion);
+
+ auto* geometry = static_cast<const nsDisplayFiltersGeometry*>(aGeometry);
+
+ if (aBuilder->ShouldSyncDecodeImages() &&
+ geometry->ShouldInvalidateToSyncDecodeImages()) {
+ bool snap;
+ nsRect bounds = GetBounds(aBuilder, &snap);
+ aInvalidRegion->Or(*aInvalidRegion, bounds);
+ }
+}
+
+void nsDisplayFilters::PaintAsLayer(nsDisplayListBuilder* aBuilder,
+ gfxContext* aCtx, LayerManager* aManager) {
+ imgDrawingParams imgParams(aBuilder->GetImageDecodeFlags());
+ nsRect borderArea = nsRect(ToReferenceFrame(), mFrame->GetSize());
+ SVGIntegrationUtils::PaintFramesParams params(*aCtx, mFrame, GetPaintRect(),
+ borderArea, aBuilder, aManager,
+ mHandleOpacity, imgParams);
+ SVGIntegrationUtils::PaintFilter(params);
+ nsDisplayFiltersGeometry::UpdateDrawResult(this, imgParams.result);
+}
+
+bool nsDisplayFilters::CanCreateWebRenderCommands() {
+ return SVGIntegrationUtils::CanCreateWebRenderFiltersForFrame(mFrame);
+}
+
+bool nsDisplayFilters::CreateWebRenderCommands(
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) {
+ float auPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel();
+
+ WrFiltersHolder wrFilters;
+ Maybe<nsRect> filterClip;
+ auto filterChain = mFrame->StyleEffects()->mFilters.AsSpan();
+ if (!SVGIntegrationUtils::CreateWebRenderCSSFilters(filterChain, mFrame,
+ wrFilters) &&
+ !SVGIntegrationUtils::BuildWebRenderFilters(mFrame, filterChain,
+ wrFilters, filterClip)) {
+ return false;
+ }
+
+ wr::WrStackingContextClip clip;
+ if (filterClip) {
+ auto devPxRect = LayoutDeviceRect::FromAppUnits(
+ filterClip.value() + ToReferenceFrame(), auPerDevPixel);
+ wr::WrClipId clipId = aBuilder.DefineRectClip(wr::ToLayoutRect(devPxRect));
+ clip = wr::WrStackingContextClip::ClipId(clipId);
+ } else {
+ clip = wr::WrStackingContextClip::ClipChain(aBuilder.CurrentClipChainId());
+ }
+
+ float opacity = mFrame->StyleEffects()->mOpacity;
+ wr::StackingContextParams params;
+ params.mFilters = std::move(wrFilters.filters);
+ params.mFilterDatas = std::move(wrFilters.filter_datas);
+ params.opacity = opacity != 1.0f && mHandleOpacity ? &opacity : nullptr;
+ params.clip = clip;
+ StackingContextHelper sc(aSc, GetActiveScrolledRoot(), mFrame, this, aBuilder,
+ params);
+
+ nsDisplayEffectsBase::CreateWebRenderCommands(aBuilder, aResources, sc,
+ aManager, aDisplayListBuilder);
+
+ return true;
+}
+
+#ifdef MOZ_DUMP_PAINTING
+void nsDisplayFilters::PrintEffects(nsACString& aTo) {
+ nsIFrame* firstFrame =
+ nsLayoutUtils::FirstContinuationOrIBSplitSibling(mFrame);
+ bool first = true;
+ aTo += " effects=(";
+ if (mHandleOpacity) {
+ first = false;
+ aTo += nsPrintfCString("opacity(%f)", mFrame->StyleEffects()->mOpacity);
+ }
+ // 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);
+}
+
+LayerState nsDisplaySVGWrapper::GetLayerState(
+ nsDisplayListBuilder* aBuilder, LayerManager* aManager,
+ const ContainerLayerParameters& aParameters) {
+ RefPtr<LayerManager> layerManager = aBuilder->GetWidgetLayerManager();
+ if (layerManager &&
+ layerManager->GetBackendType() == layers::LayersBackend::LAYERS_WR) {
+ return LayerState::LAYER_ACTIVE_FORCE;
+ }
+ return LayerState::LAYER_NONE;
+}
+
+bool nsDisplaySVGWrapper::ShouldFlattenAway(nsDisplayListBuilder* aBuilder) {
+ RefPtr<LayerManager> layerManager = aBuilder->GetWidgetLayerManager();
+ if (layerManager &&
+ layerManager->GetBackendType() == layers::LayersBackend::LAYERS_WR) {
+ return false;
+ }
+ return true;
+}
+
+already_AddRefed<Layer> nsDisplaySVGWrapper::BuildLayer(
+ nsDisplayListBuilder* aBuilder, LayerManager* aManager,
+ const ContainerLayerParameters& aContainerParameters) {
+ ContainerLayerParameters newContainerParameters = aContainerParameters;
+ newContainerParameters.mDisableSubpixelAntialiasingInDescendants = true;
+
+ RefPtr<ContainerLayer> container =
+ aManager->GetLayerBuilder()->BuildContainerLayerFor(
+ aBuilder, aManager, mFrame, this, &mList, newContainerParameters,
+ nullptr);
+
+ return container.forget();
+}
+
+bool nsDisplaySVGWrapper::CreateWebRenderCommands(
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) {
+ return nsDisplayWrapList::CreateWebRenderCommands(
+ aBuilder, aResources, aSc, aManager, aDisplayListBuilder);
+}
+
+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
+
+LayerState nsDisplayForeignObject::GetLayerState(
+ nsDisplayListBuilder* aBuilder, LayerManager* aManager,
+ const ContainerLayerParameters& aParameters) {
+ RefPtr<LayerManager> layerManager = aBuilder->GetWidgetLayerManager();
+ if (layerManager &&
+ layerManager->GetBackendType() == layers::LayersBackend::LAYERS_WR) {
+ return LayerState::LAYER_ACTIVE_FORCE;
+ }
+ return LayerState::LAYER_NONE;
+}
+
+bool nsDisplayForeignObject::ShouldFlattenAway(nsDisplayListBuilder* aBuilder) {
+ RefPtr<LayerManager> layerManager = aBuilder->GetWidgetLayerManager();
+ if (layerManager &&
+ layerManager->GetBackendType() == layers::LayersBackend::LAYERS_WR) {
+ return false;
+ }
+ return true;
+}
+
+already_AddRefed<Layer> nsDisplayForeignObject::BuildLayer(
+ nsDisplayListBuilder* aBuilder, LayerManager* aManager,
+ const ContainerLayerParameters& aContainerParameters) {
+ ContainerLayerParameters newContainerParameters = aContainerParameters;
+ newContainerParameters.mDisableSubpixelAntialiasingInDescendants = true;
+
+ RefPtr<ContainerLayer> container =
+ aManager->GetLayerBuilder()->BuildContainerLayerFor(
+ aBuilder, aManager, mFrame, this, &mList, newContainerParameters,
+ nullptr);
+
+ return container.forget();
+}
+
+bool nsDisplayForeignObject::CreateWebRenderCommands(
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) {
+ AutoRestore<bool> restoreDoGrouping(aManager->CommandBuilder().mDoGrouping);
+ aManager->CommandBuilder().mDoGrouping = false;
+ return nsDisplayWrapList::CreateWebRenderCommands(
+ aBuilder, aResources, aSc, aManager, aDisplayListBuilder);
+}
+
+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 (;;) {
+ nsDisplayItem* item = PositionedDescendants()->GetBottom();
+ if (item && item->ZIndex() < 0) {
+ PositionedDescendants()->RemoveBottom();
+ aOutResultList->AppendToTop(item);
+ continue;
+ }
+ break;
+ }
+ // 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());
+}
+
+namespace mozilla {
+
+uint32_t PaintTelemetry::sPaintLevel = 0;
+uint32_t PaintTelemetry::sMetricLevel = 0;
+EnumeratedArray<PaintTelemetry::Metric, PaintTelemetry::Metric::COUNT, double>
+ PaintTelemetry::sMetrics;
+
+PaintTelemetry::AutoRecordPaint::AutoRecordPaint() {
+ // Don't record nested paints.
+ if (sPaintLevel++ > 0) {
+ return;
+ }
+
+ // Reset metrics for a new paint.
+ for (auto& metric : sMetrics) {
+ metric = 0.0;
+ }
+ 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));
+
+ // Helpers for recording large/small paints.
+ auto recordLarge = [=](const nsCString& aKey, double aDurationMs) -> void {
+ MOZ_ASSERT(aDurationMs <= totalMs);
+ uint32_t amount = static_cast<int32_t>((aDurationMs / totalMs) * 100.0);
+ Telemetry::Accumulate(Telemetry::CONTENT_LARGE_PAINT_PHASE_WEIGHT, aKey,
+ amount);
+ };
+ auto recordSmall = [=](const nsCString& aKey, double aDurationMs) -> void {
+ MOZ_ASSERT(aDurationMs <= totalMs);
+ uint32_t amount = static_cast<int32_t>((aDurationMs / totalMs) * 100.0);
+ Telemetry::Accumulate(Telemetry::CONTENT_SMALL_PAINT_PHASE_WEIGHT, aKey,
+ amount);
+ };
+
+ double dlMs = sMetrics[Metric::DisplayList];
+ double flbMs = sMetrics[Metric::Layerization];
+ double frMs = sMetrics[Metric::FlushRasterization];
+ double rMs = sMetrics[Metric::Rasterization];
+
+ // If the total time was >= 16ms, then it's likely we missed a frame due to
+ // painting. We bucket these metrics separately.
+ if (totalMs >= 16.0) {
+ recordLarge("dl"_ns, dlMs);
+ recordLarge("flb"_ns, flbMs);
+ recordLarge("fr"_ns, frMs);
+ recordLarge("r"_ns, rMs);
+ } else {
+ recordSmall("dl"_ns, dlMs);
+ recordSmall("flb"_ns, flbMs);
+ recordSmall("fr"_ns, frMs);
+ recordSmall("r"_ns, rMs);
+ }
+
+ Telemetry::Accumulate(Telemetry::PAINT_BUILD_LAYERS_TIME, flbMs);
+}
+
+PaintTelemetry::AutoRecord::AutoRecord(Metric aMetric) : mMetric(aMetric) {
+ // Don't double-record anything nested.
+ if (sMetricLevel++ > 0) {
+ return;
+ }
+
+ // Don't record inside nested paints, or outside of paints.
+ if (sPaintLevel != 1) {
+ return;
+ }
+
+ mStart = TimeStamp::Now();
+}
+
+PaintTelemetry::AutoRecord::~AutoRecord() {
+ MOZ_ASSERT(sMetricLevel != 0);
+
+ sMetricLevel--;
+ if (mStart.IsNull()) {
+ return;
+ }
+
+ sMetrics[mMetric] += (TimeStamp::Now() - mStart).ToMilliseconds();
+}
+
+} // namespace mozilla
+
+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::GetCrossDocParentFrame(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),
+ mPrevHitTestArea(aBuilder->mHitTestArea),
+ mPrevHitTestInfo(aBuilder->mHitTestInfo),
+ mPrevOffset(aBuilder->mCurrentOffsetToReferenceFrame),
+ mPrevAdditionalOffset(aBuilder->mAdditionalOffset),
+ mPrevVisibleRect(aBuilder->mVisibleRect),
+ mPrevDirtyRect(aBuilder->mDirtyRect),
+ mPrevAGR(aBuilder->mCurrentAGR),
+ 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);
+ }
+
+ bool isAsync;
+ mCurrentAGRState = aBuilder->IsAnimatedGeometryRoot(aForChild, isAsync);
+
+ if (aBuilder->mCurrentFrame == aForChild->GetParent()) {
+ if (mCurrentAGRState == AGR_YES) {
+ aBuilder->mCurrentAGR =
+ aBuilder->WrapAGRForFrame(aForChild, isAsync, aBuilder->mCurrentAGR);
+ }
+ } else if (aBuilder->mCurrentFrame != aForChild) {
+ aBuilder->mCurrentAGR = aBuilder->FindAnimatedGeometryRootFor(aForChild);
+ }
+
+ MOZ_ASSERT(nsLayoutUtils::IsAncestorFrameCrossDoc(
+ aBuilder->RootReferenceFrame(), *aBuilder->mCurrentAGR));
+
+ // 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;
+}
diff --git a/layout/painting/nsDisplayList.h b/layout/painting/nsDisplayList.h
new file mode 100644
index 0000000000..37161e62ce
--- /dev/null
+++ b/layout/painting/nsDisplayList.h
@@ -0,0 +1,7616 @@
+/* -*- 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 "mozilla/Attributes.h"
+#include "gfxContext.h"
+#include "mozilla/ArenaAllocator.h"
+#include "mozilla/Array.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/EnumSet.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/TemplateLib.h" // mozilla::tl::Max
+#include "nsCOMPtr.h"
+#include "nsContainerFrame.h"
+#include "nsPoint.h"
+#include "nsRect.h"
+#include "nsRegion.h"
+#include "nsDisplayListInvalidation.h"
+#include "DisplayItemClipChain.h"
+#include "DisplayListClipState.h"
+#include "LayerState.h"
+#include "FrameMetrics.h"
+#include "ImgDrawResult.h"
+#include "mozilla/dom/EffectsInfo.h"
+#include "mozilla/dom/RemoteBrowser.h"
+#include "mozilla/EffectCompositor.h"
+#include "mozilla/EnumeratedArray.h"
+#include "mozilla/MotionPathUtils.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/gfx/UserData.h"
+#include "mozilla/layers/LayerAttributes.h"
+#include "mozilla/layers/ScrollableLayerGuid.h"
+#include "nsCSSRenderingBorders.h"
+#include "nsPresArena.h"
+#include "nsAutoLayoutPhase.h"
+#include "nsDisplayItemTypes.h"
+#include "RetainedDisplayListHelpers.h"
+#include "Units.h"
+
+#include <stdint.h>
+#include "nsClassHashtable.h"
+#include "nsTHashtable.h"
+
+#include <stdlib.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 nsDisplayList;
+class nsDisplayTableItem;
+class nsIScrollableFrame;
+class nsSubDocumentFrame;
+class nsDisplayCompositorHitTestInfo;
+class nsDisplayScrollInfoLayer;
+class nsDisplayTableBackgroundSet;
+class nsCaret;
+enum class nsDisplayOwnLayerFlags;
+struct WrFiltersHolder;
+
+namespace nsStyleTransformMatrix {
+class TransformReferenceBox;
+}
+
+namespace mozilla {
+class FrameLayerBuilder;
+class PresShell;
+class StickyScrollContainer;
+namespace layers {
+struct FrameMetrics;
+class RenderRootStateManager;
+class Layer;
+class ImageLayer;
+class ImageContainer;
+class StackingContextHelper;
+class WebRenderCommand;
+class WebRenderScrollData;
+class WebRenderLayerScrollData;
+} // namespace layers
+namespace wr {
+class DisplayListBuilder;
+} // namespace wr
+namespace dom {
+class Selection;
+} // namespace dom
+
+enum class DisplayListArenaObjectId {
+#define DISPLAY_LIST_ARENA_OBJECT(name_) name_,
+#include "nsDisplayListArenaTypes.h"
+#undef DISPLAY_LIST_ARENA_OBJECT
+ COUNT
+};
+
+} // namespace mozilla
+
+/*
+ * 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.
+ */
+
+/**
+ * Represents a frame that is considered to have (or will have) "animated
+ * geometry" for itself and descendant frames.
+ *
+ * For example the scrolled frames of scrollframes which are actively being
+ * scrolled fall into this category. Frames with certain CSS properties that are
+ * being animated (e.g. 'left'/'top' etc) are also placed in this category.
+ * Frames with different active geometry roots are in different PaintedLayers,
+ * so that we can animate the geometry root by changing its transform (either on
+ * the main thread or in the compositor).
+ *
+ * nsDisplayListBuilder constructs a tree of these (for fast traversals) and
+ * assigns one for each display item.
+ *
+ * The animated geometry root for a display item is required to be a descendant
+ * (or equal to) the item's ReferenceFrame(), which means that we will fall back
+ * to returning aItem->ReferenceFrame() when we can't find another animated
+ * geometry root.
+ *
+ * The animated geometry root isn't strongly defined for a frame as transforms
+ * and background-attachment:fixed can cause it to vary between display items
+ * for a given frame.
+ */
+struct AnimatedGeometryRoot {
+ static already_AddRefed<AnimatedGeometryRoot> CreateAGRForFrame(
+ nsIFrame* aFrame, AnimatedGeometryRoot* aParent, bool aIsAsync,
+ bool aIsRetained) {
+ RefPtr<AnimatedGeometryRoot> result;
+ if (aIsRetained) {
+ result = aFrame->GetProperty(AnimatedGeometryRootCache());
+ }
+
+ if (result) {
+ result->mParentAGR = aParent;
+ result->mIsAsync = aIsAsync;
+ } else {
+ result = new AnimatedGeometryRoot(aFrame, aParent, aIsAsync, aIsRetained);
+ }
+ return result.forget();
+ }
+
+ operator nsIFrame*() { return mFrame; }
+
+ nsIFrame* operator->() const { return mFrame; }
+
+ AnimatedGeometryRoot* GetAsyncAGR() {
+ AnimatedGeometryRoot* agr = this;
+ while (!agr->mIsAsync && agr->mParentAGR) {
+ agr = agr->mParentAGR;
+ }
+ return agr;
+ }
+
+ NS_INLINE_DECL_REFCOUNTING(AnimatedGeometryRoot)
+
+ nsIFrame* mFrame;
+ RefPtr<AnimatedGeometryRoot> mParentAGR;
+ bool mIsAsync;
+ bool mIsRetained;
+
+ protected:
+ static void DetachAGR(AnimatedGeometryRoot* aAGR) {
+ aAGR->mFrame = nullptr;
+ aAGR->mParentAGR = nullptr;
+ NS_RELEASE(aAGR);
+ }
+
+ NS_DECLARE_FRAME_PROPERTY_WITH_DTOR(AnimatedGeometryRootCache,
+ AnimatedGeometryRoot, DetachAGR)
+
+ AnimatedGeometryRoot(nsIFrame* aFrame, AnimatedGeometryRoot* aParent,
+ bool aIsAsync, bool aIsRetained)
+ : mFrame(aFrame),
+ mParentAGR(aParent),
+ mIsAsync(aIsAsync),
+ mIsRetained(aIsRetained) {
+ MOZ_ASSERT(mParentAGR || mIsAsync,
+ "The root AGR should always be treated as an async AGR.");
+ if (mIsRetained) {
+ NS_ADDREF(this);
+ aFrame->SetProperty(AnimatedGeometryRootCache(), this);
+ }
+ }
+
+ ~AnimatedGeometryRoot() {
+ if (mFrame && mIsRetained) {
+ mFrame->RemoveProperty(AnimatedGeometryRootCache());
+ }
+ }
+};
+
+namespace mozilla {
+
+/**
+ * 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 nsCString ToString(
+ const mozilla::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.
+ */
+ mozilla::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;
+ }
+
+ mozilla::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<mozilla::layers::ScrollableLayerGuid::ViewID> mViewId;
+
+ uint32_t mDepth;
+ bool mRetained;
+};
+} // namespace mozilla
+
+enum class nsDisplayListBuilderMode : uint8_t {
+ Painting,
+ EventDelivery,
+ PluginGeometry,
+ FrameVisibility,
+ TransformComputation,
+ GenerateGlyph,
+};
+
+class nsDisplayWrapList;
+
+/**
+ * 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 {
+ typedef mozilla::LayoutDeviceIntRect LayoutDeviceIntRect;
+ typedef mozilla::LayoutDeviceIntSize LayoutDeviceIntSize;
+ typedef mozilla::LayoutDeviceIntRegion LayoutDeviceIntRegion;
+ typedef mozilla::LayoutDeviceRect LayoutDeviceRect;
+
+ /**
+ * 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:
+ typedef mozilla::gfx::Matrix4x4 Matrix4x4;
+
+ 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.
+ 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;
+ };
+
+ /**
+ * A frame can be in one of three states of AGR.
+ * AGR_NO means the frame is not an AGR for now.
+ * AGR_YES means the frame is an AGR for now.
+ */
+ enum AGRState { AGR_NO, AGR_YES };
+
+ public:
+ typedef mozilla::FrameLayerBuilder FrameLayerBuilder;
+ typedef mozilla::DisplayItemClip DisplayItemClip;
+ typedef mozilla::DisplayItemClipChain DisplayItemClipChain;
+ typedef mozilla::DisplayItemClipChainHasher DisplayItemClipChainHasher;
+ typedef mozilla::DisplayItemClipChainEqualer DisplayItemClipChainEqualer;
+ typedef mozilla::DisplayListClipState DisplayListClipState;
+ typedef mozilla::ActiveScrolledRoot ActiveScrolledRoot;
+ typedef nsIWidget::ThemeGeometry ThemeGeometry;
+ typedef mozilla::layers::Layer Layer;
+ typedef mozilla::layers::FrameMetrics FrameMetrics;
+ typedef mozilla::layers::ScrollableLayerGuid ScrollableLayerGuid;
+ typedef mozilla::layers::ScrollableLayerGuid::ViewID ViewID;
+ typedef mozilla::gfx::CompositorHitTestInfo CompositorHitTestInfo;
+ typedef mozilla::gfx::Matrix4x4 Matrix4x4;
+ typedef mozilla::Maybe<mozilla::layers::ScrollDirection> MaybeScrollDirection;
+ typedef mozilla::dom::EffectsInfo EffectsInfo;
+ typedef mozilla::layers::LayersId LayersId;
+ typedef mozilla::dom::RemoteBrowser RemoteBrowser;
+
+ /**
+ * @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);
+ }
+
+ void SetWillComputePluginGeometry(bool aWillComputePluginGeometry) {
+ mWillComputePluginGeometry = aWillComputePluginGeometry;
+ }
+
+ void SetForPluginGeometry(bool aForPlugin) {
+ if (aForPlugin) {
+ NS_ASSERTION(mMode == nsDisplayListBuilderMode::Painting,
+ "Can only switch from Painting to PluginGeometry");
+ NS_ASSERTION(mWillComputePluginGeometry,
+ "Should have signalled this in advance");
+ mMode = nsDisplayListBuilderMode::PluginGeometry;
+ } else {
+ NS_ASSERTION(mMode == nsDisplayListBuilderMode::PluginGeometry,
+ "Can only switch from Painting to PluginGeometry");
+ mMode = nsDisplayListBuilderMode::Painting;
+ }
+ }
+
+ mozilla::layers::LayerManager* 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;
+ }
+
+ /**
+ * Be careful with this. The display list will be built in Painting mode
+ * first and then switched to PluginGeometry before a second call to
+ * ComputeVisibility.
+ * @return true if the display list is being built to compute geometry
+ * for plugins.
+ */
+ bool IsForPluginGeometry() const {
+ return mMode == nsDisplayListBuilderMode::PluginGeometry;
+ }
+
+ /**
+ * @return true if the display list is being built for painting.
+ */
+ bool IsForPainting() const {
+ return mMode == nsDisplayListBuilderMode::Painting;
+ }
+
+ /**
+ * @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;
+ }
+
+ bool WillComputePluginGeometry() const { return mWillComputePluginGeometry; }
+
+ /**
+ * @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 mozilla::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; }
+ /**
+ * 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; }
+ MaybeScrollDirection 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; }
+ /**
+ * Calling this setter makes us compute accurate visible regions at the cost
+ * of performance if regions get very complex.
+ */
+ bool GetAccurateVisibleRegions() {
+ return mMode == nsDisplayListBuilderMode::PluginGeometry;
+ }
+ /**
+ * @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;
+ }
+
+ AnimatedGeometryRoot* GetCurrentAnimatedGeometryRoot() { return mCurrentAGR; }
+ AnimatedGeometryRoot* GetRootAnimatedGeometryRoot() { return mRootAGR; }
+
+ void RecomputeCurrentAnimatedGeometryRoot();
+
+ void Check() { mPool.Check(); }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * Sets the current compositor hit test area and info to |aHitTestArea| and
+ * |aHitTestInfo|.
+ * This is used during display list building to determine if the parent frame
+ * hit test info contains the same information that child frame needs.
+ */
+ void SetCompositorHitTestInfo(const nsRect& aHitTestArea,
+ const CompositorHitTestInfo& aHitTestInfo) {
+ mHitTestArea = aHitTestArea;
+ mHitTestInfo = aHitTestInfo;
+ }
+
+ const nsRect& GetHitTestArea() const { return mHitTestArea; }
+ const CompositorHitTestInfo& GetHitTestInfo() const { return mHitTestInfo; }
+
+ /**
+ * Builds a new nsDisplayCompositorHitTestInfo for the frame |aFrame| if
+ * needed, and adds it to the top of |aList|. If |aBuildNew| is true, the
+ * previous hit test info will not be reused.
+ */
+ void BuildCompositorHitTestInfoIfNeeded(nsIFrame* aFrame,
+ nsDisplayList* aList,
+ const bool aBuildNew);
+
+ 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(mozilla::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 InEventsAndPluginsOnly() const { return mInEventsAndPluginsOnly; }
+ /**
+ * 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; }
+
+ bool IsInPageSequence() const { return mInPageSequence; }
+ void SetInPageSequence(bool aInPage) { mInPageSequence = aInPage; }
+
+ /**
+ * 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. Currently only
+ * accounts for mSyncDecodeImages.
+ */
+ uint32_t GetBackgroundPaintFlags();
+
+ /**
+ * 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<ThemeGeometry> GetThemeGeometries() const {
+ nsTArray<ThemeGeometry> geometries;
+
+ for (auto iter = mThemeGeometries.ConstIter(); !iter.Done(); iter.Next()) {
+ geometries.AppendElements(*iter.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 mozilla::LayoutDeviceIntRect& aRect) {
+ if (!mIsPaintingToWindow) {
+ return;
+ }
+
+ nsTArray<ThemeGeometry>* geometries = mThemeGeometries.LookupOrAdd(aItem);
+ geometries->AppendElement(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 nsDataHashtable<nsPtrHashKey<RemoteBrowser>, EffectsInfo>&
+ GetEffectUpdates() const {
+ return mEffectsUpdates;
+ }
+
+ void AddEffectUpdate(RemoteBrowser* aBrowser, EffectsInfo aUpdate) {
+ mEffectsUpdates.Put(aBrowser, 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, mozilla::DisplayListArenaObjectId aId) {
+ return mPool.Allocate(aId, aSize);
+ }
+ void* Allocate(size_t aSize, DisplayItemType aType) {
+ static_assert(size_t(DisplayItemType::TYPE_ZERO) ==
+ size_t(mozilla::DisplayListArenaObjectId::CLIPCHAIN),
+ "");
+#define DECLARE_DISPLAY_ITEM_TYPE(name_, ...) \
+ static_assert(size_t(DisplayItemType::TYPE_##name_) == \
+ size_t(mozilla::DisplayListArenaObjectId::name_), \
+ "");
+#include "nsDisplayItemTypesList.h"
+#undef DECLARE_DISPLAY_ITEM_TYPE
+ return Allocate(aSize, mozilla::DisplayListArenaObjectId(size_t(aType)));
+ }
+
+ void Destroy(mozilla::DisplayListArenaObjectId aId, void* aPtr) {
+ return mPool.Free(aId, aPtr);
+ }
+ void Destroy(DisplayItemType aType, void* aPtr) {
+ return Destroy(mozilla::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);
+
+ /**
+ * Clone the supplied clip chain's chain items into this builder's arena.
+ */
+ const DisplayItemClipChain* CopyWholeChain(
+ const DisplayItemClipChain* aClipChain);
+
+ /**
+ * Returns a new clip chain containing an intersection of all clips of
+ * |aClipChain| up to and including |aASR|.
+ * If there is no clip, returns nullptr.
+ */
+ const DisplayItemClipChain* FuseClipChainUpTo(
+ const DisplayItemClipChain* aClipChain, const ActiveScrolledRoot* aASR);
+
+ const ActiveScrolledRoot* GetFilterASR() const { return mFilterASR; }
+
+ /**
+ * Transfer off main thread animations to the layer. May be called
+ * with aBuilder and aItem both null, but only if the caller has
+ * already checked that off main thread animations should be sent to
+ * the layer. When they are both null, the animations are added to
+ * the layer as pending animations.
+ */
+ static void AddAnimationsAndTransitionsToLayer(Layer* aLayer,
+ nsDisplayListBuilder* aBuilder,
+ nsDisplayItem* aItem,
+ nsIFrame* aFrame,
+ DisplayItemType aType);
+
+ /**
+ * 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<nsDisplayWrapList*>& 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)
+ : AutoBuildingDisplayList(
+ aBuilder, aForChild, aBuilder->GetVisibleRect(),
+ aBuilder->GetDirtyRect(), aForChild->IsTransformed()) {}
+
+ 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 = mozilla::Some(aOffset);
+
+ mBuilder->mCurrentOffsetToReferenceFrame += aOffset;
+ mBuilder->mAdditionalOffsetFrame = mBuilder->mCurrentReferenceFrame;
+ }
+
+ bool IsAnimatedGeometryRoot() const { return mCurrentAGRState == AGR_YES; }
+
+ void RestoreBuildingInvisibleItemsValue() {
+ mBuilder->mBuildingInvisibleItems = mPrevBuildingInvisibleItems;
+ }
+
+ ~AutoBuildingDisplayList() {
+ mBuilder->mCurrentFrame = mPrevFrame;
+ mBuilder->mCurrentReferenceFrame = mPrevReferenceFrame;
+ mBuilder->mHitTestArea = mPrevHitTestArea;
+ mBuilder->mHitTestInfo = mPrevHitTestInfo;
+ mBuilder->mCurrentOffsetToReferenceFrame = mPrevOffset;
+ mBuilder->mVisibleRect = mPrevVisibleRect;
+ mBuilder->mDirtyRect = mPrevDirtyRect;
+ mBuilder->mCurrentAGR = mPrevAGR;
+ mBuilder->mAncestorHasApzAwareEventHandler =
+ mPrevAncestorHasApzAwareEventHandler;
+ mBuilder->mBuildingInvisibleItems = mPrevBuildingInvisibleItems;
+ mBuilder->mInInvalidSubtree = mPrevInInvalidSubtree;
+ mBuilder->mAdditionalOffset = mPrevAdditionalOffset;
+ }
+
+ private:
+ nsDisplayListBuilder* mBuilder;
+ AGRState mCurrentAGRState;
+ const nsIFrame* mPrevFrame;
+ const nsIFrame* mPrevReferenceFrame;
+ nsRect mPrevHitTestArea;
+ CompositorHitTestInfo mPrevHitTestInfo;
+ nsPoint mPrevOffset;
+ mozilla::Maybe<nsPoint> mPrevAdditionalOffset;
+ nsRect mPrevVisibleRect;
+ nsRect mPrevDirtyRect;
+ RefPtr<AnimatedGeometryRoot> mPrevAGR;
+ 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 AutoInEventsAndPluginsOnly {
+ public:
+ AutoInEventsAndPluginsOnly(nsDisplayListBuilder* aBuilder,
+ bool aInEventsAndPluginsOnly)
+ : mBuilder(aBuilder), mOldValue(aBuilder->mInEventsAndPluginsOnly) {
+ aBuilder->mInEventsAndPluginsOnly |= aInEventsAndPluginsOnly;
+ }
+
+ ~AutoInEventsAndPluginsOnly() {
+ mBuilder->mInEventsAndPluginsOnly = 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;
+ };
+
+ /**
+ * A helper class to temporarily set the value of mCurrentScrollParentId.
+ */
+ class AutoCurrentScrollParentIdSetter {
+ public:
+ AutoCurrentScrollParentIdSetter(nsDisplayListBuilder* aBuilder,
+ ViewID aScrollId)
+ : mBuilder(aBuilder),
+ mOldValue(aBuilder->mCurrentScrollParentId),
+ mOldForceLayer(aBuilder->mForceLayerForScrollParent) {
+ // If this AutoCurrentScrollParentIdSetter has the same scrollId 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 = (mOldValue != aScrollId);
+ aBuilder->mCurrentScrollParentId = aScrollId;
+ aBuilder->mForceLayerForScrollParent = false;
+ }
+
+ bool ShouldForceLayerForScrollParent() const {
+ // Only scrollframes participating in scroll handoff can be forced to
+ // layerize
+ return mCanBeScrollParent && mBuilder->mForceLayerForScrollParent;
+ }
+
+ ~AutoCurrentScrollParentIdSetter() {
+ mBuilder->mCurrentScrollParentId = mOldValue;
+ 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;
+ }
+ }
+
+ private:
+ nsDisplayListBuilder* mBuilder;
+ ViewID mOldValue;
+ bool mOldForceLayer;
+ bool mCanBeScrollParent;
+ };
+
+ /**
+ * 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) {}
+
+ ~AutoCurrentActiveScrolledRootSetter() {
+ mBuilder->mCurrentActiveScrolledRoot = mSavedActiveScrolledRoot;
+ }
+
+ 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;
+ };
+
+ /**
+ * 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)
+ : mBuilder(aBuilder),
+ mSavedContainerASR(aBuilder->mCurrentContainerASR) {
+ mBuilder->mCurrentContainerASR = ActiveScrolledRoot::PickDescendant(
+ mBuilder->ClipState().GetContentClipASR(),
+ mBuilder->mCurrentActiveScrolledRoot);
+ }
+
+ 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 MaybeScrollDirection& 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 = 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:
+ typedef mozilla::gfx::Matrix4x4 Matrix4x4;
+
+ explicit AutoAccumulateTransform(nsDisplayListBuilder* aBuilder)
+ : mBuilder(aBuilder),
+ mSavedTransform(aBuilder->mPreserves3DCtx.mAccumulatedTransform) {}
+
+ ~AutoAccumulateTransform() {
+ mBuilder->mPreserves3DCtx.mAccumulatedTransform = mSavedTransform;
+ }
+
+ void Accumulate(const Matrix4x4& aTransform) {
+ mBuilder->mPreserves3DCtx.mAccumulatedTransform =
+ aTransform * mBuilder->mPreserves3DCtx.mAccumulatedTransform;
+ }
+
+ const Matrix4x4& GetCurrentTransform() {
+ return mBuilder->mPreserves3DCtx.mAccumulatedTransform;
+ }
+
+ void StartRoot() {
+ mBuilder->mPreserves3DCtx.mAccumulatedTransform = Matrix4x4();
+ }
+
+ private:
+ nsDisplayListBuilder* mBuilder;
+ 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 nsRect& aVisibleRect, const nsRect& aDirtyRect)
+ : mContainingBlockClipChain(aContainingBlockClipChain),
+ mCombinedClipChain(aCombinedClipChain),
+ mContainingBlockActiveScrolledRoot(
+ aContainingBlockActiveScrolledRoot),
+ mVisibleRect(aVisibleRect),
+ mDirtyRect(aDirtyRect) {}
+ 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;
+
+ 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 {
+ RefPtr<AnimatedGeometryRoot> 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);
+
+ void SetContainsPluginItem() { mContainsPluginItem = true; }
+ bool ContainsPluginItem() { return mContainsPluginItem; }
+
+ /**
+ * 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; }
+
+ /**
+ * mContainsBackdropFilter is true if we proccessed a display item that
+ * has a backdrop filter set. We track this so we can insert a
+ * nsDisplayBackdropRootContainer in the stacking context of the nearest
+ * ancestor that forms a backdrop root.
+ */
+ void SetContainsBackdropFilter(bool aContainsBackdropFilter) {
+ mContainsBackdropFilter = aContainsBackdropFilter;
+ }
+ bool ContainsBackdropFilter() const { return mContainsBackdropFilter; }
+
+ 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;
+ }
+
+ /**
+ * This is a convenience function to ease the transition until AGRs and ASRs
+ * are unified.
+ */
+ AnimatedGeometryRoot* AnimatedGeometryRootForASR(
+ const ActiveScrolledRoot* aASR);
+
+ bool HitTestIsForVisibility() const { return mVisibleThreshold.isSome(); }
+
+ float VisibilityThreshold() const {
+ MOZ_DIAGNOSTIC_ASSERT(HitTestIsForVisibility());
+ return mVisibleThreshold.valueOr(1.0f);
+ }
+
+ void SetHitTestIsForVisibility(float aVisibleThreshold) {
+ mVisibleThreshold = mozilla::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) {}
+
+ mozilla::UniquePtr<WeakFrame> mWeakFrame;
+ void* mFrame;
+ };
+
+ nsTHashtable<nsPtrHashKey<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.PutEntry(aFrame);
+ mFrames.AppendElement(WeakFrameWrapper(aFrame));
+ mRects.AppendElement(nsRegion::RectToBox(aRect));
+ }
+
+ void Clear() {
+ mFrameSet.Clear();
+ mFrames.Clear();
+ mRects.Clear();
+ }
+
+ void RemoveModifiedFramesAndRects();
+
+ size_t SizeOfExcludingThis(mozilla::MallocSizeOf) const;
+
+ typedef mozilla::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();
+
+ private:
+ bool MarkOutOfFlowFrameForDisplay(nsIFrame* aDirtyFrame, nsIFrame* aFrame,
+ const nsRect& aVisibleRect,
+ const nsRect& aDirtyRect);
+
+ /**
+ * Returns whether a frame acts as an animated geometry root, optionally
+ * returning the next ancestor to check.
+ */
+ AGRState IsAnimatedGeometryRoot(nsIFrame* aFrame, bool& aIsAsync,
+ nsIFrame** aParent = nullptr);
+
+ /**
+ * 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, bool& aIsAsync);
+
+ friend class nsDisplayCanvasBackgroundImage;
+ friend class nsDisplayBackgroundImage;
+ friend class nsDisplayFixedPosition;
+ friend class nsDisplayPerspective;
+ AnimatedGeometryRoot* FindAnimatedGeometryRootFor(nsDisplayItem* aItem);
+
+ friend class nsDisplayItem;
+ friend class nsDisplayOwnLayer;
+ friend struct RetainedDisplayListBuilder;
+ friend struct HitTestInfo;
+ AnimatedGeometryRoot* FindAnimatedGeometryRootFor(nsIFrame* aFrame);
+
+ AnimatedGeometryRoot* WrapAGRForFrame(
+ nsIFrame* aAnimatedGeometryRoot, bool aIsAsync,
+ AnimatedGeometryRoot* aParent = nullptr);
+
+ nsDataHashtable<nsPtrHashKey<nsIFrame>, RefPtr<AnimatedGeometryRoot>>
+ mFrameToAnimatedGeometryRootMap;
+
+ /**
+ * Add the current frame to the AGR budget if possible and remember
+ * the outcome. Subsequent calls will return the same value as
+ * returned here.
+ */
+ bool AddToAGRBudget(nsIFrame* aFrame);
+
+ struct PresShellState {
+ mozilla::PresShell* mPresShell;
+#ifdef DEBUG
+ mozilla::Maybe<nsAutoLayoutPhase> mAutoLayoutPhase;
+#endif
+ mozilla::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;
+ };
+
+ nsIFrame* const mReferenceFrame;
+ nsIFrame* mIgnoreScrollFrame;
+
+ using Arena = nsPresArena<32768, mozilla::DisplayListArenaObjectId,
+ size_t(mozilla::DisplayListArenaObjectId::COUNT)>;
+ Arena mPool;
+
+ AutoTArray<PresShellState, 8> mPresShellStates;
+ AutoTArray<nsIFrame*, 400> mFramesMarkedForDisplay;
+ AutoTArray<nsIFrame*, 40> mFramesMarkedForDisplayIfVisible;
+ AutoTArray<nsIFrame*, 20> mFramesWithOOFData;
+ nsClassHashtable<nsPtrHashKey<nsDisplayItem>, nsTArray<ThemeGeometry>>
+ mThemeGeometries;
+ DisplayListClipState mClipState;
+ 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 offset from mCurrentFrame to mCurrentReferenceFrame.
+ nsPoint mCurrentOffsetToReferenceFrame;
+
+ const nsIFrame* mAdditionalOffsetFrame;
+ mozilla::Maybe<nsPoint> mAdditionalOffset;
+
+ RefPtr<AnimatedGeometryRoot> mRootAGR;
+ RefPtr<AnimatedGeometryRoot> mCurrentAGR;
+
+ // will-change budget tracker
+ typedef uint32_t DocumentWillChangeBudget;
+ nsDataHashtable<nsPtrHashKey<const nsPresContext>, DocumentWillChangeBudget>
+ mDocumentWillChangeBudgets;
+
+ // Any frame listed in this set is already counted in the budget
+ // and thus is in-budget.
+ nsDataHashtable<nsPtrHashKey<const nsIFrame>, FrameWillChangeBudget>
+ mFrameWillChangeBudgets;
+
+ uint8_t mBuildingExtraPagesForPageNum;
+
+ // Area of animated geometry root budget already allocated
+ uint32_t mUsedAGRBudget;
+ // Set of frames already counted in budget
+ nsTHashtable<nsPtrHashKey<nsIFrame>> mAGRBudgetSet;
+
+ nsDataHashtable<nsPtrHashKey<RemoteBrowser>, EffectsInfo> mEffectsUpdates;
+
+ // Relative to mCurrentFrame.
+ nsRect mVisibleRect;
+ nsRect mDirtyRect;
+
+ // Tracked regions used for retained display list.
+ WeakFrameRegion mWindowExcludeGlassRegion;
+ WeakFrameRegion mRetainedWindowDraggingRegion;
+ WeakFrameRegion mRetainedWindowNoDraggingRegion;
+
+ // Window opaque region is calculated during layer building.
+ WeakFrameRegion mRetainedWindowOpaqueRegion;
+
+ // Optimized versions for non-retained display list.
+ LayoutDeviceIntRegion mWindowDraggingRegion;
+ LayoutDeviceIntRegion mWindowNoDraggingRegion;
+ nsRegion mWindowOpaqueRegion;
+
+ // 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;
+ // If we've encountered a glass item yet, only used during partial display
+ // list builds.
+ bool mHasGlassItemDuringPartial;
+
+ nsIFrame* mCaretFrame;
+ nsRect mCaretRect;
+
+ // 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;
+ std::unordered_set<const DisplayItemClipChain*, DisplayItemClipChainHasher,
+ DisplayItemClipChainEqualer>
+ mClipDeduplicator;
+ DisplayItemClipChain* mFirstClipChainToDestroy;
+ nsTArray<nsDisplayItem*> mTemporaryItems;
+ nsDisplayListBuilderMode mMode;
+ nsDisplayTableBackgroundSet* mTableBackgroundSet;
+ ViewID mCurrentScrollParentId;
+ ViewID mCurrentScrollbarTarget;
+ MaybeScrollDirection mCurrentScrollbarDirection;
+ Preserves3DContext mPreserves3DCtx;
+ nsTArray<nsIFrame*> mSVGEffectsFrames;
+ // When we are inside a filter, the current ASR at the time we entered the
+ // filter. Otherwise nullptr.
+ const ActiveScrolledRoot* mFilterASR;
+ std::unordered_set<nsIScrollableFrame*> mScrollFramesToNotify;
+ bool mContainsBlendMode;
+ bool mIsBuildingScrollbar;
+ bool mCurrentScrollbarWillHaveLayer;
+ bool mBuildCaret;
+ bool mRetainingDisplayList;
+ bool mPartialUpdate;
+ bool mIgnoreSuppression;
+ bool mIncludeAllOutOfFlows;
+ bool mDescendIntoSubdocuments;
+ bool mSelectedFramesOnly;
+ bool mAllowMergingAndFlattening;
+ bool mWillComputePluginGeometry;
+ // True when we're building a display list that's directly or indirectly
+ // under an nsDisplayTransform
+ bool mInTransform;
+ bool mInEventsAndPluginsOnly;
+ bool mInFilter;
+ bool mInPageSequence;
+ bool mIsInChromePresContext;
+ bool mSyncDecodeImages;
+ bool mIsPaintingToWindow;
+ bool mUseHighQualityScaling;
+ bool mIsPaintingForWebRender;
+ bool mIsCompositingCheap;
+ bool mContainsPluginItem;
+ 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 mAsyncPanZoomEnabled;
+ bool mBuildingInvisibleItems;
+ bool mIsBuilding;
+ bool mInInvalidSubtree;
+ bool mBuildCompositorHitTestInfo;
+ bool mDisablePartialUpdates;
+ bool mPartialBuildFailed;
+ bool mIsInActiveDocShell;
+ bool mBuildAsyncZoomContainer;
+ bool mContainsBackdropFilter;
+ bool mIsRelativeToLayoutViewport;
+ bool mUseOverlayScrollbars;
+
+ mozilla::Maybe<float> mVisibleThreshold;
+ nsRect mHitTestArea;
+ CompositorHitTestInfo mHitTestInfo;
+};
+
+class nsDisplayItem;
+class nsDisplayItemBase;
+class nsPaintedDisplayItem;
+class nsDisplayList;
+class RetainedDisplayList;
+
+// 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* ::MakeDisplayItemWithIndex(nsDisplayListBuilder* aBuilder, \
+ F* aFrame, const uint16_t aIndex, \
+ Args&&... aArgs); \
+ \
+ public:
+
+#define NS_DISPLAY_ALLOW_CLONING() \
+ template <typename T> \
+ friend T* 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 ShouldBuildItemForEventsOrPlugins(const DisplayItemType aType);
+
+void UpdateDisplayItemData(nsPaintedDisplayItem* aItem);
+
+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->InEventsAndPluginsOnly() &&
+ !ShouldBuildItemForEventsOrPlugins(type)) {
+ // This item is not needed for events or plugins.
+ 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) {
+ UpdateDisplayItemData(paintedItem);
+ }
+
+ 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()));
+ }
+
+ mozilla::DebugOnly<bool> isContainerType =
+ (GetDisplayItemFlagsForType(type) & TYPE_IS_CONTAINER);
+
+ MOZ_ASSERT(item->HasChildren() == isContainerType,
+ "Container items must have container display item flag set.");
+#endif
+
+ 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)...);
+}
+
+/**
+ * nsDisplayItems are put in singly-linked lists rooted in an nsDisplayList.
+ * nsDisplayItemLink holds the link. The lists are linked from lowest to
+ * highest in z-order.
+ */
+class nsDisplayItemLink {
+ // This is never instantiated directly, so no need to count constructors and
+ // destructors.
+ protected:
+ nsDisplayItemLink() : mAbove(nullptr) {}
+ nsDisplayItemLink(const nsDisplayItemLink&) : mAbove(nullptr) {}
+ ~nsDisplayItemLink() { MOZ_RELEASE_ASSERT(!mAbove); }
+ nsDisplayItem* mAbove;
+
+ friend class nsDisplayList;
+};
+
+class nsPaintedDisplayItem;
+
+/*
+ * 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 nsDisplayItemBase : public nsDisplayItemLink {
+ public:
+ nsDisplayItemBase() = delete;
+
+ /**
+ * 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 nsDisplayItem* Clone(nsDisplayListBuilder* aBuilder) const {
+ return nullptr;
+ }
+
+ /**
+ * 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();
+ this->~nsDisplayItemBase();
+ 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 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) {
+ MOZ_ASSERT(!mFrame->HasDisplayItem(this));
+ 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(); }
+
+ /**
+ * 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(ItemBaseFlag::ReusedItem);
+ }
+
+ void SetReused(bool aReused) {
+ if (aReused) {
+ mItemFlags += ItemBaseFlag::ReusedItem;
+ } else {
+ mItemFlags -= ItemBaseFlag::ReusedItem;
+ }
+ }
+
+ /**
+ * Returns true if this item can be reused during display list merging.
+ */
+ bool CanBeReused() const {
+ return !mItemFlags.contains(ItemBaseFlag::CantBeReused);
+ }
+
+ void SetCantBeReused() { mItemFlags += ItemBaseFlag::CantBeReused; }
+
+ bool CanBeCached() const {
+ return !mItemFlags.contains(ItemBaseFlag::CantBeCached);
+ }
+
+ void SetCantBeCached() { mItemFlags += ItemBaseFlag::CantBeCached; }
+
+ bool IsOldItem() const { return !!mOldList; }
+
+ /**
+ * Returns true if the frame of this display item is in a modified subtree.
+ */
+ bool HasModifiedFrame() const;
+ void SetModifiedFrame(bool 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;
+ }
+
+ protected:
+ nsDisplayItemBase(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame)
+ : mFrame(aFrame), mType(DisplayItemType::TYPE_ZERO) {
+ MOZ_COUNT_CTOR(nsDisplayItemBase);
+ MOZ_ASSERT(mFrame);
+
+ if (aBuilder->IsRetainingDisplayList()) {
+ mFrame->AddDisplayItem(this);
+ }
+ }
+
+ nsDisplayItemBase(nsDisplayListBuilder* aBuilder,
+ const nsDisplayItemBase& aOther)
+ : mFrame(aOther.mFrame),
+ mItemFlags(aOther.mItemFlags),
+ mType(aOther.mType),
+ mExtraPageForPageNum(aOther.mExtraPageForPageNum),
+ mPerFrameIndex(aOther.mPerFrameIndex) {
+ MOZ_COUNT_CTOR(nsDisplayItemBase);
+ }
+
+ virtual ~nsDisplayItemBase() {
+ MOZ_COUNT_DTOR(nsDisplayItemBase);
+ 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();
+
+ nsIFrame* mFrame; // 8
+
+ private:
+ enum class ItemBaseFlag : uint8_t {
+ CantBeReused,
+ CantBeCached,
+ DeletedFrame,
+ ModifiedFrame,
+ ReusedItem,
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ MergedItem,
+ PreProcessedItem,
+#endif
+ };
+
+ mozilla::EnumSet<ItemBaseFlag, uint8_t> mItemFlags; // 1
+ DisplayItemType mType; // 1
+ uint8_t mExtraPageForPageNum = 0; // 1
+ uint16_t mPerFrameIndex; // 2
+ OldListIndex mOldListIndex; // 4
+ uintptr_t mOldList = 0; // 8
+
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ public:
+ bool IsMergedItem() const {
+ return mItemFlags.contains(ItemBaseFlag::MergedItem);
+ }
+
+ bool IsPreProcessedItem() const {
+ return mItemFlags.contains(ItemBaseFlag::PreProcessedItem);
+ }
+
+ void SetMergedPreProcessed(bool aMerged, bool aPreProcessed) {
+ if (aMerged) {
+ mItemFlags += ItemBaseFlag::MergedItem;
+ } else {
+ mItemFlags -= ItemBaseFlag::MergedItem;
+ }
+
+ if (aPreProcessed) {
+ mItemFlags += ItemBaseFlag::PreProcessedItem;
+ } else {
+ mItemFlags -= ItemBaseFlag::PreProcessedItem;
+ }
+ }
+
+ uint32_t mOldListKey = 0;
+ uint32_t mOldNestingDepth = 0;
+#endif
+};
+
+/**
+ * This is the unit of rendering and event testing. Each instance of this
+ * class represents an entity that can be drawn on the screen, e.g., a
+ * frame's CSS background, or a frame's text string.
+ */
+class nsDisplayItem : public nsDisplayItemBase {
+ public:
+ typedef mozilla::ContainerLayerParameters ContainerLayerParameters;
+ typedef mozilla::DisplayItemClip DisplayItemClip;
+ typedef mozilla::DisplayItemClipChain DisplayItemClipChain;
+ typedef mozilla::ActiveScrolledRoot ActiveScrolledRoot;
+ typedef mozilla::layers::FrameMetrics FrameMetrics;
+ typedef mozilla::layers::ScrollMetadata ScrollMetadata;
+ typedef mozilla::layers::ScrollableLayerGuid::ViewID ViewID;
+ typedef mozilla::layers::Layer Layer;
+ typedef mozilla::layers::LayerManager LayerManager;
+ typedef mozilla::layers::StackingContextHelper StackingContextHelper;
+ typedef mozilla::layers::WebRenderCommand WebRenderCommand;
+ typedef mozilla::layers::WebRenderParentCommand WebRenderParentCommand;
+ typedef mozilla::LayerState LayerState;
+ typedef mozilla::image::imgDrawingParams imgDrawingParams;
+ typedef mozilla::image::ImgDrawResult ImgDrawResult;
+ typedef class mozilla::gfx::DrawTarget DrawTarget;
+ typedef mozilla::gfx::CompositorHitTestInfo CompositorHitTestInfo;
+
+ 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);
+
+ MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayItem)
+
+ /**
+ * 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)
+ : nsDisplayItemBase(aBuilder, aOther),
+ mClipChain(aOther.mClipChain),
+ mClip(aOther.mClip),
+ mActiveScrolledRoot(aOther.mActiveScrolledRoot),
+ mReferenceFrame(aOther.mReferenceFrame),
+ mAnimatedGeometryRoot(aOther.mAnimatedGeometryRoot),
+ mToReferenceFrame(aOther.mToReferenceFrame),
+ mBuildingRect(aOther.mBuildingRect),
+ mPaintRect(aOther.mPaintRect) {
+ MOZ_COUNT_CTOR(nsDisplayItem);
+ // TODO: It might be better to remove the flags that aren't copied.
+ if (aOther.ForceNotVisible()) {
+ mItemFlags += ItemFlag::ForceNotVisible;
+ }
+ if (aOther.IsSubpixelAADisabled()) {
+ mItemFlags += ItemFlag::DisableSubpixelAA;
+ }
+ if (mFrame->In3DContextAndBackfaceIsHidden()) {
+ mItemFlags += ItemFlag::BackfaceHidden;
+ }
+ if (aOther.Combines3DTransformWithAncestors()) {
+ mItemFlags += ItemFlag::Combines3DTransformWithAncestors;
+ }
+ }
+
+ public:
+ nsDisplayItem() = delete;
+ nsDisplayItem(const nsDisplayItem&) = delete;
+
+ /**
+ * Roll back side effects carried out by processing the display list.
+ *
+ * @return true if the rollback actually modified anything, to help the caller
+ * decide whether to invalidate cached information about this node.
+ */
+ virtual bool RestoreState() {
+ if (mClipChain == mState.mClipChain && mClip == mState.mClip &&
+ !mItemFlags.contains(ItemFlag::DisableSubpixelAA)) {
+ return false;
+ }
+
+ mClipChain = mState.mClipChain;
+ mClip = mState.mClip;
+ mItemFlags -= ItemFlag::DisableSubpixelAA;
+ return true;
+ }
+
+ /**
+ * 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 mozilla::Maybe<nscolor> IsUniform(
+ nsDisplayListBuilder* aBuilder) const {
+ return mozilla::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();
+
+ /**
+ * @return LAYER_NONE if BuildLayer will return null. In this case
+ * there is no layer for the item, and Paint should be called instead
+ * to paint the content using Thebes.
+ * Return LAYER_INACTIVE if there is a layer --- BuildLayer will
+ * not return null (unless there's an error) --- but the layer contents
+ * are not changing frequently. In this case it makes sense to composite
+ * the layer into a PaintedLayer with other content, so we don't have to
+ * recomposite it every time we paint.
+ * Note: GetLayerState is only allowed to return LAYER_INACTIVE if all
+ * descendant display items returned LAYER_INACTIVE or LAYER_NONE. Also,
+ * all descendant display item frames must have an active scrolled root
+ * that's either the same as this item's frame's active scrolled root, or
+ * a descendant of this item's frame. This ensures that the entire
+ * set of display items can be collapsed onto a single PaintedLayer.
+ * Return LAYER_ACTIVE if the layer is active, that is, its contents are
+ * changing frequently. In this case it makes sense to keep the layer
+ * as a separate buffer in VRAM and composite it into the destination
+ * every time we paint.
+ *
+ * Users of GetLayerState should check ForceActiveLayers() and if it returns
+ * true, change a returned value of LAYER_INACTIVE to LAYER_ACTIVE.
+ */
+ virtual LayerState GetLayerState(
+ nsDisplayListBuilder* aBuilder, LayerManager* aManager,
+ const ContainerLayerParameters& aParameters) {
+ return mozilla::LayerState::LAYER_NONE;
+ }
+
+#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(
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ mozilla::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(
+ mozilla::layers::WebRenderScrollData* aData,
+ mozilla::layers::WebRenderLayerScrollData* aLayerData) {
+ return false;
+ }
+
+ /**
+ * On entry, aVisibleRegion contains the region (relative to ReferenceFrame())
+ * which may be visible. If the display item opaquely covers an area, it
+ * can remove that area from aVisibleRegion before returning.
+ * nsDisplayList::ComputeVisibility automatically subtracts the region
+ * returned by GetOpaqueRegion, and automatically removes items whose bounds
+ * do not intersect the visible area, so implementations of
+ * nsDisplayItem::ComputeVisibility do not need to do these things.
+ * nsDisplayList::ComputeVisibility will already have set mVisibleRect on
+ * this item to the intersection of *aVisibleRegion and this item's bounds.
+ * We rely on that, so this should only be called by
+ * nsDisplayList::ComputeVisibility or nsDisplayItem::RecomputeVisibility.
+ * aAllowVisibleRegionExpansion is a rect where we are allowed to
+ * expand the visible region and is only used for making sure the
+ * background behind a plugin is visible.
+ * This method needs to be idempotent.
+ *
+ * @return true if the item is visible, false if no part of the item
+ * is visible.
+ */
+ virtual bool ComputeVisibility(nsDisplayListBuilder* aBuilder,
+ nsRegion* aVisibleRegion);
+
+ /**
+ * 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) {
+ if (aBuildingRect == mBuildingRect) {
+ // Avoid unnecessary paint rect recompution when the
+ // building rect is staying the same.
+ return;
+ }
+ mPaintRect = mBuildingRect = aBuildingRect;
+ mItemFlags -= ItemFlag::PaintRectValid;
+ }
+
+ void SetPaintRect(const nsRect& aPaintRect) {
+ mPaintRect = aPaintRect;
+ mItemFlags += ItemFlag::PaintRectValid;
+ }
+ bool HasPaintRect() const {
+ return mItemFlags.contains(ItemFlag::PaintRectValid);
+ }
+
+ /**
+ * 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) {}
+
+ nsDisplayItem* GetAbove() { return mAbove; }
+
+ /**
+ * Like ComputeVisibility, but does the work that nsDisplayList
+ * does per-item:
+ * -- Intersects GetBounds with aVisibleRegion and puts the result
+ * in mVisibleRect
+ * -- Subtracts bounds from aVisibleRegion if the item is opaque
+ */
+ bool RecomputeVisibility(nsDisplayListBuilder* aBuilder,
+ nsRegion* aVisibleRegion);
+
+ /**
+ * Returns the result of aBuilder->ToReferenceFrame(GetUnderlyingFrame())
+ */
+ const nsPoint& ToReferenceFrame() const {
+ NS_ASSERTION(mFrame, "No frame?");
+ return mToReferenceFrame;
+ }
+ /**
+ * @return the root of the display list's frame (sub)tree, whose origin
+ * establishes the coordinate system for the display list
+ */
+ const nsIFrame* ReferenceFrame() const { return mReferenceFrame; }
+
+ /**
+ * Returns the reference frame for display item children of this item.
+ */
+ virtual const nsIFrame* ReferenceFrameForChildren() const {
+ return mReferenceFrame;
+ }
+
+ AnimatedGeometryRoot* GetAnimatedGeometryRoot() const {
+ MOZ_ASSERT(mAnimatedGeometryRoot,
+ "Must have cached AGR before accessing it!");
+ return mAnimatedGeometryRoot;
+ }
+
+ virtual struct AnimatedGeometryRoot* AnimatedGeometryRootForScrollMetadata()
+ const {
+ return GetAnimatedGeometryRoot();
+ }
+
+ /**
+ * 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();
+ }
+
+ /**
+ * Disable usage of component alpha. Currently only relevant for items that
+ * have text.
+ */
+ void DisableComponentAlpha() { mItemFlags += ItemFlag::DisableSubpixelAA; }
+
+ bool IsSubpixelAADisabled() const {
+ return mItemFlags.contains(ItemFlag::DisableSubpixelAA);
+ }
+
+ /**
+ * 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; }
+
+ const DisplayItemClip& GetClip() const {
+ return mClip ? *mClip : DisplayItemClip::NoClip();
+ }
+ 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; }
+
+ /**
+ * Intersect all clips in our clip chain up to (and including) aASR and set
+ * set the intersection as this item's clip.
+ */
+ void FuseClipChainUpTo(nsDisplayListBuilder* aBuilder,
+ const ActiveScrolledRoot* aASR);
+
+ 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 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 mozilla::Maybe<nsRect> GetClipWithRespectToASR(
+ nsDisplayListBuilder* aBuilder, const ActiveScrolledRoot* aASR) const;
+
+ const nsRect& GetPaintRect() const { return mPaintRect; }
+
+ virtual const nsRect& GetUntransformedPaintRect() const {
+ return GetPaintRect();
+ }
+
+ virtual bool HasHitTestInfo() const { return false; }
+
+#ifdef DEBUG
+ virtual bool IsHitTestItem() const { return false; }
+#endif
+
+ protected:
+ typedef bool (*PrefFunc)(void);
+ bool ShouldUseAdvancedLayer(LayerManager* aManager, PrefFunc aFunc) const;
+ bool CanUseAdvancedLayer(LayerManager* aManager) const;
+
+ RefPtr<const DisplayItemClipChain> mClipChain;
+ const DisplayItemClip* mClip;
+ RefPtr<const ActiveScrolledRoot> mActiveScrolledRoot;
+ // Result of FindReferenceFrameFor(mFrame), if mFrame is non-null
+ const nsIFrame* mReferenceFrame;
+ RefPtr<struct AnimatedGeometryRoot> mAnimatedGeometryRoot;
+
+ struct {
+ RefPtr<const DisplayItemClipChain> mClipChain;
+ const DisplayItemClip* mClip;
+ } mState;
+
+ // Result of ToReferenceFrame(mFrame), if mFrame is non-null
+ nsPoint mToReferenceFrame;
+
+ private:
+ // This is the rectangle that nsDisplayListBuilder was using as the visible
+ // rect to decide which items to construct.
+ nsRect mBuildingRect;
+
+ // nsDisplayList::ComputeVisibility sets this to the visible region
+ // of the item by intersecting the visible region with the bounds
+ // of the item. Paint implementations can use this to limit their drawing.
+ // Guaranteed to be contained in GetBounds().
+ nsRect mPaintRect;
+
+ enum class ItemFlag : uint8_t {
+ BackfaceHidden,
+ Combines3DTransformWithAncestors,
+ DisableSubpixelAA,
+ ForceNotVisible,
+ PaintRectValid,
+ IsGlassItem,
+#ifdef MOZ_DUMP_PAINTING
+ // True if this frame has been painted.
+ Painted,
+#endif
+ };
+
+ mozilla::EnumSet<ItemFlag, uint8_t> mItemFlags;
+};
+
+class nsPaintedDisplayItem : public nsDisplayItem {
+ public:
+ nsPaintedDisplayItem* AsPaintedDisplayItem() final { return this; }
+ const nsPaintedDisplayItem* AsPaintedDisplayItem() const final {
+ return this;
+ }
+
+ ~nsPaintedDisplayItem() override { SetDisplayItemData(nullptr, nullptr); }
+
+ void SetDisplayItemData(mozilla::DisplayItemData* aDID,
+ mozilla::layers::LayerManager* aLayerManager) {
+ if (mDisplayItemData) {
+ MOZ_ASSERT(!mDisplayItemData->GetItem() ||
+ mDisplayItemData->GetItem() == this);
+ mDisplayItemData->SetItem(nullptr);
+ }
+ if (aDID) {
+ if (aDID->GetItem()) {
+ aDID->GetItem()->SetDisplayItemData(nullptr, nullptr);
+ }
+ aDID->SetItem(this);
+ }
+ mDisplayItemData = aDID;
+ mDisplayItemDataLayerManager = aLayerManager;
+ }
+
+ mozilla::DisplayItemData* GetDisplayItemData() { return mDisplayItemData; }
+ mozilla::layers::LayerManager* GetDisplayItemDataLayerManager() {
+ return mDisplayItemDataLayerManager;
+ }
+
+ /**
+ * Stores the given opacity value to be applied when drawing. It is an error
+ * to call this if CanApplyOpacity returned false.
+ */
+ virtual void ApplyOpacity(nsDisplayListBuilder* aBuilder, float aOpacity,
+ const DisplayItemClipChain* aClip) {
+ MOZ_ASSERT(CanApplyOpacity(), "ApplyOpacity is not supported on this type");
+ }
+
+ /**
+ * Get the layer drawn by this display item. Call this only if
+ * GetLayerState() returns something other than LAYER_NONE.
+ * If GetLayerState returned LAYER_NONE then Paint will be called
+ * instead.
+ * This is called while aManager is in the construction phase.
+ *
+ * The caller (nsDisplayList) is responsible for setting the visible
+ * region of the layer.
+ *
+ * @param aContainerParameters should be passed to
+ * FrameLayerBuilder::BuildContainerLayerFor if a ContainerLayer is
+ * constructed.
+ */
+ virtual already_AddRefed<Layer> BuildLayer(
+ nsDisplayListBuilder* aBuilder, LayerManager* aManager,
+ const ContainerLayerParameters& aContainerParameters) {
+ return nullptr;
+ }
+
+ /**
+ * Returns true if this display item would return true from ApplyOpacity
+ * without actually applying the opacity. Otherwise returns false.
+ */
+ virtual bool CanApplyOpacity() 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) {
+ // TODO(miko): Make this a pure virtual function to force implementation.
+ MOZ_ASSERT_UNREACHABLE("Paint() is not implemented!");
+ }
+
+ /**
+ * 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.
+ */
+ mozilla::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 = mozilla::Nothing();
+ }
+
+ protected:
+ nsPaintedDisplayItem(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame)
+ : nsDisplayItem(aBuilder, aFrame) {}
+
+ nsPaintedDisplayItem(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ const ActiveScrolledRoot* aActiveScrolledRoot)
+ : nsDisplayItem(aBuilder, aFrame, aActiveScrolledRoot) {}
+
+ nsPaintedDisplayItem(nsDisplayListBuilder* aBuilder,
+ const nsPaintedDisplayItem& aOther)
+ : nsDisplayItem(aBuilder, aOther) {}
+
+ private:
+ mozilla::DisplayItemData* mDisplayItemData = nullptr;
+ mozilla::layers::LayerManager* mDisplayItemDataLayerManager = nullptr;
+ mozilla::Maybe<uint16_t> mCacheIndex;
+};
+
+/**
+ * Manages a singly-linked list of display list items.
+ *
+ * mSentinel is the sentinel list value, the first value in the null-terminated
+ * linked list of items. mTop is the last item in the list (whose 'above'
+ * pointer is null). This class has no virtual methods. So list objects are just
+ * two pointers.
+ *
+ * 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(), ComputeVisibility()) 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. AppendToBottom() is efficient too.
+ */
+class nsDisplayList {
+ public:
+ typedef mozilla::ActiveScrolledRoot ActiveScrolledRoot;
+ typedef mozilla::layers::Layer Layer;
+ typedef mozilla::layers::LayerManager LayerManager;
+ typedef mozilla::layers::PaintedLayer PaintedLayer;
+
+ template <typename T>
+ class Iterator {
+ public:
+ Iterator() : mItem(nullptr) {}
+ ~Iterator() = default;
+ Iterator(const Iterator& aOther) = default;
+ Iterator& operator=(const Iterator& aOther) = default;
+
+ explicit Iterator(const nsDisplayList* aList) : mItem(aList->GetBottom()) {}
+ explicit Iterator(const nsDisplayItem* aItem) : mItem(aItem) {}
+
+ Iterator& operator++() {
+ mItem = mItem ? mItem->GetAbove() : mItem;
+ return *this;
+ }
+
+ bool operator==(const Iterator& aOther) const {
+ return mItem == aOther.mItem;
+ }
+
+ bool operator!=(const Iterator& aOther) const {
+ return !operator==(aOther);
+ }
+
+ T* operator*() { return mItem; }
+
+ private:
+ T* mItem;
+ };
+
+ using DisplayItemIterator = Iterator<nsDisplayItem>;
+
+ DisplayItemIterator begin() const { return DisplayItemIterator(this); }
+ DisplayItemIterator end() const { return DisplayItemIterator(); }
+
+ /**
+ * Create an empty list.
+ */
+ nsDisplayList()
+ : mLength(0), mIsOpaque(false), mForceTransparentSurface(false) {
+ mTop = &mSentinel;
+ mSentinel.mAbove = nullptr;
+ }
+
+ virtual ~nsDisplayList() {
+ MOZ_RELEASE_ASSERT(!mSentinel.mAbove, "Nonempty list left over?");
+ }
+
+ nsDisplayList(nsDisplayList&& aOther) {
+ mIsOpaque = aOther.mIsOpaque;
+ mForceTransparentSurface = aOther.mForceTransparentSurface;
+
+ if (aOther.mSentinel.mAbove) {
+ AppendToTop(&aOther);
+ } else {
+ mTop = &mSentinel;
+ mLength = 0;
+ }
+ }
+
+ nsDisplayList& operator=(nsDisplayList&& aOther) {
+ if (this != &aOther) {
+ if (aOther.mSentinel.mAbove) {
+ nsDisplayList tmp;
+ tmp.AppendToTop(&aOther);
+ aOther.AppendToTop(this);
+ AppendToTop(&tmp);
+ } else {
+ mTop = &mSentinel;
+ mLength = 0;
+ }
+ mIsOpaque = aOther.mIsOpaque;
+ mForceTransparentSurface = aOther.mForceTransparentSurface;
+ }
+ return *this;
+ }
+
+ nsDisplayList(const nsDisplayList&) = delete;
+ nsDisplayList& operator=(const nsDisplayList& aOther) = delete;
+
+ /**
+ * Append an item to the top of the list. The item must not currently
+ * be in a list and cannot be null.
+ */
+ void AppendToTop(nsDisplayItem* aItem) {
+ if (!aItem) {
+ return;
+ }
+ MOZ_ASSERT(!aItem->mAbove, "Already in a list!");
+ mTop->mAbove = aItem;
+ mTop = aItem;
+ mLength++;
+ }
+
+ 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)...);
+
+ if (item) {
+ AppendToTop(item);
+ }
+ }
+
+ /**
+ * Append a new item to the bottom of the list. The item must be non-null
+ * and not already in a list.
+ */
+ void AppendToBottom(nsDisplayItem* aItem) {
+ if (!aItem) {
+ return;
+ }
+ MOZ_ASSERT(!aItem->mAbove, "Already in a list!");
+ aItem->mAbove = mSentinel.mAbove;
+ mSentinel.mAbove = aItem;
+ if (mTop == &mSentinel) {
+ mTop = aItem;
+ }
+ mLength++;
+ }
+
+ template <typename T, typename F, typename... Args>
+ void AppendNewToBottom(nsDisplayListBuilder* aBuilder, F* aFrame,
+ Args&&... aArgs) {
+ AppendNewToBottomWithIndex<T>(aBuilder, aFrame, 0,
+ std::forward<Args>(aArgs)...);
+ }
+
+ template <typename T, typename F, typename... Args>
+ void AppendNewToBottomWithIndex(nsDisplayListBuilder* aBuilder, F* aFrame,
+ const uint16_t aIndex, Args&&... aArgs) {
+ nsDisplayItem* item = MakeDisplayItemWithIndex<T>(
+ aBuilder, aFrame, aIndex, std::forward<Args>(aArgs)...);
+
+ if (item) {
+ AppendToBottom(item);
+ }
+ }
+
+ /**
+ * Removes all items from aList and appends them to the top of this list
+ */
+ void AppendToTop(nsDisplayList* aList) {
+ if (aList->mSentinel.mAbove) {
+ mTop->mAbove = aList->mSentinel.mAbove;
+ mTop = aList->mTop;
+ aList->mTop = &aList->mSentinel;
+ aList->mSentinel.mAbove = nullptr;
+ mLength += aList->mLength;
+ aList->mLength = 0;
+ }
+ }
+
+ /**
+ * Removes all items from aList and prepends them to the bottom of this list
+ */
+ void AppendToBottom(nsDisplayList* aList) {
+ if (aList->mSentinel.mAbove) {
+ aList->mTop->mAbove = mSentinel.mAbove;
+ mSentinel.mAbove = aList->mSentinel.mAbove;
+ if (mTop == &mSentinel) {
+ mTop = aList->mTop;
+ }
+
+ aList->mTop = &aList->mSentinel;
+ aList->mSentinel.mAbove = nullptr;
+ mLength += aList->mLength;
+ aList->mLength = 0;
+ }
+ }
+
+ /**
+ * Remove an item from the bottom of the list and return it.
+ */
+ nsDisplayItem* RemoveBottom();
+
+ /**
+ * Remove all items from the list and call their destructors.
+ */
+ virtual void DeleteAll(nsDisplayListBuilder* aBuilder);
+
+ /**
+ * @return the item at the top of the list, or null if the list is empty
+ */
+ nsDisplayItem* GetTop() const {
+ return mTop != &mSentinel ? static_cast<nsDisplayItem*>(mTop) : nullptr;
+ }
+ /**
+ * @return the item at the bottom of the list, or null if the list is empty
+ */
+ nsDisplayItem* GetBottom() const { return mSentinel.mAbove; }
+ bool IsEmpty() const { return mTop == &mSentinel; }
+
+ /**
+ * @return the number of items in the list
+ */
+ uint32_t Count() const { return mLength; }
+ /**
+ * Stable sort the list by the z-order of GetUnderlyingFrame() 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
+ * GetUnderlyingFrame() 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. Take care, because some of the
+ * items might be nsDisplayLists themselves.
+ * 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 (Count() < 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;
+
+ while (nsDisplayItem* item = RemoveBottom()) {
+ items.AppendElement(Item(item));
+ }
+
+ std::stable_sort(items.begin(), items.end(), aComparator);
+
+ for (Item& item : items) {
+ AppendToTop(item);
+ }
+ }
+
+ /**
+ * Compute visiblity for the items in the list.
+ * We put this logic here so it can be shared by top-level
+ * painting and also display items that maintain child lists.
+ * This is also a good place to put ComputeVisibility-related logic
+ * that must be applied to every display item. In particular, this
+ * sets mVisibleRect on each display item.
+ * This sets mIsOpaque if the entire visible area of this list has
+ * been removed from aVisibleRegion when we return.
+ * This does not remove any items from the list, so we can recompute
+ * visiblity with different regions later (see
+ * FrameLayerBuilder::DrawPaintedLayer).
+ * This method needs to be idempotent.
+ *
+ * @param aVisibleRegion the area that is visible, relative to the
+ * reference frame; on return, this contains the area visible under the list.
+ * I.e., opaque contents of this list are subtracted from aVisibleRegion.
+ * @param aListVisibleBounds must be equal to the bounds of the intersection
+ * of aVisibleRegion and GetBounds() for this list.
+ * @return true if any item in the list is visible.
+ */
+ bool ComputeVisibilityForSublist(nsDisplayListBuilder* aBuilder,
+ nsRegion* aVisibleRegion,
+ const nsRect& aListVisibleBounds);
+
+ /**
+ * As ComputeVisibilityForSublist, but computes visibility for a root
+ * list (a list that does not belong to an nsDisplayItem).
+ * This method needs to be idempotent.
+ *
+ * @param aVisibleRegion the area that is visible
+ */
+ bool ComputeVisibilityForRoot(nsDisplayListBuilder* aBuilder,
+ nsRegion* aVisibleRegion);
+
+ /**
+ * Returns true if the visible region output from ComputeVisiblity was
+ * empty, i.e. everything visible in this list is opaque.
+ */
+ bool IsOpaque() const { return mIsOpaque; }
+
+ /**
+ * Returns true if any display item requires the surface to be transparent.
+ */
+ bool NeedsTransparentSurface() const { return mForceTransparentSurface; }
+ /**
+ * 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.
+ *
+ * If PAINT_COMPRESSED is set, the FrameLayerBuilder should be set to
+ * compressed mode to avoid short cut optimizations.
+ *
+ * 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_NO_COMPOSITE = 0x08,
+ PAINT_COMPRESSED = 0x10,
+ PAINT_IDENTICAL_DISPLAY_LIST = 0x20
+ };
+ already_AddRefed<LayerManager> PaintRoot(nsDisplayListBuilder* aBuilder,
+ gfxContext* aCtx, uint32_t aFlags);
+
+ mozilla::FrameLayerBuilder* BuildLayers(nsDisplayListBuilder* aBuilder,
+ LayerManager* aLayerManager,
+ uint32_t aFlags,
+ bool aIsWidgetTransaction);
+ /**
+ * 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;
+
+ void SetIsOpaque() { mIsOpaque = true; }
+
+ void SetNeedsTransparentSurface() { mForceTransparentSurface = true; }
+
+ void RestoreState() {
+ mIsOpaque = false;
+ mForceTransparentSurface = false;
+ }
+
+ private:
+ nsDisplayItemLink mSentinel;
+ nsDisplayItemLink* mTop;
+
+ uint32_t mLength;
+
+ // This is set to true by FrameLayerBuilder if the final visible region
+ // is empty (i.e. everything that was visible is covered by some
+ // opaque content in this list).
+ bool mIsOpaque;
+ // This is set to true by FrameLayerBuilder if any display item in this
+ // list needs to force the surface containing this list to be transparent.
+ bool mForceTransparentSurface;
+};
+
+/**
+ * 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 mBorderBackground; }
+ /**
+ * @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 mBlockBorderBackgrounds;
+ }
+ /**
+ * @return a list where one should place descendant floats (step 5 of
+ * CSS 2.1 appendix E)
+ */
+ nsDisplayList* Floats() const { return mFloats; }
+ /**
+ * @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 mPositioned; }
+ /**
+ * @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 mOutlines; }
+ /**
+ * @return a list where one should place all other content
+ */
+ nsDisplayList* Content() const { return mContent; }
+
+ void DeleteAll(nsDisplayListBuilder* aBuilder) {
+ BorderBackground()->DeleteAll(aBuilder);
+ BlockBorderBackgrounds()->DeleteAll(aBuilder);
+ Floats()->DeleteAll(aBuilder);
+ PositionedDescendants()->DeleteAll(aBuilder);
+ Outlines()->DeleteAll(aBuilder);
+ Content()->DeleteAll(aBuilder);
+ }
+
+ nsDisplayListSet(nsDisplayList* aBorderBackground,
+ nsDisplayList* aBlockBorderBackgrounds,
+ nsDisplayList* aFloats, nsDisplayList* aContent,
+ nsDisplayList* aPositionedDescendants,
+ nsDisplayList* aOutlines)
+ : mBorderBackground(aBorderBackground),
+ mBlockBorderBackgrounds(aBlockBorderBackgrounds),
+ mFloats(aFloats),
+ mContent(aContent),
+ mPositioned(aPositionedDescendants),
+ mOutlines(aOutlines) {}
+
+ /**
+ * A copy constructor that lets the caller override the BorderBackground
+ * list.
+ */
+ nsDisplayListSet(const nsDisplayListSet& aLists,
+ nsDisplayList* aBorderBackground)
+ : mBorderBackground(aBorderBackground),
+ mBlockBorderBackgrounds(aLists.BlockBorderBackgrounds()),
+ mFloats(aLists.Floats()),
+ mContent(aLists.Content()),
+ mPositioned(aLists.PositionedDescendants()),
+ mOutlines(aLists.Outlines()) {}
+
+ /**
+ * 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);
+
+ protected:
+ nsDisplayList* mBorderBackground;
+ nsDisplayList* mBlockBorderBackgrounds;
+ nsDisplayList* mFloats;
+ nsDisplayList* mContent;
+ nsDisplayList* mPositioned;
+ nsDisplayList* mOutlines;
+};
+
+/**
+ * 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]) {}
+
+ explicit nsDisplayListCollection(nsDisplayListBuilder* aBuilder,
+ nsDisplayList* aBorderBackground)
+ : nsDisplayListSet(aBorderBackground, &mLists[1], &mLists[2], &mLists[3],
+ &mLists[4], &mLists[5]) {}
+
+ /**
+ * 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:
+ RetainedDisplayList() = default;
+ RetainedDisplayList(RetainedDisplayList&& aOther) {
+ AppendToTop(&aOther);
+ mDAG = std::move(aOther.mDAG);
+ }
+
+ ~RetainedDisplayList() override {
+ MOZ_ASSERT(mOldItems.IsEmpty(), "Must empty list before destroying");
+ }
+
+ RetainedDisplayList& operator=(RetainedDisplayList&& aOther) {
+ MOZ_ASSERT(!Count(), "Can only move into an empty list!");
+ MOZ_ASSERT(mOldItems.IsEmpty(), "Can only move into an empty list!");
+ AppendToTop(&aOther);
+ mDAG = std::move(aOther.mDAG);
+ mOldItems = std::move(aOther.mOldItems);
+ 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;
+};
+
+struct HitTestInfo {
+ HitTestInfo(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ const mozilla::gfx::CompositorHitTestInfo& aHitTestFlags)
+ : mArea(aFrame->GetCompositorHitTestArea(aBuilder)),
+ mFlags(aHitTestFlags),
+ mAGR(aBuilder->FindAnimatedGeometryRootFor(aFrame)),
+ mASR(aBuilder->CurrentActiveScrolledRoot()),
+ mClipChain(aBuilder->ClipState().GetCurrentCombinedClipChain(aBuilder)),
+ mClip(mozilla::DisplayItemClipChain::ClipForASR(mClipChain, mASR)) {}
+
+ HitTestInfo(const nsRect& aArea,
+ const mozilla::gfx::CompositorHitTestInfo& aHitTestFlags)
+ : mArea(aArea),
+ mFlags(aHitTestFlags),
+ mAGR(nullptr),
+ mASR(nullptr),
+ mClipChain(nullptr),
+ mClip(nullptr) {}
+
+ nsRect mArea;
+ mozilla::gfx::CompositorHitTestInfo mFlags;
+
+ RefPtr<AnimatedGeometryRoot> mAGR;
+ RefPtr<const mozilla::ActiveScrolledRoot> mASR;
+ RefPtr<const mozilla::DisplayItemClipChain> mClipChain;
+ const mozilla::DisplayItemClip* mClip;
+};
+
+class nsDisplayHitTestInfoBase : public nsPaintedDisplayItem {
+ public:
+ nsDisplayHitTestInfoBase(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame)
+ : nsPaintedDisplayItem(aBuilder, aFrame) {}
+
+ nsDisplayHitTestInfoBase(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ const ActiveScrolledRoot* aActiveScrolledRoot)
+ : nsPaintedDisplayItem(aBuilder, aFrame, aActiveScrolledRoot) {}
+
+ nsDisplayHitTestInfoBase(nsDisplayListBuilder* aBuilder,
+ const nsDisplayHitTestInfoBase& aOther)
+ : nsPaintedDisplayItem(aBuilder, aOther) {}
+
+ const HitTestInfo& GetHitTestInfo() const {
+ MOZ_ASSERT(HasHitTestInfo());
+ return *mHitTestInfo;
+ }
+
+ void SetActiveScrolledRoot(
+ const ActiveScrolledRoot* aActiveScrolledRoot) override {
+ nsPaintedDisplayItem::SetActiveScrolledRoot(aActiveScrolledRoot);
+ UpdateHitTestInfoActiveScrolledRoot(aActiveScrolledRoot);
+ }
+
+ /**
+ * Updates mASR and mClip fields using the given |aActiveScrolledRoot|.
+ */
+ void UpdateHitTestInfoActiveScrolledRoot(
+ const ActiveScrolledRoot* aActiveScrolledRoot) {
+ if (HasHitTestInfo()) {
+ mHitTestInfo->mASR = aActiveScrolledRoot;
+ mHitTestInfo->mClip = mozilla::DisplayItemClipChain::ClipForASR(
+ mHitTestInfo->mClipChain, mHitTestInfo->mASR);
+ }
+ }
+
+ void SetHitTestInfo(mozilla::UniquePtr<HitTestInfo>&& aHitTestInfo) {
+ MOZ_ASSERT(aHitTestInfo);
+ MOZ_ASSERT(aHitTestInfo->mFlags !=
+ mozilla::gfx::CompositorHitTestInvisibleToHit);
+
+ mHitTestInfo = std::move(aHitTestInfo);
+ }
+
+ void SetHitTestInfo(
+ const nsRect& aArea,
+ const mozilla::gfx::CompositorHitTestInfo& aHitTestFlags) {
+ MOZ_ASSERT(aHitTestFlags != mozilla::gfx::CompositorHitTestInvisibleToHit);
+
+ mHitTestInfo = mozilla::MakeUnique<HitTestInfo>(aArea, aHitTestFlags);
+ mHitTestInfo->mAGR = mAnimatedGeometryRoot;
+ mHitTestInfo->mASR = mActiveScrolledRoot;
+ mHitTestInfo->mClipChain = mClipChain;
+ mHitTestInfo->mClip = mClip;
+ }
+
+ const nsRect& HitTestArea() const { return mHitTestInfo->mArea; }
+
+ const mozilla::gfx::CompositorHitTestInfo& HitTestFlags() const {
+ return mHitTestInfo->mFlags;
+ }
+
+ bool HasHitTestInfo() const final { return mHitTestInfo.get(); }
+
+ void AddSizeOfExcludingThis(nsWindowSizes&) const override;
+
+#ifdef DEBUG
+ bool IsHitTestItem() const final { return true; }
+#endif
+
+ protected:
+ mozilla::UniquePtr<HitTestInfo> mHitTestInfo;
+};
+
+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 ComputeVisibility(nsDisplayListBuilder* aBuilder,
+ nsRegion* aVisibleRegion) override;
+
+ bool CreateWebRenderCommands(
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ mozilla::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;
+
+ mozilla::Maybe<nscolor> IsUniform(
+ nsDisplayListBuilder* aBuilder) const override {
+ return mozilla::Nothing();
+ }
+
+ RetainedDisplayList* GetChildren() const override { return &mChildren; }
+ RetainedDisplayList* GetSameCoordinateSystemChildren() const override {
+ return GetChildren();
+ }
+
+ mozilla::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;
+};
+
+class nsDisplayImageContainer : public nsPaintedDisplayItem {
+ public:
+ typedef mozilla::LayerIntPoint LayerIntPoint;
+ typedef mozilla::LayoutDeviceRect LayoutDeviceRect;
+ typedef mozilla::layers::ImageContainer ImageContainer;
+ typedef mozilla::layers::ImageLayer ImageLayer;
+
+ nsDisplayImageContainer(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame)
+ : nsPaintedDisplayItem(aBuilder, aFrame) {}
+
+ /**
+ * @return true if this display item can be optimized into an image layer.
+ * It is an error to call GetContainer() unless you've called
+ * CanOptimizeToImageLayer() first and it returned true.
+ */
+ virtual bool CanOptimizeToImageLayer(LayerManager* aManager,
+ nsDisplayListBuilder* aBuilder);
+
+ already_AddRefed<ImageContainer> GetContainer(LayerManager* aManager,
+ nsDisplayListBuilder* aBuilder);
+ void ConfigureLayer(ImageLayer* aLayer,
+ const ContainerLayerParameters& aParameters);
+
+ virtual void UpdateDrawResult(mozilla::image::ImgDrawResult aResult) = 0;
+ virtual already_AddRefed<imgIContainer> GetImage() = 0;
+ virtual nsRect GetDestRect() const = 0;
+
+ bool SupportsOptimizingToImage() const override { return true; }
+};
+
+/**
+ * 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, 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(), ToReferenceFrame());
+ } else {
+ mOldPaint(mFrame, aCtx, GetPaintRect(), 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<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<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(
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ mozilla::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;
+ LayerState GetLayerState(
+ nsDisplayListBuilder* aBuilder, LayerManager* aManager,
+ const ContainerLayerParameters& aParameters) override;
+ bool CreateWebRenderCommands(
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ mozilla::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[mozilla::eCornerTopLeftX],
+ radii[mozilla::eCornerTopLeftY]);
+ result.OrWith(nsRect(borderBounds.TopLeft(), cornerSize));
+ }
+ if (border.top > 0 || border.right > 0) {
+ nsSize cornerSize(radii[mozilla::eCornerTopRightX],
+ radii[mozilla::eCornerTopRightY]);
+ result.OrWith(
+ nsRect(borderBounds.TopRight() - nsPoint(cornerSize.width, 0),
+ cornerSize));
+ }
+ if (border.right > 0 || border.bottom > 0) {
+ nsSize cornerSize(radii[mozilla::eCornerBottomRightX],
+ radii[mozilla::eCornerBottomRightY]);
+ result.OrWith(nsRect(borderBounds.BottomRight() -
+ nsPoint(cornerSize.width, cornerSize.height),
+ cornerSize));
+ }
+ if (border.bottom > 0 || border.left > 0) {
+ nsSize cornerSize(radii[mozilla::eCornerBottomLeftX],
+ radii[mozilla::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;
+ }
+
+ mozilla::Maybe<nscolor> IsUniform(
+ nsDisplayListBuilder* aBuilder) const override {
+ return mozilla::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) {
+ 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;
+ LayerState GetLayerState(
+ nsDisplayListBuilder* aBuilder, LayerManager* aManager,
+ const ContainerLayerParameters& aParameters) override;
+ already_AddRefed<Layer> BuildLayer(
+ nsDisplayListBuilder* aBuilder, LayerManager* aManager,
+ const ContainerLayerParameters& aContainerParameters) override;
+ void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override;
+ void WriteDebugInfo(std::stringstream& aStream) override;
+ bool CreateWebRenderCommands(
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ mozilla::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 = mozilla::Some(aZIndex);
+ }
+
+ private:
+ nsRect mBounds;
+ mozilla::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 {
+ typedef mozilla::gfx::sRGBColor sRGBColor;
+
+ public:
+ nsDisplaySolidColorRegion(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ const nsRegion& aRegion, nscolor aColor)
+ : nsPaintedDisplayItem(aBuilder, aFrame),
+ mRegion(aRegion),
+ mColor(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(
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ mozilla::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;
+ sRGBColor mColor;
+};
+
+/**
+ * A display item to paint one background-image for a frame. Each background
+ * image layer gets its own nsDisplayBackgroundImage.
+ */
+class nsDisplayBackgroundImage : public nsDisplayImageContainer {
+ public:
+ typedef mozilla::StyleGeometryBox StyleGeometryBox;
+
+ struct InitData {
+ nsDisplayListBuilder* builder;
+ mozilla::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,
+ mozilla::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 whether we appended a themed background.
+ // aAllowWillPaintBorderOptimization should usually be left at true, unless
+ // aFrame has special border drawing that causes opaque borders to not
+ // actually be opaque.
+ static bool AppendBackgroundItemsToTop(
+ nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ const nsRect& aBackgroundRect, nsDisplayList* aList,
+ bool aAllowWillPaintBorderOptimization = true,
+ mozilla::ComputedStyle* aComputedStyle = nullptr,
+ const nsRect& aBackgroundOriginRect = nsRect(),
+ nsIFrame* aSecondaryReferenceFrame = nullptr,
+ mozilla::Maybe<nsDisplayListBuilder::AutoBuildingDisplayList>*
+ aAutoBuildingDisplayList = nullptr);
+
+ LayerState GetLayerState(
+ nsDisplayListBuilder* aBuilder, LayerManager* aManager,
+ const ContainerLayerParameters& aParameters) override;
+ already_AddRefed<Layer> BuildLayer(
+ nsDisplayListBuilder* aBuilder, LayerManager* aManager,
+ const ContainerLayerParameters& aContainerParameters) override;
+ bool CreateWebRenderCommands(
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) override;
+ void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
+ HitTestState* aState, nsTArray<nsIFrame*>* aOutFrames) override;
+ bool ComputeVisibility(nsDisplayListBuilder* aBuilder,
+ nsRegion* aVisibleRegion) override;
+ nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
+ bool* aSnap) const override;
+ mozilla::Maybe<nscolor> IsUniform(
+ 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 CanOptimizeToImageLayer(LayerManager* aManager,
+ nsDisplayListBuilder* aBuilder) override;
+ already_AddRefed<imgIContainer> GetImage() override;
+ nsRect GetDestRect() const override;
+
+ void UpdateDrawResult(mozilla::image::ImgDrawResult aResult) override {
+ nsDisplayBackgroundGeometry::UpdateDrawResult(this, aResult);
+ }
+
+ static nsRegion GetInsideClipRegion(const nsDisplayItem* aItem,
+ StyleGeometryBox aClip,
+ const nsRect& aRect,
+ const nsRect& aBackgroundRect);
+
+ bool ShouldFixToViewport(nsDisplayListBuilder* aBuilder) const override {
+ return mShouldFixToViewport;
+ }
+
+ nsIFrame* GetDependentFrame() override { return mDependentFrame; }
+
+ void SetDependentFrame(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame) {
+ if (!aBuilder->IsRetainingDisplayList()) {
+ return;
+ }
+ mDependentFrame = aFrame;
+ if (aFrame) {
+ mDependentFrame->AddDisplayItem(this);
+ }
+ }
+
+ void RemoveFrame(nsIFrame* aFrame) override {
+ if (aFrame == mDependentFrame) {
+ mDependentFrame = nullptr;
+ }
+ nsDisplayImageContainer::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:
+ typedef class mozilla::layers::ImageContainer ImageContainer;
+ typedef class mozilla::layers::ImageLayer ImageLayer;
+
+ bool CanBuildWebRenderDisplayItems(LayerManager* aManager,
+ nsDisplayListBuilder* aBuilder);
+ nsRect GetBoundsInternal(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrameForBounds = nullptr);
+
+ void PaintInternal(nsDisplayListBuilder* aBuilder, gfxContext* aCtx,
+ const nsRect& aBounds, nsRect* aClipRect);
+
+ // Determine whether we want to be separated into our own layer, independent
+ // of whether this item can actually be layerized.
+ enum ImageLayerization {
+ WHENEVER_POSSIBLE,
+ ONLY_FOR_SCALING,
+ NO_LAYER_NEEDED
+ };
+ ImageLayerization ShouldCreateOwnLayer(nsDisplayListBuilder* aBuilder,
+ LayerManager* aManager);
+
+ // Cache the result of nsCSSRendering::FindBackground. Always null if
+ // mIsThemed is true or if FindBackground returned false.
+ RefPtr<mozilla::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;
+ uint32_t mImageFlags;
+};
+
+/**
+ * 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& aInitData,
+ 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;
+ mozilla::Maybe<nscolor> IsUniform(
+ nsDisplayListBuilder* aBuilder) const override;
+ bool CreateWebRenderCommands(
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ mozilla::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;
+ mozilla::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 {
+ typedef mozilla::gfx::sRGBColor sRGBColor;
+
+ public:
+ nsDisplayBackgroundColor(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ const nsRect& aBackgroundRect,
+ const mozilla::ComputedStyle* aBackgroundStyle,
+ const nscolor& aColor)
+ : nsPaintedDisplayItem(aBuilder, aFrame),
+ mBackgroundRect(aBackgroundRect),
+ mHasStyle(aBackgroundStyle),
+ mDependentFrame(nullptr),
+ mColor(sRGBColor::FromABGR(aColor)) {
+ mState.mColor = mColor;
+
+ 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 RestoreState() override {
+ if (!nsPaintedDisplayItem::RestoreState() && mColor == mState.mColor) {
+ return false;
+ }
+
+ mColor = mState.mColor;
+ return true;
+ }
+
+ bool HasBackgroundClipText() const {
+ MOZ_ASSERT(mHasStyle);
+ return mBottomLayerClip == mozilla::StyleGeometryBox::Text;
+ }
+
+ LayerState GetLayerState(
+ nsDisplayListBuilder* aBuilder, LayerManager* aManager,
+ const ContainerLayerParameters& aParameters) override;
+ void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override;
+ void PaintWithClip(nsDisplayListBuilder* aBuilder, gfxContext* aCtx,
+ const DisplayItemClip& aClip) override;
+ already_AddRefed<Layer> BuildLayer(
+ nsDisplayListBuilder* aBuilder, LayerManager* aManager,
+ const ContainerLayerParameters& aContainerParameters) override;
+ bool CreateWebRenderCommands(
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) override;
+ nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
+ bool* aSnap) const override;
+ mozilla::Maybe<nscolor> IsUniform(
+ nsDisplayListBuilder* aBuilder) const override;
+ void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
+ HitTestState* aState, nsTArray<nsIFrame*>* aOutFrames) override;
+ void ApplyOpacity(nsDisplayListBuilder* aBuilder, float aOpacity,
+ const DisplayItemClipChain* aClip) override;
+
+ bool CanApplyOpacity() 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()) {
+ 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;
+ mozilla::StyleGeometryBox mBottomLayerClip;
+ nsIFrame* mDependentFrame;
+ mozilla::gfx::sRGBColor mColor;
+
+ struct {
+ mozilla::gfx::sRGBColor mColor;
+ } mState;
+};
+
+class nsDisplayTableBackgroundColor : public nsDisplayBackgroundColor {
+ public:
+ nsDisplayTableBackgroundColor(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame, const nsRect& aBackgroundRect,
+ const mozilla::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), mOpacity(1.0f) {
+ MOZ_COUNT_CTOR(nsDisplayBoxShadowOuter);
+ mBounds = GetBoundsInternal();
+ }
+
+ MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayBoxShadowOuter)
+
+ NS_DISPLAY_DECL_NAME("BoxShadowOuter", TYPE_BOX_SHADOW_OUTER)
+
+ bool RestoreState() override {
+ if (!nsPaintedDisplayItem::RestoreState() && mOpacity == 1.0f &&
+ mVisibleRegion.IsEmpty()) {
+ return false;
+ }
+
+ mVisibleRegion.SetEmpty();
+ mOpacity = 1.0f;
+ return true;
+ }
+
+ void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override;
+ nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) const override;
+ bool IsInvisibleInRect(const nsRect& aRect) const override;
+ bool ComputeVisibility(nsDisplayListBuilder* aBuilder,
+ nsRegion* aVisibleRegion) override;
+ void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
+ const nsDisplayItemGeometry* aGeometry,
+ nsRegion* aInvalidRegion) const override;
+
+ void ApplyOpacity(nsDisplayListBuilder* aBuilder, float aOpacity,
+ const DisplayItemClipChain* aClip) override {
+ NS_ASSERTION(CanApplyOpacity(), "ApplyOpacity should be allowed");
+ mOpacity = aOpacity;
+ IntersectClip(aBuilder, aClip, false);
+ }
+
+ bool CanApplyOpacity() const override { return true; }
+
+ nsDisplayItemGeometry* AllocateGeometry(
+ nsDisplayListBuilder* aBuilder) override {
+ return new nsDisplayBoxShadowOuterGeometry(this, aBuilder, mOpacity);
+ }
+
+ bool CanBuildWebRenderDisplayItems();
+ bool CreateWebRenderCommands(
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) override;
+ nsRect GetBoundsInternal();
+
+ private:
+ nsRegion mVisibleRegion;
+ nsRect mBounds;
+ float mOpacity;
+};
+
+/**
+ * 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)
+
+ bool RestoreState() override {
+ if (!nsPaintedDisplayItem::RestoreState() && mVisibleRegion.IsEmpty()) {
+ return false;
+ }
+
+ mVisibleRegion.SetEmpty();
+ return true;
+ }
+
+ void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override;
+ bool ComputeVisibility(nsDisplayListBuilder* aBuilder,
+ nsRegion* aVisibleRegion) 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& aReferencePoint);
+ static void CreateInsetBoxShadowWebRenderCommands(
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ const StackingContextHelper& aSc, nsRegion& aVisibleRegion,
+ nsIFrame* aFrame, const nsRect& aBorderRect);
+ bool CreateWebRenderCommands(
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) override;
+
+ private:
+ nsRegion mVisibleRegion;
+};
+
+/**
+ * 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(
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ mozilla::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:
+ bool IsThemedOutline() 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 : public nsDisplayHitTestInfoBase {
+ public:
+ nsDisplayCompositorHitTestInfo(
+ nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ const mozilla::gfx::CompositorHitTestInfo& aHitTestFlags,
+ const mozilla::Maybe<nsRect>& aArea = mozilla::Nothing());
+
+ nsDisplayCompositorHitTestInfo(
+ nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ mozilla::UniquePtr<HitTestInfo>&& aHitTestInfo);
+
+ MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayCompositorHitTestInfo)
+
+ NS_DISPLAY_DECL_NAME("CompositorHitTestInfo", TYPE_COMPOSITOR_HITTEST_INFO)
+
+ void InitializeScrollTarget(nsDisplayListBuilder* aBuilder);
+
+ bool CreateWebRenderCommands(
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) override;
+
+ int32_t ZIndex() const override;
+ void SetOverrideZIndex(int32_t aZIndex);
+
+ /**
+ * ApplyOpacity() is overriden for opacity flattening.
+ */
+ void ApplyOpacity(nsDisplayListBuilder* aBuilder, float aOpacity,
+ const DisplayItemClipChain* aClip) override {}
+
+ /**
+ * CanApplyOpacity() is overriden for opacity flattening.
+ */
+ bool CanApplyOpacity() const override { return true; }
+
+ nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) const override {
+ *aSnap = false;
+ return nsRect();
+ }
+
+ private:
+ mozilla::Maybe<mozilla::layers::ScrollableLayerGuid::ViewID> mScrollTarget;
+ mozilla::Maybe<int32_t> mOverrideZIndex;
+ int32_t mAppUnitsPerDevPixel;
+};
+
+/**
+ * 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 nsDisplayHitTestInfoBase {
+ 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)
+ : nsDisplayHitTestInfoBase(aBuilder, aFrame),
+ mFrameActiveScrolledRoot(aBuilder->CurrentActiveScrolledRoot()),
+ mOverrideZIndex(0),
+ mHasZIndexOverride(false) {
+ MOZ_COUNT_CTOR(nsDisplayWrapList);
+ mBaseBuildingRect = GetBuildingRect();
+ mListPtr = &mList;
+ }
+
+ 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)
+ : nsDisplayHitTestInfoBase(aBuilder, aOther),
+ mListPtr(&mList),
+ mFrameActiveScrolledRoot(aOther.mFrameActiveScrolledRoot),
+ mMergedFrames(aOther.mMergedFrames.Clone()),
+ mBounds(aOther.mBounds),
+ mBaseBuildingRect(aOther.mBaseBuildingRect),
+ mOverrideZIndex(aOther.mOverrideZIndex),
+ mHasZIndexOverride(aOther.mHasZIndexOverride),
+ mClearingClipChain(aOther.mClearingClipChain) {
+ MOZ_COUNT_CTOR(nsDisplayWrapList);
+ }
+
+ ~nsDisplayWrapList() override;
+
+ NS_DISPLAY_DECL_NAME("WrapList", TYPE_WRAP_LIST)
+
+ const nsDisplayWrapList* AsDisplayWrapList() const final { return this; }
+ nsDisplayWrapList* AsDisplayWrapList() final { return this; }
+
+ void Destroy(nsDisplayListBuilder* aBuilder) override {
+ mList.DeleteAll(aBuilder);
+ nsDisplayHitTestInfoBase::Destroy(aBuilder);
+ }
+
+ /**
+ * Creates a new nsDisplayWrapList that holds a pointer to the display list
+ * owned by the given nsDisplayItem. The new nsDisplayWrapList will be added
+ * to the bottom of this item's contents.
+ */
+ void MergeDisplayListFromItem(nsDisplayListBuilder* aBuilder,
+ const nsDisplayWrapList* aItem);
+
+ /**
+ * 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 = mState.mClipChain;
+ 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 SetActiveScrolledRoot(
+ const ActiveScrolledRoot* aActiveScrolledRoot) override {
+ // Skip unnecessary call to
+ // |nsDisplayHitTestInfoBase::UpdateHitTestInfoActiveScrolledRoot()|, since
+ // callers will manually call that with different ASR.
+ nsDisplayHitTestInfoBase::SetActiveScrolledRoot(aActiveScrolledRoot);
+ }
+
+ 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;
+ mozilla::Maybe<nscolor> IsUniform(
+ nsDisplayListBuilder* aBuilder) const override;
+ void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override;
+ bool ComputeVisibility(nsDisplayListBuilder* aBuilder,
+ nsRegion* aVisibleRegion) override;
+
+ /**
+ * 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; }
+
+ /**
+ * 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 {
+ NS_ASSERTION(
+ mListPtr->IsEmpty() || !ReferenceFrame() ||
+ !mListPtr->GetBottom()->ReferenceFrame() ||
+ mListPtr->GetBottom()->ReferenceFrame() == ReferenceFrame(),
+ "Children must have same reference frame");
+ return mListPtr;
+ }
+
+ RetainedDisplayList* GetChildren() const override { return mListPtr; }
+
+ int32_t ZIndex() const override {
+ return (mHasZIndexOverride) ? mOverrideZIndex
+ : nsDisplayHitTestInfoBase::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(
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) override;
+
+ 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;
+ int32_t mOverrideZIndex;
+ bool mHasZIndexOverride;
+ bool mClearingClipChain = false;
+
+ private:
+ NS_DISPLAY_ALLOW_CLONING()
+ friend class nsDisplayListBuilder;
+};
+
+/**
+ * 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 nsDisplayWrapper {
+ 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:
+ nsDisplayWrapper() = 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 aForEventsAndPluginsOnly, bool aNeedsActiveLayer);
+
+ nsDisplayOpacity(nsDisplayListBuilder* aBuilder,
+ const nsDisplayOpacity& aOther)
+ : nsDisplayWrapList(aBuilder, aOther),
+ mOpacity(aOther.mOpacity),
+ mForEventsAndPluginsOnly(aOther.mForEventsAndPluginsOnly),
+ mNeedsActiveLayer(aOther.mNeedsActiveLayer),
+ mChildOpacityState(ChildOpacityState::Unknown) {
+ 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)
+
+ bool RestoreState() override {
+ if (!nsDisplayWrapList::RestoreState() && mOpacity == mState.mOpacity) {
+ return false;
+ }
+
+ mOpacity = mState.mOpacity;
+ return true;
+ }
+
+ void InvalidateCachedChildInfo(nsDisplayListBuilder* aBuilder) override {
+ mChildOpacityState = ChildOpacityState::Unknown;
+ }
+
+ nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
+ bool* aSnap) const override;
+ already_AddRefed<Layer> BuildLayer(
+ nsDisplayListBuilder* aBuilder, LayerManager* aManager,
+ const ContainerLayerParameters& aContainerParameters) override;
+ LayerState GetLayerState(
+ nsDisplayListBuilder* aBuilder, LayerManager* aManager,
+ const ContainerLayerParameters& aParameters) override;
+ bool ComputeVisibility(nsDisplayListBuilder* aBuilder,
+ nsRegion* aVisibleRegion) 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 (mForEventsAndPluginsOnly) {
+ return false;
+ }
+ return nsDisplayWrapList::IsInvalid(aRect);
+ }
+ void ApplyOpacity(nsDisplayListBuilder* aBuilder, float aOpacity,
+ const DisplayItemClipChain* aClip) override;
+ bool CanApplyOpacity() const override;
+ bool ShouldFlattenAway(nsDisplayListBuilder* aBuilder) override;
+
+ 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,
+ bool aEnforceMinimumSize = true);
+ void WriteDebugInfo(std::stringstream& aStream) override;
+ bool CanUseAsyncAnimations(nsDisplayListBuilder* aBuilder) override;
+ bool CreateWebRenderCommands(
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) override;
+
+ float GetOpacity() const { return mOpacity; }
+
+ private:
+ NS_DISPLAY_ALLOW_CLONING()
+
+ bool ApplyToChildren(nsDisplayListBuilder* aBuilder);
+ bool ApplyToFilterOrMask(const bool aUsingLayers);
+
+ float mOpacity;
+ bool mForEventsAndPluginsOnly : 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
+
+ struct {
+ float mOpacity;
+ } mState;
+};
+
+class nsDisplayBlendMode : public nsDisplayWrapList {
+ public:
+ nsDisplayBlendMode(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ nsDisplayList* aList, mozilla::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;
+ already_AddRefed<Layer> BuildLayer(
+ nsDisplayListBuilder* aBuilder, LayerManager* aManager,
+ const ContainerLayerParameters& aContainerParameters) 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
+ }
+
+ LayerState GetLayerState(
+ nsDisplayListBuilder* aBuilder, LayerManager* aManager,
+ const ContainerLayerParameters& aParameters) override;
+ bool CreateWebRenderCommands(
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) override;
+ bool ComputeVisibility(nsDisplayListBuilder* aBuilder,
+ nsRegion* aVisibleRegion) override;
+
+ bool CanMerge(const nsDisplayItem* aItem) const override;
+
+ bool ShouldFlattenAway(nsDisplayListBuilder* aBuilder) override {
+ return false;
+ }
+
+ mozilla::gfx::CompositionOp BlendMode();
+
+ protected:
+ mozilla::StyleBlend mBlendMode;
+ bool mIsForBackground;
+
+ private:
+ NS_DISPLAY_ALLOW_CLONING()
+};
+
+class nsDisplayTableBlendMode : public nsDisplayBlendMode {
+ public:
+ nsDisplayTableBlendMode(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ nsDisplayList* aList, mozilla::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)
+
+ already_AddRefed<Layer> BuildLayer(
+ nsDisplayListBuilder* aBuilder, LayerManager* aManager,
+ const ContainerLayerParameters& aContainerParameters) override;
+ LayerState GetLayerState(
+ nsDisplayListBuilder* aBuilder, LayerManager* aManager,
+ const ContainerLayerParameters& aParameters) override;
+ bool CreateWebRenderCommands(
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ mozilla::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;
+ }
+
+ 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:
+ typedef mozilla::layers::ScrollbarData ScrollbarData;
+
+ enum OwnLayerType {
+ OwnLayerForTransformWithRoundedClip,
+ OwnLayerForStackingContext,
+ OwnLayerForImageBoxFrame,
+ 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 ScrollbarData& aScrollbarData = 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)
+
+ already_AddRefed<Layer> BuildLayer(
+ nsDisplayListBuilder* aBuilder, LayerManager* aManager,
+ const ContainerLayerParameters& aContainerParameters) override;
+ bool CreateWebRenderCommands(
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) override;
+ bool UpdateScrollData(
+ mozilla::layers::WebRenderScrollData* aData,
+ mozilla::layers::WebRenderLayerScrollData* aLayerData) override;
+ LayerState GetLayerState(
+ nsDisplayListBuilder* aBuilder, LayerManager* aManager,
+ const ContainerLayerParameters& aParameters) override;
+
+ 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;
+
+ 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.
+ */
+ 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 ComputeVisibility(nsDisplayListBuilder* aBuilder,
+ nsRegion* aVisibleRegion) override;
+ bool ShouldFlattenAway(nsDisplayListBuilder* aBuilder) override {
+ return mShouldFlatten;
+ }
+
+ void SetShouldFlattenAway(bool aShouldFlatten) {
+ mShouldFlatten = aShouldFlatten;
+ }
+
+ LayerState GetLayerState(
+ nsDisplayListBuilder* aBuilder, LayerManager* aManager,
+ const ContainerLayerParameters& aParameters) override {
+ if (mShouldFlatten) {
+ return mozilla::LayerState::LAYER_NONE;
+ }
+ return nsDisplayOwnLayer::GetLayerState(aBuilder, aManager, aParameters);
+ }
+
+ nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
+ bool* aSnap) const override;
+
+ nsIFrame* FrameForInvalidation() const override;
+ void RemoveFrame(nsIFrame* aFrame) override;
+
+ void Disown();
+
+ 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) {
+ MOZ_COUNT_CTOR(nsDisplayStickyPosition);
+ }
+
+ MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayStickyPosition)
+
+ void SetClipChain(const DisplayItemClipChain* aClipChain,
+ bool aStore) override;
+ bool IsClippedToDisplayPort() const { return mClippedToDisplayPort; }
+
+ already_AddRefed<Layer> BuildLayer(
+ nsDisplayListBuilder* aBuilder, LayerManager* aManager,
+ const ContainerLayerParameters& aContainerParameters) override;
+ NS_DISPLAY_DECL_NAME("StickyPosition", TYPE_STICKY_POSITION)
+ LayerState GetLayerState(
+ nsDisplayListBuilder* aBuilder, LayerManager* aManager,
+ const ContainerLayerParameters& aParameters) override {
+ return mozilla::LayerState::LAYER_ACTIVE;
+ }
+
+ bool CreateWebRenderCommands(
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) override;
+
+ bool UpdateScrollData(
+ mozilla::layers::WebRenderScrollData* aData,
+ mozilla::layers::WebRenderLayerScrollData* aLayerData) override;
+
+ const ActiveScrolledRoot* GetContainerASR() const { return mContainerASR; }
+
+ private:
+ NS_DISPLAY_ALLOW_CLONING()
+
+ void CalculateLayerScrollRanges(
+ mozilla::StickyScrollContainer* aStickyScrollContainer,
+ float aAppUnitsPerDevPixel, float aScaleX, float aScaleY,
+ mozilla::LayerRectAbsolute& aStickyOuter,
+ mozilla::LayerRectAbsolute& aStickyInner);
+
+ mozilla::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;
+};
+
+class nsDisplayFixedPosition : public nsDisplayOwnLayer {
+ public:
+ nsDisplayFixedPosition(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ nsDisplayList* aList,
+ const ActiveScrolledRoot* aActiveScrolledRoot,
+ const ActiveScrolledRoot* aContainerASR);
+ nsDisplayFixedPosition(nsDisplayListBuilder* aBuilder,
+ const nsDisplayFixedPosition& aOther)
+ : nsDisplayOwnLayer(aBuilder, aOther),
+ mAnimatedGeometryRootForScrollMetadata(
+ aOther.mAnimatedGeometryRootForScrollMetadata),
+ mContainerASR(aOther.mContainerASR),
+ mIsFixedBackground(aOther.mIsFixedBackground) {
+ MOZ_COUNT_CTOR(nsDisplayFixedPosition);
+ }
+
+ static nsDisplayFixedPosition* CreateForFixedBackground(
+ nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ nsIFrame* aSecondaryFrame, nsDisplayBackgroundImage* aImage,
+ const uint16_t aIndex);
+
+ MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayFixedPosition)
+
+ NS_DISPLAY_DECL_NAME("FixedPosition", TYPE_FIXED_POSITION)
+
+ already_AddRefed<Layer> BuildLayer(
+ nsDisplayListBuilder* aBuilder, LayerManager* aManager,
+ const ContainerLayerParameters& aContainerParameters) override;
+
+ LayerState GetLayerState(
+ nsDisplayListBuilder* aBuilder, LayerManager* aManager,
+ const ContainerLayerParameters& aParameters) override {
+ return mozilla::LayerState::LAYER_ACTIVE_FORCE;
+ }
+
+ bool ShouldFixToViewport(nsDisplayListBuilder* aBuilder) const override {
+ return mIsFixedBackground;
+ }
+
+ AnimatedGeometryRoot* AnimatedGeometryRootForScrollMetadata() const override {
+ return mAnimatedGeometryRootForScrollMetadata;
+ }
+
+ bool CreateWebRenderCommands(
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) override;
+ bool UpdateScrollData(
+ mozilla::layers::WebRenderScrollData* aData,
+ mozilla::layers::WebRenderLayerScrollData* aLayerData) override;
+ void WriteDebugInfo(std::stringstream& aStream) override;
+
+ protected:
+ // For background-attachment:fixed
+ nsDisplayFixedPosition(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ nsDisplayList* aList);
+ void Init(nsDisplayListBuilder* aBuilder);
+ ViewID GetScrollTargetId();
+
+ RefPtr<AnimatedGeometryRoot> mAnimatedGeometryRootForScrollMetadata;
+ RefPtr<const ActiveScrolledRoot> mContainerASR;
+ 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);
+
+ 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 CompositorHitTestInfo& aHitInfo,
+ const nsRect& aHitArea);
+
+ MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayScrollInfoLayer)
+
+ NS_DISPLAY_DECL_NAME("ScrollInfoLayer", TYPE_SCROLL_INFO_LAYER)
+
+ already_AddRefed<Layer> BuildLayer(
+ nsDisplayListBuilder* aBuilder, LayerManager* aManager,
+ const ContainerLayerParameters& aContainerParameters) override;
+
+ nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
+ bool* aSnap) const override {
+ *aSnap = false;
+ return nsRegion();
+ }
+
+ LayerState GetLayerState(
+ nsDisplayListBuilder* aBuilder, LayerManager* aManager,
+ const ContainerLayerParameters& aParameters) override;
+
+ bool ShouldFlattenAway(nsDisplayListBuilder* aBuilder) override {
+ return false;
+ }
+
+ void WriteDebugInfo(std::stringstream& aStream) override;
+ mozilla::UniquePtr<ScrollMetadata> ComputeScrollMetadata(
+ nsDisplayListBuilder* aBuilder, LayerManager* aLayerManager,
+ const ContainerLayerParameters& aContainerParameters);
+ bool UpdateScrollData(
+ mozilla::layers::WebRenderScrollData* aData,
+ mozilla::layers::WebRenderLayerScrollData* aLayerData) override;
+ bool CreateWebRenderCommands(
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) override;
+
+ protected:
+ nsIFrame* mScrollFrame;
+ nsIFrame* mScrolledFrame;
+ ViewID mScrollParentId;
+ 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;
+ bool ComputeVisibility(nsDisplayListBuilder* aBuilder,
+ nsRegion* aVisibleRegion) override;
+ LayerState GetLayerState(
+ nsDisplayListBuilder* aBuilder, LayerManager* aManager,
+ const ContainerLayerParameters& aParameters) override {
+ return mozilla::LayerState::LAYER_ACTIVE;
+ }
+
+ // 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,
+ mozilla::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;
+ already_AddRefed<Layer> BuildLayer(
+ nsDisplayListBuilder* aBuilder, LayerManager* aManager,
+ const ContainerLayerParameters& aContainerParameters) override;
+ LayerState GetLayerState(
+ nsDisplayListBuilder* aBuilder, LayerManager* aManager,
+ const ContainerLayerParameters& aParameters) override {
+ return mozilla::LayerState::LAYER_ACTIVE_FORCE;
+ }
+ bool UpdateScrollData(
+ mozilla::layers::WebRenderScrollData* aData,
+ mozilla::layers::WebRenderLayerScrollData* aLayerData) override;
+
+ protected:
+ mozilla::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),
+ mHandleOpacity(aOther.mHandleOpacity) {
+ 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 RestoreState() override {
+ if (!nsDisplayWrapList::RestoreState() && !mHandleOpacity) {
+ return false;
+ }
+
+ mHandleOpacity = false;
+ return true;
+ }
+
+ bool ShouldFlattenAway(nsDisplayListBuilder* aBuilder) override {
+ return false;
+ }
+
+ virtual void SelectOpacityOptimization(const bool /* aUsingLayers */) {
+ SetHandleOpacity();
+ }
+
+ bool ShouldHandleOpacity() const { return mHandleOpacity; }
+
+ gfxRect BBoxInUserSpace() const;
+ gfxPoint UserSpaceOffset() const;
+
+ void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
+ const nsDisplayItemGeometry* aGeometry,
+ nsRegion* aInvalidRegion) const override;
+
+ protected:
+ void SetHandleOpacity() { mHandleOpacity = true; }
+ bool ValidateSVGFrame();
+
+ // relative to mFrame
+ nsRect mEffectsBounds;
+ // True if we need to handle css opacity in this display item.
+ bool mHandleOpacity;
+};
+
+/**
+ * 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:
+ typedef mozilla::layers::ImageLayer ImageLayer;
+
+ nsDisplayMasksAndClipPaths(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ nsDisplayList* aList,
+ const ActiveScrolledRoot* aActiveScrolledRoot);
+ nsDisplayMasksAndClipPaths(nsDisplayListBuilder* aBuilder,
+ const nsDisplayMasksAndClipPaths& aOther)
+ : nsDisplayEffectsBase(aBuilder, aOther),
+ mDestRects(aOther.mDestRects.Clone()) {
+ 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));
+ }
+
+ already_AddRefed<Layer> BuildLayer(
+ nsDisplayListBuilder* aBuilder, LayerManager* aManager,
+ const ContainerLayerParameters& aContainerParameters) override;
+ LayerState GetLayerState(
+ nsDisplayListBuilder* aBuilder, LayerManager* aManager,
+ const ContainerLayerParameters& aParameters) override;
+ bool ComputeVisibility(nsDisplayListBuilder* aBuilder,
+ nsRegion* aVisibleRegion) 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 PaintAsLayer(nsDisplayListBuilder* aBuilder, gfxContext* aCtx,
+ LayerManager* aManager);
+
+ 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* aMaskPainted = nullptr);
+
+ const nsTArray<nsRect>& GetDestRects() { return mDestRects; }
+
+ void SelectOpacityOptimization(const bool aUsingLayers) override;
+
+ bool CreateWebRenderCommands(
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) override;
+
+ mozilla::Maybe<nsRect> GetClipWithRespectToASR(
+ nsDisplayListBuilder* aBuilder,
+ const ActiveScrolledRoot* aASR) const override;
+
+ private:
+ NS_DISPLAY_ALLOW_CLONING()
+
+ // According to mask property and the capability of aManager, determine
+ // whether we can paint the mask onto a dedicate mask layer.
+ bool CanPaintOnMaskLayer(LayerManager* aManager);
+
+ nsTArray<nsRect> mDestRects;
+ bool mApplyOpacityWithSimpleClipPath;
+};
+
+class nsDisplayBackdropRootContainer : public nsDisplayWrapList {
+ public:
+ nsDisplayBackdropRootContainer(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame, nsDisplayList* aList,
+ const ActiveScrolledRoot* aActiveScrolledRoot)
+ : nsDisplayWrapList(aBuilder, aFrame, aList, aActiveScrolledRoot, true) {
+ MOZ_COUNT_CTOR(nsDisplayBackdropRootContainer);
+ }
+
+ MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayBackdropRootContainer)
+
+ NS_DISPLAY_DECL_NAME("BackdropRootContainer", TYPE_BACKDROP_ROOT_CONTAINER)
+
+ already_AddRefed<Layer> BuildLayer(
+ nsDisplayListBuilder* aBuilder, LayerManager* aManager,
+ const ContainerLayerParameters& aContainerParameters) override;
+ LayerState GetLayerState(
+ nsDisplayListBuilder* aBuilder, LayerManager* aManager,
+ const ContainerLayerParameters& aParameters) override;
+
+ bool CreateWebRenderCommands(
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) override;
+
+ bool ShouldFlattenAway(nsDisplayListBuilder* aBuilder) override {
+ return !aBuilder->IsPaintingForWebRender();
+ }
+};
+
+class nsDisplayBackdropFilters : public nsDisplayWrapList {
+ public:
+ nsDisplayBackdropFilters(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ nsDisplayList* aList, const nsRect& aBackdropRect)
+ : nsDisplayWrapList(aBuilder, aFrame, aList),
+ mBackdropRect(aBackdropRect) {
+ MOZ_COUNT_CTOR(nsDisplayBackdropFilters);
+ }
+
+ MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayBackdropFilters)
+
+ NS_DISPLAY_DECL_NAME("BackdropFilter", TYPE_BACKDROP_FILTER)
+
+ bool CreateWebRenderCommands(
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) override;
+
+ static bool CanCreateWebRenderCommands(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame);
+
+ bool ShouldFlattenAway(nsDisplayListBuilder* aBuilder) override {
+ return !aBuilder->IsPaintingForWebRender();
+ }
+
+ private:
+ 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);
+
+ nsDisplayFilters(nsDisplayListBuilder* aBuilder,
+ const nsDisplayFilters& aOther)
+ : nsDisplayEffectsBase(aBuilder, aOther),
+ mEffectsBounds(aOther.mEffectsBounds) {
+ 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));
+ }
+
+ already_AddRefed<Layer> BuildLayer(
+ nsDisplayListBuilder* aBuilder, LayerManager* aManager,
+ const ContainerLayerParameters& aContainerParameters) override;
+ LayerState GetLayerState(
+ nsDisplayListBuilder* aBuilder, LayerManager* aManager,
+ const ContainerLayerParameters& aParameters) override;
+
+ nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) const override {
+ *aSnap = false;
+ return mEffectsBounds + ToReferenceFrame();
+ }
+
+ bool ComputeVisibility(nsDisplayListBuilder* aBuilder,
+ nsRegion* aVisibleRegion) override;
+
+ nsDisplayItemGeometry* AllocateGeometry(
+ nsDisplayListBuilder* aBuilder) override {
+ return new nsDisplayFiltersGeometry(this, aBuilder);
+ }
+
+ void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
+ const nsDisplayItemGeometry* aGeometry,
+ nsRegion* aInvalidRegion) const override;
+#ifdef MOZ_DUMP_PAINTING
+ void PrintEffects(nsACString& aTo);
+#endif
+
+ void PaintAsLayer(nsDisplayListBuilder* aBuilder, gfxContext* aCtx,
+ LayerManager* aManager);
+
+ bool CreateWebRenderCommands(
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) override;
+ bool CanCreateWebRenderCommands();
+
+ private:
+ NS_DISPLAY_ALLOW_CLONING()
+
+ // relative to mFrame
+ nsRect mEffectsBounds;
+};
+
+/* 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 nsDisplayHitTestInfoBase {
+ using Matrix4x4 = mozilla::gfx::Matrix4x4;
+ using Matrix4x4Flagged = mozilla::gfx::Matrix4x4Flagged;
+ using Point3D = mozilla::gfx::Point3D;
+ using TransformReferenceBox = nsStyleTransformMatrix::TransformReferenceBox;
+
+ public:
+ enum class PrerenderDecision : uint8_t { No, Full, Partial };
+
+ /**
+ * Returns a matrix (in pixels) for the current frame. The matrix should be
+ * relative to the current frame's coordinate space.
+ *
+ * @param aFrame The frame to compute the transform for.
+ * @param aAppUnitsPerPixel The number of app units per graphics unit.
+ */
+ typedef Matrix4x4 (*ComputeTransformFunction)(nsIFrame* aFrame,
+ float aAppUnitsPerPixel);
+
+ /* 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,
+ ComputeTransformFunction aTransformGetter);
+
+ MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayTransform)
+
+ NS_DISPLAY_DECL_NAME("nsDisplayTransform", TYPE_TRANSFORM)
+
+ bool RestoreState() override {
+ if (!nsDisplayHitTestInfoBase::RestoreState() && !mShouldFlatten) {
+ return false;
+ }
+
+ mShouldFlatten = false;
+ return true;
+ }
+
+ 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);
+ nsDisplayHitTestInfoBase::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;
+ LayerState GetLayerState(
+ nsDisplayListBuilder* aBuilder, LayerManager* aManager,
+ const ContainerLayerParameters& aParameters) override;
+ already_AddRefed<Layer> BuildLayer(
+ nsDisplayListBuilder* aBuilder, LayerManager* aManager,
+ const ContainerLayerParameters& aContainerParameters) override;
+ bool CreateWebRenderCommands(
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) override;
+ bool UpdateScrollData(
+ mozilla::layers::WebRenderScrollData* aData,
+ mozilla::layers::WebRenderLayerScrollData* aLayerData) override;
+ bool ComputeVisibility(nsDisplayListBuilder* aBuilder,
+ nsRegion* aVisibleRegion) 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);
+ }
+ }
+
+ bool NeedsGeometryUpdates() const override { return mShouldFlatten; }
+
+ 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 (!mTransformGetter) {
+ return mFrame;
+ }
+ return nsDisplayHitTestInfoBase::ReferenceFrameForChildren();
+ }
+
+ AnimatedGeometryRoot* AnimatedGeometryRootForScrollMetadata() const override {
+ return mAnimatedGeometryRootForScrollMetadata;
+ }
+
+ 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(
+ mozilla::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);
+
+ bool UntransformRect(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
+ nsRect* aOutRect) const;
+
+ bool UntransformBuildingRect(nsDisplayListBuilder* aBuilder,
+ nsRect* aOutRect) const {
+ return UntransformRect(aBuilder, GetBuildingRect(), aOutRect);
+ }
+
+ bool UntransformPaintRect(nsDisplayListBuilder* aBuilder,
+ nsRect* aOutRect) const {
+ return UntransformRect(aBuilder, GetPaintRect(), aOutRect);
+ }
+
+ static 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 mozilla::StyleTranslate& aTranslate,
+ const mozilla::StyleRotate& aRotate, const mozilla::StyleScale& aScale,
+ const mozilla::StyleTransform& aTransform,
+ const mozilla::Maybe<mozilla::ResolvedMotionPathData>& aMotion,
+ const 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 mozilla::StyleTranslate& mTranslate;
+ const mozilla::StyleRotate& mRotate;
+ const mozilla::StyleScale& mScale;
+ const mozilla::StyleTransform& mTransform;
+ const mozilla::Maybe<mozilla::ResolvedMotionPathData> mMotion;
+ const 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 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,
+ bool aEnforceMinimumSize = true) const;
+
+ void WriteDebugInfo(std::stringstream& aStream) override;
+
+ /**
+ * 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;
+ }
+
+ void AddSizeOfExcludingThis(nsWindowSizes&) const override;
+
+ 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);
+
+ mutable mozilla::Maybe<Matrix4x4Flagged> mTransform;
+ mutable mozilla::Maybe<Matrix4x4Flagged> mInverseTransform;
+ // Accumulated transform of ancestors on the preserves-3d chain.
+ mozilla::UniquePtr<Matrix4x4> mTransformPreserves3D;
+ ComputeTransformFunction mTransformGetter;
+ RefPtr<AnimatedGeometryRoot> mAnimatedGeometryRootForChildren;
+ RefPtr<AnimatedGeometryRoot> mAnimatedGeometryRootForScrollMetadata;
+ nsRect mChildrenBuildingRect;
+ mutable RetainedDisplayList mChildren;
+
+ // 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 this nsDisplayTransform should get flattened
+ bool mShouldFlatten : 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 nsDisplayHitTestInfoBase {
+ typedef mozilla::gfx::Point3D Point3D;
+
+ 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);
+ nsDisplayHitTestInfoBase::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);
+ }
+
+ bool ComputeVisibility(nsDisplayListBuilder* aBuilder,
+ nsRegion* aVisibleRegion) override;
+
+ void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
+ const nsDisplayItemGeometry* aGeometry,
+ nsRegion* aInvalidRegion) const override {}
+
+ nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
+ bool* aSnap) const override;
+
+ LayerState GetLayerState(
+ nsDisplayListBuilder* aBuilder, LayerManager* aManager,
+ const ContainerLayerParameters& aParameters) override;
+ bool CreateWebRenderCommands(
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) override;
+
+ already_AddRefed<Layer> BuildLayer(
+ nsDisplayListBuilder* aBuilder, LayerManager* aManager,
+ const ContainerLayerParameters& aContainerParameters) 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);
+ }
+ }
+
+ private:
+ mutable RetainedDisplayList mList;
+};
+
+/**
+ * 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,
+ const mozilla::Maybe<bool>& aIsSelected);
+ MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayText)
+
+ NS_DISPLAY_DECL_NAME("Text", TYPE_TEXT)
+
+ bool RestoreState() final {
+ if (!nsPaintedDisplayItem::RestoreState() && mIsFrameSelected.isNothing() &&
+ mOpacity == 1.0f) {
+ return false;
+ }
+
+ mIsFrameSelected.reset();
+ mOpacity = 1.0f;
+ return true;
+ }
+
+ 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(
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ mozilla::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,
+ bool aIsRecording = false);
+
+ bool CanApplyOpacity() const final;
+
+ void ApplyOpacity(nsDisplayListBuilder* aBuilder, float aOpacity,
+ const DisplayItemClipChain* aClip) final {
+ NS_ASSERTION(CanApplyOpacity(), "ApplyOpacity should be allowed");
+ mOpacity = aOpacity;
+ IntersectClip(aBuilder, aClip, false);
+ }
+
+ void WriteDebugInfo(std::stringstream& aStream) final;
+
+ static nsDisplayText* CheckCast(nsDisplayItem* aItem) {
+ return (aItem->GetType() == DisplayItemType::TYPE_TEXT)
+ ? static_cast<nsDisplayText*>(aItem)
+ : nullptr;
+ }
+
+ bool IsSelected() const;
+
+ struct ClipEdges {
+ ClipEdges(const nsIFrame* aFrame, const nsPoint& aToReferenceFrame,
+ nscoord aVisIStartEdge, nscoord aVisIEndEdge) {
+ nsRect r = aFrame->ScrollableOverflowRect() + aToReferenceFrame;
+ if (aFrame->GetWritingMode().IsVertical()) {
+ mVisIStart = aVisIStartEdge > 0 ? r.y + aVisIStartEdge : nscoord_MIN;
+ mVisIEnd = aVisIEndEdge > 0
+ ? std::max(r.YMost() - aVisIEndEdge, mVisIStart)
+ : nscoord_MAX;
+ } else {
+ mVisIStart = aVisIStartEdge > 0 ? r.x + aVisIStartEdge : nscoord_MIN;
+ mVisIEnd = aVisIEndEdge > 0
+ ? std::max(r.XMost() - aVisIEndEdge, mVisIStart)
+ : nscoord_MAX;
+ }
+ }
+
+ void Intersect(nscoord* aVisIStart, nscoord* aVisISize) const {
+ nscoord end = *aVisIStart + *aVisISize;
+ *aVisIStart = std::max(*aVisIStart, mVisIStart);
+ *aVisISize = std::max(std::min(end, mVisIEnd) - *aVisIStart, 0);
+ }
+
+ nscoord mVisIStart;
+ nscoord mVisIEnd;
+ };
+
+ nscoord& VisIStartEdge() { return mVisIStartEdge; }
+ nscoord& VisIEndEdge() { return mVisIEndEdge; }
+ float Opacity() const { return mOpacity; }
+
+ private:
+ nsRect mBounds;
+ float mOpacity;
+
+ // 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;
+
+ // Cached result of mFrame->IsSelected(). Only initialized when needed.
+ mutable mozilla::Maybe<bool> mIsFrameSelected;
+};
+
+/**
+ * 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)
+
+ already_AddRefed<Layer> BuildLayer(
+ nsDisplayListBuilder* aBuilder, LayerManager* aManager,
+ const ContainerLayerParameters& aContainerParameters) override;
+ LayerState GetLayerState(
+ nsDisplayListBuilder* aBuilder, LayerManager* aManager,
+ const ContainerLayerParameters& aParameters) override;
+ bool ShouldFlattenAway(nsDisplayListBuilder* aBuilder) override;
+ bool CreateWebRenderCommands(
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ mozilla::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 already_AddRefed<Layer> BuildLayer(
+ nsDisplayListBuilder* aBuilder, LayerManager* aManager,
+ const ContainerLayerParameters& aContainerParameters) override;
+ virtual LayerState GetLayerState(
+ nsDisplayListBuilder* aBuilder, LayerManager* aManager,
+ const ContainerLayerParameters& aParameters) override;
+ virtual bool ShouldFlattenAway(nsDisplayListBuilder* aBuilder) override;
+
+ bool CreateWebRenderCommands(
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) override;
+};
+
+class FlattenedDisplayListIterator {
+ public:
+ FlattenedDisplayListIterator(nsDisplayListBuilder* aBuilder,
+ nsDisplayList* aList)
+ : FlattenedDisplayListIterator(aBuilder, aList, true) {}
+
+ ~FlattenedDisplayListIterator() { MOZ_ASSERT(!HasNext()); }
+
+ virtual bool HasNext() const { return mNext || !mStack.IsEmpty(); }
+
+ nsDisplayItem* GetNextItem() {
+ MOZ_ASSERT(mNext);
+
+ nsDisplayItem* next = mNext;
+ mNext = next->GetAbove();
+
+ if (mNext && next->HasChildren() && mNext->HasChildren()) {
+ // Since |next| and |mNext| are container items in the same list,
+ // merging them might be possible.
+ next = TryMergingFrom(next);
+ }
+
+ ResolveFlattening();
+
+ return next;
+ }
+
+ nsDisplayItem* PeekNext() { return mNext; }
+
+ protected:
+ FlattenedDisplayListIterator(nsDisplayListBuilder* aBuilder,
+ nsDisplayList* aList,
+ const bool aResolveFlattening)
+ : mBuilder(aBuilder), mNext(aList->GetBottom()) {
+ if (aResolveFlattening) {
+ // This is done conditionally in case subclass overrides
+ // ShouldFlattenNextItem().
+ ResolveFlattening();
+ }
+ }
+
+ virtual void EnterChildList(nsDisplayItem* aContainerItem) {}
+ virtual void ExitChildList() {}
+
+ bool AtEndOfNestedList() const { return !mNext && mStack.Length() > 0; }
+
+ virtual bool ShouldFlattenNextItem() {
+ return mNext && mNext->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()) {
+ ExitChildList();
+
+ // We reached the end of the list, pop the next item from the stack.
+ mNext = mStack.PopLastElement();
+ } else {
+ EnterChildList(mNext);
+
+ // This item wants to be flattened. Store the next item on the stack,
+ // and use the first item in the child list instead.
+ mStack.AppendElement(mNext->GetAbove());
+ mNext = mNext->GetChildren()->GetBottom();
+ }
+ }
+ }
+
+ /**
+ * Tries to merge display items starting from |aCurrent|.
+ * Updates the internal pointer to the next display item.
+ */
+ nsDisplayItem* TryMergingFrom(nsDisplayItem* aCurrent) {
+ MOZ_ASSERT(aCurrent);
+ MOZ_ASSERT(aCurrent->GetAbove());
+
+ nsDisplayWrapList* current = aCurrent->AsDisplayWrapList();
+ nsDisplayWrapList* next = mNext->AsDisplayWrapList();
+
+ if (!current || !next) {
+ // Either the current or the next item do not support merging.
+ return aCurrent;
+ }
+
+ // Attempt to merge |next| with |current|.
+ if (current->CanMerge(next)) {
+ // Merging is possible, collect all the successive mergeable items.
+ AutoTArray<nsDisplayWrapList*, 2> willMerge{current};
+
+ do {
+ willMerge.AppendElement(next);
+ mNext = next->GetAbove();
+ next = mNext ? mNext->AsDisplayWrapList() : nullptr;
+ } while (next && current->CanMerge(next));
+
+ current = mBuilder->MergeItems(willMerge);
+ }
+
+ // Here |mNext| will be either the first item that could not be merged with
+ // |current|, or nullptr.
+ return current;
+ }
+
+ private:
+ nsDisplayListBuilder* mBuilder;
+ nsDisplayItem* mNext;
+ AutoTArray<nsDisplayItem*, 16> mStack;
+};
+
+namespace mozilla {
+
+class PaintTelemetry {
+ public:
+ enum class Metric {
+ DisplayList,
+ Layerization,
+ FlushRasterization,
+ Rasterization,
+ COUNT,
+ };
+
+ class AutoRecord {
+ public:
+ explicit AutoRecord(Metric aMetric);
+ ~AutoRecord();
+
+ TimeStamp GetStart() const { return mStart; }
+
+ private:
+ Metric mMetric;
+ mozilla::TimeStamp mStart;
+ };
+
+ class AutoRecordPaint {
+ public:
+ AutoRecordPaint();
+ ~AutoRecordPaint();
+
+ private:
+ mozilla::TimeStamp mStart;
+ };
+
+ private:
+ static uint32_t sPaintLevel;
+ static uint32_t sMetricLevel;
+ static mozilla::EnumeratedArray<Metric, Metric::COUNT, double> sMetrics;
+};
+
+} // namespace mozilla
+
+#endif /*NSDISPLAYLIST_H_*/
diff --git a/layout/painting/nsDisplayListArenaTypes.h b/layout/painting/nsDisplayListArenaTypes.h
new file mode 100644
index 0000000000..6a7a262664
--- /dev/null
+++ b/layout/painting/nsDisplayListArenaTypes.h
@@ -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/. */
+
+/* a list of all types that can be allocated in the display list's nsPresArena,
+ for preprocessing */
+
+DISPLAY_LIST_ARENA_OBJECT(CLIPCHAIN)
+#define DECLARE_DISPLAY_ITEM_TYPE(name_, ...) DISPLAY_LIST_ARENA_OBJECT(name_)
+#include "nsDisplayItemTypesList.h"
+#undef DECLARE_DISPLAY_ITEM_TYPE
diff --git a/layout/painting/nsDisplayListInvalidation.cpp b/layout/painting/nsDisplayListInvalidation.cpp
new file mode 100644
index 0000000000..a7a8051996
--- /dev/null
+++ b/layout/painting/nsDisplayListInvalidation.cpp
@@ -0,0 +1,122 @@
+/* -*- 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"
+
+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();
+}
+
+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),
+ nsImageGeometryMixin(aItem, aBuilder) {}
+
+nsDisplayBackgroundGeometry::nsDisplayBackgroundGeometry(
+ nsDisplayBackgroundImage* aItem, nsDisplayListBuilder* aBuilder)
+ : nsDisplayItemGeometry(aItem, aBuilder),
+ nsImageGeometryMixin(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);
+}
+
+nsDisplayBoxShadowOuterGeometry::nsDisplayBoxShadowOuterGeometry(
+ nsDisplayItem* aItem, nsDisplayListBuilder* aBuilder, float aOpacity)
+ : nsDisplayItemGenericGeometry(aItem, aBuilder), mOpacity(aOpacity) {}
+
+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()),
+ mOpacity(aItem->Frame()->StyleEffects()->mOpacity),
+ mHandleOpacity(aItem->ShouldHandleOpacity()) {}
+
+void nsDisplaySVGEffectGeometry::MoveBy(const nsPoint& aOffset) {
+ mBounds.MoveBy(aOffset);
+ mFrameOffsetToReferenceFrame += aOffset;
+}
+
+nsDisplayMasksAndClipPathsGeometry::nsDisplayMasksAndClipPathsGeometry(
+ nsDisplayMasksAndClipPaths* aItem, nsDisplayListBuilder* aBuilder)
+ : nsDisplaySVGEffectGeometry(aItem, aBuilder),
+ nsImageGeometryMixin(aItem, aBuilder),
+ mDestRects(aItem->GetDestRects().Clone()) {}
+
+nsDisplayFiltersGeometry::nsDisplayFiltersGeometry(
+ nsDisplayFilters* aItem, nsDisplayListBuilder* aBuilder)
+ : nsDisplaySVGEffectGeometry(aItem, aBuilder),
+ nsImageGeometryMixin(aItem, aBuilder) {}
+
+nsDisplayTableItemGeometry::nsDisplayTableItemGeometry(
+ nsDisplayTableItem* aItem, nsDisplayListBuilder* aBuilder,
+ const nsPoint& aFrameOffsetToViewport)
+ : nsDisplayItemGenericGeometry(aItem, aBuilder),
+ nsImageGeometryMixin(aItem, aBuilder),
+ mFrameOffsetToViewport(aFrameOffsetToViewport) {}
diff --git a/layout/painting/nsDisplayListInvalidation.h b/layout/painting/nsDisplayListInvalidation.h
new file mode 100644
index 0000000000..382b5ff038
--- /dev/null
+++ b/layout/painting/nsDisplayListInvalidation.h
@@ -0,0 +1,369 @@
+/* -*- 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 "FrameLayerBuilder.h"
+#include "nsRect.h"
+#include "nsColor.h"
+#include "gfxRect.h"
+#include "mozilla/gfx/MatrixFwd.h"
+
+class nsDisplayBackgroundImage;
+class nsCharClipDisplayItem;
+class nsDisplayItem;
+class nsDisplayListBuilder;
+class nsDisplayTableItem;
+class nsDisplayThemedBackground;
+class nsDisplayEffectsBase;
+class nsDisplayMasksAndClipPaths;
+class nsDisplayFilters;
+
+namespace mozilla {
+namespace gfx {
+struct sRGBColor;
+}
+} // namespace mozilla
+
+/**
+ * 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); }
+
+ virtual bool InvalidateForSyncDecodeImages() const { return false; }
+
+ /**
+ * 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);
+
+/**
+ * nsImageGeometryMixin is a mixin for geometry items that draw images.
+ * Geometry items that include this mixin can track drawing results and use
+ * that information to inform invalidation decisions.
+ *
+ * This mixin uses CRTP; its template parameter should be the type of the class
+ * that is inheriting from it. See nsDisplayItemGenericImageGeometry for an
+ * example.
+ */
+template <typename T>
+class nsImageGeometryMixin {
+ public:
+ nsImageGeometryMixin(nsDisplayItem* aItem, nsDisplayListBuilder* aBuilder)
+ : mLastDrawResult(mozilla::image::ImgDrawResult::NOT_READY),
+ mWaitingForPaint(false) {
+ // Transfer state from the previous version of this geometry item.
+ auto lastGeometry = static_cast<T*>(
+ mozilla::FrameLayerBuilder::GetMostRecentGeometry(aItem));
+ if (lastGeometry) {
+ mLastDrawResult = lastGeometry->mLastDrawResult;
+ mWaitingForPaint = lastGeometry->mWaitingForPaint;
+ }
+
+ // If our display item is going to invalidate to trigger sync decoding of
+ // images, mark ourselves as waiting for a paint. If we actually get
+ // painted, UpdateDrawResult will get called, and we'll clear the flag.
+ if (ShouldSyncDecodeImages(aBuilder) &&
+ ShouldInvalidateToSyncDecodeImages()) {
+ mWaitingForPaint = true;
+ }
+ }
+
+ static void UpdateDrawResult(nsDisplayItem* aItem,
+ mozilla::image::ImgDrawResult aResult) {
+ MOZ_ASSERT(aResult != mozilla::image::ImgDrawResult::NOT_SUPPORTED,
+ "ImgDrawResult::NOT_SUPPORTED should be handled already!");
+
+ auto lastGeometry = static_cast<T*>(
+ mozilla::FrameLayerBuilder::GetMostRecentGeometry(aItem));
+ if (lastGeometry) {
+ lastGeometry->mLastDrawResult = aResult;
+ lastGeometry->mWaitingForPaint = false;
+ }
+ }
+
+ bool ShouldInvalidateToSyncDecodeImages() const {
+ if (mWaitingForPaint) {
+ // We previously invalidated for sync decoding and haven't gotten painted
+ // since them. This suggests that our display item is completely occluded
+ // and there's no point in invalidating again - and because the reftest
+ // harness takes a new snapshot every time we invalidate, doing so might
+ // lead to an invalidation loop if we're in a reftest.
+ return false;
+ }
+
+ if (mLastDrawResult == mozilla::image::ImgDrawResult::SUCCESS ||
+ mLastDrawResult == mozilla::image::ImgDrawResult::BAD_IMAGE) {
+ return false;
+ }
+
+ return true;
+ }
+
+ private:
+ mozilla::image::ImgDrawResult mLastDrawResult;
+ bool mWaitingForPaint;
+};
+
+/**
+ * nsDisplayItemGenericImageGeometry is a generic geometry item class that
+ * includes nsImageGeometryMixin.
+ *
+ * This should be sufficient for most display items that draw images.
+ */
+class nsDisplayItemGenericImageGeometry
+ : public nsDisplayItemGenericGeometry,
+ public nsImageGeometryMixin<nsDisplayItemGenericImageGeometry> {
+ public:
+ nsDisplayItemGenericImageGeometry(nsDisplayItem* aItem,
+ nsDisplayListBuilder* aBuilder)
+ : nsDisplayItemGenericGeometry(aItem, aBuilder),
+ nsImageGeometryMixin(aItem, aBuilder) {}
+
+ bool InvalidateForSyncDecodeImages() const override {
+ return ShouldInvalidateToSyncDecodeImages();
+ }
+};
+
+class nsDisplayItemBoundsGeometry : public nsDisplayItemGeometry {
+ public:
+ nsDisplayItemBoundsGeometry(nsDisplayItem* aItem,
+ nsDisplayListBuilder* aBuilder);
+
+ bool mHasRoundedCorners;
+};
+
+class nsDisplayBorderGeometry
+ : public nsDisplayItemGeometry,
+ public nsImageGeometryMixin<nsDisplayBorderGeometry> {
+ public:
+ nsDisplayBorderGeometry(nsDisplayItem* aItem, nsDisplayListBuilder* aBuilder);
+
+ bool InvalidateForSyncDecodeImages() const override {
+ return ShouldInvalidateToSyncDecodeImages();
+ }
+};
+
+class nsDisplayBackgroundGeometry
+ : public nsDisplayItemGeometry,
+ public nsImageGeometryMixin<nsDisplayBackgroundGeometry> {
+ public:
+ nsDisplayBackgroundGeometry(nsDisplayBackgroundImage* aItem,
+ nsDisplayListBuilder* aBuilder);
+
+ void MoveBy(const nsPoint& aOffset) override;
+
+ bool InvalidateForSyncDecodeImages() const override {
+ return ShouldInvalidateToSyncDecodeImages();
+ }
+
+ 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 nsImageGeometryMixin<nsDisplayTreeBodyGeometry> {
+ public:
+ nsDisplayTreeBodyGeometry(nsDisplayItem* aItem,
+ nsDisplayListBuilder* aBuilder,
+ bool aWindowIsActive)
+ : nsDisplayItemGenericGeometry(aItem, aBuilder),
+ nsImageGeometryMixin(aItem, aBuilder),
+ mWindowIsActive(aWindowIsActive) {}
+
+ bool InvalidateForSyncDecodeImages() const override {
+ return ShouldInvalidateToSyncDecodeImages();
+ }
+
+ bool mWindowIsActive = false;
+};
+
+class nsDisplayBoxShadowInnerGeometry : public nsDisplayItemGeometry {
+ public:
+ nsDisplayBoxShadowInnerGeometry(nsDisplayItem* aItem,
+ nsDisplayListBuilder* aBuilder);
+
+ void MoveBy(const nsPoint& aOffset) override;
+
+ nsRect mPaddingRect;
+};
+
+class nsDisplayBoxShadowOuterGeometry : public nsDisplayItemGenericGeometry {
+ public:
+ nsDisplayBoxShadowOuterGeometry(nsDisplayItem* aItem,
+ nsDisplayListBuilder* aBuilder,
+ float aOpacity);
+
+ float mOpacity;
+};
+
+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;
+ float mOpacity;
+ bool mHandleOpacity;
+};
+
+class nsDisplayMasksAndClipPathsGeometry
+ : public nsDisplaySVGEffectGeometry,
+ public nsImageGeometryMixin<nsDisplayMasksAndClipPathsGeometry> {
+ public:
+ nsDisplayMasksAndClipPathsGeometry(nsDisplayMasksAndClipPaths* aItem,
+ nsDisplayListBuilder* aBuilder);
+
+ bool InvalidateForSyncDecodeImages() const override {
+ return ShouldInvalidateToSyncDecodeImages();
+ }
+
+ nsTArray<nsRect> mDestRects;
+};
+
+class nsDisplayFiltersGeometry
+ : public nsDisplaySVGEffectGeometry,
+ public nsImageGeometryMixin<nsDisplayFiltersGeometry> {
+ public:
+ nsDisplayFiltersGeometry(nsDisplayFilters* aItem,
+ nsDisplayListBuilder* aBuilder);
+
+ bool InvalidateForSyncDecodeImages() const override {
+ return ShouldInvalidateToSyncDecodeImages();
+ }
+};
+
+class nsDisplayTableItemGeometry
+ : public nsDisplayItemGenericGeometry,
+ public nsImageGeometryMixin<nsDisplayTableItemGeometry> {
+ public:
+ nsDisplayTableItemGeometry(nsDisplayTableItem* aItem,
+ nsDisplayListBuilder* aBuilder,
+ const nsPoint& aFrameOffsetToViewport);
+
+ bool InvalidateForSyncDecodeImages() const override {
+ return ShouldInvalidateToSyncDecodeImages();
+ }
+
+ 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;
+};
+
+#endif /*NSDISPLAYLISTINVALIDATION_H_*/
diff --git a/layout/painting/nsImageRenderer.cpp b/layout/painting/nsImageRenderer.cpp
new file mode 100644
index 0000000000..a23a900860
--- /dev/null
+++ b/layout/painting/nsImageRenderer.cpp
@@ -0,0 +1,1060 @@
+/* -*- 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/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/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()),
+ 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);
+
+ // 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;
+ }
+
+ // Special case: 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);
+ if (!syncDecodeWillComplete) {
+ mPrepareResult = ImgDrawResult::NOT_READY;
+ return false;
+ }
+ }
+ }
+
+ if (isImageRequest) {
+ nsCOMPtr<imgIContainer> srcImage;
+ DebugOnly<nsresult> rv = request->GetImage(getter_AddRefs(srcImage));
+ MOZ_ASSERT(NS_SUCCEEDED(rv) && srcImage,
+ "If GetImage() is failing, mImage->IsComplete() "
+ "should have returned false");
+
+ if (srcImage) {
+ srcImage = nsLayoutUtils::OrientImage(
+ srcImage, mForFrame->StyleVisibility()->mImageOrientation);
+ }
+
+ 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, imageIntSize, result.mRatio, haveWidth, haveHeight);
+ if (haveWidth) {
+ result.SetWidth(nsPresContext::CSSPixelsToAppUnits(imageIntSize.width));
+ }
+ if (haveHeight) {
+ result.SetHeight(
+ nsPresContext::CSSPixelsToAppUnits(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_NONE;
+ if (aImageRendererFlags & nsImageRenderer::FLAG_SYNC_DECODE_IMAGES) {
+ drawFlags |= imgIContainer::FLAG_SYNC_DECODE;
+ }
+ 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;
+ RefPtr<gfxContext> ctx = &aRenderingContext;
+ 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 =
+ gfxPlatform::GetPlatform()->CreateSimilarSoftwareDrawTarget(
+ ctx->GetDrawTarget(), 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()));
+ ctx = gfxContext::CreatePreservingTransformOrNull(tempDT);
+ 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: {
+ uint32_t containerFlags = imgIContainer::FLAG_ASYNC_NOTIFY;
+ if (mFlags & (nsImageRenderer::FLAG_PAINTING_TO_WINDOW |
+ nsImageRenderer::FLAG_HIGH_QUALITY_SCALING)) {
+ containerFlags |= imgIContainer::FLAG_HIGH_QUALITY_SCALING;
+ }
+ if (mFlags & nsImageRenderer::FLAG_SYNC_DECODE_IMAGES) {
+ containerFlags |= imgIContainer::FLAG_SYNC_DECODE;
+ }
+
+ CSSIntSize destCSSSize{
+ nsPresContext::AppUnitsToIntCSSPixels(aDest.width),
+ nsPresContext::AppUnitsToIntCSSPixels(aDest.height)};
+
+ Maybe<SVGImageContext> svgContext(
+ Some(SVGImageContext(Some(destCSSSize))));
+
+ const int32_t appUnitsPerDevPixel =
+ mForFrame->PresContext()->AppUnitsPerDevPixel();
+ LayoutDeviceRect destRect =
+ LayoutDeviceRect::FromAppUnits(aDest, appUnitsPerDevPixel);
+ auto stretchSize = wr::ToLayoutSize(destRect.Size());
+
+ gfx::IntSize decodeSize =
+ nsLayoutUtils::ComputeImageContainerDrawingParameters(
+ mImageContainer, mForFrame, destRect, aSc, containerFlags,
+ svgContext);
+
+ RefPtr<layers::ImageContainer> container;
+ drawResult = mImageContainer->GetImageContainerAtSize(
+ aManager->LayerManager(), decodeSize, svgContext, containerFlags,
+ getter_AddRefs(container));
+ if (!container) {
+ NS_WARNING("Failed to get image container");
+ break;
+ }
+
+ mozilla::wr::ImageRendering rendering = wr::ToImageRendering(
+ nsLayoutUtils::GetSamplingFilterForFrame(aItem->Frame()));
+ gfx::IntSize size;
+ Maybe<wr::ImageKey> key = aManager->CommandBuilder().CreateImageKey(
+ aItem, container, aBuilder, aResources, rendering, aSc, size,
+ Nothing());
+
+ if (key.isNothing()) {
+ break;
+ }
+
+ wr::LayoutRect dest = wr::ToLayoutRect(destRect);
+
+ wr::LayoutRect clip = wr::ToLayoutRect(
+ LayoutDeviceRect::FromAppUnits(aFill, appUnitsPerDevPixel));
+
+ if (mExtendMode == ExtendMode::CLAMP) {
+ // The image is not repeating. Just push as a regular image.
+ aBuilder.PushImage(dest, clip, !aItem->BackfaceIsHidden(), rendering,
+ key.value());
+ } 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 (mExtendMode) {
+ case ExtendMode::REPEAT_Y:
+ fill.origin.x = dest.origin.x;
+ fill.size.width = dest.size.width;
+ stretchSize.width = dest.size.width;
+ break;
+ case ExtendMode::REPEAT_X:
+ fill.origin.y = dest.origin.y;
+ fill.size.height = dest.size.height;
+ stretchSize.height = dest.size.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());
+ }
+ 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) {
+ CachedBorderImageData* cachedData =
+ mForFrame->GetProperty(nsIFrame::CachedBorderImageDataProperty());
+ if (!cachedData) {
+ cachedData = new CachedBorderImageData();
+ mForFrame->AddProperty(nsIFrame::CachedBorderImageDataProperty(),
+ cachedData);
+ }
+ if (!(subImage = cachedData->GetSubImage(aIndex))) {
+ subImage = ImageOps::Clip(mImageContainer, srcRect, aSVGViewportSize);
+ cachedData->SetSubImage(aIndex, subImage);
+ }
+ } 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,
+ /* no SVGImageContext */ Nothing(), 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, Nothing(), drawFlags, nullptr, nullptr);
+ }
+
+ 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;
+}
+
+bool nsImageRenderer::IsAnimatedImage() {
+ bool animated = false;
+ return mImageContainer &&
+ NS_SUCCEEDED(mImageContainer->GetAnimated(&animated)) && animated;
+}
+
+already_AddRefed<imgIContainer> nsImageRenderer::GetImage() {
+ return do_AddRef(mImageContainer);
+}
+
+bool nsImageRenderer::IsImageContainerAvailable(layers::LayerManager* aManager,
+ uint32_t aFlags) {
+ return mImageContainer &&
+ mImageContainer->IsImageContainerAvailable(aManager, aFlags);
+}
+
+void nsImageRenderer::PurgeCacheForViewportChange(
+ const Maybe<nsSize>& aSVGViewportSize, const bool aHasIntrinsicRatio) {
+ // Check if we should flush the cached data - only vector images need to do
+ // the check since they might not have fixed ratio.
+ if (mImageContainer &&
+ mImageContainer->GetType() == imgIContainer::TYPE_VECTOR) {
+ if (auto* cachedData =
+ mForFrame->GetProperty(nsIFrame::CachedBorderImageDataProperty())) {
+ cachedData->PurgeCacheForViewportChange(aSVGViewportSize,
+ aHasIntrinsicRatio);
+ }
+ }
+}
diff --git a/layout/painting/nsImageRenderer.h b/layout/painting/nsImageRenderer.h
new file mode 100644
index 0000000000..6d5843a73f
--- /dev/null
+++ b/layout/painting/nsImageRenderer.h
@@ -0,0 +1,316 @@
+/* -*- 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;
+class nsDisplayItem;
+namespace mozilla {
+
+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::LayerManager LayerManager;
+ typedef mozilla::layers::ImageContainer ImageContainer;
+
+ enum {
+ FLAG_SYNC_DECODE_IMAGES = 0x01,
+ FLAG_PAINTING_TO_WINDOW = 0x02,
+ FLAG_HIGH_QUALITY_SCALING = 0x04
+ };
+ 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();
+ bool IsAnimatedImage();
+
+ /// Retrieves the image associated with this nsImageRenderer, if there is one.
+ already_AddRefed<imgIContainer> GetImage();
+
+ bool IsImageContainerAvailable(layers::LayerManager* aManager,
+ uint32_t aFlags);
+ 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; }
+ void PurgeCacheForViewportChange(
+ const mozilla::Maybe<nsSize>& aSVGViewportSize, const bool aHasRatio);
+ 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;
+ 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__ */