summaryrefslogtreecommitdiffstats
path: root/layout/painting
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
commit6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch)
treea68f146d7fa01f0134297619fbe7e33db084e0aa /layout/painting
parentInitial commit. (diff)
downloadthunderbird-upstream.tar.xz
thunderbird-upstream.zip
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'layout/painting')
-rw-r--r--layout/painting/ActiveLayerTracker.cpp414
-rw-r--r--layout/painting/ActiveLayerTracker.h102
-rw-r--r--layout/painting/BorderCache.h67
-rw-r--r--layout/painting/BorderConsts.h22
-rw-r--r--layout/painting/DashedCornerFinder.cpp416
-rw-r--r--layout/painting/DashedCornerFinder.h274
-rw-r--r--layout/painting/DisplayItemClip.cpp486
-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.h301
-rw-r--r--layout/painting/DottedCornerFinder.cpp539
-rw-r--r--layout/painting/DottedCornerFinder.h432
-rw-r--r--layout/painting/HitTestInfo.cpp74
-rw-r--r--layout/painting/HitTestInfo.h59
-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.cpp1732
-rw-r--r--layout/painting/RetainedDisplayListBuilder.h287
-rw-r--r--layout/painting/RetainedDisplayListHelpers.h187
-rw-r--r--layout/painting/TransformClipNode.h138
-rw-r--r--layout/painting/WindowRenderer.cpp229
-rw-r--r--layout/painting/WindowRenderer.h284
-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/1667503-1.html16
-rw-r--r--layout/painting/crashtests/1713880-1.html10
-rw-r--r--layout/painting/crashtests/1714584-1.html5
-rw-r--r--layout/painting/crashtests/1717655-1.html19
-rw-r--r--layout/painting/crashtests/1763006-1.html23
-rw-r--r--layout/painting/crashtests/1819957-1.html14
-rw-r--r--layout/painting/crashtests/crashtests.list32
-rw-r--r--layout/painting/moz.build72
-rw-r--r--layout/painting/nsCSSRendering.cpp4944
-rw-r--r--layout/painting/nsCSSRendering.h924
-rw-r--r--layout/painting/nsCSSRenderingBorders.cpp3891
-rw-r--r--layout/painting/nsCSSRenderingBorders.h357
-rw-r--r--layout/painting/nsCSSRenderingGradients.cpp1299
-rw-r--r--layout/painting/nsCSSRenderingGradients.h125
-rw-r--r--layout/painting/nsDisplayItemTypes.h73
-rw-r--r--layout/painting/nsDisplayItemTypesList.h120
-rw-r--r--layout/painting/nsDisplayList.cpp8708
-rw-r--r--layout/painting/nsDisplayList.h6863
-rw-r--r--layout/painting/nsDisplayListArenaTypes.h15
-rw-r--r--layout/painting/nsDisplayListInvalidation.cpp124
-rw-r--r--layout/painting/nsDisplayListInvalidation.h236
-rw-r--r--layout/painting/nsImageRenderer.cpp1054
-rw-r--r--layout/painting/nsImageRenderer.h313
76 files changed, 36763 insertions, 0 deletions
diff --git a/layout/painting/ActiveLayerTracker.cpp b/layout/painting/ActiveLayerTracker.cpp
new file mode 100644
index 0000000000..fd36734c3c
--- /dev/null
+++ b/layout/painting/ActiveLayerTracker.cpp
@@ -0,0 +1,414 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ActiveLayerTracker.h"
+
+#include "mozilla/AnimationUtils.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/gfx/gfxVars.h"
+#include "mozilla/gfx/Matrix.h"
+#include "mozilla/EffectSet.h"
+#include "mozilla/MotionPathUtils.h"
+#include "mozilla/PodOperations.h"
+#include "mozilla/StaticPtr.h"
+#include "gfx2DGlue.h"
+#include "nsExpirationTracker.h"
+#include "nsContainerFrame.h"
+#include "nsIContent.h"
+#include "nsRefreshDriver.h"
+#include "nsPIDOMWindow.h"
+#include "mozilla/dom/Document.h"
+#include "nsAnimationManager.h"
+#include "nsStyleTransformMatrix.h"
+#include "nsTransitionManager.h"
+#include "nsDisplayList.h"
+#include "nsDOMCSSDeclaration.h"
+#include "nsLayoutUtils.h"
+
+namespace mozilla {
+
+using namespace gfx;
+
+/**
+ * This tracks the state of a frame that may need active layers due to
+ * ongoing content changes or style changes that indicate animation.
+ *
+ * When no changes of *any* kind are detected after 75-100ms we remove this
+ * object. Because we only track all kinds of activity with a single
+ * nsExpirationTracker, it's possible a frame might remain active somewhat
+ * spuriously if different kinds of changes kept happening, but that almost
+ * certainly doesn't matter.
+ */
+class LayerActivity {
+ public:
+ enum ActivityIndex {
+ ACTIVITY_OPACITY,
+ ACTIVITY_TRANSFORM,
+
+ ACTIVITY_SCALE,
+ ACTIVITY_TRIGGERED_REPAINT,
+
+ // keep as last item
+ ACTIVITY_COUNT
+ };
+
+ explicit LayerActivity(nsIFrame* aFrame) : mFrame(aFrame), mContent(nullptr) {
+ PodArrayZero(mRestyleCounts);
+ }
+ ~LayerActivity();
+ nsExpirationState* GetExpirationState() { return &mState; }
+ uint8_t& RestyleCountForProperty(nsCSSPropertyID aProperty) {
+ return mRestyleCounts[GetActivityIndexForProperty(aProperty)];
+ }
+
+ static ActivityIndex GetActivityIndexForProperty(nsCSSPropertyID aProperty) {
+ switch (aProperty) {
+ case eCSSProperty_opacity:
+ return ACTIVITY_OPACITY;
+ case eCSSProperty_transform:
+ case eCSSProperty_translate:
+ case eCSSProperty_rotate:
+ case eCSSProperty_scale:
+ case eCSSProperty_offset_path:
+ case eCSSProperty_offset_distance:
+ case eCSSProperty_offset_rotate:
+ case eCSSProperty_offset_anchor:
+ // TODO: Bug 1559232: Add offset-position.
+ return ACTIVITY_TRANSFORM;
+ default:
+ MOZ_ASSERT(false);
+ return ACTIVITY_OPACITY;
+ }
+ }
+
+ static ActivityIndex GetActivityIndexForPropertySet(
+ const nsCSSPropertyIDSet& aPropertySet) {
+ if (aPropertySet.IsSubsetOf(
+ nsCSSPropertyIDSet::TransformLikeProperties())) {
+ return ACTIVITY_TRANSFORM;
+ }
+ MOZ_ASSERT(
+ aPropertySet.IsSubsetOf(nsCSSPropertyIDSet::OpacityProperties()));
+ return ACTIVITY_OPACITY;
+ }
+
+ // While tracked, exactly one of mFrame or mContent is non-null, depending
+ // on whether this property is stored on a frame or on a content node.
+ // When this property is expired by the layer activity tracker, both mFrame
+ // and mContent are nulled-out and the property is deleted.
+ nsIFrame* mFrame;
+ nsIContent* mContent;
+
+ nsExpirationState mState;
+
+ // Previous scale due to the CSS transform property.
+ Maybe<MatrixScales> mPreviousTransformScale;
+
+ // Number of restyle operations detected
+ uint8_t mRestyleCounts[ACTIVITY_COUNT];
+};
+
+class LayerActivityTracker final
+ : public nsExpirationTracker<LayerActivity, 4> {
+ public:
+ // 75-100ms is a good timeout period. We use 4 generations of 25ms each.
+ enum { GENERATION_MS = 100 };
+
+ explicit LayerActivityTracker(nsIEventTarget* aEventTarget)
+ : nsExpirationTracker<LayerActivity, 4>(
+ GENERATION_MS, "LayerActivityTracker", aEventTarget) {}
+ ~LayerActivityTracker() override { AgeAllGenerations(); }
+
+ void NotifyExpired(LayerActivity* aObject) override;
+};
+
+static StaticAutoPtr<LayerActivityTracker> gLayerActivityTracker;
+
+LayerActivity::~LayerActivity() {
+ if (mFrame || mContent) {
+ NS_ASSERTION(gLayerActivityTracker, "Should still have a tracker");
+ gLayerActivityTracker->RemoveObject(this);
+ }
+}
+
+// Frames with this property have NS_FRAME_HAS_LAYER_ACTIVITY_PROPERTY set
+NS_DECLARE_FRAME_PROPERTY_DELETABLE(LayerActivityProperty, LayerActivity)
+
+void LayerActivityTracker::NotifyExpired(LayerActivity* aObject) {
+ RemoveObject(aObject);
+
+ nsIFrame* f = aObject->mFrame;
+ nsIContent* c = aObject->mContent;
+ aObject->mFrame = nullptr;
+ aObject->mContent = nullptr;
+
+ MOZ_ASSERT((f == nullptr) != (c == nullptr),
+ "A LayerActivity object should always have a reference to either "
+ "its frame or its content");
+
+ if (f) {
+ f->RemoveStateBits(NS_FRAME_HAS_LAYER_ACTIVITY_PROPERTY);
+ f->RemoveProperty(LayerActivityProperty());
+ } else {
+ c->RemoveProperty(nsGkAtoms::LayerActivity);
+ }
+}
+
+static LayerActivity* GetLayerActivity(nsIFrame* aFrame) {
+ if (!aFrame->HasAnyStateBits(NS_FRAME_HAS_LAYER_ACTIVITY_PROPERTY)) {
+ return nullptr;
+ }
+ return aFrame->GetProperty(LayerActivityProperty());
+}
+
+static LayerActivity* GetLayerActivityForUpdate(nsIFrame* aFrame) {
+ LayerActivity* layerActivity = GetLayerActivity(aFrame);
+ if (layerActivity) {
+ gLayerActivityTracker->MarkUsed(layerActivity);
+ } else {
+ if (!gLayerActivityTracker) {
+ gLayerActivityTracker =
+ new LayerActivityTracker(GetMainThreadSerialEventTarget());
+ }
+ layerActivity = new LayerActivity(aFrame);
+ gLayerActivityTracker->AddObject(layerActivity);
+ aFrame->AddStateBits(NS_FRAME_HAS_LAYER_ACTIVITY_PROPERTY);
+ aFrame->SetProperty(LayerActivityProperty(), layerActivity);
+ }
+ return layerActivity;
+}
+
+static void IncrementMutationCount(uint8_t* aCount) {
+ *aCount = uint8_t(std::min(0xFF, *aCount + 1));
+}
+
+/* static */
+void ActiveLayerTracker::TransferActivityToContent(nsIFrame* aFrame,
+ nsIContent* aContent) {
+ if (!aFrame->HasAnyStateBits(NS_FRAME_HAS_LAYER_ACTIVITY_PROPERTY)) {
+ return;
+ }
+ LayerActivity* layerActivity = aFrame->TakeProperty(LayerActivityProperty());
+ aFrame->RemoveStateBits(NS_FRAME_HAS_LAYER_ACTIVITY_PROPERTY);
+ if (!layerActivity) {
+ return;
+ }
+ layerActivity->mFrame = nullptr;
+ layerActivity->mContent = aContent;
+ aContent->SetProperty(nsGkAtoms::LayerActivity, layerActivity,
+ nsINode::DeleteProperty<LayerActivity>, true);
+}
+
+/* static */
+void ActiveLayerTracker::TransferActivityToFrame(nsIContent* aContent,
+ nsIFrame* aFrame) {
+ auto* layerActivity = static_cast<LayerActivity*>(
+ aContent->TakeProperty(nsGkAtoms::LayerActivity));
+ if (!layerActivity) {
+ return;
+ }
+ layerActivity->mContent = nullptr;
+ layerActivity->mFrame = aFrame;
+ aFrame->AddStateBits(NS_FRAME_HAS_LAYER_ACTIVITY_PROPERTY);
+ aFrame->SetProperty(LayerActivityProperty(), layerActivity);
+}
+
+static void IncrementScaleRestyleCountIfNeeded(nsIFrame* aFrame,
+ LayerActivity* aActivity) {
+ const nsStyleDisplay* display = aFrame->StyleDisplay();
+ if (!display->HasTransformProperty() && !display->HasIndividualTransform() &&
+ display->mOffsetPath.IsNone()) {
+ // The transform was removed.
+ aActivity->mPreviousTransformScale = Nothing();
+ IncrementMutationCount(
+ &aActivity->mRestyleCounts[LayerActivity::ACTIVITY_SCALE]);
+ return;
+ }
+
+ // Compute the new scale due to the CSS transform property.
+ // Note: Motion path doesn't contribute to scale factor. (It only has 2d
+ // translate and 2d rotate, so we use Nothing() for it.)
+ nsStyleTransformMatrix::TransformReferenceBox refBox(aFrame);
+ Matrix4x4 transform = nsStyleTransformMatrix::ReadTransforms(
+ display->mTranslate, display->mRotate, display->mScale, Nothing(),
+ display->mTransform, refBox, AppUnitsPerCSSPixel());
+ Matrix transform2D;
+ if (!transform.Is2D(&transform2D)) {
+ // We don't attempt to handle 3D transforms; just assume the scale changed.
+ aActivity->mPreviousTransformScale = Nothing();
+ IncrementMutationCount(
+ &aActivity->mRestyleCounts[LayerActivity::ACTIVITY_SCALE]);
+ return;
+ }
+
+ MatrixScales scale = transform2D.ScaleFactors();
+ if (aActivity->mPreviousTransformScale == Some(scale)) {
+ return; // Nothing changed.
+ }
+
+ aActivity->mPreviousTransformScale = Some(scale);
+ IncrementMutationCount(
+ &aActivity->mRestyleCounts[LayerActivity::ACTIVITY_SCALE]);
+}
+
+/* static */
+void ActiveLayerTracker::NotifyRestyle(nsIFrame* aFrame,
+ nsCSSPropertyID aProperty) {
+ LayerActivity* layerActivity = GetLayerActivityForUpdate(aFrame);
+ uint8_t& mutationCount = layerActivity->RestyleCountForProperty(aProperty);
+ IncrementMutationCount(&mutationCount);
+
+ if (nsCSSPropertyIDSet::TransformLikeProperties().HasProperty(aProperty)) {
+ IncrementScaleRestyleCountIfNeeded(aFrame, layerActivity);
+ }
+}
+
+static bool IsPresContextInScriptAnimationCallback(
+ nsPresContext* aPresContext) {
+ if (aPresContext->RefreshDriver()->IsInRefresh()) {
+ return true;
+ }
+ // Treat timeouts/setintervals as scripted animation callbacks for our
+ // purposes.
+ nsPIDOMWindowInner* win = aPresContext->Document()->GetInnerWindow();
+ return win && win->IsRunningTimeout();
+}
+
+/* static */
+void ActiveLayerTracker::NotifyInlineStyleRuleModified(
+ nsIFrame* aFrame, nsCSSPropertyID aProperty) {
+ if (IsPresContextInScriptAnimationCallback(aFrame->PresContext())) {
+ LayerActivity* layerActivity = GetLayerActivityForUpdate(aFrame);
+ // We know this is animated, so just hack the mutation count.
+ layerActivity->RestyleCountForProperty(aProperty) = 0xff;
+ }
+}
+
+/* static */
+void ActiveLayerTracker::NotifyNeedsRepaint(nsIFrame* aFrame) {
+ LayerActivity* layerActivity = GetLayerActivityForUpdate(aFrame);
+ if (IsPresContextInScriptAnimationCallback(aFrame->PresContext())) {
+ // This is mirroring NotifyInlineStyleRuleModified's NotifyAnimated logic.
+ // Just max out the restyle count if we're in an animation callback.
+ layerActivity->mRestyleCounts[LayerActivity::ACTIVITY_TRIGGERED_REPAINT] =
+ 0xFF;
+ } else {
+ IncrementMutationCount(
+ &layerActivity
+ ->mRestyleCounts[LayerActivity::ACTIVITY_TRIGGERED_REPAINT]);
+ }
+}
+
+static bool IsMotionPathAnimated(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame) {
+ return ActiveLayerTracker::IsStyleAnimated(
+ aBuilder, aFrame, nsCSSPropertyIDSet{eCSSProperty_offset_path}) ||
+ (!aFrame->StyleDisplay()->mOffsetPath.IsNone() &&
+ ActiveLayerTracker::IsStyleAnimated(
+ aBuilder, aFrame,
+ nsCSSPropertyIDSet{eCSSProperty_offset_distance,
+ eCSSProperty_offset_rotate,
+ eCSSProperty_offset_anchor}));
+}
+
+/* static */
+bool ActiveLayerTracker::IsTransformAnimated(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame) {
+ return IsStyleAnimated(aBuilder, aFrame,
+ nsCSSPropertyIDSet::CSSTransformProperties()) ||
+ IsMotionPathAnimated(aBuilder, aFrame);
+}
+
+/* static */
+bool ActiveLayerTracker::IsTransformMaybeAnimated(nsIFrame* aFrame) {
+ return IsStyleAnimated(nullptr, aFrame,
+ nsCSSPropertyIDSet::CSSTransformProperties()) ||
+ IsMotionPathAnimated(nullptr, aFrame);
+}
+
+/* static */
+bool ActiveLayerTracker::IsStyleAnimated(
+ nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ const nsCSSPropertyIDSet& aPropertySet) {
+ MOZ_ASSERT(
+ aPropertySet.IsSubsetOf(nsCSSPropertyIDSet::TransformLikeProperties()) ||
+ aPropertySet.IsSubsetOf(nsCSSPropertyIDSet::OpacityProperties()),
+ "Only subset of opacity or transform-like properties set calls this");
+
+ // For display:table content, transforms are applied to the table wrapper
+ // (primary frame) but their will-change style will be specified on the style
+ // frame and, unlike other transform properties, not inherited.
+ // As a result, for transform properties only we need to be careful to look up
+ // the will-change style on the _style_ frame.
+ const nsIFrame* styleFrame = nsLayoutUtils::GetStyleFrame(aFrame);
+ const nsCSSPropertyIDSet transformSet =
+ nsCSSPropertyIDSet::TransformLikeProperties();
+ if ((styleFrame && (styleFrame->StyleDisplay()->mWillChange.bits &
+ StyleWillChangeBits::TRANSFORM)) &&
+ aPropertySet.Intersects(transformSet) &&
+ (!aBuilder ||
+ aBuilder->IsInWillChangeBudget(aFrame, aFrame->GetSize()))) {
+ return true;
+ }
+ if ((aFrame->StyleDisplay()->mWillChange.bits &
+ StyleWillChangeBits::OPACITY) &&
+ aPropertySet.Intersects(nsCSSPropertyIDSet::OpacityProperties()) &&
+ (!aBuilder ||
+ aBuilder->IsInWillChangeBudget(aFrame, aFrame->GetSize()))) {
+ return !StaticPrefs::gfx_will_change_ignore_opacity();
+ }
+
+ LayerActivity* layerActivity = GetLayerActivity(aFrame);
+ if (layerActivity) {
+ LayerActivity::ActivityIndex activityIndex =
+ LayerActivity::GetActivityIndexForPropertySet(aPropertySet);
+ if (layerActivity->mRestyleCounts[activityIndex] >= 2) {
+ // If the frame needs to be repainted frequently, we probably don't get
+ // much from treating the property as animated, *unless* this frame's
+ // 'scale' (which includes the bounds changes of a rotation) is changing.
+ // Marking a scaling transform as animating allows us to avoid resizing
+ // the texture, even if we have to repaint the contents of that texture.
+ if (layerActivity
+ ->mRestyleCounts[LayerActivity::ACTIVITY_TRIGGERED_REPAINT] <
+ 2 ||
+ (aPropertySet.Intersects(transformSet) &&
+ IsScaleSubjectToAnimation(aFrame))) {
+ return true;
+ }
+ }
+ }
+
+ if (nsLayoutUtils::HasEffectiveAnimation(aFrame, aPropertySet)) {
+ return true;
+ }
+
+ if (!aPropertySet.Intersects(transformSet) ||
+ !aFrame->Combines3DTransformWithAncestors()) {
+ return false;
+ }
+
+ // For preserve-3d, we check if there is any transform animation on its parent
+ // frames in the 3d rendering context. If there is one, this function will
+ // return true.
+ return IsStyleAnimated(aBuilder, aFrame->GetParent(), aPropertySet);
+}
+
+/* static */
+bool ActiveLayerTracker::IsScaleSubjectToAnimation(nsIFrame* aFrame) {
+ // Check whether JavaScript is animating this frame's scale.
+ LayerActivity* layerActivity = GetLayerActivity(aFrame);
+ if (layerActivity &&
+ layerActivity->mRestyleCounts[LayerActivity::ACTIVITY_SCALE] >= 2) {
+ return true;
+ }
+
+ return AnimationUtils::FrameHasAnimatedScale(aFrame);
+}
+
+/* static */
+void ActiveLayerTracker::Shutdown() { gLayerActivityTracker = nullptr; }
+
+} // namespace mozilla
diff --git a/layout/painting/ActiveLayerTracker.h b/layout/painting/ActiveLayerTracker.h
new file mode 100644
index 0000000000..7a6005f284
--- /dev/null
+++ b/layout/painting/ActiveLayerTracker.h
@@ -0,0 +1,102 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef ACTIVELAYERTRACKER_H_
+#define ACTIVELAYERTRACKER_H_
+
+#include "nsCSSPropertyID.h"
+
+class nsIFrame;
+class nsIContent;
+class nsCSSPropertyIDSet;
+class nsDOMCSSDeclaration;
+
+namespace mozilla {
+
+class nsDisplayListBuilder;
+
+/**
+ * This class receives various notifications about style changes and content
+ * changes that affect layerization decisions, and implements the heuristics
+ * that drive those decisions. It manages per-frame state to support those
+ * heuristics.
+ */
+class ActiveLayerTracker {
+ public:
+ static void Shutdown();
+
+ /*
+ * We track style changes to selected styles:
+ * eCSSProperty_transform, eCSSProperty_translate,
+ * eCSSProperty_rotate, eCSSProperty_scale
+ * eCSSProperty_offset_path, eCSSProperty_offset_distance,
+ * eCSSProperty_offset_rotate, eCSSProperty_offset_anchor,
+ * eCSSProperty_opacity
+ * and use that information to guess whether style changes are animated.
+ */
+
+ /**
+ * Notify aFrame's style property as having changed due to a restyle,
+ * and therefore possibly wanting an active layer to render that style.
+ * Any such marking will time out after a short period.
+ * @param aProperty the property that has changed
+ */
+ static void NotifyRestyle(nsIFrame* aFrame, nsCSSPropertyID aProperty);
+
+ /**
+ * Notify that a property in the inline style rule of aFrame's element
+ * has been modified.
+ */
+ static void NotifyInlineStyleRuleModified(nsIFrame* aFrame,
+ nsCSSPropertyID aProperty);
+ /**
+ * Notify that a frame needs to be repainted. This is important for layering
+ * decisions where, say, aFrame's transform is updated from JS, but we need
+ * to repaint aFrame anyway, so we get no benefit from giving it its own
+ * layer.
+ */
+ static void NotifyNeedsRepaint(nsIFrame* aFrame);
+ /**
+ * Return true if aFrame's property style in |aPropertySet| should be
+ * considered as being animated for constructing active layers.
+ */
+ static bool IsStyleAnimated(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ const nsCSSPropertyIDSet& aPropertySet);
+ /**
+ * Return true if aFrame's transform-like property,
+ * i.e. transform/translate/rotate/scale, is animated.
+ */
+ static bool IsTransformAnimated(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame);
+ /**
+ * Return true if aFrame's transform style should be considered as being
+ * animated for pre-rendering.
+ */
+ static bool IsTransformMaybeAnimated(nsIFrame* aFrame);
+ /**
+ * Return true if aFrame either has an animated scale now, or is likely to
+ * have one in the future because it has a CSS animation or transition
+ * (which may not be playing right now) that affects its scale.
+ */
+ static bool IsScaleSubjectToAnimation(nsIFrame* aFrame);
+
+ /**
+ * Transfer the LayerActivity property to the frame's content node when the
+ * frame is about to be destroyed so that layer activity can be tracked
+ * throughout reframes of an element. Only call this when aFrame is the
+ * primary frame of aContent.
+ */
+ static void TransferActivityToContent(nsIFrame* aFrame, nsIContent* aContent);
+ /**
+ * Transfer the LayerActivity property back to the content node's primary
+ * frame after the frame has been created.
+ */
+ static void TransferActivityToFrame(nsIContent* aContent, nsIFrame* aFrame);
+};
+
+} // namespace mozilla
+
+#endif /* ACTIVELAYERTRACKER_H_ */
diff --git a/layout/painting/BorderCache.h b/layout/painting/BorderCache.h
new file mode 100644
index 0000000000..bb6bf7e4ed
--- /dev/null
+++ b/layout/painting/BorderCache.h
@@ -0,0 +1,67 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_BorderCache_h_
+#define mozilla_BorderCache_h_
+
+#include "mozilla/gfx/2D.h"
+#include "mozilla/HashFunctions.h"
+#include "PLDHashTable.h"
+
+namespace mozilla {
+// Cache for best overlap and best dashLength.
+
+struct FourFloats {
+ typedef mozilla::gfx::Float Float;
+
+ Float n[4];
+
+ FourFloats() {
+ n[0] = 0.0f;
+ n[1] = 0.0f;
+ n[2] = 0.0f;
+ n[3] = 0.0f;
+ }
+
+ FourFloats(Float a, Float b, Float c, Float d) {
+ n[0] = a;
+ n[1] = b;
+ n[2] = c;
+ n[3] = d;
+ }
+
+ bool operator==(const FourFloats& aOther) const {
+ return n[0] == aOther.n[0] && n[1] == aOther.n[1] && n[2] == aOther.n[2] &&
+ n[3] == aOther.n[3];
+ }
+};
+
+class FourFloatsHashKey : public PLDHashEntryHdr {
+ public:
+ typedef const FourFloats& KeyType;
+ typedef const FourFloats* KeyTypePointer;
+
+ explicit FourFloatsHashKey(KeyTypePointer aKey) : mValue(*aKey) {}
+ FourFloatsHashKey(const FourFloatsHashKey& aToCopy)
+ : mValue(aToCopy.mValue) {}
+ ~FourFloatsHashKey() = default;
+
+ KeyType GetKey() const { return mValue; }
+ bool KeyEquals(KeyTypePointer aKey) const { return *aKey == mValue; }
+
+ static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; }
+ static PLDHashNumber HashKey(KeyTypePointer aKey) {
+ return HashBytes(aKey->n, sizeof(mozilla::gfx::Float) * 4);
+ }
+ enum { ALLOW_MEMMOVE = true };
+
+ private:
+ const FourFloats mValue;
+};
+
+} // namespace mozilla
+
+#endif /* mozilla_BorderCache_h_ */
diff --git a/layout/painting/BorderConsts.h b/layout/painting/BorderConsts.h
new file mode 100644
index 0000000000..e25c697101
--- /dev/null
+++ b/layout/painting/BorderConsts.h
@@ -0,0 +1,22 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_BorderConsts_h_
+#define mozilla_BorderConsts_h_
+
+// thickness of dashed line relative to dotted line
+#define DOT_LENGTH 1 // square
+#define DASH_LENGTH 3 // 3 times longer than dot
+
+#define C_TL mozilla::eCornerTopLeft
+#define C_TR mozilla::eCornerTopRight
+#define C_BR mozilla::eCornerBottomRight
+#define C_BL mozilla::eCornerBottomLeft
+
+#define BORDER_SEGMENT_COUNT_MAX 100
+#define BORDER_DOTTED_CORNER_MAX_RADIUS 100000
+
+#endif /* mozilla_BorderConsts_h_ */
diff --git a/layout/painting/DashedCornerFinder.cpp b/layout/painting/DashedCornerFinder.cpp
new file mode 100644
index 0000000000..c892179df9
--- /dev/null
+++ b/layout/painting/DashedCornerFinder.cpp
@@ -0,0 +1,416 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "DashedCornerFinder.h"
+
+#include <utility>
+
+#include "BorderCache.h"
+#include "BorderConsts.h"
+#include "nsTHashMap.h"
+
+namespace mozilla {
+
+using namespace gfx;
+
+struct BestDashLength {
+ typedef mozilla::gfx::Float Float;
+
+ Float dashLength;
+ size_t count;
+
+ BestDashLength() : dashLength(0.0f), count(0) {}
+
+ BestDashLength(Float aDashLength, size_t aCount)
+ : dashLength(aDashLength), count(aCount) {}
+};
+
+static const size_t DashedCornerCacheSize = 256;
+nsTHashMap<FourFloatsHashKey, BestDashLength> DashedCornerCache;
+
+DashedCornerFinder::DashedCornerFinder(const Bezier& aOuterBezier,
+ const Bezier& aInnerBezier,
+ Float aBorderWidthH, Float aBorderWidthV,
+ const Size& aCornerDim)
+ : mOuterBezier(aOuterBezier),
+ mInnerBezier(aInnerBezier),
+ mLastOuterP(aOuterBezier.mPoints[0]),
+ mLastInnerP(aInnerBezier.mPoints[0]),
+ mLastOuterT(0.0f),
+ mLastInnerT(0.0f),
+ mBestDashLength(DOT_LENGTH * DASH_LENGTH),
+ mHasZeroBorderWidth(false),
+ mHasMore(true),
+ mMaxCount(aCornerDim.width + aCornerDim.height),
+ mType(OTHER),
+ mI(0),
+ mCount(0) {
+ NS_ASSERTION(aBorderWidthH > 0.0f || aBorderWidthV > 0.0f,
+ "At least one side should have non-zero width.");
+
+ DetermineType(aBorderWidthH, aBorderWidthV);
+
+ Reset();
+}
+
+void DashedCornerFinder::DetermineType(Float aBorderWidthH,
+ Float aBorderWidthV) {
+ if (aBorderWidthH < aBorderWidthV) {
+ // Always draw from wider side to thinner side.
+ std::swap(mInnerBezier.mPoints[0], mInnerBezier.mPoints[3]);
+ std::swap(mInnerBezier.mPoints[1], mInnerBezier.mPoints[2]);
+ std::swap(mOuterBezier.mPoints[0], mOuterBezier.mPoints[3]);
+ std::swap(mOuterBezier.mPoints[1], mOuterBezier.mPoints[2]);
+ mLastOuterP = mOuterBezier.mPoints[0];
+ mLastInnerP = mInnerBezier.mPoints[0];
+ }
+
+ // See the comment at mType declaration for each condition.
+
+ Float borderRadiusA =
+ fabs(mOuterBezier.mPoints[0].x - mOuterBezier.mPoints[3].x);
+ Float borderRadiusB =
+ fabs(mOuterBezier.mPoints[0].y - mOuterBezier.mPoints[3].y);
+ if (aBorderWidthH == aBorderWidthV && borderRadiusA == borderRadiusB &&
+ borderRadiusA > aBorderWidthH * 2.0f) {
+ Float curveHeight = borderRadiusA - aBorderWidthH / 2.0;
+
+ mType = PERFECT;
+ Float borderLength = M_PI * curveHeight / 2.0f;
+
+ Float dashWidth = aBorderWidthH * DOT_LENGTH * DASH_LENGTH;
+ size_t count = ceil(borderLength / dashWidth);
+ if (count % 2) {
+ count++;
+ }
+ mCount = count / 2 + 1;
+ mBestDashLength = borderLength / (aBorderWidthH * count);
+ }
+
+ Float minBorderWidth = std::min(aBorderWidthH, aBorderWidthV);
+ if (minBorderWidth == 0.0f) {
+ mHasZeroBorderWidth = true;
+ }
+
+ if (mType == OTHER && !mHasZeroBorderWidth) {
+ Float minBorderRadius = std::min(borderRadiusA, borderRadiusB);
+ Float maxBorderRadius = std::max(borderRadiusA, borderRadiusB);
+ Float maxBorderWidth = std::max(aBorderWidthH, aBorderWidthV);
+
+ FindBestDashLength(minBorderWidth, maxBorderWidth, minBorderRadius,
+ maxBorderRadius);
+ }
+}
+
+bool DashedCornerFinder::HasMore(void) const {
+ if (mHasZeroBorderWidth) {
+ return mI < mMaxCount && mHasMore;
+ }
+
+ return mI < mCount;
+}
+
+DashedCornerFinder::Result DashedCornerFinder::Next(void) {
+ Float lastOuterT, lastInnerT, outerT, innerT;
+
+ if (mI == 0) {
+ lastOuterT = 0.0f;
+ lastInnerT = 0.0f;
+ } else {
+ if (mType == PERFECT) {
+ lastOuterT = lastInnerT = (mI * 2.0f - 0.5f) / ((mCount - 1) * 2.0f);
+ } else {
+ Float last2OuterT = mLastOuterT;
+ Float last2InnerT = mLastInnerT;
+
+ (void)FindNext(mBestDashLength);
+
+ //
+ // mLastOuterT lastOuterT
+ // | |
+ // v v
+ // +---+---+---+---+ <- last2OuterT
+ // | |###|###| |
+ // | |###|###| |
+ // | |###|###| |
+ // +---+---+---+---+ <- last2InnerT
+ // ^ ^
+ // | |
+ // mLastInnerT lastInnerT
+ lastOuterT = (mLastOuterT + last2OuterT) / 2.0f;
+ lastInnerT = (mLastInnerT + last2InnerT) / 2.0f;
+ }
+ }
+
+ if ((!mHasZeroBorderWidth && mI == mCount - 1) ||
+ (mHasZeroBorderWidth && !mHasMore)) {
+ outerT = 1.0f;
+ innerT = 1.0f;
+ } else {
+ if (mType == PERFECT) {
+ outerT = innerT = (mI * 2.0f + 0.5f) / ((mCount - 1) * 2.0f);
+ } else {
+ Float last2OuterT = mLastOuterT;
+ Float last2InnerT = mLastInnerT;
+
+ (void)FindNext(mBestDashLength);
+
+ //
+ // outerT last2OuterT
+ // | |
+ // v v
+ // mLastOuterT -> +---+---+---+---+
+ // | |###|###| |
+ // | |###|###| |
+ // | |###|###| |
+ // mLastInnerT -> +---+---+---+---+
+ // ^ ^
+ // | |
+ // innerT last2InnerT
+ outerT = (mLastOuterT + last2OuterT) / 2.0f;
+ innerT = (mLastInnerT + last2InnerT) / 2.0f;
+ }
+ }
+
+ mI++;
+
+ Bezier outerSectionBezier;
+ Bezier innerSectionBezier;
+ GetSubBezier(&outerSectionBezier, mOuterBezier, lastOuterT, outerT);
+ GetSubBezier(&innerSectionBezier, mInnerBezier, lastInnerT, innerT);
+ return DashedCornerFinder::Result(outerSectionBezier, innerSectionBezier);
+}
+
+void DashedCornerFinder::Reset(void) {
+ mLastOuterP = mOuterBezier.mPoints[0];
+ mLastInnerP = mInnerBezier.mPoints[0];
+ mLastOuterT = 0.0f;
+ mLastInnerT = 0.0f;
+ mHasMore = true;
+}
+
+Float DashedCornerFinder::FindNext(Float dashLength) {
+ Float upper = 1.0f;
+ Float lower = mLastOuterT;
+
+ Point OuterP, InnerP;
+ // Start from upper bound to check if this is the last segment.
+ Float outerT = upper;
+ Float innerT;
+ Float W = 0.0f;
+ Float L = 0.0f;
+
+ const Float LENGTH_MARGIN = 0.1f;
+ for (size_t i = 0; i < MAX_LOOP; i++) {
+ OuterP = GetBezierPoint(mOuterBezier, outerT);
+ InnerP = FindBezierNearestPoint(mInnerBezier, OuterP, outerT, &innerT);
+
+ // Calculate approximate dash length.
+ //
+ // W = (W1 + W2) / 2
+ // L = (OuterL + InnerL) / 2
+ // dashLength = L / W
+ //
+ // ____----+----____
+ // OuterP ___--- | ---___ mLastOuterP
+ // +--- | ---+
+ // | | |
+ // | | |
+ // | W | W1 |
+ // | | |
+ // W2 | | |
+ // | | ______------+
+ // | ____+---- mLastInnerP
+ // | ___---
+ // | __---
+ // +--
+ // InnerP
+ // OuterL
+ // ____---------____
+ // OuterP ___--- ---___ mLastOuterP
+ // +--- ---+
+ // | L |
+ // | ___----------______ |
+ // | __--- -----+
+ // | __-- |
+ // +-- |
+ // | InnerL ______------+
+ // | ____----- mLastInnerP
+ // | ___---
+ // | __---
+ // +--
+ // InnerP
+ Float W1 = (mLastOuterP - mLastInnerP).Length();
+ Float W2 = (OuterP - InnerP).Length();
+ Float OuterL = GetBezierLength(mOuterBezier, mLastOuterT, outerT);
+ Float InnerL = GetBezierLength(mInnerBezier, mLastInnerT, innerT);
+ W = (W1 + W2) / 2.0f;
+ L = (OuterL + InnerL) / 2.0f;
+ if (L > W * dashLength + LENGTH_MARGIN) {
+ if (i > 0) {
+ upper = outerT;
+ }
+ } else if (L < W * dashLength - LENGTH_MARGIN) {
+ if (i == 0) {
+ // This is the last segment with shorter dashLength.
+ mHasMore = false;
+ break;
+ }
+ lower = outerT;
+ } else {
+ break;
+ }
+
+ outerT = (upper + lower) / 2.0f;
+ }
+
+ mLastOuterP = OuterP;
+ mLastInnerP = InnerP;
+ mLastOuterT = outerT;
+ mLastInnerT = innerT;
+
+ if (W == 0.0f) {
+ return 1.0f;
+ }
+
+ return L / W;
+}
+
+void DashedCornerFinder::FindBestDashLength(Float aMinBorderWidth,
+ Float aMaxBorderWidth,
+ Float aMinBorderRadius,
+ Float aMaxBorderRadius) {
+ // If dashLength is not calculateable, find it with binary search,
+ // such that there exists i that OuterP_i == OuterP_n and
+ // InnerP_i == InnerP_n with given dashLength.
+
+ FourFloats key(aMinBorderWidth, aMaxBorderWidth, aMinBorderRadius,
+ aMaxBorderRadius);
+ BestDashLength best;
+ if (DashedCornerCache.Get(key, &best)) {
+ mCount = best.count;
+ mBestDashLength = best.dashLength;
+ return;
+ }
+
+ Float lower = 1.0f;
+ Float upper = DOT_LENGTH * DASH_LENGTH;
+ Float dashLength = upper;
+ size_t targetCount = 0;
+
+ const Float LENGTH_MARGIN = 0.1f;
+ for (size_t j = 0; j < MAX_LOOP; j++) {
+ size_t count;
+ Float actualDashLength;
+ if (!GetCountAndLastDashLength(dashLength, &count, &actualDashLength)) {
+ if (j == 0) {
+ mCount = mMaxCount;
+ break;
+ }
+ }
+
+ if (j == 0) {
+ if (count == 1) {
+ // If only 1 segment fits, fill entire region
+ //
+ // count = 1
+ // mCount = 1
+ // | 1 |
+ // +---+---+
+ // |###|###|
+ // |###|###|
+ // |###|###|
+ // +---+---+
+ // 1
+ mCount = 1;
+ break;
+ }
+
+ // targetCount should be 2n.
+ //
+ // targetCount = 2
+ // mCount = 2
+ // | 1 | 2 |
+ // +---+---+---+---+
+ // |###| | |###|
+ // |###| | |###|
+ // |###| | |###|
+ // +---+---+---+---+
+ // 1 2
+ //
+ // targetCount = 6
+ // mCount = 4
+ // | 1 | 2 | 3 | 4 | 5 | 6 |
+ // +---+---+---+---+---+---+---+---+---+---+---+---+
+ // |###| | |###|###| | |###|###| | |###|
+ // |###| | |###|###| | |###|###| | |###|
+ // |###| | |###|###| | |###|###| | |###|
+ // +---+---+---+---+---+---+---+---+---+---+---+---+
+ // 1 2 3 4
+ if (count % 2) {
+ targetCount = count + 1;
+ } else {
+ targetCount = count;
+ }
+
+ mCount = targetCount / 2 + 1;
+ }
+
+ if (count == targetCount) {
+ mBestDashLength = dashLength;
+
+ // actualDashLength won't be greater than dashLength.
+ if (actualDashLength > dashLength - LENGTH_MARGIN) {
+ break;
+ }
+
+ // We started from upper bound, no need to update range when j == 0.
+ if (j > 0) {
+ upper = dashLength;
+ }
+ } else {
+ // |j == 0 && count != targetCount| means that |targetCount = count + 1|,
+ // and we started from upper bound, no need to update range when j == 0.
+ if (j > 0) {
+ if (count > targetCount) {
+ lower = dashLength;
+ } else {
+ upper = dashLength;
+ }
+ }
+ }
+
+ dashLength = (upper + lower) / 2.0f;
+ }
+
+ if (DashedCornerCache.Count() > DashedCornerCacheSize) {
+ DashedCornerCache.Clear();
+ }
+ DashedCornerCache.InsertOrUpdate(key,
+ BestDashLength(mBestDashLength, mCount));
+}
+
+bool DashedCornerFinder::GetCountAndLastDashLength(Float aDashLength,
+ size_t* aCount,
+ Float* aActualDashLength) {
+ // Return the number of segments and the last segment's dashLength for
+ // the given dashLength.
+
+ Reset();
+
+ for (size_t i = 0; i < mMaxCount; i++) {
+ Float actualDashLength = FindNext(aDashLength);
+ if (mLastOuterT >= 1.0f) {
+ *aCount = i + 1;
+ *aActualDashLength = actualDashLength;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+} // namespace mozilla
diff --git a/layout/painting/DashedCornerFinder.h b/layout/painting/DashedCornerFinder.h
new file mode 100644
index 0000000000..3a409d19f7
--- /dev/null
+++ b/layout/painting/DashedCornerFinder.h
@@ -0,0 +1,274 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_DashedCornerFinder_h_
+#define mozilla_DashedCornerFinder_h_
+
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/BezierUtils.h"
+
+namespace mozilla {
+
+// Calculate {OuterT_i, InnerT_i} for each 1 < i < n, that
+// (OuterL_i + InnerL_i) / 2 == dashLength * (W_i + W_{i-1}) / 2
+// where
+// OuterP_i: OuterCurve(OuterT_i)
+// InnerP_i: InnerCurve(OuterT_i)
+// OuterL_i: Elliptic arc length between OuterP_i - OuterP_{i-1}
+// InnerL_i: Elliptic arc length between InnerP_i - InnerP_{i-1}
+// W_i = |OuterP_i - InnerP_i|
+// 1.0 < dashLength < 3.0
+//
+// OuterP_1 OuterP_0
+// _+__-----------+ OuterCurve
+// OuterP_2 __---- | OuterL_1 |
+// __+--- | |
+// __--- | OuterL_2 | |
+// OuterP_3 _-- | | W_1 | W_0
+// _+ | | |
+// / \ W_2 | | |
+// / \ | | InnerL_1 |
+// | \ | InnerL_2|____-------+ InnerCurve
+// | \ |____----+ InnerP_0
+// | . \ __---+ InnerP_1
+// | \ / InnerP_2
+// | . /+ InnerP_3
+// | |
+// | . |
+// | |
+// | |
+// | |
+// OuterP_{n-1} +--------+ InnerP_{n-1}
+// | |
+// | |
+// | |
+// | |
+// | |
+// OuterP_n +--------+ InnerP_n
+//
+// Returns region with [OuterCurve((OuterT_{2j} + OuterT_{2j-1}) / 2),
+// OuterCurve((OuterT_{2j} + OuterT_{2j-1}) / 2),
+// InnerCurve((OuterT_{2j} + OuterT_{2j+1}) / 2),
+// InnerCurve((OuterT_{2j} + OuterT_{2j+1}) / 2)],
+// to start and end with half segment.
+//
+// _+__----+------+ OuterCurve
+// _+---- | |######|
+// __+---#| | |######|
+// _+---##|####| | |######|
+// _-- |#####|#####| | |#####|
+// _+ |#####|#####| | |#####|
+// / \ |#####|####| | |#####|
+// / \ |####|#####| | |#####|
+// | \ |####|####| |____-+-----+ InnerCurve
+// | \ |####|____+---+
+// | . \ __+---+
+// | \ /
+// | . /+
+// | |
+// | . |
+// | |
+// | |
+// | |
+// +--------+
+// | |
+// | |
+// +--------+
+// |########|
+// |########|
+// +--------+
+
+class DashedCornerFinder {
+ typedef mozilla::gfx::Bezier Bezier;
+ typedef mozilla::gfx::Float Float;
+ typedef mozilla::gfx::Point Point;
+ typedef mozilla::gfx::Size Size;
+
+ public:
+ struct Result {
+ // Control points for the outer curve and the inner curve.
+ //
+ // outerSectionBezier
+ // |
+ // v _+ 3
+ // ___---#|
+ // 0 +---#######|
+ // |###########|
+ // |###########|
+ // |##########|
+ // |##########|
+ // |#########|
+ // |#####____+ 3
+ // 0 +----
+ // ^
+ // |
+ // innerSectionBezier
+ Bezier outerSectionBezier;
+ Bezier innerSectionBezier;
+
+ Result(const Bezier& aOuterSectionBezier, const Bezier& aInnerSectionBezier)
+ : outerSectionBezier(aOuterSectionBezier),
+ innerSectionBezier(aInnerSectionBezier) {}
+ };
+
+ // aCornerDim.width
+ // |<----------------->|
+ // | |
+ // --+-------------___---+--
+ // ^ | __-- | ^
+ // | | _- | |
+ // | | / | | aBorderWidthH
+ // | | / | |
+ // | | | | v
+ // | | | __--+--
+ // aCornerDim.height | || _-
+ // | || /
+ // | | /
+ // | | |
+ // | | |
+ // | | |
+ // | | |
+ // v | |
+ // --+---------+
+ // | |
+ // |<------->|
+ // aBorderWidthV
+ DashedCornerFinder(const Bezier& aOuterBezier, const Bezier& aInnerBezier,
+ Float aBorderWidthH, Float aBorderWidthV,
+ const Size& aCornerDim);
+
+ bool HasMore(void) const;
+ Result Next(void);
+
+ private:
+ static const size_t MAX_LOOP = 32;
+
+ // Bezier control points for the outer curve and the inner curve.
+ //
+ // ___---+ outer curve
+ // __-- |
+ // _- |
+ // / |
+ // / |
+ // | |
+ // | __--+ inner curve
+ // | _-
+ // | /
+ // | /
+ // | |
+ // | |
+ // | |
+ // | |
+ // | |
+ // +---------+
+ Bezier mOuterBezier;
+ Bezier mInnerBezier;
+
+ Point mLastOuterP;
+ Point mLastInnerP;
+ Float mLastOuterT;
+ Float mLastInnerT;
+
+ // Length for each segment, ratio of the border width at that point.
+ Float mBestDashLength;
+
+ // If one of border-widths is 0, do not calculate mBestDashLength, and draw
+ // segments until it reaches the other side or exceeds mMaxCount.
+ bool mHasZeroBorderWidth;
+ bool mHasMore;
+
+ // The maximum number of segments.
+ size_t mMaxCount;
+
+ enum {
+ // radius.width
+ // |<----------------->|
+ // | |
+ // --+-------------___---+--
+ // ^ | __-- | ^
+ // | | _- | |
+ // | | / + | top-width
+ // | | / | |
+ // | | | | v
+ // | | | __--+--
+ // radius.height | || _-
+ // | || /
+ // | | /
+ // | | |
+ // | | |
+ // | | |
+ // | | |
+ // v | |
+ // --+----+----+
+ // | |
+ // |<------->|
+ // left-width
+
+ // * top-width == left-width
+ // * radius.width == radius.height
+ // * top-width < radius.width * 2
+ //
+ // Split the perfect circle's arc into 2n segments, each segment's length is
+ // top-width * dashLength. Then split the inner curve and the outer curve
+ // with same angles.
+ //
+ // radius.width
+ // |<---------------------->|
+ // | | v
+ // --+------------------------+--
+ // ^ | | | top-width / 2
+ // | | perfect | |
+ // | | circle's ___---+--
+ // | | arc __-+ | ^
+ // | | | _- | |
+ // radius.height | | | + | +--
+ // | | | / \ | |
+ // | | | | \ | |
+ // | | | | \ | |
+ // | | +->| \ | |
+ // | | +---__ \ | |
+ // | | | --__ \ | |
+ // | | | ---__ \ | |
+ // v | | --_\||
+ // --+----+----+--------------+
+ // | | |
+ // |<-->| |
+ // left-width / 2
+ PERFECT,
+
+ // Other cases.
+ //
+ // Split the outer curve and the inner curve into 2n segments, each segment
+ // satisfies following:
+ // (OuterL_i + InnerL_i) / 2 == dashLength * (W_i + W_{i-1}) / 2
+ OTHER
+ } mType;
+
+ size_t mI;
+ size_t mCount;
+
+ // Determine mType from parameters.
+ void DetermineType(Float aBorderWidthH, Float aBorderWidthV);
+
+ // Reset calculation.
+ void Reset(void);
+
+ // Find next segment.
+ Float FindNext(Float dashLength);
+
+ // Find mBestDashLength for parameters.
+ void FindBestDashLength(Float aMinBorderWidth, Float aMaxBorderWidth,
+ Float aMinBorderRadius, Float aMaxBorderRadius);
+
+ // Fill corner with dashes with given dash length, and return the number of
+ // segments and last segment's dash length.
+ bool GetCountAndLastDashLength(Float aDashLength, size_t* aCount,
+ Float* aActualDashLength);
+};
+
+} // namespace mozilla
+
+#endif /* mozilla_DashedCornerFinder_h_ */
diff --git a/layout/painting/DisplayItemClip.cpp b/layout/painting/DisplayItemClip.cpp
new file mode 100644
index 0000000000..78aae2e1ec
--- /dev/null
+++ b/layout/painting/DisplayItemClip.cpp
@@ -0,0 +1,486 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "DisplayItemClip.h"
+
+#include "gfxContext.h"
+#include "gfxUtils.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/PathHelpers.h"
+#include "mozilla/layers/StackingContextHelper.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/webrender/WebRenderTypes.h"
+#include "nsPresContext.h"
+#include "nsCSSRendering.h"
+#include "nsLayoutUtils.h"
+#include "nsRegion.h"
+
+using namespace mozilla::gfx;
+
+namespace mozilla {
+
+void DisplayItemClip::SetTo(const nsRect& aRect) { SetTo(aRect, nullptr); }
+
+void DisplayItemClip::SetTo(const nsRect& aRect, const nscoord* aRadii) {
+ mHaveClipRect = true;
+ mClipRect = aRect;
+ if (aRadii) {
+ mRoundedClipRects.SetLength(1);
+ mRoundedClipRects[0].mRect = aRect;
+ memcpy(mRoundedClipRects[0].mRadii, aRadii, sizeof(nscoord) * 8);
+ } else {
+ mRoundedClipRects.Clear();
+ }
+}
+
+void DisplayItemClip::SetTo(const nsRect& aRect, const nsRect& aRoundedRect,
+ const nscoord* aRadii) {
+ mHaveClipRect = true;
+ mClipRect = aRect;
+ mRoundedClipRects.SetLength(1);
+ mRoundedClipRects[0].mRect = aRoundedRect;
+ memcpy(mRoundedClipRects[0].mRadii, aRadii, sizeof(nscoord) * 8);
+}
+
+bool DisplayItemClip::MayIntersect(const nsRect& aRect) const {
+ if (!mHaveClipRect) {
+ return !aRect.IsEmpty();
+ }
+ nsRect r = aRect.Intersect(mClipRect);
+ if (r.IsEmpty()) {
+ return false;
+ }
+ for (uint32_t i = 0; i < mRoundedClipRects.Length(); ++i) {
+ const RoundedRect& rr = mRoundedClipRects[i];
+ if (!nsLayoutUtils::RoundedRectIntersectsRect(rr.mRect, rr.mRadii, r)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+void DisplayItemClip::IntersectWith(const DisplayItemClip& aOther) {
+ if (!aOther.mHaveClipRect) {
+ return;
+ }
+ if (!mHaveClipRect) {
+ *this = aOther;
+ return;
+ }
+ if (!mClipRect.IntersectRect(mClipRect, aOther.mClipRect)) {
+ mRoundedClipRects.Clear();
+ return;
+ }
+ mRoundedClipRects.AppendElements(aOther.mRoundedClipRects);
+}
+
+void DisplayItemClip::ApplyTo(gfxContext* aContext, int32_t A2D) const {
+ ApplyRectTo(aContext, A2D);
+ ApplyRoundedRectClipsTo(aContext, A2D, 0, mRoundedClipRects.Length());
+}
+
+void DisplayItemClip::ApplyRectTo(gfxContext* aContext, int32_t A2D) const {
+ aContext->NewPath();
+ gfxRect clip = nsLayoutUtils::RectToGfxRect(mClipRect, A2D);
+ aContext->SnappedRectangle(clip);
+ aContext->Clip();
+}
+
+void DisplayItemClip::ApplyRoundedRectClipsTo(gfxContext* aContext, int32_t A2D,
+ uint32_t aBegin,
+ uint32_t aEnd) const {
+ DrawTarget& aDrawTarget = *aContext->GetDrawTarget();
+
+ aEnd = std::min<uint32_t>(aEnd, mRoundedClipRects.Length());
+
+ for (uint32_t i = aBegin; i < aEnd; ++i) {
+ RefPtr<Path> roundedRect =
+ MakeRoundedRectPath(aDrawTarget, A2D, mRoundedClipRects[i]);
+ aContext->Clip(roundedRect);
+ }
+}
+
+void DisplayItemClip::FillIntersectionOfRoundedRectClips(
+ gfxContext* aContext, const DeviceColor& aColor,
+ int32_t aAppUnitsPerDevPixel) const {
+ DrawTarget& aDrawTarget = *aContext->GetDrawTarget();
+
+ uint32_t end = mRoundedClipRects.Length();
+ if (!end) {
+ return;
+ }
+
+ // Push clips for any rects that come BEFORE the rect at |aEnd - 1|, if any:
+ ApplyRoundedRectClipsTo(aContext, aAppUnitsPerDevPixel, 0, end - 1);
+
+ // Now fill the rect at |aEnd - 1|:
+ RefPtr<Path> roundedRect = MakeRoundedRectPath(
+ aDrawTarget, aAppUnitsPerDevPixel, mRoundedClipRects[end - 1]);
+ aDrawTarget.Fill(roundedRect, ColorPattern(aColor));
+
+ // Finally, pop any clips that we may have pushed:
+ for (uint32_t i = 0; i < end - 1; ++i) {
+ aContext->PopClip();
+ }
+}
+
+already_AddRefed<Path> DisplayItemClip::MakeRoundedRectPath(
+ DrawTarget& aDrawTarget, int32_t A2D, const RoundedRect& aRoundRect) const {
+ RectCornerRadii pixelRadii;
+ nsCSSRendering::ComputePixelRadii(aRoundRect.mRadii, A2D, &pixelRadii);
+
+ Rect rect = NSRectToSnappedRect(aRoundRect.mRect, A2D, aDrawTarget);
+
+ return MakePathForRoundedRect(aDrawTarget, rect, pixelRadii);
+}
+
+nsRect DisplayItemClip::ApproximateIntersectInward(const nsRect& aRect) const {
+ nsRect r = aRect;
+ if (mHaveClipRect) {
+ r.IntersectRect(r, mClipRect);
+ }
+ for (uint32_t i = 0, iEnd = mRoundedClipRects.Length(); i < iEnd; ++i) {
+ const RoundedRect& rr = mRoundedClipRects[i];
+ nsRegion rgn =
+ nsLayoutUtils::RoundedRectIntersectRect(rr.mRect, rr.mRadii, r);
+ r = rgn.GetLargestRectangle();
+ }
+ return r;
+}
+
+// Test if (aXPoint, aYPoint) is in the ellipse with center (aXCenter, aYCenter)
+// and radii aXRadius, aYRadius.
+static bool IsInsideEllipse(nscoord aXRadius, nscoord aXCenter, nscoord aXPoint,
+ nscoord aYRadius, nscoord aYCenter,
+ nscoord aYPoint) {
+ float scaledX = float(aXPoint - aXCenter) / float(aXRadius);
+ float scaledY = float(aYPoint - aYCenter) / float(aYRadius);
+ return scaledX * scaledX + scaledY * scaledY < 1.0f;
+}
+
+bool DisplayItemClip::IsRectClippedByRoundedCorner(const nsRect& aRect) const {
+ if (mRoundedClipRects.IsEmpty()) return false;
+
+ nsRect rect;
+ rect.IntersectRect(aRect, NonRoundedIntersection());
+ for (uint32_t i = 0, iEnd = mRoundedClipRects.Length(); i < iEnd; ++i) {
+ const RoundedRect& rr = mRoundedClipRects[i];
+ // top left
+ if (rect.x < rr.mRect.x + rr.mRadii[eCornerTopLeftX] &&
+ rect.y < rr.mRect.y + rr.mRadii[eCornerTopLeftY]) {
+ if (!IsInsideEllipse(rr.mRadii[eCornerTopLeftX],
+ rr.mRect.x + rr.mRadii[eCornerTopLeftX], rect.x,
+ rr.mRadii[eCornerTopLeftY],
+ rr.mRect.y + rr.mRadii[eCornerTopLeftY], rect.y)) {
+ return true;
+ }
+ }
+ // top right
+ if (rect.XMost() > rr.mRect.XMost() - rr.mRadii[eCornerTopRightX] &&
+ rect.y < rr.mRect.y + rr.mRadii[eCornerTopRightY]) {
+ if (!IsInsideEllipse(rr.mRadii[eCornerTopRightX],
+ rr.mRect.XMost() - rr.mRadii[eCornerTopRightX],
+ rect.XMost(), rr.mRadii[eCornerTopRightY],
+ rr.mRect.y + rr.mRadii[eCornerTopRightY], rect.y)) {
+ return true;
+ }
+ }
+ // bottom left
+ if (rect.x < rr.mRect.x + rr.mRadii[eCornerBottomLeftX] &&
+ rect.YMost() > rr.mRect.YMost() - rr.mRadii[eCornerBottomLeftY]) {
+ if (!IsInsideEllipse(rr.mRadii[eCornerBottomLeftX],
+ rr.mRect.x + rr.mRadii[eCornerBottomLeftX], rect.x,
+ rr.mRadii[eCornerBottomLeftY],
+ rr.mRect.YMost() - rr.mRadii[eCornerBottomLeftY],
+ rect.YMost())) {
+ return true;
+ }
+ }
+ // bottom right
+ if (rect.XMost() > rr.mRect.XMost() - rr.mRadii[eCornerBottomRightX] &&
+ rect.YMost() > rr.mRect.YMost() - rr.mRadii[eCornerBottomRightY]) {
+ if (!IsInsideEllipse(rr.mRadii[eCornerBottomRightX],
+ rr.mRect.XMost() - rr.mRadii[eCornerBottomRightX],
+ rect.XMost(), rr.mRadii[eCornerBottomRightY],
+ rr.mRect.YMost() - rr.mRadii[eCornerBottomRightY],
+ rect.YMost())) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+nsRect DisplayItemClip::NonRoundedIntersection() const {
+ NS_ASSERTION(mHaveClipRect, "Must have a clip rect!");
+ nsRect result = mClipRect;
+ for (uint32_t i = 0, iEnd = mRoundedClipRects.Length(); i < iEnd; ++i) {
+ result.IntersectRect(result, mRoundedClipRects[i].mRect);
+ }
+ return result;
+}
+
+bool DisplayItemClip::IsRectAffectedByClip(const nsRect& aRect) const {
+ if (mHaveClipRect && !mClipRect.Contains(aRect)) {
+ return true;
+ }
+ for (uint32_t i = 0, iEnd = mRoundedClipRects.Length(); i < iEnd; ++i) {
+ const RoundedRect& rr = mRoundedClipRects[i];
+ nsRegion rgn =
+ nsLayoutUtils::RoundedRectIntersectRect(rr.mRect, rr.mRadii, aRect);
+ if (!rgn.Contains(aRect)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool DisplayItemClip::IsRectAffectedByClip(const nsIntRect& aRect,
+ float aXScale, float aYScale,
+ int32_t A2D) const {
+ if (mHaveClipRect) {
+ nsIntRect pixelClipRect =
+ mClipRect.ScaleToNearestPixels(aXScale, aYScale, A2D);
+ if (!pixelClipRect.Contains(aRect)) {
+ return true;
+ }
+ }
+
+ // Rounded rect clipping only snaps to user-space pixels, not device space.
+ nsIntRect unscaled = aRect;
+ unscaled.Scale(1 / aXScale, 1 / aYScale);
+
+ for (uint32_t i = 0, iEnd = mRoundedClipRects.Length(); i < iEnd; ++i) {
+ const RoundedRect& rr = mRoundedClipRects[i];
+
+ nsIntRect pixelRect = rr.mRect.ToNearestPixels(A2D);
+
+ RectCornerRadii pixelRadii;
+ nsCSSRendering::ComputePixelRadii(rr.mRadii, A2D, &pixelRadii);
+
+ nsIntRegion rgn = nsLayoutUtils::RoundedRectIntersectIntRect(
+ pixelRect, pixelRadii, unscaled);
+ if (!rgn.Contains(unscaled)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+nsRect DisplayItemClip::ApplyNonRoundedIntersection(const nsRect& aRect) const {
+ if (!mHaveClipRect) {
+ return aRect;
+ }
+
+ nsRect result = aRect.Intersect(mClipRect);
+ for (uint32_t i = 0, iEnd = mRoundedClipRects.Length(); i < iEnd; ++i) {
+ result = result.Intersect(mRoundedClipRects[i].mRect);
+ }
+ return result;
+}
+
+void DisplayItemClip::RemoveRoundedCorners() {
+ if (mRoundedClipRects.IsEmpty()) return;
+
+ mClipRect = NonRoundedIntersection();
+ mRoundedClipRects.Clear();
+}
+
+// Computes the difference between aR1 and aR2, limited to aBounds.
+static void AccumulateRectDifference(const nsRect& aR1, const nsRect& aR2,
+ const nsRect& aBounds, nsRegion* aOut) {
+ if (aR1.IsEqualInterior(aR2)) return;
+ nsRegion r;
+ r.Xor(aR1, aR2);
+ r.And(r, aBounds);
+ aOut->Or(*aOut, r);
+}
+
+static void AccumulateRoundedRectDifference(
+ const DisplayItemClip::RoundedRect& aR1,
+ const DisplayItemClip::RoundedRect& aR2, const nsRect& aBounds,
+ const nsRect& aOtherBounds, nsRegion* aOut) {
+ const nsRect& rect1 = aR1.mRect;
+ const nsRect& rect2 = aR2.mRect;
+
+ // If the two rectangles are totally disjoint, just add them both - otherwise
+ // we'd end up adding one big enclosing rect
+ if (!rect1.Intersects(rect2) ||
+ memcmp(aR1.mRadii, aR2.mRadii, sizeof(aR1.mRadii))) {
+ aOut->Or(*aOut, rect1.Intersect(aBounds));
+ aOut->Or(*aOut, rect2.Intersect(aOtherBounds));
+ return;
+ }
+
+ nscoord lowestBottom = std::max(rect1.YMost(), rect2.YMost());
+ nscoord highestTop = std::min(rect1.Y(), rect2.Y());
+ nscoord maxRight = std::max(rect1.XMost(), rect2.XMost());
+ nscoord minLeft = std::min(rect1.X(), rect2.X());
+
+ // At this point, we know that the radii haven't changed, and that the bounds
+ // are different in some way. To explain how this works, consider the case
+ // where the rounded rect has just been translated along the X direction.
+ // | ______________________ _ _ _ _ _ _ |
+ // | / / \ \ |
+ // | | | |
+ // | | aR1 | | aR2 | |
+ // | | | |
+ // | \ __________\___________ / _ _ _ _ _ / |
+ // | |
+ // The invalidation region will be as if we lopped off the left rounded part
+ // of aR2, and the right rounded part of aR1, and XOR'd them:
+ // | ______________________ _ _ _ _ _ _ |
+ // | -/-----------/- -\-----------\- |
+ // | |-------------- --|------------ |
+ // | |-----aR1---|-- --|-----aR2---| |
+ // | |-------------- --|------------ |
+ // | -\ __________\-__________-/ _ _ _ _ _ /- |
+ // | |
+ // The logic below just implements this idea, but generalized to both the
+ // X and Y dimensions. The "(...)Adjusted(...)" values represent the lopped
+ // off sides.
+ nscoord highestAdjustedBottom = std::min(
+ rect1.YMost() - aR1.mRadii[eCornerBottomLeftY],
+ std::min(rect1.YMost() - aR1.mRadii[eCornerBottomRightY],
+ std::min(rect2.YMost() - aR2.mRadii[eCornerBottomLeftY],
+ rect2.YMost() - aR2.mRadii[eCornerBottomRightY])));
+ nscoord lowestAdjustedTop =
+ std::max(rect1.Y() + aR1.mRadii[eCornerTopLeftY],
+ std::max(rect1.Y() + aR1.mRadii[eCornerTopRightY],
+ std::max(rect2.Y() + aR2.mRadii[eCornerTopLeftY],
+ rect2.Y() + aR2.mRadii[eCornerTopRightY])));
+
+ nscoord minAdjustedRight = std::min(
+ rect1.XMost() - aR1.mRadii[eCornerTopRightX],
+ std::min(rect1.XMost() - aR1.mRadii[eCornerBottomRightX],
+ std::min(rect2.XMost() - aR2.mRadii[eCornerTopRightX],
+ rect2.XMost() - aR2.mRadii[eCornerBottomRightX])));
+ nscoord maxAdjustedLeft =
+ std::max(rect1.X() + aR1.mRadii[eCornerTopLeftX],
+ std::max(rect1.X() + aR1.mRadii[eCornerBottomLeftX],
+ std::max(rect2.X() + aR2.mRadii[eCornerTopLeftX],
+ rect2.X() + aR2.mRadii[eCornerBottomLeftX])));
+
+ // We only want to add an invalidation rect if the bounds have changed. If we
+ // always added all of the 4 rects below, we would always be invalidating a
+ // border around the rects, even in cases where we just translated along the X
+ // or Y axis.
+ nsRegion r;
+ // First, or with the Y delta rects, wide along the X axis
+ if (rect1.Y() != rect2.Y()) {
+ r.Or(r, nsRect(minLeft, highestTop, maxRight - minLeft,
+ lowestAdjustedTop - highestTop));
+ }
+ if (rect1.YMost() != rect2.YMost()) {
+ r.Or(r, nsRect(minLeft, highestAdjustedBottom, maxRight - minLeft,
+ lowestBottom - highestAdjustedBottom));
+ }
+ // Then, or with the X delta rects, wide along the Y axis
+ if (rect1.X() != rect2.X()) {
+ r.Or(r, nsRect(minLeft, highestTop, maxAdjustedLeft - minLeft,
+ lowestBottom - highestTop));
+ }
+ if (rect1.XMost() != rect2.XMost()) {
+ r.Or(r, nsRect(minAdjustedRight, highestTop, maxRight - minAdjustedRight,
+ lowestBottom - highestTop));
+ }
+
+ r.And(r, aBounds.Union(aOtherBounds));
+ aOut->Or(*aOut, r);
+}
+
+void DisplayItemClip::AddOffsetAndComputeDifference(
+ const nsPoint& aOffset, const nsRect& aBounds,
+ const DisplayItemClip& aOther, const nsRect& aOtherBounds,
+ nsRegion* aDifference) {
+ if (mHaveClipRect != aOther.mHaveClipRect ||
+ mRoundedClipRects.Length() != aOther.mRoundedClipRects.Length()) {
+ aDifference->Or(*aDifference, aBounds);
+ aDifference->Or(*aDifference, aOtherBounds);
+ return;
+ }
+ if (mHaveClipRect) {
+ AccumulateRectDifference(mClipRect + aOffset, aOther.mClipRect,
+ aBounds.Union(aOtherBounds), aDifference);
+ }
+ for (uint32_t i = 0; i < mRoundedClipRects.Length(); ++i) {
+ if (mRoundedClipRects[i] + aOffset != aOther.mRoundedClipRects[i]) {
+ AccumulateRoundedRectDifference(mRoundedClipRects[i] + aOffset,
+ aOther.mRoundedClipRects[i], aBounds,
+ aOtherBounds, aDifference);
+ }
+ }
+}
+
+void DisplayItemClip::AppendRoundedRects(nsTArray<RoundedRect>* aArray) const {
+ aArray->AppendElements(mRoundedClipRects.Elements(),
+ mRoundedClipRects.Length());
+}
+
+bool DisplayItemClip::ComputeRegionInClips(const DisplayItemClip* aOldClip,
+ const nsPoint& aShift,
+ nsRegion* aCombined) const {
+ if (!mHaveClipRect || (aOldClip && !aOldClip->mHaveClipRect)) {
+ return false;
+ }
+
+ if (aOldClip) {
+ *aCombined = aOldClip->NonRoundedIntersection();
+ aCombined->MoveBy(aShift);
+ aCombined->Or(*aCombined, NonRoundedIntersection());
+ } else {
+ *aCombined = NonRoundedIntersection();
+ }
+ return true;
+}
+
+void DisplayItemClip::MoveBy(const nsPoint& aPoint) {
+ if (!mHaveClipRect) {
+ return;
+ }
+ mClipRect += aPoint;
+ for (uint32_t i = 0; i < mRoundedClipRects.Length(); ++i) {
+ mRoundedClipRects[i].mRect += aPoint;
+ }
+}
+
+static StaticAutoPtr<DisplayItemClip> gNoClip;
+
+const DisplayItemClip& DisplayItemClip::NoClip() {
+ if (!gNoClip) {
+ gNoClip = new DisplayItemClip();
+ }
+ return *gNoClip;
+}
+
+void DisplayItemClip::Shutdown() { gNoClip = nullptr; }
+
+nsCString DisplayItemClip::ToString() const {
+ nsAutoCString str;
+ if (mHaveClipRect) {
+ str.AppendPrintf("%d,%d,%d,%d", mClipRect.x, mClipRect.y, mClipRect.width,
+ mClipRect.height);
+ for (uint32_t i = 0; i < mRoundedClipRects.Length(); ++i) {
+ const RoundedRect& r = mRoundedClipRects[i];
+ str.AppendPrintf(" [%d,%d,%d,%d corners %d,%d,%d,%d,%d,%d,%d,%d]",
+ r.mRect.x, r.mRect.y, r.mRect.width, r.mRect.height,
+ r.mRadii[0], r.mRadii[1], r.mRadii[2], r.mRadii[3],
+ r.mRadii[4], r.mRadii[5], r.mRadii[6], r.mRadii[7]);
+ }
+ }
+ return std::move(str);
+}
+
+void DisplayItemClip::ToComplexClipRegions(
+ int32_t aAppUnitsPerDevPixel,
+ nsTArray<wr::ComplexClipRegion>& aOutArray) const {
+ for (const auto& clipRect : mRoundedClipRects) {
+ aOutArray.AppendElement(wr::ToComplexClipRegion(
+ clipRect.mRect, clipRect.mRadii, aAppUnitsPerDevPixel));
+ }
+}
+
+} // namespace mozilla
diff --git a/layout/painting/DisplayItemClip.h b/layout/painting/DisplayItemClip.h
new file mode 100644
index 0000000000..ff9df26603
--- /dev/null
+++ b/layout/painting/DisplayItemClip.h
@@ -0,0 +1,193 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef DISPLAYITEMCLIP_H_
+#define DISPLAYITEMCLIP_H_
+
+#include "mozilla/RefPtr.h"
+#include "nsRect.h"
+#include "nsTArray.h"
+
+class gfxContext;
+class nsPresContext;
+class nsRegion;
+
+namespace mozilla {
+namespace gfx {
+class DrawTarget;
+class Path;
+} // namespace gfx
+namespace layers {
+class StackingContextHelper;
+} // namespace layers
+namespace wr {
+struct ComplexClipRegion;
+} // namespace wr
+} // namespace mozilla
+
+namespace mozilla {
+
+/**
+ * An DisplayItemClip represents the intersection of an optional rectangle
+ * with a list of rounded rectangles (which is often empty), all in appunits.
+ * It can represent everything CSS clipping can do to an element (except for
+ * SVG clip-path), including no clipping at all.
+ */
+class DisplayItemClip {
+ typedef mozilla::gfx::DeviceColor DeviceColor;
+ typedef mozilla::gfx::DrawTarget DrawTarget;
+ typedef mozilla::gfx::Path Path;
+
+ public:
+ struct RoundedRect {
+ nsRect mRect;
+ // Indices into mRadii are the HalfCorner values in gfx/2d/Types.h
+ nscoord mRadii[8];
+
+ RoundedRect operator+(const nsPoint& aOffset) const {
+ RoundedRect r = *this;
+ r.mRect += aOffset;
+ return r;
+ }
+ bool operator==(const RoundedRect& aOther) const {
+ if (!mRect.IsEqualInterior(aOther.mRect)) {
+ return false;
+ }
+
+ for (const auto corner : mozilla::AllPhysicalHalfCorners()) {
+ if (mRadii[corner] != aOther.mRadii[corner]) {
+ return false;
+ }
+ }
+ return true;
+ }
+ bool operator!=(const RoundedRect& aOther) const {
+ return !(*this == aOther);
+ }
+ };
+
+ // Constructs a DisplayItemClip that does no clipping at all.
+ DisplayItemClip() : mHaveClipRect(false) {}
+
+ void SetTo(const nsRect& aRect);
+ void SetTo(const nsRect& aRect, const nscoord* aRadii);
+ void SetTo(const nsRect& aRect, const nsRect& aRoundedRect,
+ const nscoord* aRadii);
+ void IntersectWith(const DisplayItemClip& aOther);
+
+ // Apply this |DisplayItemClip| to the given gfxContext. Any saving of state
+ // or clearing of other clips must be done by the caller.
+ // See aBegin/aEnd note on ApplyRoundedRectsTo.
+ void ApplyTo(gfxContext* aContext, int32_t A2D) const;
+
+ void ApplyRectTo(gfxContext* aContext, int32_t A2D) const;
+ // Applies the rounded rects in this Clip to aContext
+ // Will only apply rounded rects from aBegin (inclusive) to aEnd
+ // (exclusive) or the number of rounded rects, whichever is smaller.
+ void ApplyRoundedRectClipsTo(gfxContext* aContext, int32_t A2DPRInt32,
+ uint32_t aBegin, uint32_t aEnd) const;
+
+ // Draw (fill) the rounded rects in this clip to aContext
+ void FillIntersectionOfRoundedRectClips(gfxContext* aContext,
+ const DeviceColor& aColor,
+ int32_t aAppUnitsPerDevPixel) const;
+ // 'Draw' (create as a path, does not stroke or fill) aRoundRect to aContext
+ already_AddRefed<Path> MakeRoundedRectPath(
+ DrawTarget& aDrawTarget, int32_t A2D,
+ const RoundedRect& aRoundRect) const;
+
+ // Returns true if the intersection of aRect and this clip region is
+ // non-empty. This is precise for DisplayItemClips with at most one
+ // rounded rectangle. When multiple rounded rectangles are present, we just
+ // check that the rectangle intersects all of them (but possibly in different
+ // places). So it may return true when the correct answer is false.
+ bool MayIntersect(const nsRect& aRect) const;
+
+ // Return a rectangle contained in the intersection of aRect with this
+ // clip region. Tries to return the largest possible rectangle, but may
+ // not succeed.
+ nsRect ApproximateIntersectInward(const nsRect& aRect) const;
+
+ /*
+ * Computes a region which contains the clipped area of this DisplayItemClip,
+ * or if aOldClip is non-null, the union of the clipped area of this
+ * DisplayItemClip with the clipped area of aOldClip translated by aShift.
+ * The result is stored in aCombined. If the result would be infinite
+ * (because one or both of the clips does no clipping), returns false.
+ */
+ bool ComputeRegionInClips(const DisplayItemClip* aOldClip,
+ const nsPoint& aShift, nsRegion* aCombined) const;
+
+ // Returns false if aRect is definitely not clipped by a rounded corner in
+ // this clip. Returns true if aRect is clipped by a rounded corner in this
+ // clip or it can not be quickly determined that it is not clipped by a
+ // rounded corner in this clip.
+ bool IsRectClippedByRoundedCorner(const nsRect& aRect) const;
+
+ // Returns false if aRect is definitely not clipped by anything in this clip.
+ // Fast but not necessarily accurate.
+ bool IsRectAffectedByClip(const nsRect& aRect) const;
+ bool IsRectAffectedByClip(const nsIntRect& aRect, float aXScale,
+ float aYScale, int32_t A2D) const;
+
+ // Intersection of all rects in this clip ignoring any rounded corners.
+ nsRect NonRoundedIntersection() const;
+
+ // Intersect the given rects with all rects in this clip, ignoring any
+ // rounded corners.
+ nsRect ApplyNonRoundedIntersection(const nsRect& aRect) const;
+
+ // Gets rid of any rounded corners in this clip.
+ void RemoveRoundedCorners();
+
+ // Adds the difference between Intersect(*this + aPoint, aBounds) and
+ // Intersect(aOther, aOtherBounds) to aDifference (or a bounding-box thereof).
+ void AddOffsetAndComputeDifference(const nsPoint& aPoint,
+ const nsRect& aBounds,
+ const DisplayItemClip& aOther,
+ const nsRect& aOtherBounds,
+ nsRegion* aDifference);
+
+ bool operator==(const DisplayItemClip& aOther) const {
+ return mHaveClipRect == aOther.mHaveClipRect &&
+ (!mHaveClipRect || mClipRect.IsEqualInterior(aOther.mClipRect)) &&
+ mRoundedClipRects == aOther.mRoundedClipRects;
+ }
+ bool operator!=(const DisplayItemClip& aOther) const {
+ return !(*this == aOther);
+ }
+
+ bool HasClip() const { return mHaveClipRect; }
+ const nsRect& GetClipRect() const {
+ NS_ASSERTION(HasClip(), "No clip rect!");
+ return mClipRect;
+ }
+
+ void MoveBy(const nsPoint& aPoint);
+
+ nsCString ToString() const;
+
+ uint32_t GetRoundedRectCount() const { return mRoundedClipRects.Length(); }
+ void AppendRoundedRects(nsTArray<RoundedRect>* aArray) const;
+
+ void ToComplexClipRegions(int32_t aAppUnitsPerDevPixel,
+ nsTArray<wr::ComplexClipRegion>& aOutArray) const;
+
+ static const DisplayItemClip& NoClip();
+
+ static void Shutdown();
+
+ private:
+ nsRect mClipRect;
+ CopyableTArray<RoundedRect> mRoundedClipRects;
+ // If mHaveClipRect is false then this object represents no clipping at all
+ // and mRoundedClipRects must be empty.
+ bool mHaveClipRect;
+};
+
+} // namespace mozilla
+
+#endif /* DISPLAYITEMCLIP_H_ */
diff --git a/layout/painting/DisplayItemClipChain.cpp b/layout/painting/DisplayItemClipChain.cpp
new file mode 100644
index 0000000000..537c3615a1
--- /dev/null
+++ b/layout/painting/DisplayItemClipChain.cpp
@@ -0,0 +1,88 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "DisplayItemClipChain.h"
+
+#include "nsDisplayList.h"
+
+namespace mozilla {
+
+/* static */ const DisplayItemClip* DisplayItemClipChain::ClipForASR(
+ const DisplayItemClipChain* aClipChain, const ActiveScrolledRoot* aASR) {
+ while (aClipChain &&
+ !ActiveScrolledRoot::IsAncestor(aClipChain->mASR, aASR)) {
+ aClipChain = aClipChain->mParent;
+ }
+ return (aClipChain && aClipChain->mASR == aASR) ? &aClipChain->mClip
+ : nullptr;
+}
+
+bool DisplayItemClipChain::Equal(const DisplayItemClipChain* aClip1,
+ const DisplayItemClipChain* aClip2) {
+ if (aClip1 == aClip2) {
+ return true;
+ }
+
+ if (!aClip1 || !aClip2) {
+ return false;
+ }
+
+ bool ret = aClip1->mASR == aClip2->mASR && aClip1->mClip == aClip2->mClip &&
+ Equal(aClip1->mParent, aClip2->mParent);
+ // Sanity check: if two clip chains are equal they must hash to the same
+ // thing too, or Bad Things (TM) will happen.
+ MOZ_ASSERT(!ret || (Hash(aClip1) == Hash(aClip2)));
+ return ret;
+}
+
+uint32_t DisplayItemClipChain::Hash(const DisplayItemClipChain* aClip) {
+ if (!aClip) {
+ return 0;
+ }
+
+ // We include the number of rounded rects in the hash but not their contents.
+ // This is to keep the hash fast, because most clips will not have rounded
+ // rects and including them will slow down the hash in the common case. Note
+ // that the ::Equal check still checks the rounded rect contents, so in case
+ // of hash collisions the clip chains can still be distinguished using that.
+ uint32_t hash = HashGeneric(aClip->mASR, aClip->mClip.GetRoundedRectCount());
+ if (aClip->mClip.HasClip()) {
+ const nsRect& rect = aClip->mClip.GetClipRect();
+ // empty rects are considered equal in DisplayItemClipChain::Equal, even
+ // though they may have different x and y coordinates. So make sure they
+ // hash to the same thing in those cases too.
+ if (!rect.IsEmpty()) {
+ hash = AddToHash(hash, rect.x, rect.y, rect.width, rect.height);
+ }
+ }
+
+ return hash;
+}
+
+/* static */
+nsCString DisplayItemClipChain::ToString(
+ const DisplayItemClipChain* aClipChain) {
+ nsAutoCString str;
+ for (auto* sc = aClipChain; sc; sc = sc->mParent) {
+ if (sc->mASR) {
+ str.AppendPrintf("0x%p <%s> [0x%p]", sc, sc->mClip.ToString().get(),
+ sc->mASR->mScrollableFrame);
+ } else {
+ str.AppendPrintf("0x%p <%s> [root asr]", sc, sc->mClip.ToString().get());
+ }
+ if (sc->mParent) {
+ str.AppendLiteral(", ");
+ }
+ }
+ return std::move(str);
+}
+
+bool DisplayItemClipChain::HasRoundedCorners() const {
+ return mClip.GetRoundedRectCount() > 0 ||
+ (mParent && mParent->HasRoundedCorners());
+}
+
+} // namespace mozilla
diff --git a/layout/painting/DisplayItemClipChain.h b/layout/painting/DisplayItemClipChain.h
new file mode 100644
index 0000000000..32779e09c0
--- /dev/null
+++ b/layout/painting/DisplayItemClipChain.h
@@ -0,0 +1,117 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef DISPLAYITEMCLIPCHAIN_H_
+#define DISPLAYITEMCLIPCHAIN_H_
+
+#include "mozilla/Assertions.h"
+#include "DisplayItemClip.h"
+#include "nsString.h"
+
+class nsIScrollableFrame;
+
+namespace mozilla {
+
+struct ActiveScrolledRoot;
+
+/**
+ * A DisplayItemClipChain is a linked list of DisplayItemClips where each clip
+ * is associated with an active scrolled root that describes what the clip
+ * moves with.
+ * We use a chain instead of just one intersected clip due to async scrolling:
+ * A clip that moves along with a display item can be fused to the item's
+ * contents when drawing the layer contents, but all other clips in the chain
+ * need to be kept separate so that they can be applied at composition time,
+ * after any async scroll offsets have been applied.
+ * The clip chain is created during display list construction by the builder's
+ * DisplayListClipState.
+ * The clip chain order is determined by the active scrolled root order.
+ * For every DisplayItemClipChain object |clipChain|, the following holds:
+ * !clipChain->mParent ||
+ * ActiveScrolledRoot::IsAncestor(clipChain->mParent->mASR, clipChain->mASR).
+ * The clip chain can skip over active scrolled roots. That just means that
+ * there is no clip that moves with the skipped ASR in this chain.
+ */
+struct DisplayItemClipChain {
+ /**
+ * Get the display item clip in this chain that moves with aASR, or nullptr
+ * if no such clip exists. aClipChain can be null.
+ */
+ static const DisplayItemClip* ClipForASR(
+ const DisplayItemClipChain* aClipChain, const ActiveScrolledRoot* aASR);
+
+ static bool Equal(const DisplayItemClipChain* aClip1,
+ const DisplayItemClipChain* aClip2);
+ /**
+ * Hash function that returns the same value for any two clips A and B
+ * where Equal(A, B) is true.
+ */
+ static uint32_t Hash(const DisplayItemClipChain* aClip);
+
+ static nsCString ToString(const DisplayItemClipChain* aClipChain);
+
+ bool HasRoundedCorners() const;
+
+ void AddRef() { mRefCount++; }
+ void Release() {
+ MOZ_ASSERT(mRefCount > 0);
+ mRefCount--;
+ }
+
+ DisplayItemClipChain(const DisplayItemClip& aClip,
+ const ActiveScrolledRoot* aASR,
+ const DisplayItemClipChain* aParent,
+ DisplayItemClipChain* aNextClipChainToDestroy)
+ : mClip(aClip),
+ mASR(aASR),
+ mParent(aParent),
+ mNextClipChainToDestroy(aNextClipChainToDestroy)
+#ifdef DEBUG
+ ,
+ mOnStack(true)
+#endif
+ {
+ }
+
+ DisplayItemClipChain()
+ : mASR(nullptr),
+ mNextClipChainToDestroy(nullptr)
+#ifdef DEBUG
+ ,
+ mOnStack(true)
+#endif
+ {
+ }
+
+ DisplayItemClip mClip;
+ const ActiveScrolledRoot* mASR;
+ RefPtr<const DisplayItemClipChain> mParent;
+ uint32_t mRefCount = 0;
+ DisplayItemClipChain* mNextClipChainToDestroy;
+#ifdef DEBUG
+ bool mOnStack;
+#endif
+};
+
+struct DisplayItemClipChainHasher {
+ typedef const DisplayItemClipChain* Key;
+
+ std::size_t operator()(const Key& aKey) const {
+ return DisplayItemClipChain::Hash(aKey);
+ }
+};
+
+struct DisplayItemClipChainEqualer {
+ typedef const DisplayItemClipChain* Key;
+
+ bool operator()(const Key& lhs, const Key& rhs) const {
+ return DisplayItemClipChain::Equal(lhs, rhs);
+ }
+};
+
+} // namespace mozilla
+
+#endif /* DISPLAYITEMCLIPCHAIN_H_ */
diff --git a/layout/painting/DisplayListClipState.cpp b/layout/painting/DisplayListClipState.cpp
new file mode 100644
index 0000000000..8a87e2906b
--- /dev/null
+++ b/layout/painting/DisplayListClipState.cpp
@@ -0,0 +1,149 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "DisplayListClipState.h"
+
+#include "nsDisplayList.h"
+
+namespace mozilla {
+
+const DisplayItemClipChain* DisplayListClipState::GetCurrentCombinedClipChain(
+ nsDisplayListBuilder* aBuilder) {
+ if (mCurrentCombinedClipChainIsValid) {
+ return mCurrentCombinedClipChain;
+ }
+ if (!mClipChainContentDescendants && !mClipChainContainingBlockDescendants) {
+ mCurrentCombinedClipChain = nullptr;
+ mCurrentCombinedClipChainIsValid = true;
+ return nullptr;
+ }
+
+ mCurrentCombinedClipChain = aBuilder->CreateClipChainIntersection(
+ mCurrentCombinedClipChain, mClipChainContentDescendants,
+ mClipChainContainingBlockDescendants);
+ mCurrentCombinedClipChainIsValid = true;
+ return mCurrentCombinedClipChain;
+}
+
+static void ApplyClip(nsDisplayListBuilder* aBuilder,
+ const DisplayItemClipChain*& aClipToModify,
+ const ActiveScrolledRoot* aASR,
+ DisplayItemClipChain& aClipChainOnStack) {
+ aClipChainOnStack.mASR = aASR;
+ if (aClipToModify && aClipToModify->mASR == aASR) {
+ // Intersect with aClipToModify and replace the clip chain item.
+ aClipChainOnStack.mClip.IntersectWith(aClipToModify->mClip);
+ aClipChainOnStack.mParent = aClipToModify->mParent;
+ aClipToModify = &aClipChainOnStack;
+ } else if (!aClipToModify ||
+ ActiveScrolledRoot::IsAncestor(aClipToModify->mASR, aASR)) {
+ // Add a new clip chain item at the bottom.
+ aClipChainOnStack.mParent = aClipToModify;
+ aClipToModify = &aClipChainOnStack;
+ } else {
+ // We need to insert / intersect a DisplayItemClipChain in the middle of the
+ // aClipToModify chain. This is a very rare case.
+ // Find the common ancestor and have the builder create the
+ // DisplayItemClipChain intersection. This will create new
+ // DisplayItemClipChain objects for all descendants of ancestorSC and we
+ // will not hold on to a pointer to aClipChainOnStack.
+ const DisplayItemClipChain* ancestorSC = aClipToModify;
+ while (ancestorSC &&
+ ActiveScrolledRoot::IsAncestor(aASR, ancestorSC->mASR)) {
+ ancestorSC = ancestorSC->mParent;
+ }
+ ancestorSC = aBuilder->CopyWholeChain(ancestorSC);
+ aClipChainOnStack.mParent = nullptr;
+ aClipToModify = aBuilder->CreateClipChainIntersection(
+ ancestorSC, aClipToModify, &aClipChainOnStack);
+ }
+}
+
+void DisplayListClipState::ClipContainingBlockDescendants(
+ nsDisplayListBuilder* aBuilder, const nsRect& aRect, const nscoord* aRadii,
+ DisplayItemClipChain& aClipChainOnStack) {
+ if (aRadii) {
+ aClipChainOnStack.mClip.SetTo(aRect, aRadii);
+ } else {
+ aClipChainOnStack.mClip.SetTo(aRect);
+ }
+ const ActiveScrolledRoot* asr = aBuilder->CurrentActiveScrolledRoot();
+ ApplyClip(aBuilder, mClipChainContainingBlockDescendants, asr,
+ aClipChainOnStack);
+ InvalidateCurrentCombinedClipChain(asr);
+}
+
+void DisplayListClipState::ClipContentDescendants(
+ nsDisplayListBuilder* aBuilder, const nsRect& aRect, const nscoord* aRadii,
+ DisplayItemClipChain& aClipChainOnStack) {
+ if (aRadii) {
+ aClipChainOnStack.mClip.SetTo(aRect, aRadii);
+ } else {
+ aClipChainOnStack.mClip.SetTo(aRect);
+ }
+ const ActiveScrolledRoot* asr = aBuilder->CurrentActiveScrolledRoot();
+ ApplyClip(aBuilder, mClipChainContentDescendants, asr, aClipChainOnStack);
+ InvalidateCurrentCombinedClipChain(asr);
+}
+
+void DisplayListClipState::ClipContentDescendants(
+ nsDisplayListBuilder* aBuilder, const nsRect& aRect,
+ const nsRect& aRoundedRect, const nscoord* aRadii,
+ DisplayItemClipChain& aClipChainOnStack) {
+ if (aRadii) {
+ aClipChainOnStack.mClip.SetTo(aRect, aRoundedRect, aRadii);
+ } else {
+ nsRect intersect = aRect.Intersect(aRoundedRect);
+ aClipChainOnStack.mClip.SetTo(intersect);
+ }
+ const ActiveScrolledRoot* asr = aBuilder->CurrentActiveScrolledRoot();
+ ApplyClip(aBuilder, mClipChainContentDescendants, asr, aClipChainOnStack);
+ InvalidateCurrentCombinedClipChain(asr);
+}
+
+void DisplayListClipState::InvalidateCurrentCombinedClipChain(
+ const ActiveScrolledRoot* aInvalidateUpTo) {
+ mClippedToDisplayPort = false;
+ mCurrentCombinedClipChainIsValid = false;
+ while (mCurrentCombinedClipChain &&
+ ActiveScrolledRoot::IsAncestor(aInvalidateUpTo,
+ mCurrentCombinedClipChain->mASR)) {
+ mCurrentCombinedClipChain = mCurrentCombinedClipChain->mParent;
+ }
+}
+
+void DisplayListClipState::ClipContainingBlockDescendantsToContentBox(
+ nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ DisplayItemClipChain& aClipChainOnStack, uint32_t aFlags) {
+ nscoord radii[8];
+ bool hasBorderRadius = aFrame->GetContentBoxBorderRadii(radii);
+ if (!hasBorderRadius &&
+ (aFlags & ASSUME_DRAWING_RESTRICTED_TO_CONTENT_RECT)) {
+ return;
+ }
+
+ nsRect clipRect = aFrame->GetContentRectRelativeToSelf() +
+ aBuilder->ToReferenceFrame(aFrame);
+ // If we have a border-radius, we have to clip our content to that
+ // radius.
+ ClipContainingBlockDescendants(
+ aBuilder, clipRect, hasBorderRadius ? radii : nullptr, aClipChainOnStack);
+}
+
+DisplayListClipState::AutoSaveRestore::AutoSaveRestore(
+ nsDisplayListBuilder* aBuilder)
+ : mBuilder(aBuilder),
+ mState(aBuilder->ClipState()),
+ mSavedState(aBuilder->ClipState())
+#ifdef DEBUG
+ ,
+ mClipUsed(false),
+ mRestored(false)
+#endif
+{
+}
+
+} // namespace mozilla
diff --git a/layout/painting/DisplayListClipState.h b/layout/painting/DisplayListClipState.h
new file mode 100644
index 0000000000..0f1df2ca58
--- /dev/null
+++ b/layout/painting/DisplayListClipState.h
@@ -0,0 +1,301 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef DISPLAYLISTCLIPSTATE_H_
+#define DISPLAYLISTCLIPSTATE_H_
+
+#include "DisplayItemClip.h"
+#include "DisplayItemClipChain.h"
+
+#include "mozilla/DebugOnly.h"
+
+class nsIFrame;
+class nsIScrollableFrame;
+
+namespace mozilla {
+
+class nsDisplayListBuilder;
+
+/**
+ * All clip coordinates are in appunits relative to the reference frame
+ * for the display item we're building.
+ */
+class DisplayListClipState {
+ public:
+ DisplayListClipState()
+ : mClipChainContentDescendants(nullptr),
+ mClipChainContainingBlockDescendants(nullptr),
+ mCurrentCombinedClipChain(nullptr),
+ mCurrentCombinedClipChainIsValid(false),
+ mClippedToDisplayPort(false) {}
+
+ void SetClippedToDisplayPort() { mClippedToDisplayPort = true; }
+ bool IsClippedToDisplayPort() const { return mClippedToDisplayPort; }
+
+ /**
+ * Returns intersection of mClipChainContainingBlockDescendants and
+ * mClipChainContentDescendants, allocated on aBuilder's arena.
+ */
+ const DisplayItemClipChain* GetCurrentCombinedClipChain(
+ nsDisplayListBuilder* aBuilder);
+
+ const DisplayItemClipChain* GetClipChainForContainingBlockDescendants()
+ const {
+ return mClipChainContainingBlockDescendants;
+ }
+ const DisplayItemClipChain* GetClipChainForContentDescendants() const {
+ return mClipChainContentDescendants;
+ }
+
+ const ActiveScrolledRoot* GetContentClipASR() const {
+ return mClipChainContentDescendants ? mClipChainContentDescendants->mASR
+ : nullptr;
+ }
+
+ class AutoSaveRestore;
+
+ class AutoClipContainingBlockDescendantsToContentBox;
+
+ class AutoClipMultiple;
+
+ enum { ASSUME_DRAWING_RESTRICTED_TO_CONTENT_RECT = 0x01 };
+
+ private:
+ void Clear() {
+ mClipChainContentDescendants = nullptr;
+ mClipChainContainingBlockDescendants = nullptr;
+ mCurrentCombinedClipChain = nullptr;
+ mCurrentCombinedClipChainIsValid = false;
+ mClippedToDisplayPort = false;
+ }
+
+ void SetClipChainForContainingBlockDescendants(
+ const DisplayItemClipChain* aClipChain) {
+ mClipChainContainingBlockDescendants = aClipChain;
+ InvalidateCurrentCombinedClipChain(aClipChain ? aClipChain->mASR : nullptr);
+ }
+
+ /**
+ * Intersects the given clip rect (with optional aRadii) with the current
+ * mClipContainingBlockDescendants and sets mClipContainingBlockDescendants to
+ * the result, stored in aClipOnStack.
+ */
+ void ClipContainingBlockDescendants(nsDisplayListBuilder* aBuilder,
+ const nsRect& aRect,
+ const nscoord* aRadii,
+ DisplayItemClipChain& aClipChainOnStack);
+
+ void ClipContentDescendants(nsDisplayListBuilder* aBuilder,
+ const nsRect& aRect, const nscoord* aRadii,
+ DisplayItemClipChain& aClipChainOnStack);
+ void ClipContentDescendants(nsDisplayListBuilder* aBuilder,
+ const nsRect& aRect, const nsRect& aRoundedRect,
+ const nscoord* aRadii,
+ DisplayItemClipChain& aClipChainOnStack);
+
+ void InvalidateCurrentCombinedClipChain(
+ const ActiveScrolledRoot* aInvalidateUpTo);
+
+ /**
+ * Clips containing-block descendants to the frame's content-box,
+ * taking border-radius into account.
+ * If aFlags contains ASSUME_DRAWING_RESTRICTED_TO_CONTENT_RECT then
+ * we assume display items will not draw outside the content rect, so
+ * clipping is only required if there is a border-radius. This is an
+ * optimization to reduce the amount of clipping required.
+ */
+ void ClipContainingBlockDescendantsToContentBox(
+ nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ DisplayItemClipChain& aClipChainOnStack, uint32_t aFlags);
+
+ /**
+ * All content descendants (i.e. following placeholder frames to their
+ * out-of-flows if necessary) should be clipped by
+ * mClipChainContentDescendants. Null if no clipping applies.
+ */
+ const DisplayItemClipChain* mClipChainContentDescendants;
+ /**
+ * All containing-block descendants (i.e. frame descendants), including
+ * display items for the current frame, should be clipped by
+ * mClipChainContainingBlockDescendants.
+ * Null if no clipping applies.
+ */
+ const DisplayItemClipChain* mClipChainContainingBlockDescendants;
+ /**
+ * The intersection of mClipChainContentDescendants and
+ * mClipChainContainingBlockDescendants.
+ * Allocated in the nsDisplayListBuilder arena. Null if none has been
+ * allocated or both mClipChainContentDescendants and
+ * mClipChainContainingBlockDescendants are null.
+ */
+ const DisplayItemClipChain* mCurrentCombinedClipChain;
+ bool mCurrentCombinedClipChainIsValid;
+ /**
+ * A flag that is used by sticky positioned items to know if the clip applied
+ * to them is just the displayport clip or if there is additional clipping.
+ */
+ bool mClippedToDisplayPort;
+};
+
+/**
+ * A class to automatically save and restore the current clip state. Also
+ * offers methods for modifying the clip state. Only one modification is allowed
+ * to be in scope at a time using one of these objects; multiple modifications
+ * require nested objects. The interface is written this way to prevent
+ * dangling pointers to DisplayItemClips.
+ */
+class DisplayListClipState::AutoSaveRestore {
+ public:
+ explicit AutoSaveRestore(nsDisplayListBuilder* aBuilder);
+ void Restore() {
+ mState = mSavedState;
+#ifdef DEBUG
+ mRestored = true;
+#endif
+ }
+ ~AutoSaveRestore() { Restore(); }
+
+ void Clear() {
+ NS_ASSERTION(!mRestored, "Already restored!");
+ mState.Clear();
+#ifdef DEBUG
+ mClipUsed = false;
+#endif
+ }
+
+ void SetClipChainForContainingBlockDescendants(
+ const DisplayItemClipChain* aClipChain) {
+ mState.SetClipChainForContainingBlockDescendants(aClipChain);
+ }
+
+ /**
+ * Intersects the given clip rect (with optional aRadii) with the current
+ * mClipContainingBlockDescendants and sets mClipContainingBlockDescendants to
+ * the result, stored in aClipOnStack.
+ */
+ void ClipContainingBlockDescendants(const nsRect& aRect,
+ const nscoord* aRadii = nullptr) {
+ NS_ASSERTION(!mRestored, "Already restored!");
+ NS_ASSERTION(!mClipUsed, "mClip already used");
+#ifdef DEBUG
+ mClipUsed = true;
+#endif
+ mState.ClipContainingBlockDescendants(mBuilder, aRect, aRadii, mClipChain);
+ }
+
+ void ClipContentDescendants(const nsRect& aRect,
+ const nscoord* aRadii = nullptr) {
+ NS_ASSERTION(!mRestored, "Already restored!");
+ NS_ASSERTION(!mClipUsed, "mClip already used");
+#ifdef DEBUG
+ mClipUsed = true;
+#endif
+ mState.ClipContentDescendants(mBuilder, aRect, aRadii, mClipChain);
+ }
+
+ void ClipContentDescendants(const nsRect& aRect, const nsRect& aRoundedRect,
+ const nscoord* aRadii = nullptr) {
+ NS_ASSERTION(!mRestored, "Already restored!");
+ NS_ASSERTION(!mClipUsed, "mClip already used");
+#ifdef DEBUG
+ mClipUsed = true;
+#endif
+ mState.ClipContentDescendants(mBuilder, aRect, aRoundedRect, aRadii,
+ mClipChain);
+ }
+
+ /**
+ * Clips containing-block descendants to the frame's content-box,
+ * taking border-radius into account.
+ * If aFlags contains ASSUME_DRAWING_RESTRICTED_TO_CONTENT_RECT then
+ * we assume display items will not draw outside the content rect, so
+ * clipping is only required if there is a border-radius. This is an
+ * optimization to reduce the amount of clipping required.
+ */
+ void ClipContainingBlockDescendantsToContentBox(
+ nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, uint32_t aFlags = 0) {
+ NS_ASSERTION(!mRestored, "Already restored!");
+ NS_ASSERTION(!mClipUsed, "mClip already used");
+#ifdef DEBUG
+ mClipUsed = true;
+#endif
+ mState.ClipContainingBlockDescendantsToContentBox(aBuilder, aFrame,
+ mClipChain, aFlags);
+ }
+
+ void SetClippedToDisplayPort() { mState.SetClippedToDisplayPort(); }
+ bool IsClippedToDisplayPort() const {
+ return mState.IsClippedToDisplayPort();
+ }
+
+ protected:
+ nsDisplayListBuilder* mBuilder;
+ DisplayListClipState& mState;
+ DisplayListClipState mSavedState;
+ DisplayItemClipChain mClipChain;
+#ifdef DEBUG
+ bool mClipUsed;
+ bool mRestored;
+#endif
+};
+
+class DisplayListClipState::AutoClipContainingBlockDescendantsToContentBox
+ : public AutoSaveRestore {
+ public:
+ AutoClipContainingBlockDescendantsToContentBox(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame,
+ uint32_t aFlags = 0)
+ : AutoSaveRestore(aBuilder) {
+#ifdef DEBUG
+ mClipUsed = true;
+#endif
+ mState.ClipContainingBlockDescendantsToContentBox(aBuilder, aFrame,
+ mClipChain, aFlags);
+ }
+};
+
+/**
+ * Do not use this outside of nsIFrame::BuildDisplayListForChild, use
+ * multiple AutoSaveRestores instead. We provide this class just to ensure
+ * BuildDisplayListForChild is as efficient as possible.
+ */
+class DisplayListClipState::AutoClipMultiple : public AutoSaveRestore {
+ public:
+ explicit AutoClipMultiple(nsDisplayListBuilder* aBuilder)
+ : AutoSaveRestore(aBuilder)
+#ifdef DEBUG
+ ,
+ mExtraClipUsed(false)
+#endif
+ {
+ }
+
+ /**
+ * Intersects the given clip rect (with optional aRadii) with the current
+ * mClipContainingBlockDescendants and sets mClipContainingBlockDescendants to
+ * the result, stored in aClipOnStack.
+ */
+ void ClipContainingBlockDescendantsExtra(const nsRect& aRect,
+ const nscoord* aRadii) {
+ NS_ASSERTION(!mRestored, "Already restored!");
+ NS_ASSERTION(!mExtraClipUsed, "mExtraClip already used");
+#ifdef DEBUG
+ mExtraClipUsed = true;
+#endif
+ mState.ClipContainingBlockDescendants(mBuilder, aRect, aRadii,
+ mExtraClipChain);
+ }
+
+ protected:
+ DisplayItemClipChain mExtraClipChain;
+#ifdef DEBUG
+ bool mExtraClipUsed;
+#endif
+};
+
+} // namespace mozilla
+
+#endif /* DISPLAYLISTCLIPSTATE_H_ */
diff --git a/layout/painting/DottedCornerFinder.cpp b/layout/painting/DottedCornerFinder.cpp
new file mode 100644
index 0000000000..2e8408d80b
--- /dev/null
+++ b/layout/painting/DottedCornerFinder.cpp
@@ -0,0 +1,539 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "DottedCornerFinder.h"
+
+#include <utility>
+
+#include "BorderCache.h"
+#include "BorderConsts.h"
+#include "nsTHashMap.h"
+
+namespace mozilla {
+
+using namespace gfx;
+
+static inline Float Square(Float x) { return x * x; }
+
+static Point PointRotateCCW90(const Point& aP) { return Point(aP.y, -aP.x); }
+
+struct BestOverlap {
+ Float overlap;
+ size_t count;
+
+ BestOverlap() : overlap(0.0f), count(0) {}
+
+ BestOverlap(Float aOverlap, size_t aCount)
+ : overlap(aOverlap), count(aCount) {}
+};
+
+static const size_t DottedCornerCacheSize = 256;
+nsTHashMap<FourFloatsHashKey, BestOverlap> DottedCornerCache;
+
+DottedCornerFinder::DottedCornerFinder(const Bezier& aOuterBezier,
+ const Bezier& aInnerBezier,
+ Corner aCorner, Float aBorderRadiusX,
+ Float aBorderRadiusY, const Point& aC0,
+ Float aR0, const Point& aCn, Float aRn,
+ const Size& aCornerDim)
+ : mOuterBezier(aOuterBezier),
+ mInnerBezier(aInnerBezier),
+ mCorner(aCorner),
+ mNormalSign((aCorner == C_TL || aCorner == C_BR) ? -1.0f : 1.0f),
+ mC0(aC0),
+ mCn(aCn),
+ mR0(aR0),
+ mRn(aRn),
+ mMaxR(std::max(aR0, aRn)),
+ mCenterCurveOrigin(mC0.x, mCn.y),
+ mCenterCurveR(0.0),
+ mInnerCurveOrigin(mInnerBezier.mPoints[0].x, mInnerBezier.mPoints[3].y),
+ mBestOverlap(0.0f),
+ mHasZeroBorderWidth(false),
+ mHasMore(true),
+ mMaxCount(aCornerDim.width + aCornerDim.height),
+ mType(OTHER),
+ mI(0),
+ mCount(0) {
+ NS_ASSERTION(mR0 > 0.0f || mRn > 0.0f,
+ "At least one side should have non-zero radius.");
+
+ mInnerWidth = fabs(mInnerBezier.mPoints[0].x - mInnerBezier.mPoints[3].x);
+ mInnerHeight = fabs(mInnerBezier.mPoints[0].y - mInnerBezier.mPoints[3].y);
+
+ DetermineType(aBorderRadiusX, aBorderRadiusY);
+
+ Reset();
+}
+
+static bool IsSingleCurve(Float aMinR, Float aMaxR, Float aMinBorderRadius,
+ Float aMaxBorderRadius) {
+ return aMinR > 0.0f && aMinBorderRadius > aMaxR * 4.0f &&
+ aMinBorderRadius / aMaxBorderRadius > 0.5f;
+}
+
+void DottedCornerFinder::DetermineType(Float aBorderRadiusX,
+ Float aBorderRadiusY) {
+ // Calculate parameters for the center curve before swap.
+ Float centerCurveWidth = fabs(mC0.x - mCn.x);
+ Float centerCurveHeight = fabs(mC0.y - mCn.y);
+ Point cornerPoint(mCn.x, mC0.y);
+
+ bool swapped = false;
+ if (mR0 < mRn) {
+ // Always draw from wider side to thinner side.
+ std::swap(mC0, mCn);
+ std::swap(mR0, mRn);
+ std::swap(mInnerBezier.mPoints[0], mInnerBezier.mPoints[3]);
+ std::swap(mInnerBezier.mPoints[1], mInnerBezier.mPoints[2]);
+ std::swap(mOuterBezier.mPoints[0], mOuterBezier.mPoints[3]);
+ std::swap(mOuterBezier.mPoints[1], mOuterBezier.mPoints[2]);
+ mNormalSign = -mNormalSign;
+ swapped = true;
+ }
+
+ // See the comment at mType declaration for each condition.
+
+ Float minR = std::min(mR0, mRn);
+ Float minBorderRadius = std::min(aBorderRadiusX, aBorderRadiusY);
+ Float maxBorderRadius = std::max(aBorderRadiusX, aBorderRadiusY);
+ if (IsSingleCurve(minR, mMaxR, minBorderRadius, maxBorderRadius)) {
+ if (mR0 == mRn) {
+ Float borderLength;
+ if (minBorderRadius == maxBorderRadius) {
+ mType = PERFECT;
+ borderLength = M_PI * centerCurveHeight / 2.0f;
+
+ mCenterCurveR = centerCurveWidth;
+ } else {
+ mType = SINGLE_CURVE_AND_RADIUS;
+ borderLength =
+ GetQuarterEllipticArcLength(centerCurveWidth, centerCurveHeight);
+ }
+
+ Float diameter = mR0 * 2.0f;
+ size_t count = round(borderLength / diameter);
+ if (count % 2) {
+ count++;
+ }
+ mCount = count / 2 - 1;
+ if (mCount > 0) {
+ mBestOverlap = 1.0f - borderLength / (diameter * count);
+ }
+ } else {
+ mType = SINGLE_CURVE;
+ }
+ }
+
+ if (mType == SINGLE_CURVE_AND_RADIUS || mType == SINGLE_CURVE) {
+ Size cornerSize(centerCurveWidth, centerCurveHeight);
+ GetBezierPointsForCorner(&mCenterBezier, mCorner, cornerPoint, cornerSize);
+ if (swapped) {
+ std::swap(mCenterBezier.mPoints[0], mCenterBezier.mPoints[3]);
+ std::swap(mCenterBezier.mPoints[1], mCenterBezier.mPoints[2]);
+ }
+ }
+
+ if (minR == 0.0f) {
+ mHasZeroBorderWidth = true;
+ }
+
+ if ((mType == SINGLE_CURVE || mType == OTHER) && !mHasZeroBorderWidth) {
+ FindBestOverlap(minR, minBorderRadius, maxBorderRadius);
+ }
+}
+
+bool DottedCornerFinder::HasMore(void) const {
+ if (mHasZeroBorderWidth) {
+ return mI < mMaxCount && mHasMore;
+ }
+
+ return mI < mCount;
+}
+
+DottedCornerFinder::Result DottedCornerFinder::Next(void) {
+ mI++;
+
+ if (mType == PERFECT) {
+ Float phi = mI * 4.0f * mR0 * (1 - mBestOverlap) / mCenterCurveR;
+ if (mCorner == C_TL) {
+ phi = -M_PI / 2.0f - phi;
+ } else if (mCorner == C_TR) {
+ phi = -M_PI / 2.0f + phi;
+ } else if (mCorner == C_BR) {
+ phi = M_PI / 2.0f - phi;
+ } else {
+ phi = M_PI / 2.0f + phi;
+ }
+
+ Point C(mCenterCurveOrigin.x + mCenterCurveR * cos(phi),
+ mCenterCurveOrigin.y + mCenterCurveR * sin(phi));
+ return DottedCornerFinder::Result(C, mR0);
+ }
+
+ // Find unfilled and filled circles.
+ (void)FindNext(mBestOverlap);
+ if (mHasMore) {
+ (void)FindNext(mBestOverlap);
+ }
+
+ return Result(mLastC, mLastR);
+}
+
+void DottedCornerFinder::Reset(void) {
+ mLastC = mC0;
+ mLastR = mR0;
+ mLastT = 0.0f;
+ mHasMore = true;
+}
+
+void DottedCornerFinder::FindPointAndRadius(Point& C, Float& r,
+ const Point& innerTangent,
+ const Point& normal, Float t) {
+ // Find radius for the given tangent point on the inner curve such that the
+ // circle is also tangent to the outer curve.
+
+ NS_ASSERTION(mType == OTHER, "Wrong mType");
+
+ Float lower = 0.0f;
+ Float upper = mMaxR;
+ const Float DIST_MARGIN = 0.1f;
+ for (size_t i = 0; i < MAX_LOOP; i++) {
+ r = (upper + lower) / 2.0f;
+ C = innerTangent + normal * r;
+
+ Point Near = FindBezierNearestPoint(mOuterBezier, C, t);
+ Float distSquare = (C - Near).LengthSquare();
+
+ if (distSquare > Square(r + DIST_MARGIN)) {
+ lower = r;
+ } else if (distSquare < Square(r - DIST_MARGIN)) {
+ upper = r;
+ } else {
+ break;
+ }
+ }
+}
+
+Float DottedCornerFinder::FindNext(Float overlap) {
+ Float lower = mLastT;
+ Float upper = 1.0f;
+ Float t;
+
+ Point C = mLastC;
+ Float r = 0.0f;
+
+ Float factor = (1.0f - overlap);
+
+ Float circlesDist = 0.0f;
+ Float expectedDist = 0.0f;
+
+ const Float DIST_MARGIN = 0.1f;
+ if (mType == SINGLE_CURVE_AND_RADIUS) {
+ r = mR0;
+
+ expectedDist = (r + mLastR) * factor;
+
+ // Find C_i on the center curve.
+ for (size_t i = 0; i < MAX_LOOP; i++) {
+ t = (upper + lower) / 2.0f;
+ C = GetBezierPoint(mCenterBezier, t);
+
+ // Check overlap along arc.
+ circlesDist = GetBezierLength(mCenterBezier, mLastT, t);
+ if (circlesDist < expectedDist - DIST_MARGIN) {
+ lower = t;
+ } else if (circlesDist > expectedDist + DIST_MARGIN) {
+ upper = t;
+ } else {
+ break;
+ }
+ }
+ } else if (mType == SINGLE_CURVE) {
+ // Find C_i on the center curve, and calculate r_i.
+ for (size_t i = 0; i < MAX_LOOP; i++) {
+ t = (upper + lower) / 2.0f;
+ C = GetBezierPoint(mCenterBezier, t);
+
+ Point Diff = GetBezierDifferential(mCenterBezier, t);
+ Float DiffLength = Diff.Length();
+ if (DiffLength == 0.0f) {
+ // Basically this shouldn't happen.
+ // If differential is 0, we cannot calculate tangent circle,
+ // skip this point.
+ t = (t + upper) / 2.0f;
+ continue;
+ }
+
+ Point normal = PointRotateCCW90(Diff / DiffLength) * (-mNormalSign);
+ r = CalculateDistanceToEllipticArc(C, normal, mInnerCurveOrigin,
+ mInnerWidth, mInnerHeight);
+
+ // Check overlap along arc.
+ circlesDist = GetBezierLength(mCenterBezier, mLastT, t);
+ expectedDist = (r + mLastR) * factor;
+ if (circlesDist < expectedDist - DIST_MARGIN) {
+ lower = t;
+ } else if (circlesDist > expectedDist + DIST_MARGIN) {
+ upper = t;
+ } else {
+ break;
+ }
+ }
+ } else {
+ Float distSquareMax = Square(mMaxR * 3.0f);
+ Float circlesDistSquare = 0.0f;
+
+ // Find C_i and r_i.
+ for (size_t i = 0; i < MAX_LOOP; i++) {
+ t = (upper + lower) / 2.0f;
+ Point innerTangent = GetBezierPoint(mInnerBezier, t);
+ if ((innerTangent - mLastC).LengthSquare() > distSquareMax) {
+ // It's clear that this tangent point is too far, skip it.
+ upper = t;
+ continue;
+ }
+
+ Point Diff = GetBezierDifferential(mInnerBezier, t);
+ Float DiffLength = Diff.Length();
+ if (DiffLength == 0.0f) {
+ // Basically this shouldn't happen.
+ // If differential is 0, we cannot calculate tangent circle,
+ // skip this point.
+ t = (t + upper) / 2.0f;
+ continue;
+ }
+
+ Point normal = PointRotateCCW90(Diff / DiffLength) * mNormalSign;
+ FindPointAndRadius(C, r, innerTangent, normal, t);
+
+ // Check overlap with direct distance.
+ circlesDistSquare = (C - mLastC).LengthSquare();
+ expectedDist = (r + mLastR) * factor;
+ if (circlesDistSquare < Square(expectedDist - DIST_MARGIN)) {
+ lower = t;
+ } else if (circlesDistSquare > Square(expectedDist + DIST_MARGIN)) {
+ upper = t;
+ } else {
+ break;
+ }
+ }
+
+ circlesDist = sqrt(circlesDistSquare);
+ }
+
+ if (mHasZeroBorderWidth) {
+ // When calculating circle around r=0, it may result in wrong radius that
+ // is bigger than previous circle. Detect it and stop calculating.
+ const Float R_MARGIN = 0.1f;
+ if (mLastR < R_MARGIN && r > mLastR) {
+ mHasMore = false;
+ mLastR = 0.0f;
+ return 0.0f;
+ }
+ }
+
+ mLastT = t;
+ mLastC = C;
+ mLastR = r;
+
+ if (mHasZeroBorderWidth) {
+ const Float T_MARGIN = 0.001f;
+ if (mLastT >= 1.0f - T_MARGIN ||
+ (mLastC - mCn).LengthSquare() < Square(mLastR)) {
+ mHasMore = false;
+ }
+ }
+
+ if (expectedDist == 0.0f) {
+ return 0.0f;
+ }
+
+ return 1.0f - circlesDist * factor / expectedDist;
+}
+
+void DottedCornerFinder::FindBestOverlap(Float aMinR, Float aMinBorderRadius,
+ Float aMaxBorderRadius) {
+ // If overlap is not calculateable, find it with binary search,
+ // such that there exists i that C_i == C_n with the given overlap.
+
+ FourFloats key(aMinR, mMaxR, aMinBorderRadius, aMaxBorderRadius);
+ BestOverlap best;
+ if (DottedCornerCache.Get(key, &best)) {
+ mCount = best.count;
+ mBestOverlap = best.overlap;
+ return;
+ }
+
+ Float lower = 0.0f;
+ Float upper = 0.5f;
+ // Start from lower bound to find the minimum number of circles.
+ Float overlap = 0.0f;
+ mBestOverlap = overlap;
+ size_t targetCount = 0;
+
+ const Float OVERLAP_MARGIN = 0.1f;
+ for (size_t j = 0; j < MAX_LOOP; j++) {
+ Reset();
+
+ size_t count;
+ Float actualOverlap;
+ if (!GetCountAndLastOverlap(overlap, &count, &actualOverlap)) {
+ if (j == 0) {
+ mCount = mMaxCount;
+ break;
+ }
+ }
+
+ if (j == 0) {
+ if (count < 3 || (count == 3 && actualOverlap > 0.5f)) {
+ // |count == 3 && actualOverlap > 0.5f| means there could be
+ // a circle but it is too near from both ends.
+ //
+ // if actualOverlap == 0.0
+ // 1 2 3
+ // +-------+-------+-------+-------+
+ // | ##### | ***** | ##### | ##### |
+ // |#######|*******|#######|#######|
+ // |###+###|***+***|###+###|###+###|
+ // |# C_0 #|* C_1 *|# C_2 #|# C_n #|
+ // | ##### | ***** | ##### | ##### |
+ // +-------+-------+-------+-------+
+ // |
+ // V
+ // +-------+---+-------+---+-------+
+ // | ##### | | ##### | | ##### |
+ // |#######| |#######| |#######|
+ // |###+###| |###+###| |###+###| Find the best overlap to place
+ // |# C_0 #| |# C_1 #| |# C_n #| C_1 at the middle of them
+ // | ##### | | ##### | | ##### |
+ // +-------+---+-------+---|-------+
+ //
+ // if actualOverlap == 0.5
+ // 1 2 3
+ // +-------+-------+-------+---+
+ // | ##### | ***** | ##### |## |
+ // |#######|*******|##### C_n #|
+ // |###+###|***+***|###+###+###|
+ // |# C_0 #|* C_1 *|# C_2 #|###|
+ // | ##### | ***** | ##### |## |
+ // +-------+-------+-------+---+
+ // |
+ // V
+ // +-------+-+-------+-+-------+
+ // | ##### | | ##### | | ##### |
+ // |#######| |#######| |#######|
+ // |###+###| |###+###| |###+###| Even if we place C_1 at the middle
+ // |# C_0 #| |# C_1 #| |# C_n #| of them, it's too near from them
+ // | ##### | | ##### | | ##### |
+ // +-------+-+-------+-|-------+
+ // |
+ // V
+ // +-------+-----------+-------+
+ // | ##### | | ##### |
+ // |#######| |#######|
+ // |###+###| |###+###| Do not draw any circle
+ // |# C_0 #| |# C_n #|
+ // | ##### | | ##### |
+ // +-------+-----------+-------+
+ mCount = 0;
+ break;
+ }
+
+ // targetCount should be 2n, as we're searching C_1 to C_n.
+ //
+ // targetCount = 4
+ // mCount = 1
+ // 1 2 3 4
+ // +-------+-------+-------+-------+-------+
+ // | ##### | ***** | ##### | ***** | ##### |
+ // |#######|*******|#######|*******|#######|
+ // |###+###|***+***|###+###|***+***|###+###|
+ // |# C_0 #|* C_1 *|# C_2 #|* C_3 *|# C_n #|
+ // | ##### | ***** | ##### | ***** | ##### |
+ // +-------+-------+-------+-------+-------+
+ // 1
+ //
+ // targetCount = 6
+ // mCount = 2
+ // 1 2 3 4 5 6
+ // +-------+-------+-------+-------+-------+-------+-------+
+ // | ##### | ***** | ##### | ***** | ##### | ***** | ##### |
+ // |#######|*******|#######|*******|#######|*******|#######|
+ // |###+###|***+***|###+###|***+***|###+###|***+***|###+###|
+ // |# C_0 #|* C_1 *|# C_2 #|* C_3 *|# C_4 #|* C_5 *|# C_n #|
+ // | ##### | ***** | ##### | ***** | ##### | ***** | ##### |
+ // +-------+-------+-------+-------+-------+-------+-------+
+ // 1 2
+ if (count % 2) {
+ targetCount = count + 1;
+ } else {
+ targetCount = count;
+ }
+
+ mCount = targetCount / 2 - 1;
+ }
+
+ if (count == targetCount) {
+ mBestOverlap = overlap;
+
+ if (fabs(actualOverlap - overlap) < OVERLAP_MARGIN) {
+ break;
+ }
+
+ // We started from upper bound, no need to update range when j == 0.
+ if (j > 0) {
+ if (actualOverlap > overlap) {
+ lower = overlap;
+ } else {
+ upper = overlap;
+ }
+ }
+ } else {
+ // |j == 0 && count != targetCount| means that |targetCount = count + 1|,
+ // and we started from upper bound, no need to update range when j == 0.
+ if (j > 0) {
+ if (count > targetCount) {
+ upper = overlap;
+ } else {
+ lower = overlap;
+ }
+ }
+ }
+
+ overlap = (upper + lower) / 2.0f;
+ }
+
+ if (DottedCornerCache.Count() > DottedCornerCacheSize) {
+ DottedCornerCache.Clear();
+ }
+ DottedCornerCache.InsertOrUpdate(key, BestOverlap(mBestOverlap, mCount));
+}
+
+bool DottedCornerFinder::GetCountAndLastOverlap(Float aOverlap, size_t* aCount,
+ Float* aActualOverlap) {
+ // Return the number of circles and the last circles' overlap for the
+ // given overlap.
+
+ Reset();
+
+ const Float T_MARGIN = 0.001f;
+ const Float DIST_MARGIN = 0.1f;
+ const Float DIST_MARGIN_SQUARE = Square(DIST_MARGIN);
+ for (size_t i = 0; i < mMaxCount; i++) {
+ Float actualOverlap = FindNext(aOverlap);
+ if (mLastT >= 1.0f - T_MARGIN ||
+ (mLastC - mCn).LengthSquare() < DIST_MARGIN_SQUARE) {
+ *aCount = i + 1;
+ *aActualOverlap = actualOverlap;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+} // namespace mozilla
diff --git a/layout/painting/DottedCornerFinder.h b/layout/painting/DottedCornerFinder.h
new file mode 100644
index 0000000000..fd3a9e5d04
--- /dev/null
+++ b/layout/painting/DottedCornerFinder.h
@@ -0,0 +1,432 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_DottedCornerFinder_h_
+#define mozilla_DottedCornerFinder_h_
+
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/BezierUtils.h"
+#include "gfxRect.h"
+
+namespace mozilla {
+
+// Calculate C_i and r_i for each filled/unfilled circles in dotted corner.
+// Returns circle with C_{2j} and r_{2j} where 0 < 2j < n.
+//
+// ____-----------+
+// __---- ***** ###|
+// __---- ********* ####|
+// __--- ##### ***********#####|
+// _-- ######### *****+*****#####+ C_0
+// _- ########### *** C_1****#####|
+// / #####+##### ********* ####|
+// / . ### C_2 ### ***** ###|
+// | ######### ____-------+
+// | . #####____-----
+// | __----
+// | . /
+// | /
+// | ***** |
+// | ******* |
+// |*********|
+// |****+****|
+// | C_{n-1} |
+// | ******* |
+// | ***** |
+// | ##### |
+// | ####### |
+// |#########|
+// +----+----+
+// C_n
+
+class DottedCornerFinder {
+ typedef mozilla::gfx::Bezier Bezier;
+ typedef mozilla::gfx::Float Float;
+ typedef mozilla::gfx::Point Point;
+ typedef mozilla::gfx::Size Size;
+
+ public:
+ struct Result {
+ // Center point of dot and its radius.
+ Point C;
+ Float r;
+
+ Result(const Point& aC, Float aR) : C(aC), r(aR) { MOZ_ASSERT(aR >= 0); }
+ };
+
+ // aBorderRadiusX
+ // aCornerDim.width
+ // |<----------------->|
+ // | | v
+ // --+-------------___---+--
+ // ^ | __-- | |
+ // | | _- | | aR0
+ // | | / aC0 +--
+ // | | / | ^
+ // | | | |
+ // aBorderRadiusY | | | __--+
+ // aCornerDim.height | || _-
+ // | || /
+ // | | /
+ // | | |
+ // | | |
+ // | | |
+ // | | |
+ // v | aCn |
+ // --+----+----+
+ // | |
+ // |<-->|
+ // aRn
+ //
+ // aCornerDim and (aBorderRadiusX, aBorderRadiusY) can be different when
+ // aBorderRadiusX is smaller than aRn*2 or
+ // aBorderRadiusY is smaller than aR0*2.
+ //
+ // aCornerDim.width
+ // |<----------------->|
+ // | |
+ // | aBorderRadiusX |
+ // |<--------->| |
+ // | | |
+ // -------------------+-------__--+-------+--
+ // ^ ^ | _- | ^
+ // | | | / | |
+ // | | | / | |
+ // | aBorderRadiusY | | | | | aR0
+ // | | || | |
+ // | | | | |
+ // aCornerDim.height | v | | v
+ // | --+ aC0 +--
+ // | | |
+ // | | |
+ // | | |
+ // | | |
+ // | | |
+ // v | aCn |
+ // -------------------+---------+---------+
+ // | |
+ // |<------->|
+ // aRn
+ DottedCornerFinder(const Bezier& aOuterBezier, const Bezier& aInnerBezier,
+ mozilla::Corner aCorner, Float aBorderRadiusX,
+ Float aBorderRadiusY, const Point& aC0, Float aR0,
+ const Point& aCn, Float aRn, const Size& aCornerDim);
+
+ bool HasMore(void) const;
+ Result Next(void);
+
+ private:
+ static const size_t MAX_LOOP = 32;
+
+ // Bezier control points for the outer curve, the inner curve, and a curve
+ // that center points of circles are on (center curve).
+ //
+ // ___---+ outer curve
+ // __-- |
+ // _- |
+ // / __---+ center curve
+ // / __-- |
+ // | / |
+ // | / __--+ inner curve
+ // | | _-
+ // | | /
+ // | | /
+ // | | |
+ // | | |
+ // | | |
+ // | | |
+ // | | |
+ // +----+----+
+ Bezier mOuterBezier;
+ Bezier mInnerBezier;
+ Bezier mCenterBezier;
+
+ mozilla::Corner mCorner;
+
+ // Sign of the normal vector used in radius calculation, flipped depends on
+ // corner and start and end radii.
+ Float mNormalSign;
+
+ // Center points and raii for start and end circles, mR0 >= mRn.
+ // mMaxR = max(mR0, mRn)
+ //
+ // v
+ // ___---+------
+ // __-- #|# | mRn
+ // _- ##|## |
+ // / ##+## ---
+ // / mCn ^
+ // | #|#
+ // | __--+
+ // | _-
+ // | /
+ // | /
+ // | |
+ // | |
+ // | ##### |
+ // | ####### |
+ // |#########|
+ // +----+----+
+ // |## mC0 ##|
+ // | ####### |
+ // | ##### |
+ // | |
+ // |<-->|
+ //
+ // mR0
+ //
+ Point mC0;
+ Point mCn;
+ Float mR0;
+ Float mRn;
+ Float mMaxR;
+
+ // Parameters for the center curve with perfect circle and the inner curve.
+ // The center curve doesn't necessarily share the origin with others.
+ //
+ // ___---+
+ // __-- |
+ // _- |
+ // / __-+ |
+ // / __-- |
+ // | / |
+ // | / __--+--
+ // | | _- | ^
+ // | | / | |
+ // | | / | |
+ // | | | | |
+ // | | | | | mInnerHeight
+ // | | | | |
+ // | + | | |
+ // | | | v
+ // +---------+---------+
+ // | | mInnerCurveOrigin
+ // |<------->|
+ // mInnerWidth
+ //
+ // ___---+
+ // __--
+ // _-
+ // / __-+
+ // / __-- |
+ // | / |
+ // | / __--+
+ // | | _- |
+ // | | / |
+ // | | / |
+ // | | | |
+ // | | | |
+ // | | | |
+ // | +--- | ------+
+ // | | | | mCenterCurveOrigin
+ // + | + |
+ // | |
+ // | |
+ // | |
+ // | |
+ // |<---------->|
+ // mCenterCurveR
+ //
+ Point mCenterCurveOrigin;
+ Float mCenterCurveR;
+ Point mInnerCurveOrigin;
+ Float mInnerWidth;
+ Float mInnerHeight;
+
+ Point mLastC;
+ Float mLastR;
+ Float mLastT;
+
+ // Overlap between two circles.
+ // It uses arc length on PERFECT, SINGLE_CURVE_AND_RADIUS, and SINGLE_CURVE,
+ // and direct distance on OTHER.
+ Float mBestOverlap;
+
+ // If one of border-widths is 0, do not calculate overlap, and draw circles
+ // until it reaches the other side or exceeds mMaxCount.
+ bool mHasZeroBorderWidth;
+ bool mHasMore;
+
+ // The maximum number of filled/unfilled circles.
+ size_t mMaxCount;
+
+ enum {
+ // radius.width
+ // |<----------------->|
+ // | |
+ // --+-------------___---+----
+ // ^ | __-- #|# ^
+ // | | _- ##|## |
+ // | | / ##+## | top-width
+ // | | / ##|## |
+ // | | | #|# v
+ // | | | __--+----
+ // radius.height | || _-
+ // | || /
+ // | | /
+ // | | |
+ // | | |
+ // | | ##### |
+ // | | ####### |
+ // v |#########|
+ // --+----+----+
+ // |#########|
+ // | ####### |
+ // | ##### |
+ // | |
+ // |<------->|
+ // left-width
+
+ // * top-width == left-width
+ // * radius.width == radius.height
+ // * top-width < radius.width * 2
+ //
+ // All circles has same radii and are on single perfect circle's arc.
+ // Overlap is known.
+ //
+ // Split the perfect circle's arc into 2n segments, each segment's length is
+ // top-width * (1 - overlap). Place each circle's center point C_i on each
+ // end of the segment, each circle's radius r_i is top-width / 2
+ //
+ // #####
+ // #######
+ // perfect #########
+ // circle's ___---+####
+ // arc ##### __-- ## C_0 ##
+ // | #####_- ###|###
+ // | ####+#### ##|##
+ // | ##/C_i ## |
+ // | |###### |
+ // | | ##### |
+ // +->| |
+ // | |
+ // ##|## |
+ // ###|### |
+ // ####|#### |
+ // ####+-------------------+
+ // ## C_n ##
+ // #######
+ // #####
+ PERFECT,
+
+ // * top-width == left-width
+ // * 0.5 < radius.width / radius.height < 2.0
+ // * top-width < min(radius.width, radius.height) * 2
+ //
+ // All circles has same radii and are on single elliptic arc.
+ // Overlap is known.
+ //
+ // Split the elliptic arc into 2n segments, each segment's length is
+ // top-width * (1 - overlap). Place each circle's center point C_i on each
+ // end of the segment, each circle's radius r_i is top-width / 2
+ //
+ // #####
+ // #######
+ // ##### #########
+ // ####### ____----+####
+ // elliptic ######__--- ## C_0 ##
+ // arc ##__+-### ###|###
+ // | / # C_i # ##|##
+ // +--> / ##### |
+ // | |
+ // ###|# |
+ // ###|### |
+ // ####|#### |
+ // ####+------------------------+
+ // ## C_n ##
+ // #######
+ // #####
+ SINGLE_CURVE_AND_RADIUS,
+
+ // * top-width != left-width
+ // * 0 < min(top-width, left-width)
+ // * 0.5 < radius.width / radius.height < 2.0
+ // * max(top-width, left-width) < min(radius.width, radius.height) * 2
+ //
+ // All circles are on single elliptic arc.
+ // Overlap is unknown.
+ //
+ // Place each circle's center point C_i on elliptic arc, each circle's
+ // radius r_i is the distance between the center point and the inner curve.
+ // The arc segment's length between C_i and C_{i-1} is
+ // (r_i + r_{i-1}) * (1 - overlap).
+ //
+ // outer curve
+ // /
+ // /
+ // / / center curve
+ // / ####### /
+ // /## /#
+ // +# / #
+ // /# / #
+ // / # C_i / #
+ // / # + # /
+ // / # / \ # / inner curve
+ // # / \ #/
+ // # / r_i \+
+ // #/ ##/
+ // / ####### /
+ // /
+ SINGLE_CURVE,
+
+ // Other cases.
+ // Circles are not on single elliptic arc.
+ // Overlap are unknown.
+ //
+ // Place tangent point innerTangent on the inner curve and find circle's
+ // center point C_i and radius r_i where the circle is also tangent to the
+ // outer curve.
+ // Distance between C_i and C_{i-1} is (r_i + r_{i-1}) * (1 - overlap).
+ //
+ // outer curve
+ // /
+ // /
+ // /
+ // / #######
+ // /## ##
+ // +# #
+ // /# \ #
+ // / # \ #
+ // / # + # /
+ // / # C_i \ # / inner curve
+ // # \ #/
+ // # r_i \+
+ // ## ##/ innerTangent
+ // ####### /
+ // /
+ OTHER
+ } mType;
+
+ size_t mI;
+ size_t mCount;
+
+ // Determine mType from parameters.
+ void DetermineType(Float aBorderRadiusX, Float aBorderRadiusY);
+
+ // Reset calculation.
+ void Reset(void);
+
+ // Find radius for the given tangent point on the inner curve such that the
+ // circle is also tangent to the outer curve.
+ void FindPointAndRadius(Point& C, Float& r, const Point& innerTangent,
+ const Point& normal, Float t);
+
+ // Find next dot.
+ Float FindNext(Float overlap);
+
+ // Find mBestOverlap for parameters.
+ void FindBestOverlap(Float aMinR, Float aMinBorderRadius,
+ Float aMaxBorderRadius);
+
+ // Fill corner with dots with given overlap, and return the number of dots
+ // and last two dots's overlap.
+ bool GetCountAndLastOverlap(Float aOverlap, size_t* aCount,
+ Float* aActualOverlap);
+};
+
+} // namespace mozilla
+
+#endif /* mozilla_DottedCornerFinder_h_ */
diff --git a/layout/painting/HitTestInfo.cpp b/layout/painting/HitTestInfo.cpp
new file mode 100644
index 0000000000..872773c32f
--- /dev/null
+++ b/layout/painting/HitTestInfo.cpp
@@ -0,0 +1,74 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "HitTestInfo.h"
+
+#include "mozilla/StaticPtr.h"
+#include "mozilla/webrender/WebRenderAPI.h"
+#include "nsDisplayList.h"
+#include "nsIFrame.h"
+#include "mozilla/layers/ScrollableLayerGuid.h"
+
+using namespace mozilla::gfx;
+
+namespace mozilla {
+
+static StaticAutoPtr<const HitTestInfo> gEmptyHitTestInfo;
+
+const HitTestInfo& HitTestInfo::Empty() {
+ if (!gEmptyHitTestInfo) {
+ gEmptyHitTestInfo = new HitTestInfo();
+ }
+
+ return *gEmptyHitTestInfo;
+}
+
+void HitTestInfo::Shutdown() { gEmptyHitTestInfo = nullptr; }
+
+using ViewID = layers::ScrollableLayerGuid::ViewID;
+
+ViewID HitTestInfo::GetViewId(wr::DisplayListBuilder& aBuilder,
+ const ActiveScrolledRoot* aASR) const {
+ if (mScrollTarget) {
+ return *mScrollTarget;
+ }
+
+ Maybe<ViewID> fixedTarget = aBuilder.GetContainingFixedPosScrollTarget(aASR);
+
+ if (fixedTarget) {
+ return *fixedTarget;
+ }
+
+ if (aASR) {
+ return aASR->GetViewId();
+ }
+
+ return layers::ScrollableLayerGuid::NULL_SCROLL_ID;
+}
+
+void HitTestInfo::Initialize(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame) {
+ if (!aBuilder->BuildCompositorHitTestInfo()) {
+ return;
+ }
+
+ mInfo = aFrame->GetCompositorHitTestInfo(aBuilder);
+ if (mInfo != gfx::CompositorHitTestInvisibleToHit) {
+ mArea = aFrame->GetCompositorHitTestArea(aBuilder);
+ InitializeScrollTarget(aBuilder);
+ }
+}
+
+void HitTestInfo::InitializeScrollTarget(nsDisplayListBuilder* aBuilder) {
+ if (aBuilder->GetCurrentScrollbarDirection().isSome()) {
+ // In the case of scrollbar frames, we use the scrollbar's target
+ // scrollframe instead of the scrollframe with which the scrollbar actually
+ // moves.
+ MOZ_ASSERT(Info().contains(CompositorHitTestFlags::eScrollbar));
+ mScrollTarget = Some(aBuilder->GetCurrentScrollbarTarget());
+ }
+}
+
+} // namespace mozilla
diff --git a/layout/painting/HitTestInfo.h b/layout/painting/HitTestInfo.h
new file mode 100644
index 0000000000..95438b810c
--- /dev/null
+++ b/layout/painting/HitTestInfo.h
@@ -0,0 +1,59 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GFX_HITTESTINFO_H
+#define GFX_HITTESTINFO_H
+
+#include "mozilla/gfx/CompositorHitTestInfo.h"
+#include "mozilla/layers/ScrollableLayerGuid.h"
+#include "nsRect.h"
+
+class nsIFrame;
+
+namespace mozilla {
+
+class nsDisplayListBuilder;
+struct ActiveScrolledRoot;
+
+namespace wr {
+class DisplayListBuilder;
+} // namespace wr
+
+/**
+ * A helper class that manages compositor hit testing information.
+ */
+class HitTestInfo {
+ public:
+ using CompositorHitTestInfo = gfx::CompositorHitTestInfo;
+ using ViewID = layers::ScrollableLayerGuid::ViewID;
+
+ ViewID GetViewId(wr::DisplayListBuilder& aBuilder,
+ const ActiveScrolledRoot* aASR) const;
+
+ void Initialize(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame);
+ void InitializeScrollTarget(nsDisplayListBuilder* aBuilder);
+
+ void SetAreaAndInfo(const nsRect& aArea, const CompositorHitTestInfo& aInfo) {
+ mArea = aArea;
+ mInfo = aInfo;
+ }
+
+ static void Shutdown();
+
+ const nsRect& Area() const { return mArea; }
+ const CompositorHitTestInfo& Info() const { return mInfo; }
+
+ static const HitTestInfo& Empty();
+
+ private:
+ nsRect mArea;
+ CompositorHitTestInfo mInfo;
+ mozilla::Maybe<ViewID> mScrollTarget;
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/layout/painting/MaskLayerImageCache.cpp b/layout/painting/MaskLayerImageCache.cpp
new file mode 100644
index 0000000000..2b3b63dc57
--- /dev/null
+++ b/layout/painting/MaskLayerImageCache.cpp
@@ -0,0 +1,64 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "MaskLayerImageCache.h"
+
+#include "ImageContainer.h"
+#include "mozilla/layers/KnowsCompositor.h"
+
+using namespace mozilla::layers;
+
+namespace mozilla {
+
+MaskLayerImageCache::MaskLayerImageCache() {
+ MOZ_COUNT_CTOR(MaskLayerImageCache);
+}
+MaskLayerImageCache::~MaskLayerImageCache() {
+ MOZ_COUNT_DTOR(MaskLayerImageCache);
+}
+
+void MaskLayerImageCache::Sweep() {
+ for (auto iter = mMaskImageContainers.Iter(); !iter.Done(); iter.Next()) {
+ const auto& key = iter.Get()->mKey;
+ if (key->HasZeroLayerCount()) {
+ iter.Remove();
+ }
+ }
+}
+
+ImageContainer* MaskLayerImageCache::FindImageFor(
+ const MaskLayerImageKey** aKey) {
+ if (MaskLayerImageEntry* entry = mMaskImageContainers.GetEntry(**aKey)) {
+ *aKey = entry->mKey.get();
+ return entry->mContainer;
+ }
+
+ return nullptr;
+}
+
+void MaskLayerImageCache::PutImage(const MaskLayerImageKey* aKey,
+ ImageContainer* aContainer) {
+ MaskLayerImageEntry* entry = mMaskImageContainers.PutEntry(*aKey);
+ entry->mContainer = aContainer;
+}
+
+MaskLayerImageCache::MaskLayerImageKey::MaskLayerImageKey()
+ : mRoundedClipRects(), mLayerCount(0) {
+ MOZ_COUNT_CTOR(MaskLayerImageKey);
+}
+
+MaskLayerImageCache::MaskLayerImageKey::MaskLayerImageKey(
+ const MaskLayerImageKey& aKey)
+ : mRoundedClipRects(aKey.mRoundedClipRects.Clone()),
+ mLayerCount(aKey.mLayerCount) {
+ MOZ_COUNT_CTOR(MaskLayerImageKey);
+}
+
+MaskLayerImageCache::MaskLayerImageKey::~MaskLayerImageKey() {
+ MOZ_COUNT_DTOR(MaskLayerImageKey);
+}
+
+} // namespace mozilla
diff --git a/layout/painting/MaskLayerImageCache.h b/layout/painting/MaskLayerImageCache.h
new file mode 100644
index 0000000000..7e20f72a20
--- /dev/null
+++ b/layout/painting/MaskLayerImageCache.h
@@ -0,0 +1,257 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef MASKLAYERIMAGECACHE_H_
+#define MASKLAYERIMAGECACHE_H_
+
+#include "DisplayItemClip.h"
+#include "nsPresContext.h"
+#include "mozilla/gfx/Matrix.h"
+#include "mozilla/UniquePtr.h"
+
+namespace mozilla {
+
+namespace layers {
+class ImageContainer;
+class KnowsCompositor;
+} // namespace layers
+
+/**
+ * Keeps a record of image containers for mask layers, containers are mapped
+ * from the rounded rects used to create them.
+ * The cache stores MaskLayerImageEntries indexed by MaskLayerImageKeys.
+ * Each MaskLayerImageEntry owns a heap-allocated MaskLayerImageKey
+ * (heap-allocated so that a mask layer's userdata can keep a pointer to the
+ * key for its image, in spite of the hashtable moving its entries around).
+ * The key consists of the rounded rects used to create the mask,
+ * an nsRefPtr to the ImageContainer containing the image, and a count
+ * of the number of layers currently using this ImageContainer.
+ * When the key's layer count is zero, the cache
+ * may remove the entry, which deletes the key object.
+ */
+class MaskLayerImageCache {
+ typedef mozilla::layers::ImageContainer ImageContainer;
+ typedef mozilla::layers::KnowsCompositor KnowsCompositor;
+
+ public:
+ MaskLayerImageCache();
+ ~MaskLayerImageCache();
+
+ /**
+ * Representation of a rounded rectangle in device pixel coordinates, in
+ * contrast to DisplayItemClip::RoundedRect, which uses app units.
+ * In particular, our internal representation uses a gfxRect, rather than
+ * an nsRect, so this class is easier to use with transforms.
+ */
+ struct PixelRoundedRect {
+ PixelRoundedRect() = delete;
+
+ PixelRoundedRect(const DisplayItemClip::RoundedRect& aRRect,
+ nsPresContext* aPresContext)
+ : mRect(aPresContext->AppUnitsToGfxUnits(aRRect.mRect.x),
+ aPresContext->AppUnitsToGfxUnits(aRRect.mRect.y),
+ aPresContext->AppUnitsToGfxUnits(aRRect.mRect.width),
+ aPresContext->AppUnitsToGfxUnits(aRRect.mRect.height)) {
+ MOZ_COUNT_CTOR(PixelRoundedRect);
+ for (const auto corner : mozilla::AllPhysicalHalfCorners()) {
+ mRadii[corner] =
+ aPresContext->AppUnitsToGfxUnits(aRRect.mRadii[corner]);
+ }
+ }
+
+ PixelRoundedRect(const PixelRoundedRect& aPRR) : mRect(aPRR.mRect) {
+ MOZ_COUNT_CTOR(PixelRoundedRect);
+ for (const auto corner : mozilla::AllPhysicalHalfCorners()) {
+ mRadii[corner] = aPRR.mRadii[corner];
+ }
+ }
+
+ MOZ_COUNTED_DTOR(PixelRoundedRect)
+
+ // Applies the scale and translate components of aTransform.
+ // It is an error to pass a matrix which does more than just scale
+ // and translate.
+ void ScaleAndTranslate(const gfx::Matrix& aTransform) {
+ NS_ASSERTION(aTransform._12 == 0 && aTransform._21 == 0,
+ "Transform has a component other than scale and translate");
+
+ mRect = aTransform.TransformBounds(mRect);
+
+ for (size_t i = 0; i < ArrayLength(mRadii); i += 2) {
+ mRadii[i] *= aTransform._11;
+ mRadii[i + 1] *= aTransform._22;
+ }
+ }
+
+ bool operator==(const PixelRoundedRect& aOther) const {
+ if (!mRect.IsEqualInterior(aOther.mRect)) {
+ return false;
+ }
+
+ for (const auto corner : mozilla::AllPhysicalHalfCorners()) {
+ if (mRadii[corner] != aOther.mRadii[corner]) {
+ return false;
+ }
+ }
+ return true;
+ }
+ bool operator!=(const PixelRoundedRect& aOther) const {
+ return !(*this == aOther);
+ }
+
+ // Create a hash for this object.
+ PLDHashNumber Hash() const {
+ PLDHashNumber hash = HashBytes(&mRect.x, 4 * sizeof(gfxFloat));
+ hash = AddToHash(hash, HashBytes(mRadii, 8 * sizeof(gfxFloat)));
+
+ return hash;
+ }
+
+ gfx::Rect mRect;
+ // Indices into mRadii are the enum HalfCorner constants in gfx/2d/Types.h
+ gfxFloat mRadii[8];
+ };
+
+ struct MaskLayerImageKeyRef;
+
+ /**
+ * A key to identify cached image containers.
+ * The const-ness of this class is with respect to its use as a key into a
+ * hashtable, so anything not used to create the hash is mutable.
+ * mLayerCount counts the number of mask layers which have a reference to
+ * MaskLayerImageEntry::mContainer; it is maintained by MaskLayerUserData,
+ * which keeps a reference to the key. There will usually be mLayerCount + 1
+ * pointers to a key object (the +1 being from the hashtable entry), but this
+ * invariant may be temporarily broken.
+ */
+ struct MaskLayerImageKey {
+ friend struct MaskLayerImageKeyRef;
+
+ MaskLayerImageKey();
+ MaskLayerImageKey(const MaskLayerImageKey& aKey);
+
+ ~MaskLayerImageKey();
+
+ bool HasZeroLayerCount() const { return mLayerCount == 0; }
+
+ PLDHashNumber Hash() const {
+ PLDHashNumber hash = 0;
+
+ for (uint32_t i = 0; i < mRoundedClipRects.Length(); ++i) {
+ hash = AddToHash(hash, mRoundedClipRects[i].Hash());
+ }
+ hash = AddToHash(hash, mKnowsCompositor.get());
+
+ return hash;
+ }
+
+ bool operator==(const MaskLayerImageKey& aOther) const {
+ return mKnowsCompositor == aOther.mKnowsCompositor &&
+ mRoundedClipRects == aOther.mRoundedClipRects;
+ }
+
+ nsTArray<PixelRoundedRect> mRoundedClipRects;
+ RefPtr<KnowsCompositor> mKnowsCompositor;
+
+ private:
+ void IncLayerCount() const { ++mLayerCount; }
+ void DecLayerCount() const {
+ NS_ASSERTION(mLayerCount > 0, "Inconsistent layer count");
+ --mLayerCount;
+ }
+ mutable uint32_t mLayerCount;
+ };
+
+ /**
+ * This struct maintains a reference to a MaskLayerImageKey, via a variant on
+ * refcounting. When a key is passed in via Reset(), we increment the
+ * passed-in key's mLayerCount, and we decrement its mLayerCount when we're
+ * destructed (or when the key is replaced via a second Reset() call).
+ *
+ * However, unlike standard refcounting smart-pointers, this object does
+ * *not* delete the tracked MaskLayerImageKey -- instead, deletion happens
+ * in MaskLayerImageCache::Sweep(), for any keys whose mLayerCount is 0.
+ */
+ struct MaskLayerImageKeyRef {
+ ~MaskLayerImageKeyRef() {
+ if (mRawPtr) {
+ mRawPtr->DecLayerCount();
+ }
+ }
+
+ MaskLayerImageKeyRef() : mRawPtr(nullptr) {}
+ MaskLayerImageKeyRef(const MaskLayerImageKeyRef&) = delete;
+ void operator=(const MaskLayerImageKeyRef&) = delete;
+
+ void Reset(const MaskLayerImageKey* aPtr) {
+ MOZ_ASSERT(
+ aPtr, "Cannot initialize a MaskLayerImageKeyRef with a null pointer");
+ aPtr->IncLayerCount();
+ if (mRawPtr) {
+ mRawPtr->DecLayerCount();
+ }
+ mRawPtr = aPtr;
+ }
+
+ private:
+ const MaskLayerImageKey* mRawPtr;
+ };
+
+ // Find an image container for aKey, returns nullptr if there is no suitable
+ // cached image. If there is an image, then aKey is set to point at the stored
+ // key for the image.
+ ImageContainer* FindImageFor(const MaskLayerImageKey** aKey);
+
+ // Add an image container with a key to the cache
+ // The image container used will be set as the container in aKey and aKey
+ // itself will be linked from this cache
+ void PutImage(const MaskLayerImageKey* aKey, ImageContainer* aContainer);
+
+ // Sweep the cache for old image containers that can be deleted
+ void Sweep();
+
+ protected:
+ class MaskLayerImageEntry : public PLDHashEntryHdr {
+ public:
+ typedef const MaskLayerImageKey& KeyType;
+ typedef const MaskLayerImageKey* KeyTypePointer;
+
+ explicit MaskLayerImageEntry(KeyTypePointer aKey) : mKey(aKey) {
+ MOZ_COUNT_CTOR(MaskLayerImageEntry);
+ }
+ MaskLayerImageEntry(const MaskLayerImageEntry& aOther)
+ : mKey(aOther.mKey.get()) {
+ NS_ERROR("ALLOW_MEMMOVE == true, should never be called");
+ }
+ MOZ_COUNTED_DTOR(MaskLayerImageEntry)
+
+ // KeyEquals(): does this entry match this key?
+ bool KeyEquals(KeyTypePointer aKey) const { return *mKey == *aKey; }
+
+ // KeyToPointer(): Convert KeyType to KeyTypePointer
+ static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; }
+
+ // HashKey(): calculate the hash number
+ static PLDHashNumber HashKey(KeyTypePointer aKey) { return aKey->Hash(); }
+
+ // ALLOW_MEMMOVE can we move this class with memmove(), or do we have
+ // to use the copy constructor?
+ enum { ALLOW_MEMMOVE = true };
+
+ bool operator==(const MaskLayerImageEntry& aOther) const {
+ return KeyEquals(aOther.mKey.get());
+ }
+
+ mozilla::UniquePtr<const MaskLayerImageKey> mKey;
+ RefPtr<ImageContainer> mContainer;
+ };
+
+ nsTHashtable<MaskLayerImageEntry> mMaskImageContainers;
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/layout/painting/MatrixStack.h b/layout/painting/MatrixStack.h
new file mode 100644
index 0000000000..320d08317b
--- /dev/null
+++ b/layout/painting/MatrixStack.h
@@ -0,0 +1,61 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef MOZILLA_PAINTING_MATRIXSTACK_H
+#define MOZILLA_PAINTING_MATRIXSTACK_H
+
+#include "nsTArray.h"
+#include "mozilla/gfx/MatrixFwd.h"
+
+namespace mozilla {
+
+/**
+ * MatrixStack stores a stack of matrices and keeps track of the accumulated
+ * transform matrix.
+ */
+template <typename T>
+class MatrixStack {
+ public:
+ MatrixStack() = default;
+
+ ~MatrixStack() { MOZ_ASSERT(mMatrices.IsEmpty()); }
+
+ /**
+ * Returns the current accumulated transform matrix.
+ */
+ const T& CurrentMatrix() const { return mCurrentMatrix; }
+
+ /**
+ * Returns true if any matrices are currently pushed to the stack.
+ */
+ bool HasTransform() const { return mMatrices.Length() > 0; }
+
+ /**
+ * Pushes the given |aMatrix| to the stack.
+ */
+ void Push(const T& aMatrix) {
+ mMatrices.AppendElement(mCurrentMatrix);
+ mCurrentMatrix = aMatrix * mCurrentMatrix;
+ }
+
+ /**
+ * Pops the most recently added matrix from the stack.
+ */
+ void Pop() {
+ MOZ_ASSERT(mMatrices.Length() > 0);
+ mCurrentMatrix = mMatrices.PopLastElement();
+ }
+
+ private:
+ T mCurrentMatrix;
+ AutoTArray<T, 2> mMatrices;
+};
+
+typedef MatrixStack<gfx::Matrix4x4Flagged> MatrixStack4x4;
+
+} // namespace mozilla
+
+#endif /* MOZILLA_PAINTING_MATRIXSTACK_H */
diff --git a/layout/painting/PaintTracker.cpp b/layout/painting/PaintTracker.cpp
new file mode 100644
index 0000000000..208f037e7a
--- /dev/null
+++ b/layout/painting/PaintTracker.cpp
@@ -0,0 +1,13 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/PaintTracker.h"
+
+namespace mozilla {
+
+int PaintTracker::gPaintTracker;
+
+} // namespace mozilla
diff --git a/layout/painting/PaintTracker.h b/layout/painting/PaintTracker.h
new file mode 100644
index 0000000000..1f5554f983
--- /dev/null
+++ b/layout/painting/PaintTracker.h
@@ -0,0 +1,31 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_PaintTracker_h
+#define mozilla_PaintTracker_h
+
+#include "mozilla/Attributes.h"
+#include "nsDebug.h"
+
+namespace mozilla {
+
+class MOZ_STACK_CLASS PaintTracker {
+ public:
+ PaintTracker() { ++gPaintTracker; }
+ ~PaintTracker() {
+ NS_ASSERTION(gPaintTracker > 0, "Mismatched constructor/destructor");
+ --gPaintTracker;
+ }
+
+ static bool IsPainting() { return !!gPaintTracker; }
+
+ private:
+ static int gPaintTracker;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_PaintTracker_h
diff --git a/layout/painting/RetainedDisplayListBuilder.cpp b/layout/painting/RetainedDisplayListBuilder.cpp
new file mode 100644
index 0000000000..3e3b8e9a94
--- /dev/null
+++ b/layout/painting/RetainedDisplayListBuilder.cpp
@@ -0,0 +1,1732 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include "RetainedDisplayListBuilder.h"
+
+#include "mozilla/Attributes.h"
+#include "mozilla/StaticPrefs_layout.h"
+#include "nsIFrame.h"
+#include "nsIFrameInlines.h"
+#include "nsIScrollableFrame.h"
+#include "nsPlaceholderFrame.h"
+#include "nsSubDocumentFrame.h"
+#include "nsViewManager.h"
+#include "nsCanvasFrame.h"
+#include "mozilla/AutoRestore.h"
+#include "mozilla/DisplayPortUtils.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/ProfilerLabels.h"
+
+/**
+ * Code for doing display list building for a modified subset of the window,
+ * and then merging it into the existing display list (for the full window).
+ *
+ * The approach primarily hinges on the observation that the 'true' ordering
+ * of display items is represented by a DAG (only items that intersect in 2d
+ * space have a defined ordering). Our display list is just one of a many
+ * possible linear representations of this ordering.
+ *
+ * Each time a frame changes (gets a new ComputedStyle, or has a size/position
+ * change), we schedule a paint (as we do currently), but also reord the frame
+ * that changed.
+ *
+ * When the next paint occurs we union the overflow areas (in screen space) of
+ * the changed frames, and compute a rect/region that contains all changed
+ * items. We then build a display list just for this subset of the screen and
+ * merge it into the display list from last paint.
+ *
+ * Any items that exist in one list and not the other must not have a defined
+ * ordering in the DAG, since they need to intersect to have an ordering and
+ * we would have built both in the new list if they intersected. Given that, we
+ * can align items that appear in both lists, and any items that appear between
+ * matched items can be inserted into the merged list in any order.
+ *
+ * Frames that are a stacking context, containing blocks for position:fixed
+ * descendants, and don't have any continuations (see
+ * CanStoreDisplayListBuildingRect) trigger recursion into the algorithm with
+ * separate retaining decisions made.
+ *
+ * RDL defines the concept of an AnimatedGeometryRoot (AGR), the nearest
+ * ancestor frame which can be moved asynchronously on the compositor thread.
+ * These are currently nsDisplayItems which return true from CanMoveAsync
+ * (animated nsDisplayTransform and nsDisplayStickyPosition) and
+ * ActiveScrolledRoots.
+ *
+ * For each context that we run the retaining algorithm, there can only be
+ * mutations to one AnimatedGeometryRoot. This is because we are unable to
+ * reason about intersections of items that might then move relative to each
+ * other without RDL running again. If there are mutations to multiple
+ * AnimatedGeometryRoots, then we bail out and rebuild all the items in the
+ * context.
+ *
+ * Otherwise, when mutations are restricted to a single AGR, we pre-process the
+ * old display list and mark the frames for all existing (unmodified!) items
+ * that belong to a different AGR and ensure that we rebuild those items for
+ * correct sorting with the modified ones.
+ */
+
+namespace mozilla {
+
+RetainedDisplayListData::RetainedDisplayListData()
+ : mModifiedFrameLimit(
+ StaticPrefs::layout_display_list_rebuild_frame_limit()) {}
+
+void RetainedDisplayListData::AddModifiedFrame(nsIFrame* aFrame) {
+ MOZ_ASSERT(!aFrame->IsFrameModified());
+ Flags(aFrame) += RetainedDisplayListData::FrameFlag::Modified;
+ aFrame->SetFrameIsModified(true);
+ mModifiedFrameCount++;
+}
+
+static void MarkFramesWithItemsAndImagesModified(nsDisplayList* aList) {
+ for (nsDisplayItem* i : *aList) {
+ if (!i->HasDeletedFrame() && i->CanBeReused() &&
+ !i->Frame()->IsFrameModified()) {
+ // If we have existing cached geometry for this item, then check that for
+ // whether we need to invalidate for a sync decode. If we don't, then
+ // use the item's flags.
+ // XXX: handle webrender case by looking up retained data for the item
+ // and checking InvalidateForSyncDecodeImages
+ bool invalidate = false;
+ if (!(i->GetFlags() & TYPE_RENDERS_NO_IMAGES)) {
+ invalidate = true;
+ }
+
+ if (invalidate) {
+ DL_LOGV("RDL - Invalidating item %p (%s)", i, i->Name());
+ i->FrameForInvalidation()->MarkNeedsDisplayItemRebuild();
+ if (i->GetDependentFrame()) {
+ i->GetDependentFrame()->MarkNeedsDisplayItemRebuild();
+ }
+ }
+ }
+ if (i->GetChildren()) {
+ MarkFramesWithItemsAndImagesModified(i->GetChildren());
+ }
+ }
+}
+
+static nsIFrame* SelectAGRForFrame(nsIFrame* aFrame, nsIFrame* aParentAGR) {
+ if (!aFrame->IsStackingContext() || !aFrame->IsFixedPosContainingBlock()) {
+ return aParentAGR;
+ }
+
+ if (!aFrame->HasOverrideDirtyRegion()) {
+ return nullptr;
+ }
+
+ nsDisplayListBuilder::DisplayListBuildingData* data =
+ aFrame->GetProperty(nsDisplayListBuilder::DisplayListBuildingRect());
+
+ return data && data->mModifiedAGR ? data->mModifiedAGR : nullptr;
+}
+
+void RetainedDisplayListBuilder::AddSizeOfIncludingThis(
+ nsWindowSizes& aSizes) const {
+ aSizes.mLayoutRetainedDisplayListSize += aSizes.mState.mMallocSizeOf(this);
+ mBuilder.AddSizeOfExcludingThis(aSizes);
+ mList.AddSizeOfExcludingThis(aSizes);
+}
+
+bool AnyContentAncestorModified(nsIFrame* aFrame, nsIFrame* aStopAtFrame) {
+ nsIFrame* f = aFrame;
+ while (f) {
+ if (f->IsFrameModified()) {
+ return true;
+ }
+
+ if (aStopAtFrame && f == aStopAtFrame) {
+ break;
+ }
+
+ f = nsLayoutUtils::GetDisplayListParent(f);
+ }
+
+ return false;
+}
+
+// Removes any display items that belonged to a frame that was deleted,
+// and mark frames that belong to a different AGR so that get their
+// items built again.
+// TODO: We currently descend into all children even if we don't have an AGR
+// to mark, as child stacking contexts might. It would be nice if we could
+// jump into those immediately rather than walking the entire thing.
+bool RetainedDisplayListBuilder::PreProcessDisplayList(
+ RetainedDisplayList* aList, nsIFrame* aAGR, PartialUpdateResult& aUpdated,
+ nsIFrame* aAsyncAncestor, const ActiveScrolledRoot* aAsyncAncestorASR,
+ nsIFrame* aOuterFrame, uint32_t aCallerKey, uint32_t aNestingDepth,
+ bool aKeepLinked) {
+ // The DAG merging algorithm does not have strong mechanisms in place to keep
+ // the complexity of the resulting DAG under control. In some cases we can
+ // build up edges very quickly. Detect those cases and force a full display
+ // list build if we hit them.
+ static const uint32_t kMaxEdgeRatio = 5;
+ const bool initializeDAG = !aList->mDAG.Length();
+ if (!aKeepLinked && !initializeDAG &&
+ aList->mDAG.mDirectPredecessorList.Length() >
+ (aList->mDAG.mNodesInfo.Length() * kMaxEdgeRatio)) {
+ return false;
+ }
+
+ // If we had aKeepLinked=true for this list on the previous paint, then
+ // mOldItems will already be initialized as it won't have been consumed during
+ // a merge.
+ const bool initializeOldItems = aList->mOldItems.IsEmpty();
+ if (initializeOldItems) {
+ aList->mOldItems.SetCapacity(aList->Length());
+ } else {
+ MOZ_RELEASE_ASSERT(!initializeDAG);
+ }
+
+ MOZ_RELEASE_ASSERT(
+ initializeDAG ||
+ aList->mDAG.Length() ==
+ (initializeOldItems ? aList->Length() : aList->mOldItems.Length()));
+
+ nsDisplayList out(Builder());
+
+ size_t i = 0;
+ while (nsDisplayItem* item = aList->RemoveBottom()) {
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ item->SetMergedPreProcessed(false, true);
+#endif
+
+ // If we have a previously initialized old items list, then it can differ
+ // from the current list due to items removed for having a deleted frame.
+ // We can't easily remove these, since the DAG has entries for those indices
+ // and it's hard to rewrite in-place.
+ // Skip over entries with no current item to keep the iterations in sync.
+ if (!initializeOldItems) {
+ while (!aList->mOldItems[i].mItem) {
+ i++;
+ }
+ }
+
+ if (initializeDAG) {
+ if (i == 0) {
+ aList->mDAG.AddNode(Span<const MergedListIndex>());
+ } else {
+ MergedListIndex previous(i - 1);
+ aList->mDAG.AddNode(Span<const MergedListIndex>(&previous, 1));
+ }
+ }
+
+ if (!item->CanBeReused() || item->HasDeletedFrame() ||
+ AnyContentAncestorModified(item->FrameForInvalidation(), aOuterFrame)) {
+ if (initializeOldItems) {
+ aList->mOldItems.AppendElement(OldItemInfo(nullptr));
+ } else {
+ MOZ_RELEASE_ASSERT(aList->mOldItems[i].mItem == item);
+ aList->mOldItems[i].mItem = nullptr;
+ }
+
+ if (item->IsGlassItem() && item == mBuilder.GetGlassDisplayItem()) {
+ mBuilder.ClearGlassDisplayItem();
+ }
+
+ item->Destroy(&mBuilder);
+ Metrics()->mRemovedItems++;
+
+ i++;
+ aUpdated = PartialUpdateResult::Updated;
+ continue;
+ }
+
+ if (initializeOldItems) {
+ aList->mOldItems.AppendElement(OldItemInfo(item));
+ }
+
+ // If we're not going to keep the list linked, then this old item entry
+ // is the only pointer to the item. Let it know that it now strongly
+ // owns the item, so it can destroy it if it goes away.
+ aList->mOldItems[i].mOwnsItem = !aKeepLinked;
+
+ item->SetOldListIndex(aList, OldListIndex(i), aCallerKey, aNestingDepth);
+
+ nsIFrame* f = item->Frame();
+
+ if (item->GetChildren()) {
+ // If children inside this list were invalid, then we'd have walked the
+ // ancestors and set ForceDescendIntoVisible on the current frame. If an
+ // ancestor is modified, then we'll throw this away entirely. Either way,
+ // we won't need to run merging on this sublist, and we can keep the items
+ // linked into their display list.
+ // The caret can move without invalidating, but we always set the force
+ // descend into frame state bit on that frame, so check for that too.
+ // TODO: AGR marking below can call MarkFrameForDisplayIfVisible and make
+ // us think future siblings need to be merged, even though we don't really
+ // need to.
+ bool keepLinked = aKeepLinked;
+ nsIFrame* invalid = item->FrameForInvalidation();
+ if (!invalid->ForceDescendIntoIfVisible() &&
+ !invalid->HasAnyStateBits(NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO)) {
+ keepLinked = true;
+ }
+
+ // If this item's frame is an AGR (can be moved asynchronously by the
+ // compositor), then use that frame for descendants. Also pass the ASR
+ // for that item, so that descendants can compare to see if any new
+ // ASRs have been pushed since.
+ nsIFrame* asyncAncestor = aAsyncAncestor;
+ const ActiveScrolledRoot* asyncAncestorASR = aAsyncAncestorASR;
+ if (item->CanMoveAsync()) {
+ asyncAncestor = item->Frame();
+ asyncAncestorASR = item->GetActiveScrolledRoot();
+ }
+
+ if (!PreProcessDisplayList(
+ item->GetChildren(), SelectAGRForFrame(f, aAGR), aUpdated,
+ asyncAncestor, asyncAncestorASR, item->Frame(),
+ item->GetPerFrameKey(), aNestingDepth + 1, keepLinked)) {
+ MOZ_RELEASE_ASSERT(
+ !aKeepLinked,
+ "Can't early return since we need to move the out list back");
+ return false;
+ }
+ }
+
+ // TODO: We should be able to check the clipped bounds relative
+ // to the common AGR (of both the existing item and the invalidated
+ // frame) and determine if they can ever intersect.
+ // TODO: We only really need to build the ancestor container item that is a
+ // sibling of the changed thing to get correct ordering. The changed content
+ // is a frame though, and it's hard to map that to container items in this
+ // list.
+ // If an ancestor display item is an AGR, and our ASR matches the ASR
+ // of that item, then there can't have been any new ASRs pushed since that
+ // item, so that item is our AGR. Otherwise, our AGR is our ASR.
+ // TODO: If aAsyncAncestorASR is non-null, then item->GetActiveScrolledRoot
+ // should be the same or a descendant and also non-null. Unfortunately an
+ // RDL bug means this can be wrong for sticky items after a partial update,
+ // so we have to work around it. Bug 1730749 and bug 1730826 should resolve
+ // this.
+ nsIFrame* agrFrame = nullptr;
+ if (aAsyncAncestorASR == item->GetActiveScrolledRoot() ||
+ !item->GetActiveScrolledRoot()) {
+ agrFrame = aAsyncAncestor;
+ } else {
+ agrFrame =
+ item->GetActiveScrolledRoot()->mScrollableFrame->GetScrolledFrame();
+ }
+
+ if (aAGR && agrFrame != aAGR) {
+ mBuilder.MarkFrameForDisplayIfVisible(f, RootReferenceFrame());
+ }
+
+ // If we're going to keep this linked list and not merge it, then mark the
+ // item as used and put it back into the list.
+ if (aKeepLinked) {
+ item->SetReused(true);
+ if (item->GetChildren()) {
+ item->UpdateBounds(Builder());
+ }
+ if (item->GetType() == DisplayItemType::TYPE_SUBDOCUMENT) {
+ IncrementSubDocPresShellPaintCount(item);
+ }
+ out.AppendToTop(item);
+ }
+ i++;
+ }
+
+ MOZ_RELEASE_ASSERT(aList->mOldItems.Length() == aList->mDAG.Length());
+
+ if (aKeepLinked) {
+ aList->AppendToTop(&out);
+ }
+
+ return true;
+}
+
+void IncrementPresShellPaintCount(nsDisplayListBuilder* aBuilder,
+ nsDisplayItem* aItem) {
+ MOZ_ASSERT(aItem->GetType() == DisplayItemType::TYPE_SUBDOCUMENT);
+
+ nsSubDocumentFrame* subDocFrame =
+ static_cast<nsDisplaySubDocument*>(aItem)->SubDocumentFrame();
+ MOZ_ASSERT(subDocFrame);
+
+ PresShell* presShell = subDocFrame->GetSubdocumentPresShellForPainting(0);
+ MOZ_ASSERT(presShell);
+
+ aBuilder->IncrementPresShellPaintCount(presShell);
+}
+
+void RetainedDisplayListBuilder::IncrementSubDocPresShellPaintCount(
+ nsDisplayItem* aItem) {
+ IncrementPresShellPaintCount(&mBuilder, aItem);
+}
+
+static Maybe<const ActiveScrolledRoot*> SelectContainerASR(
+ const DisplayItemClipChain* aClipChain, const ActiveScrolledRoot* aItemASR,
+ Maybe<const ActiveScrolledRoot*>& aContainerASR) {
+ const ActiveScrolledRoot* itemClipASR =
+ aClipChain ? aClipChain->mASR : nullptr;
+
+ const ActiveScrolledRoot* finiteBoundsASR =
+ ActiveScrolledRoot::PickDescendant(itemClipASR, aItemASR);
+
+ if (!aContainerASR) {
+ return Some(finiteBoundsASR);
+ }
+
+ return Some(
+ ActiveScrolledRoot::PickAncestor(*aContainerASR, finiteBoundsASR));
+}
+
+static void UpdateASR(nsDisplayItem* aItem,
+ Maybe<const ActiveScrolledRoot*>& aContainerASR) {
+ if (!aContainerASR) {
+ return;
+ }
+
+ nsDisplayWrapList* wrapList = aItem->AsDisplayWrapList();
+ if (!wrapList) {
+ aItem->SetActiveScrolledRoot(*aContainerASR);
+ return;
+ }
+
+ wrapList->SetActiveScrolledRoot(ActiveScrolledRoot::PickAncestor(
+ wrapList->GetFrameActiveScrolledRoot(), *aContainerASR));
+}
+
+static void CopyASR(nsDisplayItem* aOld, nsDisplayItem* aNew) {
+ aNew->SetActiveScrolledRoot(aOld->GetActiveScrolledRoot());
+}
+
+OldItemInfo::OldItemInfo(nsDisplayItem* aItem)
+ : mItem(aItem), mUsed(false), mDiscarded(false), mOwnsItem(false) {
+ if (mItem) {
+ // Clear cached modified frame state when adding an item to the old list.
+ mItem->SetModifiedFrame(false);
+ }
+}
+
+void OldItemInfo::AddedMatchToMergedList(RetainedDisplayListBuilder* aBuilder,
+ MergedListIndex aIndex) {
+ AddedToMergedList(aIndex);
+}
+
+void OldItemInfo::Discard(RetainedDisplayListBuilder* aBuilder,
+ nsTArray<MergedListIndex>&& aDirectPredecessors) {
+ MOZ_ASSERT(!IsUsed());
+ mUsed = mDiscarded = true;
+ mDirectPredecessors = std::move(aDirectPredecessors);
+ if (mItem) {
+ MOZ_ASSERT(mOwnsItem);
+ mItem->Destroy(aBuilder->Builder());
+ aBuilder->Metrics()->mRemovedItems++;
+ }
+ mItem = nullptr;
+}
+
+bool OldItemInfo::IsChanged() {
+ return !mItem || !mItem->CanBeReused() || mItem->HasDeletedFrame();
+}
+
+/**
+ * A C++ implementation of Markus Stange's merge-dags algorithm.
+ * https://github.com/mstange/merge-dags
+ *
+ * MergeState handles combining a new list of display items into an existing
+ * DAG and computes the new DAG in a single pass.
+ * Each time we add a new item, we resolve all dependencies for it, so that the
+ * resulting list and DAG are built in topological ordering.
+ */
+class MergeState {
+ public:
+ MergeState(RetainedDisplayListBuilder* aBuilder,
+ RetainedDisplayList& aOldList, nsDisplayItem* aOuterItem)
+ : mBuilder(aBuilder),
+ mOldList(&aOldList),
+ mOldItems(std::move(aOldList.mOldItems)),
+ mOldDAG(
+ std::move(*reinterpret_cast<DirectedAcyclicGraph<OldListUnits>*>(
+ &aOldList.mDAG))),
+ mMergedItems(aBuilder->Builder()),
+ mOuterItem(aOuterItem),
+ mResultIsModified(false) {
+ mMergedDAG.EnsureCapacityFor(mOldDAG);
+ MOZ_RELEASE_ASSERT(mOldItems.Length() == mOldDAG.Length());
+ }
+
+ Maybe<MergedListIndex> ProcessItemFromNewList(
+ nsDisplayItem* aNewItem, const Maybe<MergedListIndex>& aPreviousItem) {
+ OldListIndex oldIndex;
+ MOZ_DIAGNOSTIC_ASSERT(aNewItem->HasModifiedFrame() ==
+ HasModifiedFrame(aNewItem));
+ if (!aNewItem->HasModifiedFrame() &&
+ HasMatchingItemInOldList(aNewItem, &oldIndex)) {
+ mBuilder->Metrics()->mRebuiltItems++;
+ nsDisplayItem* oldItem = mOldItems[oldIndex.val].mItem;
+ MOZ_DIAGNOSTIC_ASSERT(oldItem->GetPerFrameKey() ==
+ aNewItem->GetPerFrameKey() &&
+ oldItem->Frame() == aNewItem->Frame());
+ if (!mOldItems[oldIndex.val].IsChanged()) {
+ MOZ_DIAGNOSTIC_ASSERT(!mOldItems[oldIndex.val].IsUsed());
+ nsDisplayItem* destItem;
+ if (ShouldUseNewItem(aNewItem)) {
+ destItem = aNewItem;
+ } else {
+ destItem = oldItem;
+ // The building rect can depend on the overflow rect (when the parent
+ // frame is position:fixed), which can change without invalidating
+ // the frame/items. If we're using the old item, copy the building
+ // rect across from the new item.
+ oldItem->SetBuildingRect(aNewItem->GetBuildingRect());
+ }
+
+ if (destItem == aNewItem) {
+ if (oldItem->IsGlassItem() &&
+ oldItem == mBuilder->Builder()->GetGlassDisplayItem()) {
+ mBuilder->Builder()->ClearGlassDisplayItem();
+ }
+ } // aNewItem can't be the glass item on the builder yet.
+
+ if (destItem->IsGlassItem()) {
+ if (destItem != oldItem ||
+ destItem != mBuilder->Builder()->GetGlassDisplayItem()) {
+ mBuilder->Builder()->SetGlassDisplayItem(destItem);
+ }
+ }
+
+ MergeChildLists(aNewItem, oldItem, destItem);
+
+ AutoTArray<MergedListIndex, 2> directPredecessors =
+ ProcessPredecessorsOfOldNode(oldIndex);
+ MergedListIndex newIndex = AddNewNode(
+ destItem, Some(oldIndex), directPredecessors, aPreviousItem);
+ mOldItems[oldIndex.val].AddedMatchToMergedList(mBuilder, newIndex);
+ if (destItem == aNewItem) {
+ oldItem->Destroy(mBuilder->Builder());
+ } else {
+ aNewItem->Destroy(mBuilder->Builder());
+ }
+ return Some(newIndex);
+ }
+ }
+ mResultIsModified = true;
+ if (aNewItem->IsGlassItem()) {
+ mBuilder->Builder()->SetGlassDisplayItem(aNewItem);
+ }
+ return Some(AddNewNode(aNewItem, Nothing(), Span<MergedListIndex>(),
+ aPreviousItem));
+ }
+
+ void MergeChildLists(nsDisplayItem* aNewItem, nsDisplayItem* aOldItem,
+ nsDisplayItem* aOutItem) {
+ if (!aOutItem->GetChildren()) {
+ return;
+ }
+
+ Maybe<const ActiveScrolledRoot*> containerASRForChildren;
+ nsDisplayList empty(mBuilder->Builder());
+ const bool modified = mBuilder->MergeDisplayLists(
+ aNewItem ? aNewItem->GetChildren() : &empty, aOldItem->GetChildren(),
+ aOutItem->GetChildren(), containerASRForChildren, aOutItem);
+ if (modified) {
+ aOutItem->InvalidateCachedChildInfo(mBuilder->Builder());
+ UpdateASR(aOutItem, containerASRForChildren);
+ mResultIsModified = true;
+ } else if (aOutItem == aNewItem) {
+ // If nothing changed, but we copied the contents across to
+ // the new item, then also copy the ASR data.
+ CopyASR(aOldItem, aNewItem);
+ }
+ // Ideally we'd only UpdateBounds if something changed, but
+ // nsDisplayWrapList also uses this to update the clip chain for the
+ // current ASR, which gets reset during RestoreState(), so we always need
+ // to run it again.
+ aOutItem->UpdateBounds(mBuilder->Builder());
+ }
+
+ bool ShouldUseNewItem(nsDisplayItem* aNewItem) {
+ // Generally we want to use the old item when the frame isn't marked as
+ // modified so that any cached information on the item (or referencing the
+ // item) gets retained. Quite a few FrameLayerBuilder performance
+ // improvements benefit by this. Sometimes, however, we can end up where the
+ // new item paints something different from the old item, even though we
+ // haven't modified the frame, and it's hard to fix. In these cases we just
+ // always use the new item to be safe.
+ DisplayItemType type = aNewItem->GetType();
+ if (type == DisplayItemType::TYPE_CANVAS_BACKGROUND_COLOR ||
+ type == DisplayItemType::TYPE_SOLID_COLOR) {
+ // The canvas background color item can paint the color from another
+ // frame, and even though we schedule a paint, we don't mark the canvas
+ // frame as invalid.
+ return true;
+ }
+
+ if (type == DisplayItemType::TYPE_TABLE_BORDER_COLLAPSE) {
+ // We intentionally don't mark the root table frame as modified when a
+ // subframe changes, even though the border collapse item for the root
+ // frame is what paints the changed border. Marking the root frame as
+ // modified would rebuild display items for the whole table area, and we
+ // don't want that.
+ return true;
+ }
+
+ if (type == DisplayItemType::TYPE_TEXT_OVERFLOW) {
+ // Text overflow marker items are created with the wrapping block as their
+ // frame, and have an index value to note which line they are created for.
+ // Their rendering can change if the items on that line change, which may
+ // not mark the block as modified. We rebuild them if we build any item on
+ // the line, so we should always get new items if they might have changed
+ // rendering, and it's easier to just use the new items rather than
+ // computing if we actually need them.
+ return true;
+ }
+
+ if (type == DisplayItemType::TYPE_SUBDOCUMENT ||
+ type == DisplayItemType::TYPE_STICKY_POSITION) {
+ // nsDisplaySubDocument::mShouldFlatten can change without an invalidation
+ // (and is the reason we unconditionally build the subdocument item), so
+ // always use the new one to make sure we get the right value.
+ // Same for |nsDisplayStickyPosition::mShouldFlatten|.
+ return true;
+ }
+
+ if (type == DisplayItemType::TYPE_CARET) {
+ // The caret can change position while still being owned by the same frame
+ // and we don't invalidate in that case. Use the new version since the
+ // changed bounds are needed for DLBI.
+ return true;
+ }
+
+ if (type == DisplayItemType::TYPE_MASK ||
+ type == DisplayItemType::TYPE_FILTER ||
+ type == DisplayItemType::TYPE_SVG_WRAPPER) {
+ // SVG items have some invalidation issues, see bugs 1494110 and 1494663.
+ return true;
+ }
+
+ if (type == DisplayItemType::TYPE_TRANSFORM) {
+ // Prerendering of transforms can change without frame invalidation.
+ return true;
+ }
+
+ return false;
+ }
+
+ RetainedDisplayList Finalize() {
+ for (size_t i = 0; i < mOldDAG.Length(); i++) {
+ if (mOldItems[i].IsUsed()) {
+ continue;
+ }
+
+ AutoTArray<MergedListIndex, 2> directPredecessors =
+ ResolveNodeIndexesOldToMerged(
+ mOldDAG.GetDirectPredecessors(OldListIndex(i)));
+ ProcessOldNode(OldListIndex(i), std::move(directPredecessors));
+ }
+
+ RetainedDisplayList result(mBuilder->Builder());
+ result.AppendToTop(&mMergedItems);
+ result.mDAG = std::move(mMergedDAG);
+ MOZ_RELEASE_ASSERT(result.mDAG.Length() == result.Length());
+ return result;
+ }
+
+ bool HasMatchingItemInOldList(nsDisplayItem* aItem, OldListIndex* aOutIndex) {
+ // Look for an item that matches aItem's frame and per-frame-key, but isn't
+ // the same item.
+ uint32_t outerKey = mOuterItem ? mOuterItem->GetPerFrameKey() : 0;
+ nsIFrame* frame = aItem->Frame();
+ for (nsDisplayItem* i : frame->DisplayItems()) {
+ if (i != aItem && i->Frame() == frame &&
+ i->GetPerFrameKey() == aItem->GetPerFrameKey()) {
+ if (i->GetOldListIndex(mOldList, outerKey, aOutIndex)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ bool HasModifiedFrame(nsDisplayItem* aItem) {
+ nsIFrame* stopFrame = mOuterItem ? mOuterItem->Frame() : nullptr;
+ return AnyContentAncestorModified(aItem->FrameForInvalidation(), stopFrame);
+ }
+#endif
+
+ void UpdateContainerASR(nsDisplayItem* aItem) {
+ mContainerASR = SelectContainerASR(
+ aItem->GetClipChain(), aItem->GetActiveScrolledRoot(), mContainerASR);
+ }
+
+ MergedListIndex AddNewNode(
+ nsDisplayItem* aItem, const Maybe<OldListIndex>& aOldIndex,
+ Span<const MergedListIndex> aDirectPredecessors,
+ const Maybe<MergedListIndex>& aExtraDirectPredecessor) {
+ UpdateContainerASR(aItem);
+ aItem->NotifyUsed(mBuilder->Builder());
+
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ for (nsDisplayItem* i : aItem->Frame()->DisplayItems()) {
+ if (i->Frame() == aItem->Frame() &&
+ i->GetPerFrameKey() == aItem->GetPerFrameKey()) {
+ MOZ_DIAGNOSTIC_ASSERT(!i->IsMergedItem());
+ }
+ }
+
+ aItem->SetMergedPreProcessed(true, false);
+#endif
+
+ mMergedItems.AppendToTop(aItem);
+ mBuilder->Metrics()->mTotalItems++;
+
+ MergedListIndex newIndex =
+ mMergedDAG.AddNode(aDirectPredecessors, aExtraDirectPredecessor);
+ return newIndex;
+ }
+
+ void ProcessOldNode(OldListIndex aNode,
+ nsTArray<MergedListIndex>&& aDirectPredecessors) {
+ nsDisplayItem* item = mOldItems[aNode.val].mItem;
+ if (mOldItems[aNode.val].IsChanged()) {
+ if (item && item->IsGlassItem() &&
+ item == mBuilder->Builder()->GetGlassDisplayItem()) {
+ mBuilder->Builder()->ClearGlassDisplayItem();
+ }
+
+ mOldItems[aNode.val].Discard(mBuilder, std::move(aDirectPredecessors));
+ mResultIsModified = true;
+ } else {
+ MergeChildLists(nullptr, item, item);
+
+ if (item->GetType() == DisplayItemType::TYPE_SUBDOCUMENT) {
+ mBuilder->IncrementSubDocPresShellPaintCount(item);
+ }
+ item->SetReused(true);
+ mBuilder->Metrics()->mReusedItems++;
+ mOldItems[aNode.val].AddedToMergedList(
+ AddNewNode(item, Some(aNode), aDirectPredecessors, Nothing()));
+ }
+ }
+
+ struct PredecessorStackItem {
+ PredecessorStackItem(OldListIndex aNode, Span<OldListIndex> aPredecessors)
+ : mNode(aNode),
+ mDirectPredecessors(aPredecessors),
+ mCurrentPredecessorIndex(0) {}
+
+ bool IsFinished() {
+ return mCurrentPredecessorIndex == mDirectPredecessors.Length();
+ }
+
+ OldListIndex GetAndIncrementCurrentPredecessor() {
+ return mDirectPredecessors[mCurrentPredecessorIndex++];
+ }
+
+ OldListIndex mNode;
+ Span<OldListIndex> mDirectPredecessors;
+ size_t mCurrentPredecessorIndex;
+ };
+
+ AutoTArray<MergedListIndex, 2> ProcessPredecessorsOfOldNode(
+ OldListIndex aNode) {
+ AutoTArray<PredecessorStackItem, 256> mStack;
+ mStack.AppendElement(
+ PredecessorStackItem(aNode, mOldDAG.GetDirectPredecessors(aNode)));
+
+ while (true) {
+ if (mStack.LastElement().IsFinished()) {
+ // If we've finished processing all the entries in the current set, then
+ // pop it off the processing stack and process it.
+ PredecessorStackItem item = mStack.PopLastElement();
+ AutoTArray<MergedListIndex, 2> result =
+ ResolveNodeIndexesOldToMerged(item.mDirectPredecessors);
+
+ if (mStack.IsEmpty()) {
+ return result;
+ }
+
+ ProcessOldNode(item.mNode, std::move(result));
+ } else {
+ // Grab the current predecessor, push predecessors of that onto the
+ // processing stack (if it hasn't already been processed), and then
+ // advance to the next entry.
+ OldListIndex currentIndex =
+ mStack.LastElement().GetAndIncrementCurrentPredecessor();
+ if (!mOldItems[currentIndex.val].IsUsed()) {
+ mStack.AppendElement(PredecessorStackItem(
+ currentIndex, mOldDAG.GetDirectPredecessors(currentIndex)));
+ }
+ }
+ }
+ }
+
+ AutoTArray<MergedListIndex, 2> ResolveNodeIndexesOldToMerged(
+ Span<OldListIndex> aDirectPredecessors) {
+ AutoTArray<MergedListIndex, 2> result;
+ result.SetCapacity(aDirectPredecessors.Length());
+ for (OldListIndex index : aDirectPredecessors) {
+ OldItemInfo& oldItem = mOldItems[index.val];
+ if (oldItem.IsDiscarded()) {
+ for (MergedListIndex inner : oldItem.mDirectPredecessors) {
+ if (!result.Contains(inner)) {
+ result.AppendElement(inner);
+ }
+ }
+ } else {
+ result.AppendElement(oldItem.mIndex);
+ }
+ }
+ return result;
+ }
+
+ RetainedDisplayListBuilder* mBuilder;
+ RetainedDisplayList* mOldList;
+ Maybe<const ActiveScrolledRoot*> mContainerASR;
+ nsTArray<OldItemInfo> mOldItems;
+ DirectedAcyclicGraph<OldListUnits> mOldDAG;
+ // Unfortunately we can't use strong typing for the hashtables
+ // since they internally encode the type with the mOps pointer,
+ // and assert when we try swap the contents
+ nsDisplayList mMergedItems;
+ DirectedAcyclicGraph<MergedListUnits> mMergedDAG;
+ nsDisplayItem* mOuterItem;
+ bool mResultIsModified;
+};
+
+#ifdef DEBUG
+void VerifyNotModified(nsDisplayList* aList) {
+ for (nsDisplayItem* item : *aList) {
+ MOZ_ASSERT(!AnyContentAncestorModified(item->FrameForInvalidation()));
+
+ if (item->GetChildren()) {
+ VerifyNotModified(item->GetChildren());
+ }
+ }
+}
+#endif
+
+/**
+ * Takes two display lists and merges them into an output list.
+ *
+ * Display lists wthout an explicit DAG are interpreted as linear DAGs (with a
+ * maximum of one direct predecessor and one direct successor per node). We add
+ * the two DAGs together, and then output the topological sorted ordering as the
+ * final display list.
+ *
+ * Once we've merged a list, we then retain the DAG (as part of the
+ * RetainedDisplayList object) to use for future merges.
+ */
+bool RetainedDisplayListBuilder::MergeDisplayLists(
+ nsDisplayList* aNewList, RetainedDisplayList* aOldList,
+ RetainedDisplayList* aOutList,
+ mozilla::Maybe<const mozilla::ActiveScrolledRoot*>& aOutContainerASR,
+ nsDisplayItem* aOuterItem) {
+ AUTO_PROFILER_LABEL_CATEGORY_PAIR(GRAPHICS_DisplayListMerging);
+
+ if (!aOldList->IsEmpty()) {
+ // If we still have items in the actual list, then it is because
+ // PreProcessDisplayList decided that it was sure it can't be modified. We
+ // can just use it directly, and throw any new items away.
+
+ aNewList->DeleteAll(&mBuilder);
+#ifdef DEBUG
+ VerifyNotModified(aOldList);
+#endif
+
+ if (aOldList != aOutList) {
+ *aOutList = std::move(*aOldList);
+ }
+
+ return false;
+ }
+
+ MergeState merge(this, *aOldList, aOuterItem);
+
+ Maybe<MergedListIndex> previousItemIndex;
+ for (nsDisplayItem* item : aNewList->TakeItems()) {
+ Metrics()->mNewItems++;
+ previousItemIndex = merge.ProcessItemFromNewList(item, previousItemIndex);
+ }
+
+ *aOutList = merge.Finalize();
+ aOutContainerASR = merge.mContainerASR;
+ return merge.mResultIsModified;
+}
+
+void RetainedDisplayListBuilder::GetModifiedAndFramesWithProps(
+ nsTArray<nsIFrame*>* aOutModifiedFrames,
+ nsTArray<nsIFrame*>* aOutFramesWithProps) {
+ for (auto it = Data()->ConstIterator(); !it.Done(); it.Next()) {
+ nsIFrame* frame = it.Key();
+ const RetainedDisplayListData::FrameFlags& flags = it.Data();
+
+ if (flags.contains(RetainedDisplayListData::FrameFlag::Modified)) {
+ aOutModifiedFrames->AppendElement(frame);
+ }
+
+ if (flags.contains(RetainedDisplayListData::FrameFlag::HasProps)) {
+ aOutFramesWithProps->AppendElement(frame);
+ }
+
+ if (flags.contains(RetainedDisplayListData::FrameFlag::HadWillChange)) {
+ Builder()->RemoveFromWillChangeBudgets(frame);
+ }
+ }
+
+ Data()->Clear();
+}
+
+// ComputeRebuildRegion debugging
+// #define CRR_DEBUG 1
+#if CRR_DEBUG
+# define CRR_LOG(...) printf_stderr(__VA_ARGS__)
+#else
+# define CRR_LOG(...)
+#endif
+
+static nsDisplayItem* GetFirstDisplayItemWithChildren(nsIFrame* aFrame) {
+ for (nsDisplayItem* i : aFrame->DisplayItems()) {
+ if (i->HasDeletedFrame() || i->Frame() != aFrame) {
+ // The main frame for the display item has been deleted or the display
+ // item belongs to another frame.
+ continue;
+ }
+
+ if (i->HasChildren()) {
+ return static_cast<nsDisplayItem*>(i);
+ }
+ }
+ return nullptr;
+}
+
+static bool IsInPreserve3DContext(const nsIFrame* aFrame) {
+ return aFrame->Extend3DContext() ||
+ aFrame->Combines3DTransformWithAncestors();
+}
+
+// Returns true if |aFrame| can store a display list building rect.
+// These limitations are necessary to guarantee that
+// 1) Just enough items are rebuilt to properly update display list
+// 2) Modified frames will be visited during a partial display list build.
+static bool CanStoreDisplayListBuildingRect(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame) {
+ return aFrame != aBuilder->RootReferenceFrame() &&
+ aFrame->IsStackingContext() && aFrame->IsFixedPosContainingBlock() &&
+ // Split frames might have placeholders for modified frames in their
+ // unmodified continuation frame.
+ !aFrame->GetPrevContinuation() && !aFrame->GetNextContinuation();
+}
+
+static bool ProcessFrameInternal(nsIFrame* aFrame,
+ nsDisplayListBuilder* aBuilder,
+ nsIFrame** aAGR, nsRect& aOverflow,
+ const nsIFrame* aStopAtFrame,
+ nsTArray<nsIFrame*>& aOutFramesWithProps,
+ const bool aStopAtStackingContext) {
+ nsIFrame* currentFrame = aFrame;
+
+ while (currentFrame != aStopAtFrame) {
+ CRR_LOG("currentFrame: %p (placeholder=%d), aOverflow: %d %d %d %d\n",
+ currentFrame, !aStopAtStackingContext, aOverflow.x, aOverflow.y,
+ aOverflow.width, aOverflow.height);
+
+ // If the current frame is an OOF frame, DisplayListBuildingData needs to be
+ // set on all the ancestor stacking contexts of the placeholder frame, up
+ // to the containing block of the OOF frame. This is done to ensure that the
+ // content that might be behind the OOF frame is built for merging.
+ nsIFrame* placeholder = currentFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)
+ ? currentFrame->GetPlaceholderFrame()
+ : nullptr;
+
+ if (placeholder) {
+ nsRect placeholderOverflow = aOverflow;
+ auto rv = nsLayoutUtils::TransformRect(currentFrame, placeholder,
+ placeholderOverflow);
+ if (rv != nsLayoutUtils::TRANSFORM_SUCCEEDED) {
+ placeholderOverflow = nsRect();
+ }
+
+ CRR_LOG("Processing placeholder %p for OOF frame %p\n", placeholder,
+ currentFrame);
+
+ CRR_LOG("OOF frame draw area: %d %d %d %d\n", placeholderOverflow.x,
+ placeholderOverflow.y, placeholderOverflow.width,
+ placeholderOverflow.height);
+
+ // Tracking AGRs for the placeholder processing is not necessary, as the
+ // goal is to only modify the DisplayListBuildingData rect.
+ nsIFrame* dummyAGR = nullptr;
+
+ // Find a common ancestor frame to handle frame continuations.
+ // TODO: It might be possible to write a more specific and efficient
+ // function for this.
+ const nsIFrame* ancestor = nsLayoutUtils::FindNearestCommonAncestorFrame(
+ currentFrame->GetParent(), placeholder->GetParent());
+
+ if (!ProcessFrameInternal(placeholder, aBuilder, &dummyAGR,
+ placeholderOverflow, ancestor,
+ aOutFramesWithProps, false)) {
+ return false;
+ }
+ }
+
+ // Convert 'aOverflow' into the coordinate space of the nearest stacking
+ // context or display port ancestor and update 'currentFrame' to point to
+ // that frame.
+ aOverflow = nsLayoutUtils::TransformFrameRectToAncestor(
+ currentFrame, aOverflow, aStopAtFrame, nullptr, nullptr,
+ /* aStopAtStackingContextAndDisplayPortAndOOFFrame = */ true,
+ &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 (CanStoreDisplayListBuildingRect(aBuilder, currentFrame)) {
+ CRR_LOG("Frame belongs to stacking context frame %p\n", currentFrame);
+ // If we found an intermediate stacking context with an existing display
+ // item then we can store the dirty rect there and stop. If we couldn't
+ // find one then we need to keep bubbling up to the next stacking context.
+ nsDisplayItem* wrapperItem =
+ GetFirstDisplayItemWithChildren(currentFrame);
+ if (!wrapperItem) {
+ continue;
+ }
+
+ // Store the stacking context relative dirty area such
+ // that display list building will pick it up when it
+ // gets to it.
+ nsDisplayListBuilder::DisplayListBuildingData* data =
+ currentFrame->GetProperty(
+ nsDisplayListBuilder::DisplayListBuildingRect());
+ if (!data) {
+ data = new nsDisplayListBuilder::DisplayListBuildingData();
+ currentFrame->SetProperty(
+ nsDisplayListBuilder::DisplayListBuildingRect(), data);
+ currentFrame->SetHasOverrideDirtyRegion(true);
+ aOutFramesWithProps.AppendElement(currentFrame);
+ }
+ CRR_LOG("Adding area to stacking context draw area: %d %d %d %d\n",
+ aOverflow.x, aOverflow.y, aOverflow.width, aOverflow.height);
+ data->mDirtyRect.UnionRect(data->mDirtyRect, aOverflow);
+
+ if (!aStopAtStackingContext) {
+ // Continue ascending the frame tree until we reach aStopAtFrame.
+ continue;
+ }
+
+ // Grab the visible (display list building) rect for children of this
+ // wrapper item and convert into into coordinate relative to the current
+ // frame.
+ nsRect previousVisible = wrapperItem->GetBuildingRectForChildren();
+ if (wrapperItem->ReferenceFrameForChildren() != wrapperItem->Frame()) {
+ previousVisible -= wrapperItem->ToReferenceFrame();
+ }
+
+ if (!previousVisible.Contains(aOverflow)) {
+ // If the overflow area of the changed frame isn't contained within the
+ // old item, then we might change the size of the item and need to
+ // update its sorting accordingly. Keep propagating the overflow area up
+ // so that we build intersecting items for sorting.
+ continue;
+ }
+
+ if (!data->mModifiedAGR) {
+ data->mModifiedAGR = *aAGR;
+ } else if (data->mModifiedAGR != *aAGR) {
+ data->mDirtyRect = currentFrame->InkOverflowRectRelativeToSelf();
+ CRR_LOG(
+ "Found multiple modified AGRs within this stacking context, "
+ "giving up\n");
+ }
+
+ // Don't contribute to the root dirty area at all.
+ aOverflow.SetEmpty();
+ *aAGR = nullptr;
+
+ break;
+ }
+ }
+ return true;
+}
+
+bool RetainedDisplayListBuilder::ProcessFrame(
+ nsIFrame* aFrame, nsDisplayListBuilder* aBuilder, nsIFrame* aStopAtFrame,
+ nsTArray<nsIFrame*>& aOutFramesWithProps, const bool aStopAtStackingContext,
+ nsRect* aOutDirty, nsIFrame** aOutModifiedAGR) {
+ if (aFrame->HasOverrideDirtyRegion()) {
+ aOutFramesWithProps.AppendElement(aFrame);
+ }
+
+ if (aFrame->HasAnyStateBits(NS_FRAME_IN_POPUP)) {
+ return true;
+ }
+
+ // TODO: There is almost certainly a faster way of doing this, probably can be
+ // combined with the ancestor walk for TransformFrameRectToAncestor.
+ nsIFrame* agrFrame = aBuilder->FindAnimatedGeometryRootFrameFor(aFrame);
+
+ CRR_LOG("Processing frame %p with agr %p\n", aFrame, agr->mFrame);
+
+ // Convert the frame's overflow rect into the coordinate space
+ // of the nearest stacking context that has an existing display item.
+ // We store that as a dirty rect on that stacking context so that we build
+ // all items that intersect the changed frame within the stacking context,
+ // and then we use MarkFrameForDisplayIfVisible to make sure the stacking
+ // context itself gets built. We don't need to build items that intersect
+ // outside of the stacking context, since we know the stacking context item
+ // exists in the old list, so we can trivially merge without needing other
+ // items.
+ nsRect overflow = aFrame->InkOverflowRectRelativeToSelf();
+
+ // If the modified frame is also a caret frame, include the caret area.
+ // This is needed because some frames (for example text frames without text)
+ // might have an empty overflow rect.
+ if (aFrame == aBuilder->GetCaretFrame()) {
+ overflow.UnionRect(overflow, aBuilder->GetCaretRect());
+ }
+
+ if (!ProcessFrameInternal(aFrame, aBuilder, &agrFrame, overflow, aStopAtFrame,
+ aOutFramesWithProps, aStopAtStackingContext)) {
+ return false;
+ }
+
+ if (!overflow.IsEmpty()) {
+ aOutDirty->UnionRect(*aOutDirty, overflow);
+ CRR_LOG("Adding area to root draw area: %d %d %d %d\n", overflow.x,
+ overflow.y, overflow.width, overflow.height);
+
+ // If we get changed frames from multiple AGRS, then just give up as it gets
+ // really complex to track which items would need to be marked in
+ // MarkFramesForDifferentAGR.
+ if (!*aOutModifiedAGR) {
+ CRR_LOG("Setting %p as root stacking context AGR\n", agrFrame);
+ *aOutModifiedAGR = agrFrame;
+ } else if (agrFrame && *aOutModifiedAGR != agrFrame) {
+ CRR_LOG("Found multiple AGRs in root stacking context, giving up\n");
+ return false;
+ }
+ }
+ return true;
+}
+
+static void AddFramesForContainingBlock(nsIFrame* aBlock,
+ const nsFrameList& aFrames,
+ nsTArray<nsIFrame*>& aExtraFrames) {
+ for (nsIFrame* f : aFrames) {
+ if (!f->IsFrameModified() && AnyContentAncestorModified(f, aBlock)) {
+ CRR_LOG("Adding invalid OOF %p\n", f);
+ aExtraFrames.AppendElement(f);
+ }
+ }
+}
+
+// Placeholder descendants of aFrame don't contribute to aFrame's overflow area.
+// Find all the containing blocks that might own placeholders under us, walk
+// their OOF frames list, and manually invalidate any frames that are
+// descendants of a modified frame (us, or another frame we'll get to soon).
+// This is combined with the work required for MarkFrameForDisplayIfVisible,
+// so that we can avoid an extra ancestor walk, and we can reuse the flag
+// to detect when we've already visited an ancestor (and thus all further
+// ancestors must also be visited).
+static void FindContainingBlocks(nsIFrame* aFrame,
+ nsTArray<nsIFrame*>& aExtraFrames) {
+ for (nsIFrame* f = aFrame; f; f = nsLayoutUtils::GetDisplayListParent(f)) {
+ if (f->ForceDescendIntoIfVisible()) {
+ return;
+ }
+ f->SetForceDescendIntoIfVisible(true);
+ CRR_LOG("Considering OOFs for %p\n", f);
+
+ AddFramesForContainingBlock(f, f->GetChildList(FrameChildListID::Float),
+ aExtraFrames);
+ AddFramesForContainingBlock(f, f->GetChildList(f->GetAbsoluteListID()),
+ aExtraFrames);
+
+ // This condition must match the condition in
+ // nsLayoutUtils::GetParentOrPlaceholderFor which is used by
+ // nsLayoutUtils::GetDisplayListParent
+ if (f->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW) && !f->GetPrevInFlow()) {
+ nsIFrame* parent = f->GetParent();
+ if (parent && !parent->ForceDescendIntoIfVisible()) {
+ // If the GetDisplayListParent call is going to walk to a placeholder,
+ // in rare cases the placeholder might be contained in a different
+ // continuation from the oof. So we have to make sure to mark the oofs
+ // parent. In the common case this doesn't make us do any extra work,
+ // just changes the order in which we visit the frames since walking
+ // through placeholders will walk through the parent, and we stop when
+ // we find a ForceDescendIntoIfVisible bit set.
+ FindContainingBlocks(parent, aExtraFrames);
+ }
+ }
+ }
+}
+
+/**
+ * Given a list of frames that has been modified, computes the region that we
+ * need to do display list building for in order to build all modified display
+ * items.
+ *
+ * When a modified frame is within a stacking context (with an existing display
+ * item), then we only contribute to the build area within the stacking context,
+ * as well as forcing display list building to descend to the stacking context.
+ * We don't need to add build area outside of the stacking context (and force
+ * items above/below the stacking context container item to be built), since
+ * just matching the position of the stacking context container item is
+ * sufficient to ensure correct ordering during merging.
+ *
+ * We need to rebuild all items that might intersect with the modified frame,
+ * both now and during async changes on the compositor. We do this by rebuilding
+ * the area covered by the changed frame, as well as rebuilding all items that
+ * have a different (async) AGR to the changed frame. If we have changes to
+ * multiple AGRs (within a stacking context), then we rebuild that stacking
+ * context entirely.
+ *
+ * @param aModifiedFrames The list of modified frames.
+ * @param aOutDirty The result region to use for display list building.
+ * @param aOutModifiedAGR The modified AGR for the root stacking context.
+ * @param aOutFramesWithProps The list of frames to which we attached partial
+ * build data so that it can be cleaned up.
+ *
+ * @return true if we succesfully computed a partial rebuild region, false if a
+ * full build is required.
+ */
+bool RetainedDisplayListBuilder::ComputeRebuildRegion(
+ nsTArray<nsIFrame*>& aModifiedFrames, nsRect* aOutDirty,
+ nsIFrame** aOutModifiedAGR, nsTArray<nsIFrame*>& aOutFramesWithProps) {
+ CRR_LOG("Computing rebuild regions for %zu frames:\n",
+ aModifiedFrames.Length());
+ nsTArray<nsIFrame*> extraFrames;
+ for (nsIFrame* f : aModifiedFrames) {
+ MOZ_ASSERT(f);
+
+ mBuilder.AddFrameMarkedForDisplayIfVisible(f);
+ FindContainingBlocks(f, extraFrames);
+
+ if (!ProcessFrame(f, &mBuilder, RootReferenceFrame(), aOutFramesWithProps,
+ true, aOutDirty, aOutModifiedAGR)) {
+ return false;
+ }
+ }
+
+ // Since we set modified to true on the extraFrames, add them to
+ // aModifiedFrames so that it will get reverted.
+ aModifiedFrames.AppendElements(extraFrames);
+
+ for (nsIFrame* f : extraFrames) {
+ f->SetFrameIsModified(true);
+
+ if (!ProcessFrame(f, &mBuilder, RootReferenceFrame(), aOutFramesWithProps,
+ true, aOutDirty, aOutModifiedAGR)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool RetainedDisplayListBuilder::ShouldBuildPartial(
+ nsTArray<nsIFrame*>& aModifiedFrames) {
+ if (mList.IsEmpty()) {
+ // Partial builds without a previous display list do not make sense.
+ Metrics()->mPartialUpdateFailReason = PartialUpdateFailReason::EmptyList;
+ return false;
+ }
+
+ if (aModifiedFrames.Length() >
+ StaticPrefs::layout_display_list_rebuild_frame_limit()) {
+ // Computing a dirty rect with too many modified frames can be slow.
+ Metrics()->mPartialUpdateFailReason = PartialUpdateFailReason::RebuildLimit;
+ return false;
+ }
+
+ // We don't support retaining with overlay scrollbars, since they require
+ // us to look at the display list and pick the highest z-index, which
+ // we can't do during partial building.
+ if (mBuilder.DisablePartialUpdates()) {
+ mBuilder.SetDisablePartialUpdates(false);
+ Metrics()->mPartialUpdateFailReason = PartialUpdateFailReason::Disabled;
+ return false;
+ }
+
+ for (nsIFrame* f : aModifiedFrames) {
+ MOZ_ASSERT(f);
+
+ const LayoutFrameType type = f->Type();
+
+ // If we have any modified frames of the following types, it is likely that
+ // doing a partial rebuild of the display list will be slower than doing a
+ // full rebuild.
+ // This is because these frames either intersect or may intersect with most
+ // of the page content. This is either due to display port size or different
+ // async AGR.
+ if (type == LayoutFrameType::Viewport ||
+ type == LayoutFrameType::PageContent ||
+ type == LayoutFrameType::Canvas || type == LayoutFrameType::Scrollbar) {
+ Metrics()->mPartialUpdateFailReason = PartialUpdateFailReason::FrameType;
+ return false;
+ }
+
+ // Detect root scroll frame and do a full rebuild for them too for the same
+ // reasons as above, but also because top layer items should to be marked
+ // modified if the root scroll frame is modified. Putting this check here
+ // means we don't need to check everytime a frame is marked modified though.
+ if (type == LayoutFrameType::Scroll && f->GetParent() &&
+ !f->GetParent()->GetParent()) {
+ Metrics()->mPartialUpdateFailReason = PartialUpdateFailReason::FrameType;
+ return false;
+ }
+ }
+
+ return true;
+}
+
+void RetainedDisplayListBuilder::InvalidateCaretFramesIfNeeded() {
+ if (mPreviousCaret == mBuilder.GetCaretFrame()) {
+ // The current caret frame is the same as the previous one.
+ return;
+ }
+
+ if (mPreviousCaret) {
+ mPreviousCaret->MarkNeedsDisplayItemRebuild();
+ }
+
+ if (mBuilder.GetCaretFrame()) {
+ mBuilder.GetCaretFrame()->MarkNeedsDisplayItemRebuild();
+ }
+
+ mPreviousCaret = mBuilder.GetCaretFrame();
+}
+
+static void ClearFrameProps(nsTArray<nsIFrame*>& aFrames) {
+ for (nsIFrame* f : aFrames) {
+ DL_LOGV("RDL - Clearing modified flags for frame %p", f);
+ if (f->HasOverrideDirtyRegion()) {
+ f->SetHasOverrideDirtyRegion(false);
+ f->RemoveProperty(nsDisplayListBuilder::DisplayListBuildingRect());
+ f->RemoveProperty(
+ nsDisplayListBuilder::DisplayListBuildingDisplayPortRect());
+ }
+
+ f->SetFrameIsModified(false);
+ f->SetHasModifiedDescendants(false);
+ }
+}
+
+class AutoClearFramePropsArray {
+ public:
+ explicit AutoClearFramePropsArray(size_t aCapacity) : mFrames(aCapacity) {}
+ AutoClearFramePropsArray() = default;
+ ~AutoClearFramePropsArray() { ClearFrameProps(mFrames); }
+
+ nsTArray<nsIFrame*>& Frames() { return mFrames; }
+ bool IsEmpty() const { return mFrames.IsEmpty(); }
+
+ private:
+ nsTArray<nsIFrame*> mFrames;
+};
+
+void RetainedDisplayListBuilder::ClearFramesWithProps() {
+ AutoClearFramePropsArray modifiedFrames;
+ AutoClearFramePropsArray framesWithProps;
+ GetModifiedAndFramesWithProps(&modifiedFrames.Frames(),
+ &framesWithProps.Frames());
+}
+
+void RetainedDisplayListBuilder::ClearRetainedData() {
+ DL_LOGI("(%p) RDL - Clearing retained display list builder data", this);
+ List()->DeleteAll(Builder());
+ ClearFramesWithProps();
+ ClearReuseableDisplayItems();
+}
+
+namespace RDLUtils {
+
+MOZ_NEVER_INLINE_DEBUG void AssertFrameSubtreeUnmodified(
+ const nsIFrame* aFrame) {
+ MOZ_ASSERT(!aFrame->IsFrameModified());
+ MOZ_ASSERT(!aFrame->HasModifiedDescendants());
+
+ for (const auto& childList : aFrame->ChildLists()) {
+ for (nsIFrame* child : childList.mList) {
+ AssertFrameSubtreeUnmodified(child);
+ }
+ }
+}
+
+MOZ_NEVER_INLINE_DEBUG void AssertDisplayListUnmodified(nsDisplayList* aList) {
+ for (nsDisplayItem* item : *aList) {
+ AssertDisplayItemUnmodified(item);
+ }
+}
+
+MOZ_NEVER_INLINE_DEBUG void AssertDisplayItemUnmodified(nsDisplayItem* aItem) {
+ MOZ_ASSERT(!aItem->HasDeletedFrame());
+ MOZ_ASSERT(!AnyContentAncestorModified(aItem->FrameForInvalidation()));
+
+ if (aItem->GetChildren()) {
+ AssertDisplayListUnmodified(aItem->GetChildren());
+ }
+}
+
+} // namespace RDLUtils
+
+namespace RDL {
+
+void MarkAncestorFrames(nsIFrame* aFrame,
+ nsTArray<nsIFrame*>& aOutFramesWithProps) {
+ nsIFrame* frame = nsLayoutUtils::GetDisplayListParent(aFrame);
+ while (frame && !frame->HasModifiedDescendants()) {
+ aOutFramesWithProps.AppendElement(frame);
+ frame->SetHasModifiedDescendants(true);
+ frame = nsLayoutUtils::GetDisplayListParent(frame);
+ }
+}
+
+/**
+ * Iterates over the modified frames array and updates the frame tree flags
+ * so that container frames know whether they have modified descendant frames.
+ * Frames that were marked modified are added to |aOutFramesWithProps|, so that
+ * the modified status can be cleared after the display list build.
+ */
+void MarkAllAncestorFrames(const nsTArray<nsIFrame*>& aModifiedFrames,
+ nsTArray<nsIFrame*>& aOutFramesWithProps) {
+ nsAutoString frameName;
+ DL_LOGI("RDL - Modified frames: %zu", aModifiedFrames.Length());
+ for (nsIFrame* frame : aModifiedFrames) {
+#ifdef DEBUG
+ frame->GetFrameName(frameName);
+#endif
+ DL_LOGV("RDL - Processing modified frame: %p (%s)", frame,
+ NS_ConvertUTF16toUTF8(frameName).get());
+
+ MarkAncestorFrames(frame, aOutFramesWithProps);
+ }
+}
+
+/**
+ * Marks the given display item |aItem| as reuseable container, and updates the
+ * bounds in case some child items were destroyed.
+ */
+MOZ_NEVER_INLINE_DEBUG void ReuseStackingContextItem(
+ nsDisplayListBuilder* aBuilder, nsDisplayItem* aItem) {
+ aItem->SetPreProcessed();
+
+ if (aItem->HasChildren()) {
+ aItem->UpdateBounds(aBuilder);
+ }
+
+ aBuilder->AddReusableDisplayItem(aItem);
+ DL_LOGD("Reusing display item %p", aItem);
+}
+
+bool IsSupportedFrameType(const nsIFrame* aFrame) {
+ // The way table backgrounds are handled makes these frames incompatible with
+ // this retained display list approach.
+ if (aFrame->IsTableColFrame()) {
+ return false;
+ }
+
+ if (aFrame->IsTableColGroupFrame()) {
+ return false;
+ }
+
+ if (aFrame->IsTableRowFrame()) {
+ return false;
+ }
+
+ if (aFrame->IsTableRowGroupFrame()) {
+ return false;
+ }
+
+ if (aFrame->IsTableCellFrame()) {
+ return false;
+ }
+
+ // Everything else should work.
+ return true;
+}
+
+bool IsReuseableStackingContextItem(nsDisplayItem* aItem) {
+ if (!IsSupportedFrameType(aItem->Frame())) {
+ return false;
+ }
+
+ if (!aItem->IsReusable()) {
+ return false;
+ }
+
+ const nsIFrame* frame = aItem->FrameForInvalidation();
+ return !frame->HasModifiedDescendants() && !frame->GetPrevContinuation() &&
+ !frame->GetNextContinuation();
+}
+
+/**
+ * Recursively visits every display item of the display list and destroys all
+ * display items that depend on deleted or modified frames.
+ * The stacking context display items for unmodified frame subtrees are kept
+ * linked and collected in given |aOutItems| array.
+ */
+void CollectStackingContextItems(nsDisplayListBuilder* aBuilder,
+ nsDisplayList* aList, nsIFrame* aOuterFrame,
+ int aDepth = 0, bool aParentReused = false) {
+ for (nsDisplayItem* item : aList->TakeItems()) {
+ if (DL_LOG_TEST(LogLevel::Debug)) {
+ DL_LOGD(
+ "%*s Preprocessing item %p (%s) (frame: %p) "
+ "(children: %zu) (depth: %d) (parentReused: %d)",
+ aDepth, "", item, item->Name(),
+ item->HasDeletedFrame() ? nullptr : item->Frame(),
+ item->GetChildren() ? item->GetChildren()->Length() : 0, aDepth,
+ aParentReused);
+ }
+
+ if (!item->CanBeReused() || item->HasDeletedFrame() ||
+ AnyContentAncestorModified(item->FrameForInvalidation(), aOuterFrame)) {
+ DL_LOGD("%*s Deleted modified or temporary item %p", aDepth, "", item);
+ item->Destroy(aBuilder);
+ continue;
+ }
+
+ MOZ_ASSERT(!AnyContentAncestorModified(item->FrameForInvalidation()));
+ MOZ_ASSERT(!item->IsPreProcessed());
+ item->InvalidateCachedChildInfo(aBuilder);
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ item->SetMergedPreProcessed(false, true);
+#endif
+ item->SetReused(true);
+
+ const bool isStackingContextItem = IsReuseableStackingContextItem(item);
+
+ if (item->GetChildren()) {
+ CollectStackingContextItems(aBuilder, item->GetChildren(), item->Frame(),
+ aDepth + 1,
+ aParentReused || isStackingContextItem);
+ }
+
+ if (aParentReused) {
+ // Keep the contents of the current container item linked.
+#ifdef DEBUG
+ RDLUtils::AssertDisplayItemUnmodified(item);
+#endif
+ aList->AppendToTop(item);
+ } else if (isStackingContextItem) {
+ // |item| is a stacking context item that can be reused.
+ ReuseStackingContextItem(aBuilder, item);
+ } else {
+ // |item| is inside a container item that will be destroyed later.
+ DL_LOGD("%*s Deleted unused item %p", aDepth, "", item);
+ item->Destroy(aBuilder);
+ continue;
+ }
+
+ if (item->GetType() == DisplayItemType::TYPE_SUBDOCUMENT) {
+ IncrementPresShellPaintCount(aBuilder, item);
+ }
+ }
+}
+
+} // namespace RDL
+
+bool RetainedDisplayListBuilder::TrySimpleUpdate(
+ const nsTArray<nsIFrame*>& aModifiedFrames,
+ nsTArray<nsIFrame*>& aOutFramesWithProps) {
+ if (!mBuilder.IsReusingStackingContextItems()) {
+ return false;
+ }
+
+ RDL::MarkAllAncestorFrames(aModifiedFrames, aOutFramesWithProps);
+ RDL::CollectStackingContextItems(&mBuilder, &mList, RootReferenceFrame());
+
+ return true;
+}
+
+PartialUpdateResult RetainedDisplayListBuilder::AttemptPartialUpdate(
+ nscolor aBackstop) {
+ DL_LOGI("(%p) RDL - AttemptPartialUpdate, root frame: %p", this,
+ RootReferenceFrame());
+
+ mBuilder.RemoveModifiedWindowRegions();
+
+ if (mBuilder.ShouldSyncDecodeImages()) {
+ DL_LOGI("RDL - Sync decoding images");
+ MarkFramesWithItemsAndImagesModified(&mList);
+ }
+
+ InvalidateCaretFramesIfNeeded();
+
+ // We set the override dirty regions during ComputeRebuildRegion or in
+ // DisplayPortUtils::InvalidateForDisplayPortChange. The display port change
+ // also marks the frame modified, so those regions are cleared here as well.
+ AutoClearFramePropsArray modifiedFrames(64);
+ AutoClearFramePropsArray framesWithProps(64);
+ GetModifiedAndFramesWithProps(&modifiedFrames.Frames(),
+ &framesWithProps.Frames());
+
+ if (!ShouldBuildPartial(modifiedFrames.Frames())) {
+ // Do not allow partial builds if the |ShouldBuildPartial()| heuristic
+ // fails.
+ mBuilder.SetPartialBuildFailed(true);
+ return PartialUpdateResult::Failed;
+ }
+
+ nsRect modifiedDirty;
+ nsDisplayList modifiedDL(&mBuilder);
+ nsIFrame* modifiedAGR = nullptr;
+ PartialUpdateResult result = PartialUpdateResult::NoChange;
+ const bool simpleUpdate =
+ TrySimpleUpdate(modifiedFrames.Frames(), framesWithProps.Frames());
+
+ mBuilder.EnterPresShell(RootReferenceFrame());
+
+ if (!simpleUpdate) {
+ if (!ComputeRebuildRegion(modifiedFrames.Frames(), &modifiedDirty,
+ &modifiedAGR, framesWithProps.Frames()) ||
+ !PreProcessDisplayList(&mList, modifiedAGR, result,
+ RootReferenceFrame(), nullptr)) {
+ DL_LOGI("RDL - Partial update aborted");
+ mBuilder.SetPartialBuildFailed(true);
+ mBuilder.LeavePresShell(RootReferenceFrame(), nullptr);
+ mList.DeleteAll(&mBuilder);
+ return PartialUpdateResult::Failed;
+ }
+ } else {
+ modifiedDirty = mBuilder.GetVisibleRect();
+ }
+
+ // This is normally handled by EnterPresShell, but we skipped it so that we
+ // didn't call MarkFrameForDisplayIfVisible before ComputeRebuildRegion.
+ nsIScrollableFrame* sf =
+ RootReferenceFrame()->PresShell()->GetRootScrollFrameAsScrollable();
+ if (sf) {
+ nsCanvasFrame* canvasFrame = do_QueryFrame(sf->GetScrolledFrame());
+ if (canvasFrame) {
+ mBuilder.MarkFrameForDisplayIfVisible(canvasFrame, RootReferenceFrame());
+ }
+ }
+
+ nsRect rootOverflow = RootOverflowRect();
+ modifiedDirty.IntersectRect(modifiedDirty, rootOverflow);
+
+ mBuilder.SetDirtyRect(modifiedDirty);
+ mBuilder.SetPartialUpdate(true);
+ mBuilder.SetPartialBuildFailed(false);
+
+ DL_LOGI("RDL - Starting display list build");
+ RootReferenceFrame()->BuildDisplayListForStackingContext(&mBuilder,
+ &modifiedDL);
+ DL_LOGI("RDL - Finished display list build");
+
+ if (!modifiedDL.IsEmpty()) {
+ nsLayoutUtils::AddExtraBackgroundItems(
+ &mBuilder, &modifiedDL, RootReferenceFrame(),
+ nsRect(nsPoint(0, 0), rootOverflow.Size()), rootOverflow, aBackstop);
+ }
+ mBuilder.SetPartialUpdate(false);
+
+ if (mBuilder.PartialBuildFailed()) {
+ DL_LOGI("RDL - Partial update failed!");
+ mBuilder.LeavePresShell(RootReferenceFrame(), nullptr);
+ mBuilder.ClearReuseableDisplayItems();
+ mList.DeleteAll(&mBuilder);
+ modifiedDL.DeleteAll(&mBuilder);
+ Metrics()->mPartialUpdateFailReason = PartialUpdateFailReason::Content;
+ return PartialUpdateResult::Failed;
+ }
+
+ // printf_stderr("Painting --- Modified list (dirty %d,%d,%d,%d):\n",
+ // modifiedDirty.x, modifiedDirty.y, modifiedDirty.width,
+ // modifiedDirty.height);
+ // nsIFrame::PrintDisplayList(&mBuilder, modifiedDL);
+
+ // |modifiedDL| can sometimes be empty here. We still perform the
+ // display list merging to prune unused items (for example, items that
+ // are not visible anymore) from the old list.
+ // TODO: Optimization opportunity. In this case, MergeDisplayLists()
+ // unnecessarily creates a hashtable of the old items.
+ // TODO: Ideally we could skip this if result is NoChange, but currently when
+ // we call RestoreState on nsDisplayWrapList it resets the clip to the base
+ // clip, and we need the UpdateBounds call (within MergeDisplayLists) to
+ // move it to the correct inner clip.
+ if (!simpleUpdate) {
+ Maybe<const ActiveScrolledRoot*> dummy;
+ if (MergeDisplayLists(&modifiedDL, &mList, &mList, dummy)) {
+ result = PartialUpdateResult::Updated;
+ }
+ } else {
+ MOZ_ASSERT(mList.IsEmpty());
+ mList = std::move(modifiedDL);
+ mBuilder.ClearReuseableDisplayItems();
+ result = PartialUpdateResult::Updated;
+ }
+
+#if 0
+ if (DL_LOG_TEST(LogLevel::Verbose)) {
+ printf_stderr("Painting --- Display list:\n");
+ nsIFrame::PrintDisplayList(&mBuilder, mList);
+ }
+#endif
+
+ mBuilder.LeavePresShell(RootReferenceFrame(), List());
+ return result;
+}
+
+nsRect RetainedDisplayListBuilder::RootOverflowRect() const {
+ const nsIFrame* rootReferenceFrame = RootReferenceFrame();
+ nsRect rootOverflowRect = rootReferenceFrame->InkOverflowRectRelativeToSelf();
+ const nsPresContext* presContext = rootReferenceFrame->PresContext();
+ if (!rootReferenceFrame->GetParent() &&
+ presContext->IsRootContentDocumentCrossProcess() &&
+ presContext->HasDynamicToolbar()) {
+ rootOverflowRect.SizeTo(nsLayoutUtils::ExpandHeightForDynamicToolbar(
+ presContext, rootOverflowRect.Size()));
+ }
+
+ return rootOverflowRect;
+}
+
+} // namespace mozilla
diff --git a/layout/painting/RetainedDisplayListBuilder.h b/layout/painting/RetainedDisplayListBuilder.h
new file mode 100644
index 0000000000..98c085fee0
--- /dev/null
+++ b/layout/painting/RetainedDisplayListBuilder.h
@@ -0,0 +1,287 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef RETAINEDDISPLAYLISTBUILDER_H_
+#define RETAINEDDISPLAYLISTBUILDER_H_
+
+#include "nsDisplayList.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/EnumSet.h"
+
+class nsWindowSizes;
+
+namespace mozilla {
+
+class nsDisplayItem;
+class nsDisplayList;
+
+/**
+ * RetainedDisplayListData contains frame invalidation information.
+ * Currently this is implemented as a map of frame pointers to flags.
+ */
+struct RetainedDisplayListData {
+ enum class FrameFlag : uint8_t { Modified, HasProps, HadWillChange };
+ using FrameFlags = mozilla::EnumSet<FrameFlag, uint8_t>;
+
+ RetainedDisplayListData();
+
+ /**
+ * Adds the frame to modified frames list.
+ */
+ void AddModifiedFrame(nsIFrame* aFrame);
+
+ /**
+ * Removes all the frames from this RetainedDisplayListData.
+ */
+ void Clear() {
+ mFrames.Clear();
+ mModifiedFrameCount = 0;
+ }
+
+ /**
+ * Returns a mutable reference to flags set for the given |aFrame|.
+ */
+ FrameFlags& Flags(nsIFrame* aFrame) { return mFrames.LookupOrInsert(aFrame); }
+
+ /**
+ * Returns flags set for the given |aFrame|, or FrameFlags::None if the frame
+ * is not in this RetainedDisplayListData.
+ */
+ FrameFlags GetFlags(nsIFrame* aFrame) const { return mFrames.Get(aFrame); }
+
+ bool IsModified(nsIFrame* aFrame) const {
+ return GetFlags(aFrame).contains(FrameFlag::Modified);
+ }
+
+ bool HasProps(nsIFrame* aFrame) const {
+ return GetFlags(aFrame).contains(FrameFlag::HasProps);
+ }
+
+ bool HadWillChange(nsIFrame* aFrame) const {
+ return GetFlags(aFrame).contains(FrameFlag::HadWillChange);
+ }
+
+ /**
+ * Returns an iterator to the underlying frame storage.
+ */
+ auto ConstIterator() { return mFrames.ConstIter(); }
+
+ /**
+ * Returns true if the modified frame limit has been reached.
+ */
+ bool AtModifiedFrameLimit() {
+ return mModifiedFrameCount >= mModifiedFrameLimit;
+ }
+
+ /**
+ * Removes the given |aFrame| from this RetainedDisplayListData.
+ */
+ bool Remove(nsIFrame* aFrame) { return mFrames.Remove(aFrame); }
+
+ private:
+ nsTHashMap<nsPtrHashKey<nsIFrame>, FrameFlags> mFrames;
+ uint32_t mModifiedFrameCount = 0;
+ uint32_t mModifiedFrameLimit; // initialized to a pref value in constructor
+};
+
+enum class PartialUpdateResult { Failed, NoChange, Updated };
+
+enum class PartialUpdateFailReason {
+ NA,
+ EmptyList,
+ RebuildLimit,
+ FrameType,
+ Disabled,
+ Content,
+ VisibleRect,
+};
+
+struct RetainedDisplayListMetrics {
+ RetainedDisplayListMetrics() { Reset(); }
+
+ void Reset() {
+ mNewItems = 0;
+ mRebuiltItems = 0;
+ mRemovedItems = 0;
+ mReusedItems = 0;
+ mTotalItems = 0;
+ mPartialBuildDuration = 0;
+ mFullBuildDuration = 0;
+ mPartialUpdateFailReason = PartialUpdateFailReason::NA;
+ mPartialUpdateResult = PartialUpdateResult::NoChange;
+ }
+
+ void StartBuild() { mStartTime = mozilla::TimeStamp::Now(); }
+
+ void EndFullBuild() { mFullBuildDuration = Elapsed(); }
+
+ void EndPartialBuild(PartialUpdateResult aResult) {
+ mPartialBuildDuration = Elapsed();
+ mPartialUpdateResult = aResult;
+ }
+
+ double Elapsed() {
+ return (mozilla::TimeStamp::Now() - mStartTime).ToMilliseconds();
+ }
+
+ const char* FailReasonString() const {
+ switch (mPartialUpdateFailReason) {
+ case PartialUpdateFailReason::NA:
+ return "N/A";
+ case PartialUpdateFailReason::EmptyList:
+ return "Empty list";
+ case PartialUpdateFailReason::RebuildLimit:
+ return "Rebuild limit";
+ case PartialUpdateFailReason::FrameType:
+ return "Frame type";
+ case PartialUpdateFailReason::Disabled:
+ return "Disabled";
+ case PartialUpdateFailReason::Content:
+ return "Content";
+ case PartialUpdateFailReason::VisibleRect:
+ return "VisibleRect";
+ default:
+ MOZ_ASSERT_UNREACHABLE("Enum value not handled!");
+ }
+ }
+
+ unsigned int mNewItems;
+ unsigned int mRebuiltItems;
+ unsigned int mRemovedItems;
+ unsigned int mReusedItems;
+ unsigned int mTotalItems;
+
+ mozilla::TimeStamp mStartTime;
+ double mPartialBuildDuration;
+ double mFullBuildDuration;
+ PartialUpdateFailReason mPartialUpdateFailReason;
+ PartialUpdateResult mPartialUpdateResult;
+};
+
+class RetainedDisplayListBuilder {
+ public:
+ RetainedDisplayListBuilder(nsIFrame* aReferenceFrame,
+ nsDisplayListBuilderMode aMode, bool aBuildCaret)
+ : mBuilder(aReferenceFrame, aMode, aBuildCaret, true), mList(&mBuilder) {}
+ ~RetainedDisplayListBuilder() { mList.DeleteAll(&mBuilder); }
+
+ nsDisplayListBuilder* Builder() { return &mBuilder; }
+
+ nsDisplayList* List() { return &mList; }
+
+ RetainedDisplayListMetrics* Metrics() { return &mMetrics; }
+
+ RetainedDisplayListData* Data() { return &mData; }
+
+ PartialUpdateResult AttemptPartialUpdate(nscolor aBackstop);
+
+ /**
+ * Clears the modified state for frames in the retained display list data.
+ */
+ void ClearFramesWithProps();
+
+ void ClearRetainedData();
+
+ void ClearReuseableDisplayItems() { mBuilder.ClearReuseableDisplayItems(); }
+
+ void AddSizeOfIncludingThis(nsWindowSizes&) const;
+
+ NS_DECLARE_FRAME_PROPERTY_DELETABLE(Cached, RetainedDisplayListBuilder)
+
+ private:
+ void GetModifiedAndFramesWithProps(nsTArray<nsIFrame*>* aOutModifiedFrames,
+ nsTArray<nsIFrame*>* aOutFramesWithProps);
+
+ void IncrementSubDocPresShellPaintCount(nsDisplayItem* aItem);
+
+ /**
+ * Invalidates the current and previous caret frame if they have changed.
+ */
+ void InvalidateCaretFramesIfNeeded();
+
+ /**
+ * A simple early exit heuristic to avoid slow partial display list rebuilds.
+ * Returns true if a partial display list build should be attempted.
+ */
+ bool ShouldBuildPartial(nsTArray<nsIFrame*>& aModifiedFrames);
+
+ /**
+ * Recursively pre-processes the old display list tree before building the
+ * new partial display lists, and serializes the old list into an array,
+ * recording indices on items for fast lookup during merging. Builds an
+ * initial linear DAG for the list if we don't have an existing one. Finds
+ * items that have a different AGR from the specified one, and marks them to
+ * also be built so that we get relative ordering correct. Passes
+ * aKeepLinked=true internally for sub-lists that can't be changed to keep the
+ * original list structure linked for fast re-use.
+ */
+ bool PreProcessDisplayList(
+ RetainedDisplayList* aList, nsIFrame* aAGR, PartialUpdateResult& aUpdated,
+ nsIFrame* aAsyncAncestor, const ActiveScrolledRoot* aAsyncAncestorASR,
+ nsIFrame* aOuterFrame = nullptr, uint32_t aCallerKey = 0,
+ uint32_t aNestingDepth = 0, bool aKeepLinked = false);
+
+ /**
+ * Merges items from aNewList into non-invalidated items from aOldList and
+ * stores the result in aOutList.
+ *
+ * aOuterItem is a pointer to an item that owns one of the lists, if
+ * available. If both lists are populated, then both outer items must not be
+ * invalidated, and identical, so either can be passed here.
+ *
+ * Returns true if changes were made, and the resulting display list (in
+ * aOutList) is different from aOldList.
+ */
+ bool MergeDisplayLists(
+ nsDisplayList* aNewList, RetainedDisplayList* aOldList,
+ RetainedDisplayList* aOutList,
+ mozilla::Maybe<const mozilla::ActiveScrolledRoot*>& aOutContainerASR,
+ nsDisplayItem* aOuterItem = nullptr);
+
+ bool ComputeRebuildRegion(nsTArray<nsIFrame*>& aModifiedFrames,
+ nsRect* aOutDirty, nsIFrame** aOutModifiedAGR,
+ nsTArray<nsIFrame*>& aOutFramesWithProps);
+
+ bool ProcessFrame(nsIFrame* aFrame, nsDisplayListBuilder* aBuilder,
+ nsIFrame* aStopAtFrame,
+ nsTArray<nsIFrame*>& aOutFramesWithProps,
+ const bool aStopAtStackingContext, nsRect* aOutDirty,
+ nsIFrame** aOutModifiedAGR);
+
+ nsIFrame* RootReferenceFrame() { return mBuilder.RootReferenceFrame(); }
+ const nsIFrame* RootReferenceFrame() const {
+ return mBuilder.RootReferenceFrame();
+ }
+
+ nsRect RootOverflowRect() const;
+
+ /**
+ * Tries to perform a simple partial display list build without display list
+ * merging. In this mode, only the top-level stacking context items and their
+ * contents are reused, when the frame subtree has not been modified.
+ */
+ bool TrySimpleUpdate(const nsTArray<nsIFrame*>& aModifiedFrames,
+ nsTArray<nsIFrame*>& aOutFramesWithProps);
+
+ friend class MergeState;
+
+ nsDisplayListBuilder mBuilder;
+ RetainedDisplayList mList;
+ WeakFrame mPreviousCaret;
+ RetainedDisplayListMetrics mMetrics;
+ RetainedDisplayListData mData;
+};
+
+namespace RDLUtils {
+
+void AssertFrameSubtreeUnmodified(const nsIFrame* aFrame);
+void AssertDisplayItemUnmodified(nsDisplayItem* aItem);
+void AssertDisplayListUnmodified(nsDisplayList* aList);
+
+} // namespace RDLUtils
+} // namespace mozilla
+
+#endif // RETAINEDDISPLAYLISTBUILDER_H_
diff --git a/layout/painting/RetainedDisplayListHelpers.h b/layout/painting/RetainedDisplayListHelpers.h
new file mode 100644
index 0000000000..933ab31a7a
--- /dev/null
+++ b/layout/painting/RetainedDisplayListHelpers.h
@@ -0,0 +1,187 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef RETAINEDDISPLAYLISTHELPERS_H_
+#define RETAINEDDISPLAYLISTHELPERS_H_
+
+#include "mozilla/Span.h"
+#include "PLDHashTable.h"
+
+class nsIFrame;
+
+namespace mozilla {
+
+struct DisplayItemKey {
+ bool operator==(const DisplayItemKey& aOther) const {
+ return mFrame == aOther.mFrame && mPerFrameKey == aOther.mPerFrameKey;
+ }
+
+ nsIFrame* mFrame;
+ uint32_t mPerFrameKey;
+};
+
+class DisplayItemHashEntry : public PLDHashEntryHdr {
+ public:
+ typedef DisplayItemKey KeyType;
+ typedef const DisplayItemKey* KeyTypePointer;
+
+ explicit DisplayItemHashEntry(KeyTypePointer aKey) : mKey(*aKey) {}
+ DisplayItemHashEntry(DisplayItemHashEntry&&) = default;
+
+ ~DisplayItemHashEntry() = default;
+
+ KeyType GetKey() const { return mKey; }
+ bool KeyEquals(KeyTypePointer aKey) const { return mKey == *aKey; }
+
+ static KeyTypePointer KeyToPointer(KeyType& aKey) { return &aKey; }
+ static PLDHashNumber HashKey(KeyTypePointer aKey) {
+ if (!aKey) {
+ return 0;
+ }
+
+ return mozilla::HashGeneric(aKey->mFrame, aKey->mPerFrameKey);
+ }
+ enum { ALLOW_MEMMOVE = true };
+
+ DisplayItemKey mKey;
+};
+
+template <typename T>
+bool SpanContains(mozilla::Span<const T>& aSpan, T aItem) {
+ for (const T& i : aSpan) {
+ if (i == aItem) {
+ return true;
+ }
+ }
+ return false;
+}
+
+class OldListUnits {};
+class MergedListUnits {};
+
+template <typename Units>
+struct Index {
+ Index() : val(0) {}
+ explicit Index(size_t aVal) : val(aVal) {
+ MOZ_RELEASE_ASSERT(aVal < std::numeric_limits<uint32_t>::max(),
+ "List index overflowed");
+ }
+
+ bool operator==(const Index<Units>& aOther) const {
+ return val == aOther.val;
+ }
+
+ uint32_t val;
+};
+typedef Index<OldListUnits> OldListIndex;
+typedef Index<MergedListUnits> MergedListIndex;
+
+template <typename T>
+class DirectedAcyclicGraph {
+ public:
+ DirectedAcyclicGraph() = default;
+ DirectedAcyclicGraph(DirectedAcyclicGraph&& aOther)
+ : mNodesInfo(std::move(aOther.mNodesInfo)),
+ mDirectPredecessorList(std::move(aOther.mDirectPredecessorList)) {}
+
+ DirectedAcyclicGraph& operator=(DirectedAcyclicGraph&& aOther) {
+ mNodesInfo = std::move(aOther.mNodesInfo);
+ mDirectPredecessorList = std::move(aOther.mDirectPredecessorList);
+ return *this;
+ }
+
+ Index<T> AddNode(
+ mozilla::Span<const Index<T>> aDirectPredecessors,
+ const mozilla::Maybe<Index<T>>& aExtraPredecessor = mozilla::Nothing()) {
+ size_t index = mNodesInfo.Length();
+ mNodesInfo.AppendElement(NodeInfo(mDirectPredecessorList.Length(),
+ aDirectPredecessors.Length()));
+ if (aExtraPredecessor &&
+ !SpanContains(aDirectPredecessors, aExtraPredecessor.value())) {
+ mNodesInfo.LastElement().mDirectPredecessorCount++;
+ mDirectPredecessorList.SetCapacity(mDirectPredecessorList.Length() +
+ aDirectPredecessors.Length() + 1);
+ mDirectPredecessorList.AppendElements(aDirectPredecessors);
+ mDirectPredecessorList.AppendElement(aExtraPredecessor.value());
+ } else {
+ mDirectPredecessorList.AppendElements(aDirectPredecessors);
+ }
+ return Index<T>(index);
+ }
+
+ size_t Length() { return mNodesInfo.Length(); }
+
+ mozilla::Span<Index<T>> GetDirectPredecessors(Index<T> aNodeIndex) {
+ NodeInfo& node = mNodesInfo[aNodeIndex.val];
+ const auto span = mozilla::Span{mDirectPredecessorList};
+ return span.Subspan(node.mIndexInDirectPredecessorList,
+ node.mDirectPredecessorCount);
+ }
+
+ template <typename OtherUnits>
+ void EnsureCapacityFor(const DirectedAcyclicGraph<OtherUnits>& aOther) {
+ mNodesInfo.SetCapacity(aOther.mNodesInfo.Length());
+ mDirectPredecessorList.SetCapacity(aOther.mDirectPredecessorList.Length());
+ }
+
+ void Clear() {
+ mNodesInfo.Clear();
+ mDirectPredecessorList.Clear();
+ }
+
+ struct NodeInfo {
+ NodeInfo(size_t aIndexInDirectPredecessorList,
+ size_t aDirectPredecessorCount)
+ : mIndexInDirectPredecessorList(aIndexInDirectPredecessorList),
+ mDirectPredecessorCount(aDirectPredecessorCount) {}
+ size_t mIndexInDirectPredecessorList;
+ size_t mDirectPredecessorCount;
+ };
+
+ nsTArray<NodeInfo> mNodesInfo;
+ nsTArray<Index<T>> mDirectPredecessorList;
+};
+
+class RetainedDisplayListBuilder;
+class nsDisplayItem;
+
+struct OldItemInfo {
+ explicit OldItemInfo(nsDisplayItem* aItem);
+
+ void AddedToMergedList(MergedListIndex aIndex) {
+ MOZ_ASSERT(!IsUsed());
+ mUsed = true;
+ mIndex = aIndex;
+ mItem = nullptr;
+ }
+
+ void AddedMatchToMergedList(RetainedDisplayListBuilder* aBuilder,
+ MergedListIndex aIndex);
+ void Discard(RetainedDisplayListBuilder* aBuilder,
+ nsTArray<MergedListIndex>&& aDirectPredecessors);
+ bool IsUsed() { return mUsed; }
+
+ bool IsDiscarded() {
+ MOZ_ASSERT(IsUsed());
+ return mDiscarded;
+ }
+
+ bool IsChanged();
+
+ nsDisplayItem* mItem;
+ nsTArray<MergedListIndex> mDirectPredecessors;
+ MergedListIndex mIndex;
+ bool mUsed;
+ bool mDiscarded;
+ bool mOwnsItem;
+};
+
+bool AnyContentAncestorModified(nsIFrame* aFrame,
+ nsIFrame* aStopAtFrame = nullptr);
+
+} // namespace mozilla
+
+#endif // RETAINEDDISPLAYLISTHELPERS_H_
diff --git a/layout/painting/TransformClipNode.h b/layout/painting/TransformClipNode.h
new file mode 100644
index 0000000000..a28dc3dbc0
--- /dev/null
+++ b/layout/painting/TransformClipNode.h
@@ -0,0 +1,138 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef MOZILLA_PAINTING_TRANSFORMCLIPNODE_H
+#define MOZILLA_PAINTING_TRANSFORMCLIPNODE_H
+
+#include "mozilla/gfx/MatrixFwd.h"
+#include "mozilla/gfx/Rect.h"
+#include "mozilla/Maybe.h"
+#include "nsISupports.h"
+#include "nsRegionFwd.h"
+
+namespace mozilla {
+
+/**
+ * TransformClipNode stores a transformation matrix and a post-transform
+ * clip rect.
+ * They can be used to transform and clip a display item inside a flattened
+ * nsDisplayTransform to the coordinate space of that nsDisplayTransform.
+ */
+class TransformClipNode {
+ NS_INLINE_DECL_REFCOUNTING(TransformClipNode);
+
+ public:
+ TransformClipNode(const RefPtr<TransformClipNode>& aParent,
+ const gfx::Matrix4x4Flagged& aTransform,
+ const Maybe<gfx::IntRect>& aClip)
+ : mParent(aParent), mTransform(aTransform), mClip(aClip) {
+ MOZ_COUNT_CTOR(TransformClipNode);
+ }
+
+ /**
+ * Returns the parent node, or nullptr if this is the root node.
+ */
+ const RefPtr<TransformClipNode>& Parent() const { return mParent; }
+
+ /**
+ * Transforms and clips |aRect| up to the root transform node.
+ * |aRect| is expected to be in app units.
+ */
+ nsRect TransformRect(const nsRect& aRect, const int32_t aA2D) const {
+ if (aRect.IsEmpty()) {
+ return aRect;
+ }
+
+ gfx::Rect result(NSAppUnitsToFloatPixels(aRect.x, aA2D),
+ NSAppUnitsToFloatPixels(aRect.y, aA2D),
+ NSAppUnitsToFloatPixels(aRect.width, aA2D),
+ NSAppUnitsToFloatPixels(aRect.height, aA2D));
+ TransformRect(result);
+ return nsRect(NSFloatPixelsToAppUnits(result.x, aA2D),
+ NSFloatPixelsToAppUnits(result.y, aA2D),
+ NSFloatPixelsToAppUnits(result.width, aA2D),
+ NSFloatPixelsToAppUnits(result.height, aA2D));
+ }
+
+ /**
+ * Transforms and clips |aRect| up to the root transform node.
+ * |aRect| is expected to be in integer pixels.
+ */
+ gfx::IntRect TransformRect(const gfx::IntRect& aRect) const {
+ if (aRect.IsEmpty()) {
+ return aRect;
+ }
+
+ gfx::Rect result(IntRectToRect(aRect));
+ TransformRect(result);
+ return RoundedToInt(result);
+ }
+
+ /**
+ * Transforms and clips |aRegion| up to the root transform node.
+ * |aRegion| is expected be in integer pixels.
+ */
+ nsIntRegion TransformRegion(const nsIntRegion& aRegion) {
+ if (aRegion.IsEmpty()) {
+ return aRegion;
+ }
+
+ nsIntRegion result = aRegion;
+
+ const TransformClipNode* node = this;
+ while (node) {
+ const gfx::Matrix4x4Flagged& transform = node->Transform();
+ result = result.Transform(transform.GetMatrix());
+
+ if (node->Clip()) {
+ const gfx::IntRect clipRect = *node->Clip();
+ result.AndWith(clipRect);
+ }
+
+ node = node->Parent();
+ }
+
+ return result;
+ }
+
+ protected:
+ /**
+ * Returns the post-transform clip, if there is one.
+ */
+ const Maybe<gfx::IntRect>& Clip() const { return mClip; }
+
+ /**
+ * Returns the matrix that transforms the item bounds to the coordinate space
+ * of the flattened nsDisplayTransform.
+ */
+ const gfx::Matrix4x4Flagged& Transform() const { return mTransform; }
+
+ void TransformRect(gfx::Rect& aRect) const {
+ const TransformClipNode* node = this;
+ while (node) {
+ const gfx::Matrix4x4Flagged& transform = node->Transform();
+ gfx::Rect maxBounds = gfx::Rect::MaxIntRect();
+
+ if (node->Clip()) {
+ maxBounds = IntRectToRect(*node->Clip());
+ }
+
+ aRect = transform.TransformAndClipBounds(aRect, maxBounds);
+ node = node->Parent();
+ }
+ }
+
+ private:
+ MOZ_COUNTED_DTOR(TransformClipNode)
+
+ const RefPtr<TransformClipNode> mParent;
+ const gfx::Matrix4x4Flagged mTransform;
+ const Maybe<gfx::IntRect> mClip;
+};
+
+} // namespace mozilla
+
+#endif /* MOZILLA_PAINTING_TRANSFORMCLIPNODE_H */
diff --git a/layout/painting/WindowRenderer.cpp b/layout/painting/WindowRenderer.cpp
new file mode 100644
index 0000000000..d695ef8eba
--- /dev/null
+++ b/layout/painting/WindowRenderer.cpp
@@ -0,0 +1,229 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WindowRenderer.h"
+
+#include "gfxPlatform.h"
+#include "mozilla/dom/Animation.h" // for Animation
+#include "mozilla/dom/AnimationEffect.h"
+#include "mozilla/EffectSet.h"
+#include "mozilla/layers/PersistentBufferProvider.h" // for PersistentBufferProviderBasic, PersistentBufferProvider (ptr only)
+#include "nsDisplayList.h"
+
+using namespace mozilla::gfx;
+using namespace mozilla::layers;
+
+namespace mozilla {
+
+/**
+ * StartFrameTimeRecording, together with StopFrameTimeRecording
+ * enable recording of frame intervals.
+ *
+ * To allow concurrent consumers, a cyclic array is used which serves all
+ * consumers, practically stateless with regard to consumers.
+ *
+ * To save resources, the buffer is allocated on first call to
+ * StartFrameTimeRecording and recording is paused if no consumer which called
+ * StartFrameTimeRecording is able to get valid results (because the cyclic
+ * buffer was overwritten since that call).
+ *
+ * To determine availability of the data upon StopFrameTimeRecording:
+ * - mRecording.mNextIndex increases on each RecordFrame, and never resets.
+ * - Cyclic buffer position is realized as mNextIndex % bufferSize.
+ * - StartFrameTimeRecording returns mNextIndex. When StopFrameTimeRecording is
+ * called, the required start index is passed as an arg, and we're able to
+ * calculate the required length. If this length is bigger than bufferSize, it
+ * means data was overwritten. otherwise, we can return the entire sequence.
+ * - To determine if we need to pause, mLatestStartIndex is updated to
+ * mNextIndex on each call to StartFrameTimeRecording. If this index gets
+ * overwritten, it means that all earlier start indices obtained via
+ * StartFrameTimeRecording were also overwritten, hence, no point in
+ * recording, so pause.
+ * - mCurrentRunStartIndex indicates the oldest index of the recording after
+ * which the recording was not paused. If StopFrameTimeRecording is invoked
+ * with a start index older than this, it means that some frames were not
+ * recorded, so data is invalid.
+ */
+uint32_t FrameRecorder::StartFrameTimeRecording(int32_t aBufferSize) {
+ if (mRecording.mIsPaused) {
+ mRecording.mIsPaused = false;
+
+ if (!mRecording.mIntervals.Length()) { // Initialize recording buffers
+ mRecording.mIntervals.SetLength(aBufferSize);
+ }
+
+ // After being paused, recent values got invalid. Update them to now.
+ mRecording.mLastFrameTime = TimeStamp::Now();
+
+ // Any recording which started before this is invalid, since we were paused.
+ mRecording.mCurrentRunStartIndex = mRecording.mNextIndex;
+ }
+
+ // If we'll overwrite this index, there are no more consumers with aStartIndex
+ // for which we're able to provide the full recording, so no point in keep
+ // recording.
+ mRecording.mLatestStartIndex = mRecording.mNextIndex;
+ return mRecording.mNextIndex;
+}
+
+void FrameRecorder::RecordFrame() {
+ if (!mRecording.mIsPaused) {
+ TimeStamp now = TimeStamp::Now();
+ uint32_t i = mRecording.mNextIndex % mRecording.mIntervals.Length();
+ mRecording.mIntervals[i] =
+ static_cast<float>((now - mRecording.mLastFrameTime).ToMilliseconds());
+ mRecording.mNextIndex++;
+ mRecording.mLastFrameTime = now;
+
+ if (mRecording.mNextIndex >
+ (mRecording.mLatestStartIndex + mRecording.mIntervals.Length())) {
+ // We've just overwritten the most recent recording start -> pause.
+ mRecording.mIsPaused = true;
+ }
+ }
+}
+
+void FrameRecorder::StopFrameTimeRecording(uint32_t aStartIndex,
+ nsTArray<float>& aFrameIntervals) {
+ uint32_t bufferSize = mRecording.mIntervals.Length();
+ uint32_t length = mRecording.mNextIndex - aStartIndex;
+ if (mRecording.mIsPaused || length > bufferSize ||
+ aStartIndex < mRecording.mCurrentRunStartIndex) {
+ // aStartIndex is too old. Also if aStartIndex was issued before
+ // mRecordingNextIndex overflowed (uint32_t)
+ // and stopped after the overflow (would happen once every 828 days of
+ // constant 60fps).
+ length = 0;
+ }
+
+ if (!length) {
+ aFrameIntervals.Clear();
+ return; // empty recording, return empty arrays.
+ }
+ // Set length in advance to avoid possibly repeated reallocations
+ aFrameIntervals.SetLength(length);
+
+ uint32_t cyclicPos = aStartIndex % bufferSize;
+ for (uint32_t i = 0; i < length; i++, cyclicPos++) {
+ if (cyclicPos == bufferSize) {
+ cyclicPos = 0;
+ }
+ aFrameIntervals[i] = mRecording.mIntervals[cyclicPos];
+ }
+}
+
+already_AddRefed<PersistentBufferProvider>
+WindowRenderer::CreatePersistentBufferProvider(
+ const mozilla::gfx::IntSize& aSize, mozilla::gfx::SurfaceFormat aFormat) {
+ RefPtr<PersistentBufferProviderBasic> bufferProvider;
+ // If we are using remote canvas we don't want to use acceleration in
+ // non-remote layer managers, so we always use the fallback software one.
+ if (!gfxPlatform::UseRemoteCanvas() ||
+ !gfxPlatform::IsBackendAccelerated(
+ gfxPlatform::GetPlatform()->GetPreferredCanvasBackend())) {
+ bufferProvider = PersistentBufferProviderBasic::Create(
+ aSize, aFormat,
+ gfxPlatform::GetPlatform()->GetPreferredCanvasBackend());
+ }
+
+ if (!bufferProvider) {
+ bufferProvider = PersistentBufferProviderBasic::Create(
+ aSize, aFormat, gfxPlatform::GetPlatform()->GetFallbackCanvasBackend());
+ }
+
+ return bufferProvider.forget();
+}
+
+void WindowRenderer::AddPartialPrerenderedAnimation(
+ uint64_t aCompositorAnimationId, dom::Animation* aAnimation) {
+ mPartialPrerenderedAnimations.InsertOrUpdate(aCompositorAnimationId,
+ RefPtr{aAnimation});
+ aAnimation->SetPartialPrerendered(aCompositorAnimationId);
+}
+void WindowRenderer::RemovePartialPrerenderedAnimation(
+ uint64_t aCompositorAnimationId, dom::Animation* aAnimation) {
+ MOZ_ASSERT(aAnimation);
+#ifdef DEBUG
+ RefPtr<dom::Animation> animation;
+ if (mPartialPrerenderedAnimations.Remove(aCompositorAnimationId,
+ getter_AddRefs(animation)) &&
+ // It may be possible that either animation's effect has already been
+ // nulled out via Animation::SetEffect() so ignore such cases.
+ aAnimation->GetEffect() && aAnimation->GetEffect()->AsKeyframeEffect() &&
+ animation->GetEffect() && animation->GetEffect()->AsKeyframeEffect()) {
+ MOZ_ASSERT(
+ EffectSet::GetForEffect(aAnimation->GetEffect()->AsKeyframeEffect()) ==
+ EffectSet::GetForEffect(animation->GetEffect()->AsKeyframeEffect()));
+ }
+#else
+ mPartialPrerenderedAnimations.Remove(aCompositorAnimationId);
+#endif
+ aAnimation->ResetPartialPrerendered();
+}
+void WindowRenderer::UpdatePartialPrerenderedAnimations(
+ const nsTArray<uint64_t>& aJankedAnimations) {
+ for (uint64_t id : aJankedAnimations) {
+ RefPtr<dom::Animation> animation;
+ if (mPartialPrerenderedAnimations.Remove(id, getter_AddRefs(animation))) {
+ animation->UpdatePartialPrerendered();
+ }
+ }
+}
+
+void FallbackRenderer::SetTarget(gfxContext* aTarget,
+ layers::BufferMode aDoubleBuffering) {
+ mTarget = aTarget;
+ mBufferMode = aDoubleBuffering;
+}
+
+bool FallbackRenderer::BeginTransaction(const nsCString& aURL) {
+ if (!mTarget) {
+ return false;
+ }
+
+ return true;
+}
+
+void FallbackRenderer::EndTransactionWithColor(const nsIntRect& aRect,
+ const gfx::DeviceColor& aColor) {
+ mTarget->GetDrawTarget()->FillRect(Rect(aRect), ColorPattern(aColor));
+ mAnimationReadyTime = TimeStamp::Now();
+}
+
+void FallbackRenderer::EndTransactionWithList(nsDisplayListBuilder* aBuilder,
+ nsDisplayList* aList,
+ int32_t aAppUnitsPerDevPixel,
+ EndTransactionFlags aFlags) {
+ if (aFlags & EndTransactionFlags::END_NO_COMPOSITE) {
+ return;
+ }
+
+ DrawTarget* dt = mTarget->GetDrawTarget();
+
+ BackendType backend = gfxPlatform::GetPlatform()->GetContentBackendFor(
+ LayersBackend::LAYERS_NONE);
+ RefPtr<DrawTarget> dest =
+ gfxPlatform::GetPlatform()->CreateDrawTargetForBackend(
+ backend, dt->GetSize(), dt->GetFormat());
+ if (dest) {
+ gfxContext ctx(dest, /* aPreserveTransform */ true);
+
+ nsRegion opaque = aList->GetOpaqueRegion(aBuilder);
+ if (opaque.Contains(aList->GetComponentAlphaBounds(aBuilder))) {
+ dest->SetPermitSubpixelAA(true);
+ }
+
+ aList->Paint(aBuilder, &ctx, aAppUnitsPerDevPixel);
+
+ RefPtr<SourceSurface> snapshot = dest->Snapshot();
+ dt->DrawSurface(snapshot, Rect(dest->GetRect()), Rect(dest->GetRect()),
+ DrawSurfaceOptions(),
+ DrawOptions(1.0f, CompositionOp::OP_SOURCE));
+ }
+ mAnimationReadyTime = TimeStamp::Now();
+}
+
+} // namespace mozilla
diff --git a/layout/painting/WindowRenderer.h b/layout/painting/WindowRenderer.h
new file mode 100644
index 0000000000..e018a4cf56
--- /dev/null
+++ b/layout/painting/WindowRenderer.h
@@ -0,0 +1,284 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef MOZILLA_PAINTING_WINDOWRENDERER_H
+#define MOZILLA_PAINTING_WINDOWRENDERER_H
+
+#include "mozilla/webrender/webrender_ffi.h"
+#include "mozilla/layers/LayersTypes.h"
+#include "mozilla/dom/Animation.h" // for Animation
+#include "mozilla/layers/ScrollableLayerGuid.h" // for ScrollableLayerGuid, ScrollableLayerGuid::ViewID
+#include "mozilla/ScrollPositionUpdate.h" // for ScrollPositionUpdate
+#include "nsRefPtrHashtable.h" // for nsRefPtrHashtable
+#include "gfxContext.h"
+
+namespace mozilla {
+namespace layers {
+class LayerManager;
+class WebRenderLayerManager;
+class KnowsCompositor;
+class CompositorBridgeChild;
+class FrameUniformityData;
+class PersistentBufferProvider;
+} // namespace layers
+class FallbackRenderer;
+class nsDisplayListBuilder;
+class nsDisplayList;
+
+class FrameRecorder {
+ public:
+ /**
+ * Record (and return) frame-intervals and paint-times for frames which were
+ * presented between calling StartFrameTimeRecording and
+ * StopFrameTimeRecording.
+ *
+ * - Uses a cyclic buffer and serves concurrent consumers, so if Stop is
+ * called too late
+ * (elements were overwritten since Start), result is considered invalid
+ * and hence empty.)
+ * - Buffer is capable of holding 10 seconds @ 60fps (or more if frames were
+ * less frequent).
+ * Can be changed (up to 1 hour) via pref:
+ * toolkit.framesRecording.bufferSize.
+ * - Note: the first frame-interval may be longer than expected because last
+ * frame
+ * might have been presented some time before calling
+ * StartFrameTimeRecording.
+ */
+
+ /**
+ * Returns a handle which represents current recording start position.
+ */
+ virtual uint32_t StartFrameTimeRecording(int32_t aBufferSize);
+
+ /**
+ * Clears, then populates aFrameIntervals with the recorded frame timing
+ * data. The array will be empty if data was overwritten since
+ * aStartIndex was obtained.
+ */
+ virtual void StopFrameTimeRecording(uint32_t aStartIndex,
+ nsTArray<float>& aFrameIntervals);
+
+ void RecordFrame();
+
+ private:
+ struct FramesTimingRecording {
+ // Stores state and data for frame intervals and paint times recording.
+ // see LayerManager::StartFrameTimeRecording() at Layers.cpp for more
+ // details.
+ FramesTimingRecording()
+ : mNextIndex(0),
+ mLatestStartIndex(0),
+ mCurrentRunStartIndex(0),
+ mIsPaused(true) {}
+ nsTArray<float> mIntervals;
+ TimeStamp mLastFrameTime;
+ uint32_t mNextIndex;
+ uint32_t mLatestStartIndex;
+ uint32_t mCurrentRunStartIndex;
+ bool mIsPaused;
+ };
+ FramesTimingRecording mRecording;
+};
+
+/**
+ * WindowRenderer is the retained rendering object owned by an nsIWidget for
+ * drawing the contents of that window, the role previously handled by
+ * LayerManager.
+ *
+ * It can be WebRender, (deprecated) Layers, or an immediate-mode
+ * FallbackRenderer.
+ *
+ * The intention is for LayerManager to be removed entirely in the near future,
+ * with WebRender inheriting directly from this class. It is likely that more
+ * cleanup can be done once that happens.
+ */
+class WindowRenderer : public FrameRecorder {
+ NS_INLINE_DECL_REFCOUNTING(WindowRenderer)
+
+ public:
+ // Cast to implementation types.
+ virtual layers::WebRenderLayerManager* AsWebRender() { return nullptr; }
+ virtual FallbackRenderer* AsFallback() { return nullptr; }
+
+ // Required functionality
+
+ /**
+ * Start a new transaction. Nested transactions are not allowed so
+ * there must be no transaction currently in progress.
+ * This transaction will update the state of the window from which
+ * this LayerManager was obtained.
+ */
+ virtual bool BeginTransaction(const nsCString& aURL = nsCString()) = 0;
+
+ enum EndTransactionFlags {
+ END_DEFAULT = 0,
+ END_NO_IMMEDIATE_REDRAW = 1 << 0, // Do not perform the drawing phase
+ END_NO_COMPOSITE =
+ 1 << 1, // Do not composite after drawing painted layer contents.
+ END_NO_REMOTE_COMPOSITE = 1 << 2 // Do not schedule a composition with a
+ // remote Compositor, if one exists.
+ };
+
+ /**
+ * Attempts to end an "empty transaction". There must have been no
+ * changes to the layer tree since the BeginTransaction().
+ * It's possible for this to fail; PaintedLayers may need to be updated
+ * due to VRAM data being lost, for example. In such cases this method
+ * returns false, and the caller must proceed with a normal layer tree
+ * update and EndTransaction.
+ */
+ virtual bool EndEmptyTransaction(
+ EndTransactionFlags aFlags = END_DEFAULT) = 0;
+
+ virtual void Destroy() {}
+
+ /**
+ * Type of layer manager this is. This is to be used sparsely in order to
+ * avoid a lot of Layers backend specific code. It should be used only when
+ * Layers backend specific functionality is necessary.
+ */
+ virtual layers::LayersBackend GetBackendType() = 0;
+
+ /**
+ * Type of layers backend that will be used to composite this layer tree.
+ * When compositing is done remotely, then this returns the layers type
+ * of the compositor.
+ */
+ virtual layers::LayersBackend GetCompositorBackendType() {
+ return GetBackendType();
+ }
+
+ /**
+ * Checks if we need to invalidate the OS widget to trigger
+ * painting when updating this renderer.
+ */
+ virtual bool NeedsWidgetInvalidation() { return true; }
+
+ /**
+ * Make sure that the previous transaction has been entirely
+ * completed.
+ *
+ * Note: This may sychronously wait on a remote compositor
+ * to complete rendering.
+ */
+ virtual void FlushRendering(wr::RenderReasons aReasons) {}
+
+ /**
+ * Make sure that the previous transaction has been
+ * received. This will synchronsly wait on a remote compositor.
+ */
+ virtual void WaitOnTransactionProcessed() {}
+
+ virtual bool IsCompositingCheap() { return true; }
+
+ /**
+ * returns the maximum texture size on this layer backend, or INT32_MAX
+ * if there is no maximum
+ */
+ virtual int32_t GetMaxTextureSize() const { return INT32_MAX; }
+
+ /**
+ * Return the name of the layer manager's backend.
+ */
+ virtual void GetBackendName(nsAString& aName) = 0;
+
+ virtual void GetFrameUniformity(layers::FrameUniformityData* aOutData) {}
+
+ virtual bool AddPendingScrollUpdateForNextTransaction(
+ layers::ScrollableLayerGuid::ViewID aScrollId,
+ const ScrollPositionUpdate& aUpdateInfo) {
+ return false;
+ }
+
+ /**
+ * Creates a PersistentBufferProvider for use with canvas which is optimized
+ * for inter-operating with this layermanager.
+ */
+ virtual already_AddRefed<layers::PersistentBufferProvider>
+ CreatePersistentBufferProvider(const mozilla::gfx::IntSize& aSize,
+ mozilla::gfx::SurfaceFormat aFormat);
+
+ // Helper wrappers around cast to impl and then cast again.
+
+ virtual layers::KnowsCompositor* AsKnowsCompositor() { return nullptr; }
+
+ virtual layers::CompositorBridgeChild* GetCompositorBridgeChild() {
+ return nullptr;
+ }
+
+ // Provided functionality
+
+ void AddPartialPrerenderedAnimation(uint64_t aCompositorAnimationId,
+ dom::Animation* aAnimation);
+ void RemovePartialPrerenderedAnimation(uint64_t aCompositorAnimationId,
+ dom::Animation* aAnimation);
+ void UpdatePartialPrerenderedAnimations(
+ const nsTArray<uint64_t>& aJankedAnimations);
+
+ const TimeStamp& GetAnimationReadyTime() const { return mAnimationReadyTime; }
+
+ protected:
+ virtual ~WindowRenderer() = default;
+
+ // Transform animations which are not fully pre-rendered because it's on a
+ // large frame. We need to update the pre-rendered area once after we tried
+ // to composite area which is outside of the pre-rendered area on the
+ // compositor.
+ nsRefPtrHashtable<nsUint64HashKey, dom::Animation>
+ mPartialPrerenderedAnimations;
+
+ // The time when painting most recently finished. This is recorded so that
+ // we can time any play-pending animations from this point.
+ TimeStamp mAnimationReadyTime;
+};
+
+/**
+ * FallbackRenderer is non-retained renderer that acts as a direct wrapper
+ * around calling Paint on the provided DisplayList. This is used for cases
+ * where initializing WebRender is too costly, and we don't need
+ * retaining/invalidation (like small popup windows).
+ *
+ * It doesn't support any sort of EmptyTransaction, and only draws during
+ * EndTransaction if a composite is requested (no END_NO_COMPOSITE flag
+ * provided)
+ */
+class FallbackRenderer : public WindowRenderer {
+ public:
+ FallbackRenderer* AsFallback() override { return this; }
+
+ void SetTarget(gfxContext* aContext, layers::BufferMode aDoubleBuffering);
+
+ bool BeginTransaction(const nsCString& aURL = nsCString()) override;
+
+ bool EndEmptyTransaction(EndTransactionFlags aFlags = END_DEFAULT) override {
+ return false;
+ }
+
+ layers::LayersBackend GetBackendType() override {
+ return layers::LayersBackend::LAYERS_NONE;
+ }
+
+ virtual void GetBackendName(nsAString& name) override {
+ name.AssignLiteral("Fallback");
+ }
+
+ bool IsCompositingCheap() override { return false; }
+
+ void EndTransactionWithColor(const nsIntRect& aRect,
+ const gfx::DeviceColor& aColor);
+ void EndTransactionWithList(nsDisplayListBuilder* aBuilder,
+ nsDisplayList* aList,
+ int32_t aAppUnitsPerDevPixel,
+ EndTransactionFlags aFlags);
+
+ gfxContext* mTarget;
+ layers::BufferMode mBufferMode;
+};
+
+} // namespace mozilla
+
+#endif /* MOZILLA_PAINTING_WINDOWRENDERER_H */
diff --git a/layout/painting/crashtests/1402183-1.html b/layout/painting/crashtests/1402183-1.html
new file mode 100644
index 0000000000..ea54512894
--- /dev/null
+++ b/layout/painting/crashtests/1402183-1.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Bug 1402183</title>
+<style type="text/css">
+:not(article) {
+ -webkit-text-stroke-width: 1px;
+ box-decoration-break: clone;
+ clip-path: polygon(69px 1px, 49px 1px, 0px 0px);
+}
+</style>
+</head>
+<body>
+<ins>
+<p>
+r6O#i/q]
+</p>
+</ins>
+</body>
+</html>
diff --git a/layout/painting/crashtests/1405881-1.html b/layout/painting/crashtests/1405881-1.html
new file mode 100644
index 0000000000..44c8ab1ac0
--- /dev/null
+++ b/layout/painting/crashtests/1405881-1.html
@@ -0,0 +1,24 @@
+<style type="text/css">
+ #container {
+ height: 300px;
+ width: 300px;
+ }
+ #box {
+ height: 100px;
+ width: 100px;
+ background: red;
+ animation: 2s anim;
+ }
+ @keyframes anim {
+ from {
+ -moz-transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 10%, 10%, 0, 1);
+ }
+ to {
+ -moz-transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 40%, 40%, 0, 1);
+ }
+ }
+
+</style>
+<div id=container>
+ <div id=box></div>
+</div>
diff --git a/layout/painting/crashtests/1407470-1.html b/layout/painting/crashtests/1407470-1.html
new file mode 100644
index 0000000000..0ddf957182
--- /dev/null
+++ b/layout/painting/crashtests/1407470-1.html
@@ -0,0 +1,19 @@
+<body id='test_body'>
+<script>
+let o = [];
+o[0] = document.createElement("tt");
+test_body.appendChild(o[0]);
+o[1] = document.createElement("center");
+o[2] = document.createElement("footer");
+o[0].appendChild(o[2]);
+o[1].animate([{
+ "padding": "80.40vw 0.0vmax",
+ "transform": "matrix(6287.56,268.76,237.34,222.80,186.72,287.94) rotateX(2.046rad)"
+ }], { duration:3845.75 });
+o[2].appendChild(o[1]);
+test_body.animate([{
+ "transform": "scale3d(0.0,5961462.820,250.41)",
+ "outline": "auto thick",
+ "mask": "exclude no-clip url(data:image/gif;base64,R0lGODlhAQABAAD/ACH/C05FVFNDQVBFMi4wAwEAAAAsAAAAAAEAAQAAAgAsAAAAAAEAAQAAAgA7),exclude",
+ }], 3617.63433129);
+</script> \ No newline at end of file
diff --git a/layout/painting/crashtests/1413073-1.html b/layout/painting/crashtests/1413073-1.html
new file mode 100644
index 0000000000..ddecfb2424
--- /dev/null
+++ b/layout/painting/crashtests/1413073-1.html
@@ -0,0 +1,15 @@
+<html class="reftest-wait">
+<body>
+<div style="position:fixed; width:200px; height:200px; background-color:blue">
+ <div style="position:fixed; width:200px; height:200px; left:400px; background-color:red" id="inner"></div>
+</div>
+</body>
+<script>
+ function doTest() {
+ var d = document.getElementById("inner");
+ d.style.backgroundColor = "green";
+ document.documentElement.className = "";
+ }
+ window.addEventListener("MozReftestInvalidate", doTest);
+</script>
+</html>
diff --git a/layout/painting/crashtests/1413073-2.html b/layout/painting/crashtests/1413073-2.html
new file mode 100644
index 0000000000..01d1718745
--- /dev/null
+++ b/layout/painting/crashtests/1413073-2.html
@@ -0,0 +1,18 @@
+<html class="reftest-wait">
+<body>
+<div style="position:absolute; width:200px; height:200px; opacity:0.5">
+ <div style="opacity:0.9">
+ <div style="position:fixed; width:200px; height:200px; left:400px; background-color:blue" id="fixed"></div>
+ </div>
+ <div style="width:200px; height:200px; background-color:red" id="inner"></div>
+</div>
+</body>
+<script>
+ function doTest() {
+ var d = document.getElementById("inner");
+ d.style.backgroundColor = "green";
+ document.documentElement.className = "";
+ }
+ window.addEventListener("MozReftestInvalidate", doTest);
+</script>
+</html>
diff --git a/layout/painting/crashtests/1418177-1.html b/layout/painting/crashtests/1418177-1.html
new file mode 100644
index 0000000000..6420814bce
--- /dev/null
+++ b/layout/painting/crashtests/1418177-1.html
@@ -0,0 +1,34 @@
+<html>
+ <head>
+ <style>
+ * {
+ background: url(16.png), url(16.png) transparent;
+ -webkit-background-clip: text, text;
+ -moz-tab-size: calc(1 + 1) !important;
+ background-blend-mode: screen;
+ align-content: baseline;
+ background-color: green;
+ }
+ </style>
+ <script>
+ function fun_0() {
+ try { o3 = document.createElement('th') } catch (e) {};
+ try { o1.appendChild(o3) } catch (e) {}
+ }
+
+ try { o1 = document.createElement('tr') } catch (e) {}
+ try { o2 = document.createElement('ol') } catch (e) {}
+ try { xhr = new XMLHttpRequest({mozAnon: false }) } catch (e) {}
+ try { document.documentElement.appendChild(o1) } catch (e) {}
+ try { document.documentElement.appendChild(o2) } catch (e) {}
+ for (let i = 0; i < 100; i++) {
+ try { xhr.open('GET', 'data:text/html,1', false); } catch (e) {};
+ try { xhr.send(); } catch (e) {};
+ try { fuzzPriv.GC(); fuzzPriv.CC(); fuzzPriv.GC(); fuzzPriv.CC(); } catch (e) {};
+ try { xhr.addEventListener('readystatechange', fun_0, true) } catch (e) {};
+ try { o2.offsetLeft } catch (e) {};
+ try { document.styleSheets[0].cssRules[0].style['background-origin'] = 'border-box, border-box' } catch (e) {}
+ }
+ </script>
+ </head>
+</html>
diff --git a/layout/painting/crashtests/1418722-1.html b/layout/painting/crashtests/1418722-1.html
new file mode 100644
index 0000000000..93415d3012
--- /dev/null
+++ b/layout/painting/crashtests/1418722-1.html
@@ -0,0 +1,17 @@
+<html class="reftest-wait">
+<body>
+ <div style="width:200px; height:200px; perspective:1000px">
+ <div style="width:200px; height:200px; transform:translateZ(2px); background-color:green" id="transformed"></div>
+ </div>
+ <div style="width: 200px; height:200px; background-color:red" id="helper"></div>
+</body>
+<script>
+ function doTest() {
+ var element = document.getElementById("transformed");
+ element.parentNode.removeChild(element);
+ document.getElementById("helper").style.backgroundColor = "blue";
+ document.documentElement.className = "";
+ }
+ window.addEventListener("MozReftestInvalidate", doTest);
+</script>
+</html>
diff --git a/layout/painting/crashtests/1419917.html b/layout/painting/crashtests/1419917.html
new file mode 100644
index 0000000000..a60dd15403
--- /dev/null
+++ b/layout/painting/crashtests/1419917.html
@@ -0,0 +1,15 @@
+<html>
+ <head>
+ <style>
+ :nth-last-child(2) { -moz-appearance:listbox }
+ </style>
+ <script>
+ try { o1 = document.createElement('tr') } catch(e) { }
+ try { o2 = document.createElement('th') } catch(e) { }
+ try { o3 = document.createElement('canvas') } catch(e) { }
+ try { document.documentElement.appendChild(o1) } catch(e) { }
+ try { o1.appendChild(o2) } catch(e) { }
+ try { o1.appendChild(o3) } catch(e) { }
+ </script>
+ </head>
+</html>
diff --git a/layout/painting/crashtests/1425271-1.html b/layout/painting/crashtests/1425271-1.html
new file mode 100644
index 0000000000..164cb0f11f
--- /dev/null
+++ b/layout/painting/crashtests/1425271-1.html
@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+
+<head>
+ <meta http-equiv="content-type" content="text/html; charset=UTF-8">
+ <meta charset="utf-8">
+</head>
+
+<body>
+<div id="container">
+ <div id="element">
+ <!--
+ The HTML code for this element has no other meaning than to create
+ display items that are merged together.
+ -->
+ <div style="column-count:2; column-count:2; width:300px; height:100px;">
+ <div id="o" style="opacity:0.5; width:100px; height:200px; background:lime;">
+ <div id="d" style="height:50px; width:80px; background:red; padding:2px">Text</div>
+ </div>
+ </div>
+ </div>
+</div>
+
+<script type="text/javascript">
+function redirect() {
+ document.documentElement.removeAttribute("class");
+
+ // Trigger root frame deletion.
+ window.location.replace("about:blank");
+}
+
+function removeElements(container) {
+ document.body.removeChild(container);
+
+ setTimeout(redirect, 0);
+}
+
+function createElements() {
+ var c = document.getElementById("container");
+ var e = document.getElementById("element");
+ for (var i = 0; i < 1000; ++i) {
+ // Populate the container with elements that cause display item merges.
+ c.appendChild(e.cloneNode(true));
+ }
+
+ setTimeout(() => removeElements(c), 0);
+}
+
+document.addEventListener("MozReftestInvalidate", createElements);
+// window.addEventListener("load", createElements);
+</script>
+
+</body>
+</html>
diff --git a/layout/painting/crashtests/1428906-1.html b/layout/painting/crashtests/1428906-1.html
new file mode 100644
index 0000000000..03e6d3aaf8
--- /dev/null
+++ b/layout/painting/crashtests/1428906-1.html
@@ -0,0 +1,16 @@
+<html><head>
+<meta http-equiv="content-type" content="text/html; charset=UTF-8"><style>
+*,m{background:url()repeat center top fixed}
+#a{transform:scale(1)}
+</style>
+<script>
+function eh1(){
+ try{c=a.insertRow()}catch(e){}
+ try{c.appendChild(b)}catch(e){}
+}
+</script>
+</head><body><table id="a">
+<tbody><tr><d id="b">
+<video>
+<source onerror="eh1()">
+</video></d></tr></tbody></table></body></html>
diff --git a/layout/painting/crashtests/1430589-1.html b/layout/painting/crashtests/1430589-1.html
new file mode 100644
index 0000000000..88bb0494a7
--- /dev/null
+++ b/layout/painting/crashtests/1430589-1.html
@@ -0,0 +1,55 @@
+<html class="reftest-wait"><head>
+<meta http-equiv="content-type" content="text/html; charset=UTF-8">
+ <style>
+ :root {
+ flex-wrap: wrap-reverse;
+ }
+ menuitem {
+ -webkit-mask-image: url(data:image/gif;base64,R0lGODlhEAAQAMQAAORHHOVSKudfOulrSOp3WOyDZu6QdvCchPGolfO0o/XBs/fNwfjZ0frl3/zy7////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAkAABAALAAAAAAQABAAAAVVICSOZGlCQAosJ6mu7fiyZeKqNKToQGDsM8hBADgUXoGAiqhSvp5QAnQKGIgUhwFUYLCVDFCrKUE1lBavAViFIDlTImbKC5Gm2hB0SlBCBMQiB0UjIQA7);
+ }
+
+ * {
+ margin-right: -1px;
+ display: -webkit-box;
+ overflow: scroll;
+ }
+
+
+ .css1 {
+ -webkit-border-bottom-left-radius: 10px;
+ }
+
+
+ </style>
+<script>
+function BOOM(){
+ setTimeout(() => {
+ var elem = document.getElementById("vuln");
+ elem.style.width = 600;
+ elem.style.height = 600;
+ window.requestAnimationFrame(() => {
+ window.requestAnimationFrame(() => {
+ document.documentElement.removeAttribute("class");
+ });
+ });
+ }, 0);
+}
+
+document.addEventListener("MozReftestInvalidate", BOOM);
+//window.addEventListener("load", BOOM);
+</script></head>
+
+<body>
+ <footer>
+ <textarea id="vuln" style="width: 500px; height: 500px;">Ashitaka</textarea>
+ </footer>
+ <menu>
+ <menuitem>
+ <iframe></iframe>
+ </menuitem></menu>
+ <menuitem>
+ <hr>
+ <dialog class="css1"></dialog>
+
+
+</menuitem></body></html>
diff --git a/layout/painting/crashtests/1454105-1.html b/layout/painting/crashtests/1454105-1.html
new file mode 100644
index 0000000000..946d45992b
--- /dev/null
+++ b/layout/painting/crashtests/1454105-1.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<style>
+#a {
+ filter: brightness(1);
+ border-style: solid;
+}
+#b {
+ opacity: 0.2;
+}
+</style>
+</head>
+<body>
+<span id="b">
+ <span id="a">
+ <div></div>
+ </span>
+</span>
+</body>
+</html>
diff --git a/layout/painting/crashtests/1455944-1.html b/layout/painting/crashtests/1455944-1.html
new file mode 100644
index 0000000000..c115f4dd8e
--- /dev/null
+++ b/layout/painting/crashtests/1455944-1.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta http-equiv="content-type" content="text/html; charset=UTF-8">
+<meta charset="utf-8">
+</head>
+
+<body>
+<div style="opacity: 0.9;">
+ <iframe style="border: none;" src=""></iframe>
+</div>
+
+</body>
+</html>
diff --git a/layout/painting/crashtests/1458145.html b/layout/painting/crashtests/1458145.html
new file mode 100644
index 0000000000..dc5f07c204
--- /dev/null
+++ b/layout/painting/crashtests/1458145.html
@@ -0,0 +1,11 @@
+<style>
+* { position: fixed; }
+#a {
+perspective: 0px;
+overflow: scroll;
+}
+.cl2 { -webkit-transform-style: preserve-3d; }
+.cl1 { border-bottom-right-radius: 0px 1px; }
+</style>
+<del id="a" class="cl1">
+<dl class="cl2">#_k</dl>
diff --git a/layout/painting/crashtests/1465305-1.html b/layout/painting/crashtests/1465305-1.html
new file mode 100644
index 0000000000..084f524fba
--- /dev/null
+++ b/layout/painting/crashtests/1465305-1.html
@@ -0,0 +1,8 @@
+<style>
+:not(cursor) {
+ -webkit-background-clip: text;
+ border-top-right-radius: 0vh;
+}
+</style>
+><input>
+<dialog open="">
diff --git a/layout/painting/crashtests/1468124-1.html b/layout/painting/crashtests/1468124-1.html
new file mode 100644
index 0000000000..9acf1427db
--- /dev/null
+++ b/layout/painting/crashtests/1468124-1.html
@@ -0,0 +1,27 @@
+<html class="reftest-wait">
+<style>
+:not(feFuncB) {
+ position: fixed;
+}
+a:last-child {
+ -webkit-transform-style: preserve-3d;
+}
+* {
+ -webkit-backface-visibility: hidden;
+</style>
+<script>
+window.requestIdleCallback(function() {
+ document.documentElement.getBoundingClientRect();
+});
+function go() {
+ var c = document.createElement("a")
+ c.text = "-";
+ try { c.replaceChild(b, c.childNodes[0]); } catch(e) { }
+ try { document.body.appendChild(c); } catch(e) { }
+ document.documentElement.className = "";
+}
+</script>
+<body onload=go()>
+<d id="b">|
+<audio controls="">
+</html>
diff --git a/layout/painting/crashtests/1469472.html b/layout/painting/crashtests/1469472.html
new file mode 100644
index 0000000000..f53d027704
--- /dev/null
+++ b/layout/painting/crashtests/1469472.html
@@ -0,0 +1,7 @@
+<style>
+:not(polygon) {
+background-image: url(#x);
+background-blend-mode: hue, normal;
+</style>
+<table>
+<th>
diff --git a/layout/painting/crashtests/1477831-1.html b/layout/painting/crashtests/1477831-1.html
new file mode 100644
index 0000000000..d6483bde02
--- /dev/null
+++ b/layout/painting/crashtests/1477831-1.html
@@ -0,0 +1,11 @@
+<style>
+* {
+ margin-left: 1vw;
+ columns: 2;
+ opacity: 0.2;
+ -webkit-transform: rotate(0deg);
+}
+#a { float: left; }
+</style>
+<content id="a">
+<dd>A</dd>
diff --git a/layout/painting/crashtests/1504033.html b/layout/painting/crashtests/1504033.html
new file mode 100644
index 0000000000..9aeb7973be
--- /dev/null
+++ b/layout/painting/crashtests/1504033.html
@@ -0,0 +1,13 @@
+<style>
+* { -webkit-transform-style: preserve-3d }
+</style>
+<script>
+function go() {
+ a.append("x");
+}
+</script>
+<body onload=go()>
+<svg overflow="auto">
+<use xlink:href="#b" style="-webkit-transform-style: flat"/>
+<use id="b" xlink:href="#a">
+<text id="a">
diff --git a/layout/painting/crashtests/1514544-1.html b/layout/painting/crashtests/1514544-1.html
new file mode 100644
index 0000000000..8e9efdc3c4
--- /dev/null
+++ b/layout/painting/crashtests/1514544-1.html
@@ -0,0 +1,15 @@
+<html class="reftest-wait">
+<script>
+setTimeout(function() {
+ a.appendChild(b);
+ document.documentElement.className = "";
+}, 100)
+</script>
+<style>
+:root { opacity: 0 }
+</style>
+A
+<textarea id="a" hidden=""></textarea>
+<object id="b" >
+A
+</html>
diff --git a/layout/painting/crashtests/1547420-1.html b/layout/painting/crashtests/1547420-1.html
new file mode 100644
index 0000000000..ce54d67d09
--- /dev/null
+++ b/layout/painting/crashtests/1547420-1.html
@@ -0,0 +1,20 @@
+<script></script>
+<style>
+* {
+ text-align-last: right;
+ min-height: max-content;
+ min-width: 1vmin;
+ writing-mode: vertical-rl;
+}
+</style>
+<q style="writing-mode: lr">
+<marquee></marquee>
+<style></style>
+</q>
+<dl style="-webkit-transform: skew(0deg); mso-ignore: colspan">
+<dd>
+<table>
+<dt style="margin-left: 67%; scale: 7 46 0.006057077979">
+</dt>
+<marquee bgcolor="-moz-mac-accentdarkestshadow">
+<button autofocus="autofocus">
diff --git a/layout/painting/crashtests/1549909.html b/layout/painting/crashtests/1549909.html
new file mode 100644
index 0000000000..0542ee91a8
--- /dev/null
+++ b/layout/painting/crashtests/1549909.html
@@ -0,0 +1,9 @@
+<style>
+* {
+ -webkit-column-break-after: always;
+ float: left;
+ column-width: 0px;
+}
+</style>
+}
+<video controls="controls">
diff --git a/layout/painting/crashtests/1551389-1.html b/layout/painting/crashtests/1551389-1.html
new file mode 100644
index 0000000000..4ee12c1ce9
--- /dev/null
+++ b/layout/painting/crashtests/1551389-1.html
@@ -0,0 +1,6 @@
+<style>
+dl::first-letter { float: right }
+* { -webkit-box-shadow: -moz-cellhighlighttext 0px 34px 1px }
+</style>
+<s dir="RTL">
+<dl style="break-inside: avoid">AA</iframe>
diff --git a/layout/painting/crashtests/1555819-1.html b/layout/painting/crashtests/1555819-1.html
new file mode 100644
index 0000000000..0327e3690f
--- /dev/null
+++ b/layout/painting/crashtests/1555819-1.html
@@ -0,0 +1,12 @@
+<style>
+body {
+ width: 1px;
+ height: 5vmax;
+ -webkit-filter: brightness(1);
+}
+* {
+ grid-template-areas: '';
+ columns: 1px;
+}
+</style>
+<keygen autofocus="">aaaa
diff --git a/layout/painting/crashtests/1574392.html b/layout/painting/crashtests/1574392.html
new file mode 100644
index 0000000000..2e8c4d15aa
--- /dev/null
+++ b/layout/painting/crashtests/1574392.html
@@ -0,0 +1 @@
+<u>🏴󠁵󠁳󠁣󠁡󠁿</u>
diff --git a/layout/painting/crashtests/1589800-1.html b/layout/painting/crashtests/1589800-1.html
new file mode 100644
index 0000000000..8af28028a0
--- /dev/null
+++ b/layout/painting/crashtests/1589800-1.html
@@ -0,0 +1,47 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta http-equiv="content-type" content="text/html; charset=utf-8">
+ <style>
+ #parent {
+ transform: rotateZ(1deg);
+ position: absolute;
+ opacity: 0.5;
+ overflow-x: hidden;
+ }
+
+ #child {
+ width: 10px;
+ height: 10px;
+ }
+
+ .blend {
+ mix-blend-mode: color-burn;
+ }
+ </style>
+</head>
+
+<body>
+ <div id="parent">
+ <div id="child">a</div>
+ </div>
+ <div>
+ <div id="blend">a</div>
+ </div>
+ <script type="text/javascript">
+ function modify() {
+ var e = document.getElementById("child");
+ e.style.backgroundColor = "red";
+ setTimeout(addBlend, 0);
+ }
+
+ function addBlend() {
+ var e = document.getElementById("blend");
+ e.classList.add("blend");
+ }
+
+ // setTimeout(modify, 3000);
+ window.addEventListener("MozAfterPaint", modify);
+ </script>
+</body>
+</html>
diff --git a/layout/painting/crashtests/1667503-1.html b/layout/painting/crashtests/1667503-1.html
new file mode 100644
index 0000000000..70f99b3f37
--- /dev/null
+++ b/layout/painting/crashtests/1667503-1.html
@@ -0,0 +1,16 @@
+<script>
+function go() {
+ b.src = "x:"
+ b.requestFullscreen()
+}
+function fuzz() {
+ a.close("")
+ b.src = "x"
+ a.showModal()
+ a.appendChild(document.createElement("b"))
+}
+</script>
+<body onload=go()>
+<dialog id="a">
+<nav>x</nav>
+<video id="b" onerror="fuzz()"></video>
diff --git a/layout/painting/crashtests/1713880-1.html b/layout/painting/crashtests/1713880-1.html
new file mode 100644
index 0000000000..1ccd59743a
--- /dev/null
+++ b/layout/painting/crashtests/1713880-1.html
@@ -0,0 +1,10 @@
+<script>
+window.onload = () => {
+ let a = document.createElement("dt")
+ c.appendChild(a)
+ let b = document.createElement("button")
+ a.appendChild(b)
+ b.animate([{"all": "revert"}], {delay: 9.04})
+}
+</script>
+<body id='c'></body>
diff --git a/layout/painting/crashtests/1714584-1.html b/layout/painting/crashtests/1714584-1.html
new file mode 100644
index 0000000000..1ef2812a2d
--- /dev/null
+++ b/layout/painting/crashtests/1714584-1.html
@@ -0,0 +1,5 @@
+<style>
+output:only-of-type { opacity: 0 }
+</style>
+<output>
+<iframe src="x">x</iframe>
diff --git a/layout/painting/crashtests/1717655-1.html b/layout/painting/crashtests/1717655-1.html
new file mode 100644
index 0000000000..940dac6dfb
--- /dev/null
+++ b/layout/painting/crashtests/1717655-1.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <style>
+ * {
+ transform: perspective(2337713741.755817Q) !important;
+ height: 2637992280%;
+ }
+
+ * {
+ border-width: medium;
+ display: table;
+ }
+ </style>
+</head>
+<table border="32767" rules="all">
+ <caption></caption>
+</table>
+</html>
diff --git a/layout/painting/crashtests/1763006-1.html b/layout/painting/crashtests/1763006-1.html
new file mode 100644
index 0000000000..080c88e7d8
--- /dev/null
+++ b/layout/painting/crashtests/1763006-1.html
@@ -0,0 +1,23 @@
+<style>
+#a {
+ -webkit-filter: invert(10%);
+ background: url(data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=) repeat-x center top fixed
+}
+</style>
+<script>
+function go() {
+ window.find("1")
+ requestIdleCallback(() => {
+ a.insertCell().appendChild(b)
+ var x = window.getSelection()
+ x.extend(b)
+ x.deleteFromDocument()
+ })
+}
+</script>
+<body onload=go()>
+<table>
+<colgroup>Z1xNfp~Zdx</colgroup>
+<tr id="a">
+<th></th>
+<li id="b">
diff --git a/layout/painting/crashtests/1819957-1.html b/layout/painting/crashtests/1819957-1.html
new file mode 100644
index 0000000000..647c9b7f12
--- /dev/null
+++ b/layout/painting/crashtests/1819957-1.html
@@ -0,0 +1,14 @@
+<style>
+::selection {
+ text-shadow: 0 1px 0px -moz-mac-menushadow;
+}
+</style>
+<script>
+document.addEventListener('DOMContentLoaded', () => {
+ document.execCommand("selectAll", false)
+})
+</script>
+<dir>
+<menu id="foo"></menu>
+<menu>
+<link>a</link>
diff --git a/layout/painting/crashtests/crashtests.list b/layout/painting/crashtests/crashtests.list
new file mode 100644
index 0000000000..7bc56b335c
--- /dev/null
+++ b/layout/painting/crashtests/crashtests.list
@@ -0,0 +1,32 @@
+load 1402183-1.html
+load 1407470-1.html
+load 1413073-1.html
+load 1413073-2.html
+load 1405881-1.html
+load 1418177-1.html
+load 1418722-1.html
+load 1419917.html
+load 1425271-1.html
+load 1428906-1.html
+pref(widget.windows.window_occlusion_tracking.enabled,false) load 1430589-1.html # Bug 1819154
+load 1454105-1.html
+load 1455944-1.html
+load 1458145.html
+load 1465305-1.html
+load 1468124-1.html
+load 1469472.html
+load 1477831-1.html
+load 1504033.html
+load 1514544-1.html
+asserts(0-1) asserts-if(Android,0-2) load 1547420-1.html
+load 1549909.html
+load 1551389-1.html
+asserts(0-2) load 1555819-1.html
+load 1574392.html
+load 1589800-1.html
+load 1667503-1.html
+load 1713880-1.html
+load 1717655-1.html
+load 1714584-1.html
+load 1763006-1.html
+load 1819957-1.html
diff --git a/layout/painting/moz.build b/layout/painting/moz.build
new file mode 100644
index 0000000000..b5f1d02a89
--- /dev/null
+++ b/layout/painting/moz.build
@@ -0,0 +1,72 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+with Files("**"):
+ BUG_COMPONENT = ("Core", "Web Painting")
+
+EXPORTS += [
+ "ActiveLayerTracker.h",
+ "DisplayItemClip.h",
+ "DisplayItemClipChain.h",
+ "DisplayListClipState.h",
+ "HitTestInfo.h",
+ "MatrixStack.h",
+ "nsCSSRendering.h",
+ "nsCSSRenderingBorders.h",
+ "nsCSSRenderingGradients.h",
+ "nsDisplayItemTypes.h",
+ "nsDisplayItemTypesList.h",
+ "nsDisplayList.h",
+ "nsDisplayListArenaTypes.h",
+ "nsDisplayListInvalidation.h",
+ "nsImageRenderer.h",
+ "RetainedDisplayListBuilder.h",
+ "RetainedDisplayListHelpers.h",
+ "TransformClipNode.h",
+ "WindowRenderer.h",
+]
+
+EXPORTS.mozilla += [
+ "PaintTracker.h",
+]
+
+UNIFIED_SOURCES += [
+ "ActiveLayerTracker.cpp",
+ "DashedCornerFinder.cpp",
+ "DisplayItemClip.cpp",
+ "DisplayItemClipChain.cpp",
+ "DisplayListClipState.cpp",
+ "DottedCornerFinder.cpp",
+ "HitTestInfo.cpp",
+ "MaskLayerImageCache.cpp",
+ "nsCSSRendering.cpp",
+ "nsCSSRenderingBorders.cpp",
+ "nsCSSRenderingGradients.cpp",
+ "nsDisplayList.cpp",
+ "nsDisplayListInvalidation.cpp",
+ "nsImageRenderer.cpp",
+ "PaintTracker.cpp",
+ "RetainedDisplayListBuilder.cpp",
+ "WindowRenderer.cpp",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+LOCAL_INCLUDES += [
+ "/docshell/base",
+ "/dom/base",
+ "/gfx/2d",
+ "/gfx/cairo/cairo/src",
+ "/layout/base",
+ "/layout/generic",
+ "/layout/style",
+ "/layout/tables",
+ "/layout/xul",
+]
+
+LOCAL_INCLUDES += CONFIG["SKIA_INCLUDES"]
+
+FINAL_LIBRARY = "xul"
diff --git a/layout/painting/nsCSSRendering.cpp b/layout/painting/nsCSSRendering.cpp
new file mode 100644
index 0000000000..1a1bc3d01f
--- /dev/null
+++ b/layout/painting/nsCSSRendering.cpp
@@ -0,0 +1,4944 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* utility functions for drawing borders and backgrounds */
+
+#include "nsCSSRendering.h"
+
+#include <ctime>
+
+#include "gfx2DGlue.h"
+#include "gfxContext.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/ComputedStyle.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/StaticPrefs_layout.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/Helpers.h"
+#include "mozilla/gfx/Logging.h"
+#include "mozilla/gfx/PathHelpers.h"
+#include "mozilla/HashFunctions.h"
+#include "mozilla/MathAlgorithms.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/SVGImageContext.h"
+#include "gfxFont.h"
+#include "ScaledFontBase.h"
+#include "skia/include/core/SkTextBlob.h"
+
+#include "BorderConsts.h"
+#include "nsCanvasFrame.h"
+#include "nsStyleConsts.h"
+#include "nsPresContext.h"
+#include "nsIFrame.h"
+#include "nsIFrameInlines.h"
+#include "nsPageSequenceFrame.h"
+#include "nsPoint.h"
+#include "nsRect.h"
+#include "nsFrameManager.h"
+#include "nsGkAtoms.h"
+#include "nsCSSAnonBoxes.h"
+#include "nsIContent.h"
+#include "mozilla/dom/DocumentInlines.h"
+#include "nsIScrollableFrame.h"
+#include "imgIContainer.h"
+#include "ImageOps.h"
+#include "nsCSSColorUtils.h"
+#include "nsITheme.h"
+#include "nsLayoutUtils.h"
+#include "nsBlockFrame.h"
+#include "nsStyleStructInlines.h"
+#include "nsCSSFrameConstructor.h"
+#include "nsCSSProps.h"
+#include "nsContentUtils.h"
+#include "gfxDrawable.h"
+#include "nsCSSRenderingBorders.h"
+#include "mozilla/css/ImageLoader.h"
+#include "ImageContainer.h"
+#include "mozilla/ProfilerLabels.h"
+#include "mozilla/StaticPrefs_layout.h"
+#include "mozilla/Telemetry.h"
+#include "gfxUtils.h"
+#include "gfxGradientCache.h"
+#include "nsInlineFrame.h"
+#include "nsRubyTextContainerFrame.h"
+#include <algorithm>
+#include "TextDrawTarget.h"
+
+using namespace mozilla;
+using namespace mozilla::css;
+using namespace mozilla::gfx;
+using namespace mozilla::image;
+using mozilla::CSSSizeOrRatio;
+using mozilla::dom::Document;
+
+static int gFrameTreeLockCount = 0;
+
+// To avoid storing this data on nsInlineFrame (bloat) and to avoid
+// recalculating this for each frame in a continuation (perf), hold
+// a cache of various coordinate information that we need in order
+// to paint inline backgrounds.
+struct InlineBackgroundData {
+ InlineBackgroundData()
+ : mFrame(nullptr),
+ mLineContainer(nullptr),
+ mContinuationPoint(0),
+ mUnbrokenMeasure(0),
+ mLineContinuationPoint(0),
+ mPIStartBorderData{},
+ mBidiEnabled(false),
+ mVertical(false) {}
+
+ ~InlineBackgroundData() = default;
+
+ void Reset() {
+ mBoundingBox.SetRect(0, 0, 0, 0);
+ mContinuationPoint = mLineContinuationPoint = mUnbrokenMeasure = 0;
+ mFrame = mLineContainer = nullptr;
+ mPIStartBorderData.Reset();
+ }
+
+ /**
+ * Return a continuous rect for (an inline) aFrame relative to the
+ * continuation that draws the left-most part of the background.
+ * This is used when painting backgrounds.
+ */
+ nsRect GetContinuousRect(nsIFrame* aFrame) {
+ MOZ_ASSERT(static_cast<nsInlineFrame*>(do_QueryFrame(aFrame)));
+
+ SetFrame(aFrame);
+
+ nscoord pos; // an x coordinate if writing-mode is horizontal;
+ // y coordinate if vertical
+ if (mBidiEnabled) {
+ pos = mLineContinuationPoint;
+
+ // Scan continuations on the same line as aFrame and accumulate the widths
+ // of frames that are to the left (if this is an LTR block) or right
+ // (if it's RTL) of the current one.
+ bool isRtlBlock = (mLineContainer->StyleVisibility()->mDirection ==
+ StyleDirection::Rtl);
+ nscoord curOffset = mVertical ? aFrame->GetOffsetTo(mLineContainer).y
+ : aFrame->GetOffsetTo(mLineContainer).x;
+
+ // If the continuation is fluid we know inlineFrame is not on the same
+ // line. If it's not fluid, we need to test further to be sure.
+ nsIFrame* inlineFrame = aFrame->GetPrevContinuation();
+ while (inlineFrame && !inlineFrame->GetNextInFlow() &&
+ AreOnSameLine(aFrame, inlineFrame)) {
+ nscoord frameOffset = mVertical
+ ? inlineFrame->GetOffsetTo(mLineContainer).y
+ : inlineFrame->GetOffsetTo(mLineContainer).x;
+ if (isRtlBlock == (frameOffset >= curOffset)) {
+ pos += mVertical ? inlineFrame->GetSize().height
+ : inlineFrame->GetSize().width;
+ }
+ inlineFrame = inlineFrame->GetPrevContinuation();
+ }
+
+ inlineFrame = aFrame->GetNextContinuation();
+ while (inlineFrame && !inlineFrame->GetPrevInFlow() &&
+ AreOnSameLine(aFrame, inlineFrame)) {
+ nscoord frameOffset = mVertical
+ ? inlineFrame->GetOffsetTo(mLineContainer).y
+ : inlineFrame->GetOffsetTo(mLineContainer).x;
+ if (isRtlBlock == (frameOffset >= curOffset)) {
+ pos += mVertical ? inlineFrame->GetSize().height
+ : inlineFrame->GetSize().width;
+ }
+ inlineFrame = inlineFrame->GetNextContinuation();
+ }
+ if (isRtlBlock) {
+ // aFrame itself is also to the right of its left edge, so add its
+ // width.
+ pos += mVertical ? aFrame->GetSize().height : aFrame->GetSize().width;
+ // pos is now the distance from the left [top] edge of aFrame to the
+ // right [bottom] edge of the unbroken content. Change it to indicate
+ // the distance from the left [top] edge of the unbroken content to the
+ // left [top] edge of aFrame.
+ pos = mUnbrokenMeasure - pos;
+ }
+ } else {
+ pos = mContinuationPoint;
+ }
+
+ // Assume background-origin: border and return a rect with offsets
+ // relative to (0,0). If we have a different background-origin,
+ // then our rect should be deflated appropriately by our caller.
+ return mVertical
+ ? nsRect(0, -pos, mFrame->GetSize().width, mUnbrokenMeasure)
+ : nsRect(-pos, 0, mUnbrokenMeasure, mFrame->GetSize().height);
+ }
+
+ /**
+ * Return a continuous rect for (an inline) aFrame relative to the
+ * continuation that should draw the left[top]-border. This is used when
+ * painting borders and clipping backgrounds. This may NOT be the same
+ * continuous rect as for drawing backgrounds; the continuation with the
+ * left[top]-border might be somewhere in the middle of that rect (e.g. BIDI),
+ * in those cases we need the reverse background order starting at the
+ * left[top]-border continuation.
+ */
+ nsRect GetBorderContinuousRect(nsIFrame* aFrame, nsRect aBorderArea) {
+ // Calling GetContinuousRect(aFrame) here may lead to Reset/Init which
+ // resets our mPIStartBorderData so we save it ...
+ PhysicalInlineStartBorderData saved(mPIStartBorderData);
+ nsRect joinedBorderArea = GetContinuousRect(aFrame);
+ if (!saved.mIsValid || saved.mFrame != mPIStartBorderData.mFrame) {
+ if (aFrame == mPIStartBorderData.mFrame) {
+ if (mVertical) {
+ mPIStartBorderData.SetCoord(joinedBorderArea.y);
+ } else {
+ mPIStartBorderData.SetCoord(joinedBorderArea.x);
+ }
+ } else if (mPIStartBorderData.mFrame) {
+ // Copy data to a temporary object so that computing the
+ // continous rect here doesn't clobber our normal state.
+ InlineBackgroundData temp = *this;
+ if (mVertical) {
+ mPIStartBorderData.SetCoord(
+ temp.GetContinuousRect(mPIStartBorderData.mFrame).y);
+ } else {
+ mPIStartBorderData.SetCoord(
+ temp.GetContinuousRect(mPIStartBorderData.mFrame).x);
+ }
+ }
+ } else {
+ // ... and restore it when possible.
+ mPIStartBorderData.SetCoord(saved.mCoord);
+ }
+ if (mVertical) {
+ if (joinedBorderArea.y > mPIStartBorderData.mCoord) {
+ joinedBorderArea.y =
+ -(mUnbrokenMeasure + joinedBorderArea.y - aBorderArea.height);
+ } else {
+ joinedBorderArea.y -= mPIStartBorderData.mCoord;
+ }
+ } else {
+ if (joinedBorderArea.x > mPIStartBorderData.mCoord) {
+ joinedBorderArea.x =
+ -(mUnbrokenMeasure + joinedBorderArea.x - aBorderArea.width);
+ } else {
+ joinedBorderArea.x -= mPIStartBorderData.mCoord;
+ }
+ }
+ return joinedBorderArea;
+ }
+
+ nsRect GetBoundingRect(nsIFrame* aFrame) {
+ SetFrame(aFrame);
+
+ // Move the offsets relative to (0,0) which puts the bounding box into
+ // our coordinate system rather than our parent's. We do this by
+ // moving it the back distance from us to the bounding box.
+ // This also assumes background-origin: border, so our caller will
+ // need to deflate us if needed.
+ nsRect boundingBox(mBoundingBox);
+ nsPoint point = mFrame->GetPosition();
+ boundingBox.MoveBy(-point.x, -point.y);
+
+ return boundingBox;
+ }
+
+ protected:
+ // This is a coordinate on the inline axis, but is not a true logical inline-
+ // coord because it is always measured from left to right (if horizontal) or
+ // from top to bottom (if vertical), ignoring any bidi RTL directionality.
+ // We'll call this "physical inline start", or PIStart for short.
+ struct PhysicalInlineStartBorderData {
+ nsIFrame* mFrame; // the continuation that may have a left-border
+ nscoord mCoord; // cached GetContinuousRect(mFrame).x or .y
+ bool mIsValid; // true if mCoord is valid
+ void Reset() {
+ mFrame = nullptr;
+ mIsValid = false;
+ }
+ void SetCoord(nscoord aCoord) {
+ mCoord = aCoord;
+ mIsValid = true;
+ }
+ };
+
+ nsIFrame* mFrame;
+ nsIFrame* mLineContainer;
+ nsRect mBoundingBox;
+ nscoord mContinuationPoint;
+ nscoord mUnbrokenMeasure;
+ nscoord mLineContinuationPoint;
+ PhysicalInlineStartBorderData mPIStartBorderData;
+ bool mBidiEnabled;
+ bool mVertical;
+
+ void SetFrame(nsIFrame* aFrame) {
+ MOZ_ASSERT(aFrame, "Need a frame");
+ NS_ASSERTION(gFrameTreeLockCount > 0,
+ "Can't call this when frame tree is not locked");
+
+ if (aFrame == mFrame) {
+ return;
+ }
+
+ nsIFrame* prevContinuation = GetPrevContinuation(aFrame);
+
+ if (!prevContinuation || mFrame != prevContinuation) {
+ // Ok, we've got the wrong frame. We have to start from scratch.
+ Reset();
+ Init(aFrame);
+ return;
+ }
+
+ // Get our last frame's size and add its width to our continuation
+ // point before we cache the new frame.
+ mContinuationPoint +=
+ mVertical ? mFrame->GetSize().height : mFrame->GetSize().width;
+
+ // If this a new line, update mLineContinuationPoint.
+ if (mBidiEnabled &&
+ (aFrame->GetPrevInFlow() || !AreOnSameLine(mFrame, aFrame))) {
+ mLineContinuationPoint = mContinuationPoint;
+ }
+
+ mFrame = aFrame;
+ }
+
+ nsIFrame* GetPrevContinuation(nsIFrame* aFrame) {
+ nsIFrame* prevCont = aFrame->GetPrevContinuation();
+ if (!prevCont && aFrame->HasAnyStateBits(NS_FRAME_PART_OF_IBSPLIT)) {
+ nsIFrame* block = aFrame->GetProperty(nsIFrame::IBSplitPrevSibling());
+ if (block) {
+ // The {ib} properties are only stored on first continuations
+ NS_ASSERTION(!block->GetPrevContinuation(),
+ "Incorrect value for IBSplitPrevSibling");
+ prevCont = block->GetProperty(nsIFrame::IBSplitPrevSibling());
+ NS_ASSERTION(prevCont, "How did that happen?");
+ }
+ }
+ return prevCont;
+ }
+
+ nsIFrame* GetNextContinuation(nsIFrame* aFrame) {
+ nsIFrame* nextCont = aFrame->GetNextContinuation();
+ if (!nextCont && aFrame->HasAnyStateBits(NS_FRAME_PART_OF_IBSPLIT)) {
+ // The {ib} properties are only stored on first continuations
+ aFrame = aFrame->FirstContinuation();
+ nsIFrame* block = aFrame->GetProperty(nsIFrame::IBSplitSibling());
+ if (block) {
+ nextCont = block->GetProperty(nsIFrame::IBSplitSibling());
+ NS_ASSERTION(nextCont, "How did that happen?");
+ }
+ }
+ return nextCont;
+ }
+
+ void Init(nsIFrame* aFrame) {
+ mPIStartBorderData.Reset();
+ mBidiEnabled = aFrame->PresContext()->BidiEnabled();
+ if (mBidiEnabled) {
+ // Find the line container frame
+ mLineContainer = aFrame;
+ while (mLineContainer &&
+ mLineContainer->IsFrameOfType(nsIFrame::eLineParticipant)) {
+ mLineContainer = mLineContainer->GetParent();
+ }
+
+ MOZ_ASSERT(mLineContainer, "Cannot find line containing frame.");
+ MOZ_ASSERT(mLineContainer != aFrame,
+ "line container frame "
+ "should be an ancestor of the target frame.");
+ }
+
+ mVertical = aFrame->GetWritingMode().IsVertical();
+
+ // Start with the previous flow frame as our continuation point
+ // is the total of the widths of the previous frames.
+ nsIFrame* inlineFrame = GetPrevContinuation(aFrame);
+ bool changedLines = false;
+ while (inlineFrame) {
+ if (!mPIStartBorderData.mFrame &&
+ !(mVertical ? inlineFrame->GetSkipSides().Top()
+ : inlineFrame->GetSkipSides().Left())) {
+ mPIStartBorderData.mFrame = inlineFrame;
+ }
+ nsRect rect = inlineFrame->GetRect();
+ mContinuationPoint += mVertical ? rect.height : rect.width;
+ if (mBidiEnabled &&
+ (changedLines || !AreOnSameLine(aFrame, inlineFrame))) {
+ mLineContinuationPoint += mVertical ? rect.height : rect.width;
+ changedLines = true;
+ }
+ mUnbrokenMeasure += mVertical ? rect.height : rect.width;
+ mBoundingBox.UnionRect(mBoundingBox, rect);
+ inlineFrame = GetPrevContinuation(inlineFrame);
+ }
+
+ // Next add this frame and subsequent frames to the bounding box and
+ // unbroken width.
+ inlineFrame = aFrame;
+ while (inlineFrame) {
+ if (!mPIStartBorderData.mFrame &&
+ !(mVertical ? inlineFrame->GetSkipSides().Top()
+ : inlineFrame->GetSkipSides().Left())) {
+ mPIStartBorderData.mFrame = inlineFrame;
+ }
+ nsRect rect = inlineFrame->GetRect();
+ mUnbrokenMeasure += mVertical ? rect.height : rect.width;
+ mBoundingBox.UnionRect(mBoundingBox, rect);
+ inlineFrame = GetNextContinuation(inlineFrame);
+ }
+
+ mFrame = aFrame;
+ }
+
+ bool AreOnSameLine(nsIFrame* aFrame1, nsIFrame* aFrame2) {
+ if (nsBlockFrame* blockFrame = do_QueryFrame(mLineContainer)) {
+ bool isValid1, isValid2;
+ nsBlockInFlowLineIterator it1(blockFrame, aFrame1, &isValid1);
+ nsBlockInFlowLineIterator it2(blockFrame, aFrame2, &isValid2);
+ return isValid1 && isValid2 &&
+ // Make sure aFrame1 and aFrame2 are in the same continuation of
+ // blockFrame.
+ it1.GetContainer() == it2.GetContainer() &&
+ // And on the same line in it
+ it1.GetLine().get() == it2.GetLine().get();
+ }
+ if (nsRubyTextContainerFrame* rtcFrame = do_QueryFrame(mLineContainer)) {
+ nsBlockFrame* block = nsLayoutUtils::FindNearestBlockAncestor(rtcFrame);
+ // Ruby text container can only hold one line of text, so if they
+ // are in the same continuation, they are in the same line. Since
+ // ruby text containers are bidi isolate, they are never split for
+ // bidi reordering, which means being in different continuation
+ // indicates being in different lines.
+ for (nsIFrame* frame = rtcFrame->FirstContinuation(); frame;
+ frame = frame->GetNextContinuation()) {
+ bool isDescendant1 =
+ nsLayoutUtils::IsProperAncestorFrame(frame, aFrame1, block);
+ bool isDescendant2 =
+ nsLayoutUtils::IsProperAncestorFrame(frame, aFrame2, block);
+ if (isDescendant1 && isDescendant2) {
+ return true;
+ }
+ if (isDescendant1 || isDescendant2) {
+ return false;
+ }
+ }
+ MOZ_ASSERT_UNREACHABLE("None of the frames is a descendant of this rtc?");
+ }
+ MOZ_ASSERT_UNREACHABLE("Do we have any other type of line container?");
+ return false;
+ }
+};
+
+static StaticAutoPtr<InlineBackgroundData> gInlineBGData;
+
+// Initialize any static variables used by nsCSSRendering.
+void nsCSSRendering::Init() {
+ NS_ASSERTION(!gInlineBGData, "Init called twice");
+ gInlineBGData = new InlineBackgroundData();
+}
+
+// Clean up any global variables used by nsCSSRendering.
+void nsCSSRendering::Shutdown() { gInlineBGData = nullptr; }
+
+/**
+ * Make a bevel color
+ */
+static nscolor MakeBevelColor(mozilla::Side whichSide, StyleBorderStyle style,
+ nscolor aBorderColor) {
+ nscolor colors[2];
+ nscolor theColor;
+
+ // Given a background color and a border color
+ // calculate the color used for the shading
+ NS_GetSpecial3DColors(colors, aBorderColor);
+
+ if ((style == StyleBorderStyle::Outset) ||
+ (style == StyleBorderStyle::Ridge)) {
+ // Flip colors for these two border styles
+ switch (whichSide) {
+ case eSideBottom:
+ whichSide = eSideTop;
+ break;
+ case eSideRight:
+ whichSide = eSideLeft;
+ break;
+ case eSideTop:
+ whichSide = eSideBottom;
+ break;
+ case eSideLeft:
+ whichSide = eSideRight;
+ break;
+ }
+ }
+
+ switch (whichSide) {
+ case eSideBottom:
+ theColor = colors[1];
+ break;
+ case eSideRight:
+ theColor = colors[1];
+ break;
+ case eSideTop:
+ theColor = colors[0];
+ break;
+ case eSideLeft:
+ default:
+ theColor = colors[0];
+ break;
+ }
+ return theColor;
+}
+
+static bool GetRadii(nsIFrame* aForFrame, const nsStyleBorder& aBorder,
+ const nsRect& aOrigBorderArea, const nsRect& aBorderArea,
+ nscoord aRadii[8]) {
+ bool haveRoundedCorners;
+ nsSize sz = aBorderArea.Size();
+ nsSize frameSize = aForFrame->GetSize();
+ if (&aBorder == aForFrame->StyleBorder() &&
+ frameSize == aOrigBorderArea.Size()) {
+ haveRoundedCorners = aForFrame->GetBorderRadii(sz, sz, Sides(), aRadii);
+ } else {
+ haveRoundedCorners = nsIFrame::ComputeBorderRadii(
+ aBorder.mBorderRadius, frameSize, sz, Sides(), aRadii);
+ }
+
+ return haveRoundedCorners;
+}
+
+static bool GetRadii(nsIFrame* aForFrame, const nsStyleBorder& aBorder,
+ const nsRect& aOrigBorderArea, const nsRect& aBorderArea,
+ RectCornerRadii* aBgRadii) {
+ nscoord radii[8];
+ bool haveRoundedCorners =
+ GetRadii(aForFrame, aBorder, aOrigBorderArea, aBorderArea, radii);
+
+ if (haveRoundedCorners) {
+ auto d2a = aForFrame->PresContext()->AppUnitsPerDevPixel();
+ nsCSSRendering::ComputePixelRadii(radii, d2a, aBgRadii);
+ }
+ return haveRoundedCorners;
+}
+
+static nsRect JoinBoxesForBlockAxisSlice(nsIFrame* aFrame,
+ const nsRect& aBorderArea) {
+ // Inflate the block-axis size as if our continuations were laid out
+ // adjacent in that axis. Note that we don't touch the inline size.
+ const auto wm = aFrame->GetWritingMode();
+ const nsSize dummyContainerSize;
+ LogicalRect borderArea(wm, aBorderArea, dummyContainerSize);
+ nscoord bSize = 0;
+ nsIFrame* f = aFrame->GetNextContinuation();
+ for (; f; f = f->GetNextContinuation()) {
+ bSize += f->BSize(wm);
+ }
+ borderArea.BSize(wm) += bSize;
+ bSize = 0;
+ f = aFrame->GetPrevContinuation();
+ for (; f; f = f->GetPrevContinuation()) {
+ bSize += f->BSize(wm);
+ }
+ borderArea.BStart(wm) -= bSize;
+ borderArea.BSize(wm) += bSize;
+ return borderArea.GetPhysicalRect(wm, dummyContainerSize);
+}
+
+/**
+ * Inflate aBorderArea which is relative to aFrame's origin to calculate
+ * a hypothetical non-split frame area for all the continuations.
+ * See "Joining Boxes for 'slice'" in
+ * http://dev.w3.org/csswg/css-break/#break-decoration
+ */
+enum InlineBoxOrder { eForBorder, eForBackground };
+static nsRect JoinBoxesForSlice(nsIFrame* aFrame, const nsRect& aBorderArea,
+ InlineBoxOrder aOrder) {
+ if (static_cast<nsInlineFrame*>(do_QueryFrame(aFrame))) {
+ return (aOrder == eForBorder
+ ? gInlineBGData->GetBorderContinuousRect(aFrame, aBorderArea)
+ : gInlineBGData->GetContinuousRect(aFrame)) +
+ aBorderArea.TopLeft();
+ }
+ return JoinBoxesForBlockAxisSlice(aFrame, aBorderArea);
+}
+
+/* static */
+bool nsCSSRendering::IsBoxDecorationSlice(const nsStyleBorder& aStyleBorder) {
+ return aStyleBorder.mBoxDecorationBreak == StyleBoxDecorationBreak::Slice;
+}
+
+/* static */
+nsRect nsCSSRendering::BoxDecorationRectForBorder(
+ nsIFrame* aFrame, const nsRect& aBorderArea, Sides aSkipSides,
+ const nsStyleBorder* aStyleBorder) {
+ if (!aStyleBorder) {
+ aStyleBorder = aFrame->StyleBorder();
+ }
+ // If aSkipSides.IsEmpty() then there are no continuations, or it's
+ // a ::first-letter that wants all border sides on the first continuation.
+ return IsBoxDecorationSlice(*aStyleBorder) && !aSkipSides.IsEmpty()
+ ? ::JoinBoxesForSlice(aFrame, aBorderArea, eForBorder)
+ : aBorderArea;
+}
+
+/* static */
+nsRect nsCSSRendering::BoxDecorationRectForBackground(
+ nsIFrame* aFrame, const nsRect& aBorderArea, Sides aSkipSides,
+ const nsStyleBorder* aStyleBorder) {
+ if (!aStyleBorder) {
+ aStyleBorder = aFrame->StyleBorder();
+ }
+ // If aSkipSides.IsEmpty() then there are no continuations, or it's
+ // a ::first-letter that wants all border sides on the first continuation.
+ return IsBoxDecorationSlice(*aStyleBorder) && !aSkipSides.IsEmpty()
+ ? ::JoinBoxesForSlice(aFrame, aBorderArea, eForBackground)
+ : aBorderArea;
+}
+
+//----------------------------------------------------------------------
+// Thebes Border Rendering Code Start
+
+/*
+ * Compute the float-pixel radii that should be used for drawing
+ * this border/outline, given the various input bits.
+ */
+/* static */
+void nsCSSRendering::ComputePixelRadii(const nscoord* aAppUnitsRadii,
+ nscoord aAppUnitsPerPixel,
+ RectCornerRadii* oBorderRadii) {
+ Float radii[8];
+ for (const auto corner : mozilla::AllPhysicalHalfCorners()) {
+ radii[corner] = Float(aAppUnitsRadii[corner]) / aAppUnitsPerPixel;
+ }
+
+ (*oBorderRadii)[C_TL] = Size(radii[eCornerTopLeftX], radii[eCornerTopLeftY]);
+ (*oBorderRadii)[C_TR] =
+ Size(radii[eCornerTopRightX], radii[eCornerTopRightY]);
+ (*oBorderRadii)[C_BR] =
+ Size(radii[eCornerBottomRightX], radii[eCornerBottomRightY]);
+ (*oBorderRadii)[C_BL] =
+ Size(radii[eCornerBottomLeftX], radii[eCornerBottomLeftY]);
+}
+
+static Maybe<nsStyleBorder> GetBorderIfVisited(const ComputedStyle& aStyle) {
+ Maybe<nsStyleBorder> result;
+ // Don't check RelevantLinkVisited here, since we want to take the
+ // same amount of time whether or not it's true.
+ const ComputedStyle* styleIfVisited = aStyle.GetStyleIfVisited();
+ if (MOZ_LIKELY(!styleIfVisited)) {
+ return result;
+ }
+
+ result.emplace(*aStyle.StyleBorder());
+ auto& newBorder = result.ref();
+ for (const auto side : mozilla::AllPhysicalSides()) {
+ nscolor color = aStyle.GetVisitedDependentColor(
+ nsStyleBorder::BorderColorFieldFor(side));
+ newBorder.BorderColorFor(side) = StyleColor::FromColor(color);
+ }
+
+ return result;
+}
+
+ImgDrawResult nsCSSRendering::PaintBorder(
+ nsPresContext* aPresContext, gfxContext& aRenderingContext,
+ nsIFrame* aForFrame, const nsRect& aDirtyRect, const nsRect& aBorderArea,
+ ComputedStyle* aStyle, PaintBorderFlags aFlags, Sides aSkipSides) {
+ AUTO_PROFILER_LABEL("nsCSSRendering::PaintBorder", GRAPHICS);
+
+ Maybe<nsStyleBorder> visitedBorder = GetBorderIfVisited(*aStyle);
+ return PaintBorderWithStyleBorder(
+ aPresContext, aRenderingContext, aForFrame, aDirtyRect, aBorderArea,
+ visitedBorder.refOr(*aStyle->StyleBorder()), aStyle, aFlags, aSkipSides);
+}
+
+Maybe<nsCSSBorderRenderer> nsCSSRendering::CreateBorderRenderer(
+ nsPresContext* aPresContext, DrawTarget* aDrawTarget, nsIFrame* aForFrame,
+ const nsRect& aDirtyRect, const nsRect& aBorderArea, ComputedStyle* aStyle,
+ bool* aOutBorderIsEmpty, Sides aSkipSides) {
+ Maybe<nsStyleBorder> visitedBorder = GetBorderIfVisited(*aStyle);
+ return CreateBorderRendererWithStyleBorder(
+ aPresContext, aDrawTarget, aForFrame, aDirtyRect, aBorderArea,
+ visitedBorder.refOr(*aStyle->StyleBorder()), aStyle, aOutBorderIsEmpty,
+ aSkipSides);
+}
+
+ImgDrawResult nsCSSRendering::CreateWebRenderCommandsForBorder(
+ nsDisplayItem* aItem, nsIFrame* aForFrame, const nsRect& aBorderArea,
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const mozilla::layers::StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) {
+ const auto* style = aForFrame->Style();
+ Maybe<nsStyleBorder> visitedBorder = GetBorderIfVisited(*style);
+ return nsCSSRendering::CreateWebRenderCommandsForBorderWithStyleBorder(
+ aItem, aForFrame, aBorderArea, aBuilder, aResources, aSc, aManager,
+ aDisplayListBuilder, visitedBorder.refOr(*style->StyleBorder()));
+}
+
+void nsCSSRendering::CreateWebRenderCommandsForNullBorder(
+ nsDisplayItem* aItem, nsIFrame* aForFrame, const nsRect& aBorderArea,
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const mozilla::layers::StackingContextHelper& aSc,
+ const nsStyleBorder& aStyleBorder) {
+ bool borderIsEmpty = false;
+ Maybe<nsCSSBorderRenderer> br =
+ nsCSSRendering::CreateNullBorderRendererWithStyleBorder(
+ aForFrame->PresContext(), nullptr, aForFrame, nsRect(), aBorderArea,
+ aStyleBorder, aForFrame->Style(), &borderIsEmpty,
+ aForFrame->GetSkipSides());
+ if (!borderIsEmpty && br) {
+ br->CreateWebRenderCommands(aItem, aBuilder, aResources, aSc);
+ }
+}
+
+ImgDrawResult nsCSSRendering::CreateWebRenderCommandsForBorderWithStyleBorder(
+ nsDisplayItem* aItem, nsIFrame* aForFrame, const nsRect& aBorderArea,
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const mozilla::layers::StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder,
+ const nsStyleBorder& aStyleBorder) {
+ auto& borderImage = aStyleBorder.mBorderImageSource;
+ // First try to create commands for simple borders.
+ if (borderImage.IsNone()) {
+ CreateWebRenderCommandsForNullBorder(
+ aItem, aForFrame, aBorderArea, aBuilder, aResources, aSc, aStyleBorder);
+ return ImgDrawResult::SUCCESS;
+ }
+
+ // Next we try image and gradient borders. Gradients are not supported at
+ // this very moment.
+ if (!borderImage.IsImageRequestType()) {
+ return ImgDrawResult::NOT_SUPPORTED;
+ }
+
+ if (aStyleBorder.mBorderImageRepeatH == StyleBorderImageRepeat::Space ||
+ aStyleBorder.mBorderImageRepeatV == StyleBorderImageRepeat::Space) {
+ return ImgDrawResult::NOT_SUPPORTED;
+ }
+
+ uint32_t flags = 0;
+ if (aDisplayListBuilder->IsPaintingToWindow()) {
+ flags |= nsImageRenderer::FLAG_PAINTING_TO_WINDOW;
+ }
+ if (aDisplayListBuilder->ShouldSyncDecodeImages()) {
+ flags |= nsImageRenderer::FLAG_SYNC_DECODE_IMAGES;
+ }
+
+ bool dummy;
+ image::ImgDrawResult result;
+ Maybe<nsCSSBorderImageRenderer> bir =
+ nsCSSBorderImageRenderer::CreateBorderImageRenderer(
+ aForFrame->PresContext(), aForFrame, aBorderArea, aStyleBorder,
+ aItem->GetBounds(aDisplayListBuilder, &dummy),
+ aForFrame->GetSkipSides(), flags, &result);
+
+ if (!bir) {
+ // We aren't ready. Try to fallback to the null border image if present but
+ // return the draw result for the border image renderer.
+ CreateWebRenderCommandsForNullBorder(
+ aItem, aForFrame, aBorderArea, aBuilder, aResources, aSc, aStyleBorder);
+ return result;
+ }
+
+ return bir->CreateWebRenderCommands(aItem, aForFrame, aBuilder, aResources,
+ aSc, aManager, aDisplayListBuilder);
+}
+
+static nsCSSBorderRenderer ConstructBorderRenderer(
+ nsPresContext* aPresContext, ComputedStyle* aStyle, DrawTarget* aDrawTarget,
+ nsIFrame* aForFrame, const nsRect& aDirtyRect, const nsRect& aBorderArea,
+ const nsStyleBorder& aStyleBorder, Sides aSkipSides, bool* aNeedsClip) {
+ nsMargin border = aStyleBorder.GetComputedBorder();
+
+ // Compute the outermost boundary of the area that might be painted.
+ // Same coordinate space as aBorderArea & aBGClipRect.
+ nsRect joinedBorderArea = nsCSSRendering::BoxDecorationRectForBorder(
+ aForFrame, aBorderArea, aSkipSides, &aStyleBorder);
+ RectCornerRadii bgRadii;
+ ::GetRadii(aForFrame, aStyleBorder, aBorderArea, joinedBorderArea, &bgRadii);
+
+ PrintAsFormatString(" joinedBorderArea: %d %d %d %d\n", joinedBorderArea.x,
+ joinedBorderArea.y, joinedBorderArea.width,
+ joinedBorderArea.height);
+
+ // start drawing
+ if (nsCSSRendering::IsBoxDecorationSlice(aStyleBorder)) {
+ if (joinedBorderArea.IsEqualEdges(aBorderArea)) {
+ // No need for a clip, just skip the sides we don't want.
+ border.ApplySkipSides(aSkipSides);
+ } else {
+ // We're drawing borders around the joined continuation boxes so we need
+ // to clip that to the slice that we want for this frame.
+ *aNeedsClip = true;
+ }
+ } else {
+ MOZ_ASSERT(joinedBorderArea.IsEqualEdges(aBorderArea),
+ "Should use aBorderArea for box-decoration-break:clone");
+ MOZ_ASSERT(
+ aForFrame->GetSkipSides().IsEmpty() ||
+ aForFrame->IsTrueOverflowContainer() ||
+ aForFrame->IsColumnSetFrame(), // a little broader than column-rule
+ "Should not skip sides for box-decoration-break:clone except "
+ "::first-letter/line continuations or other frame types that "
+ "don't have borders but those shouldn't reach this point. "
+ "Overflow containers do reach this point though, as does "
+ "column-rule drawing (which always involves a columnset).");
+ border.ApplySkipSides(aSkipSides);
+ }
+
+ // Convert to dev pixels.
+ nscoord oneDevPixel = aPresContext->DevPixelsToAppUnits(1);
+ Rect joinedBorderAreaPx = NSRectToRect(joinedBorderArea, oneDevPixel);
+ Float borderWidths[4] = {
+ Float(border.top) / oneDevPixel, Float(border.right) / oneDevPixel,
+ Float(border.bottom) / oneDevPixel, Float(border.left) / oneDevPixel};
+ Rect dirtyRect = NSRectToRect(aDirtyRect, oneDevPixel);
+
+ StyleBorderStyle borderStyles[4];
+ nscolor borderColors[4];
+
+ // pull out styles, colors
+ for (const auto i : mozilla::AllPhysicalSides()) {
+ borderStyles[i] = aStyleBorder.GetBorderStyle(i);
+ borderColors[i] = aStyleBorder.BorderColorFor(i).CalcColor(*aStyle);
+ }
+
+ PrintAsFormatString(
+ " borderStyles: %d %d %d %d\n", static_cast<int>(borderStyles[0]),
+ static_cast<int>(borderStyles[1]), static_cast<int>(borderStyles[2]),
+ static_cast<int>(borderStyles[3]));
+
+ return nsCSSBorderRenderer(
+ aPresContext, aDrawTarget, dirtyRect, joinedBorderAreaPx, borderStyles,
+ borderWidths, bgRadii, borderColors, !aForFrame->BackfaceIsHidden(),
+ *aNeedsClip ? Some(NSRectToRect(aBorderArea, oneDevPixel)) : Nothing());
+}
+
+ImgDrawResult nsCSSRendering::PaintBorderWithStyleBorder(
+ nsPresContext* aPresContext, gfxContext& aRenderingContext,
+ nsIFrame* aForFrame, const nsRect& aDirtyRect, const nsRect& aBorderArea,
+ const nsStyleBorder& aStyleBorder, ComputedStyle* aStyle,
+ PaintBorderFlags aFlags, Sides aSkipSides) {
+ DrawTarget& aDrawTarget = *aRenderingContext.GetDrawTarget();
+
+ PrintAsStringNewline("++ PaintBorder");
+
+ // Check to see if we have an appearance defined. If so, we let the theme
+ // renderer draw the border. DO not get the data from aForFrame, since the
+ // passed in ComputedStyle may be different! Always use |aStyle|!
+ StyleAppearance appearance = aStyle->StyleDisplay()->EffectiveAppearance();
+ if (appearance != StyleAppearance::None) {
+ nsITheme* theme = aPresContext->Theme();
+ if (theme->ThemeSupportsWidget(aPresContext, aForFrame, appearance)) {
+ return ImgDrawResult::SUCCESS; // Let the theme handle it.
+ }
+ }
+
+ if (!aStyleBorder.mBorderImageSource.IsNone()) {
+ ImgDrawResult result = ImgDrawResult::SUCCESS;
+
+ uint32_t irFlags = 0;
+ if (aFlags & PaintBorderFlags::SyncDecodeImages) {
+ irFlags |= nsImageRenderer::FLAG_SYNC_DECODE_IMAGES;
+ }
+
+ // Creating the border image renderer will request a decode, and we rely on
+ // that happening.
+ Maybe<nsCSSBorderImageRenderer> renderer =
+ nsCSSBorderImageRenderer::CreateBorderImageRenderer(
+ aPresContext, aForFrame, aBorderArea, aStyleBorder, aDirtyRect,
+ aSkipSides, irFlags, &result);
+ // renderer was created successfully, which means border image is ready to
+ // be used.
+ if (renderer) {
+ MOZ_ASSERT(result == ImgDrawResult::SUCCESS);
+ return renderer->DrawBorderImage(aPresContext, aRenderingContext,
+ aForFrame, aDirtyRect);
+ }
+ }
+
+ ImgDrawResult result = ImgDrawResult::SUCCESS;
+
+ // If we had a border-image, but it wasn't loaded, then we should return
+ // ImgDrawResult::NOT_READY; we'll want to try again if we do a paint with
+ // sync decoding enabled.
+ if (!aStyleBorder.mBorderImageSource.IsNone()) {
+ result = ImgDrawResult::NOT_READY;
+ }
+
+ nsMargin border = aStyleBorder.GetComputedBorder();
+ if (0 == border.left && 0 == border.right && 0 == border.top &&
+ 0 == border.bottom) {
+ // Empty border area
+ return result;
+ }
+
+ bool needsClip = false;
+ nsCSSBorderRenderer br = ConstructBorderRenderer(
+ aPresContext, aStyle, &aDrawTarget, aForFrame, aDirtyRect, aBorderArea,
+ aStyleBorder, aSkipSides, &needsClip);
+ if (needsClip) {
+ aDrawTarget.PushClipRect(NSRectToSnappedRect(
+ aBorderArea, aForFrame->PresContext()->AppUnitsPerDevPixel(),
+ aDrawTarget));
+ }
+
+ br.DrawBorders();
+
+ if (needsClip) {
+ aDrawTarget.PopClip();
+ }
+
+ PrintAsStringNewline();
+
+ return result;
+}
+
+Maybe<nsCSSBorderRenderer> nsCSSRendering::CreateBorderRendererWithStyleBorder(
+ nsPresContext* aPresContext, DrawTarget* aDrawTarget, nsIFrame* aForFrame,
+ const nsRect& aDirtyRect, const nsRect& aBorderArea,
+ const nsStyleBorder& aStyleBorder, ComputedStyle* aStyle,
+ bool* aOutBorderIsEmpty, Sides aSkipSides) {
+ if (!aStyleBorder.mBorderImageSource.IsNone()) {
+ return Nothing();
+ }
+ return CreateNullBorderRendererWithStyleBorder(
+ aPresContext, aDrawTarget, aForFrame, aDirtyRect, aBorderArea,
+ aStyleBorder, aStyle, aOutBorderIsEmpty, aSkipSides);
+}
+
+Maybe<nsCSSBorderRenderer>
+nsCSSRendering::CreateNullBorderRendererWithStyleBorder(
+ nsPresContext* aPresContext, DrawTarget* aDrawTarget, nsIFrame* aForFrame,
+ const nsRect& aDirtyRect, const nsRect& aBorderArea,
+ const nsStyleBorder& aStyleBorder, ComputedStyle* aStyle,
+ bool* aOutBorderIsEmpty, Sides aSkipSides) {
+ StyleAppearance appearance = aStyle->StyleDisplay()->EffectiveAppearance();
+ if (appearance != StyleAppearance::None) {
+ nsITheme* theme = aPresContext->Theme();
+ if (theme->ThemeSupportsWidget(aPresContext, aForFrame, appearance)) {
+ // The border will be draw as part of the themed background item created
+ // for this same frame. If no themed background item was created then not
+ // drawing also matches that we do without webrender and what
+ // nsDisplayBorder does for themed borders.
+ if (aOutBorderIsEmpty) {
+ *aOutBorderIsEmpty = true;
+ }
+ return Nothing();
+ }
+ }
+
+ nsMargin border = aStyleBorder.GetComputedBorder();
+ if (0 == border.left && 0 == border.right && 0 == border.top &&
+ 0 == border.bottom) {
+ // Empty border area
+ if (aOutBorderIsEmpty) {
+ *aOutBorderIsEmpty = true;
+ }
+ return Nothing();
+ }
+
+ bool needsClip = false;
+ nsCSSBorderRenderer br = ConstructBorderRenderer(
+ aPresContext, aStyle, aDrawTarget, aForFrame, aDirtyRect, aBorderArea,
+ aStyleBorder, aSkipSides, &needsClip);
+ return Some(br);
+}
+
+Maybe<nsCSSBorderRenderer>
+nsCSSRendering::CreateBorderRendererForNonThemedOutline(
+ nsPresContext* aPresContext, DrawTarget* aDrawTarget, nsIFrame* aForFrame,
+ const nsRect& aDirtyRect, const nsRect& aInnerRect, ComputedStyle* aStyle) {
+ // Get our ComputedStyle's color struct.
+ const nsStyleOutline* ourOutline = aStyle->StyleOutline();
+ if (!ourOutline->ShouldPaintOutline()) {
+ // Empty outline
+ return Nothing();
+ }
+
+ const nscoord offset = ourOutline->mOutlineOffset.ToAppUnits();
+ nsRect innerRect = aInnerRect;
+ innerRect.Inflate(offset);
+
+ // If the dirty rect is completely inside the border area (e.g., only the
+ // content is being painted), then we can skip out now
+ // XXX this isn't exactly true for rounded borders, where the inside curves
+ // may encroach into the content area. A safer calculation would be to
+ // shorten insideRect by the radius one each side before performing this test.
+ if (innerRect.Contains(aDirtyRect)) {
+ return Nothing();
+ }
+
+ nscoord width = ourOutline->GetOutlineWidth();
+
+ StyleBorderStyle outlineStyle;
+ // Themed outlines are handled by our callers, if supported.
+ if (ourOutline->mOutlineStyle.IsAuto()) {
+ if (width == 0) {
+ return Nothing(); // empty outline
+ }
+ // http://dev.w3.org/csswg/css-ui/#outline
+ // "User agents may treat 'auto' as 'solid'."
+ outlineStyle = StyleBorderStyle::Solid;
+ } else {
+ outlineStyle = ourOutline->mOutlineStyle.AsBorderStyle();
+ }
+
+ RectCornerRadii outlineRadii;
+ nsRect outerRect = innerRect;
+ outerRect.Inflate(width);
+
+ const nscoord oneDevPixel = aPresContext->AppUnitsPerDevPixel();
+ Rect oRect(NSRectToRect(outerRect, oneDevPixel));
+
+ const Float outlineWidths[4] = {
+ Float(width) / oneDevPixel, Float(width) / oneDevPixel,
+ Float(width) / oneDevPixel, Float(width) / oneDevPixel};
+
+ // convert the radii
+ nscoord twipsRadii[8];
+
+ // get the radius for our outline
+ if (aForFrame->GetBorderRadii(twipsRadii)) {
+ RectCornerRadii innerRadii;
+ ComputePixelRadii(twipsRadii, oneDevPixel, &innerRadii);
+
+ Float devPixelOffset = aPresContext->AppUnitsToFloatDevPixels(offset);
+ const Float widths[4] = {
+ outlineWidths[0] + devPixelOffset, outlineWidths[1] + devPixelOffset,
+ outlineWidths[2] + devPixelOffset, outlineWidths[3] + devPixelOffset};
+ nsCSSBorderRenderer::ComputeOuterRadii(innerRadii, widths, &outlineRadii);
+ }
+
+ StyleBorderStyle outlineStyles[4] = {outlineStyle, outlineStyle, outlineStyle,
+ outlineStyle};
+
+ // This handles treating the initial color as 'currentColor'; if we
+ // ever want 'invert' back we'll need to do a bit of work here too.
+ nscolor outlineColor =
+ aStyle->GetVisitedDependentColor(&nsStyleOutline::mOutlineColor);
+ nscolor outlineColors[4] = {outlineColor, outlineColor, outlineColor,
+ outlineColor};
+
+ Rect dirtyRect = NSRectToRect(aDirtyRect, oneDevPixel);
+
+ return Some(nsCSSBorderRenderer(
+ aPresContext, aDrawTarget, dirtyRect, oRect, outlineStyles, outlineWidths,
+ outlineRadii, outlineColors, !aForFrame->BackfaceIsHidden(), Nothing()));
+}
+
+void nsCSSRendering::PaintNonThemedOutline(nsPresContext* aPresContext,
+ gfxContext& aRenderingContext,
+ nsIFrame* aForFrame,
+ const nsRect& aDirtyRect,
+ const nsRect& aInnerRect,
+ ComputedStyle* aStyle) {
+ Maybe<nsCSSBorderRenderer> br = CreateBorderRendererForNonThemedOutline(
+ aPresContext, aRenderingContext.GetDrawTarget(), aForFrame, aDirtyRect,
+ aInnerRect, aStyle);
+ if (!br) {
+ return;
+ }
+
+ // start drawing
+ br->DrawBorders();
+
+ PrintAsStringNewline();
+}
+
+void nsCSSRendering::PaintFocus(nsPresContext* aPresContext,
+ DrawTarget* aDrawTarget,
+ const nsRect& aFocusRect, nscolor aColor) {
+ nscoord oneCSSPixel = nsPresContext::CSSPixelsToAppUnits(1);
+ nscoord oneDevPixel = aPresContext->DevPixelsToAppUnits(1);
+
+ Rect focusRect(NSRectToRect(aFocusRect, oneDevPixel));
+
+ RectCornerRadii focusRadii;
+ {
+ nscoord twipsRadii[8] = {0, 0, 0, 0, 0, 0, 0, 0};
+ ComputePixelRadii(twipsRadii, oneDevPixel, &focusRadii);
+ }
+ Float focusWidths[4] = {
+ Float(oneCSSPixel) / oneDevPixel, Float(oneCSSPixel) / oneDevPixel,
+ Float(oneCSSPixel) / oneDevPixel, Float(oneCSSPixel) / oneDevPixel};
+
+ StyleBorderStyle focusStyles[4] = {
+ StyleBorderStyle::Dotted, StyleBorderStyle::Dotted,
+ StyleBorderStyle::Dotted, StyleBorderStyle::Dotted};
+ nscolor focusColors[4] = {aColor, aColor, aColor, aColor};
+
+ // Because this renders a dotted border, the background color
+ // should not be used. Therefore, we provide a value that will
+ // be blatantly wrong if it ever does get used. (If this becomes
+ // something that CSS can style, this function will then have access
+ // to a ComputedStyle and can use the same logic that PaintBorder
+ // and PaintOutline do.)
+ //
+ // WebRender layers-free mode don't use PaintFocus function. Just assign
+ // the backface-visibility to true for this case.
+ nsCSSBorderRenderer br(aPresContext, aDrawTarget, focusRect, focusRect,
+ focusStyles, focusWidths, focusRadii, focusColors,
+ true, Nothing());
+ br.DrawBorders();
+
+ PrintAsStringNewline();
+}
+
+// Thebes Border Rendering Code End
+//----------------------------------------------------------------------
+
+//----------------------------------------------------------------------
+
+/**
+ * Helper for ComputeObjectAnchorPoint; parameters are the same as for
+ * that function, except they're for a single coordinate / a single size
+ * dimension. (so, x/width vs. y/height)
+ */
+static void ComputeObjectAnchorCoord(const LengthPercentage& aCoord,
+ const nscoord aOriginBounds,
+ const nscoord aImageSize,
+ nscoord* aTopLeftCoord,
+ nscoord* aAnchorPointCoord) {
+ nscoord extraSpace = aOriginBounds - aImageSize;
+
+ // The anchor-point doesn't care about our image's size; just the size
+ // of the region we're rendering into.
+ *aAnchorPointCoord = aCoord.Resolve(
+ aOriginBounds, static_cast<nscoord (*)(float)>(NSToCoordRoundWithClamp));
+ // Adjust aTopLeftCoord by the specified % of the extra space.
+ *aTopLeftCoord = aCoord.Resolve(
+ extraSpace, static_cast<nscoord (*)(float)>(NSToCoordRoundWithClamp));
+}
+
+void nsImageRenderer::ComputeObjectAnchorPoint(const Position& aPos,
+ const nsSize& aOriginBounds,
+ const nsSize& aImageSize,
+ nsPoint* aTopLeft,
+ nsPoint* aAnchorPoint) {
+ ComputeObjectAnchorCoord(aPos.horizontal, aOriginBounds.width,
+ aImageSize.width, &aTopLeft->x, &aAnchorPoint->x);
+
+ ComputeObjectAnchorCoord(aPos.vertical, aOriginBounds.height,
+ aImageSize.height, &aTopLeft->y, &aAnchorPoint->y);
+}
+
+// In print / print preview we have multiple canvas frames (one for each page,
+// and one for the document as a whole). For the topmost one, we really want the
+// page sequence page background, not the root or body's background.
+static nsIFrame* GetPageSequenceForCanvas(const nsIFrame* aCanvasFrame) {
+ MOZ_ASSERT(aCanvasFrame->IsCanvasFrame(), "not a canvas frame");
+ nsPresContext* pc = aCanvasFrame->PresContext();
+ if (!pc->IsPrintingOrPrintPreview()) {
+ return nullptr;
+ }
+ auto* ps = pc->PresShell()->GetPageSequenceFrame();
+ if (!ps) {
+ return nullptr;
+ }
+ if (ps->GetParent() != aCanvasFrame) {
+ return nullptr;
+ }
+ return ps;
+}
+
+auto nsCSSRendering::FindEffectiveBackgroundColor(nsIFrame* aFrame,
+ bool aStopAtThemed,
+ bool aPreferBodyToCanvas)
+ -> EffectiveBackgroundColor {
+ MOZ_ASSERT(aFrame);
+ nsPresContext* pc = aFrame->PresContext();
+ auto BgColorIfNotTransparent = [&](nsIFrame* aFrame) -> Maybe<nscolor> {
+ nscolor c =
+ aFrame->GetVisitedDependentColor(&nsStyleBackground::mBackgroundColor);
+ if (NS_GET_A(c) == 255) {
+ return Some(c);
+ }
+ if (NS_GET_A(c)) {
+ // TODO(emilio): We should maybe just blend with ancestor bg colors and
+ // such, but this is probably good enough for now, matches pre-existing
+ // behavior.
+ const nscolor defaultBg = pc->DefaultBackgroundColor();
+ MOZ_ASSERT(NS_GET_A(defaultBg) == 255, "PreferenceSheet guarantees this");
+ return Some(NS_ComposeColors(defaultBg, c));
+ }
+ return Nothing();
+ };
+
+ for (nsIFrame* frame = aFrame; frame;
+ frame = nsLayoutUtils::GetParentOrPlaceholderForCrossDoc(frame)) {
+ if (auto bg = BgColorIfNotTransparent(frame)) {
+ return {*bg};
+ }
+
+ if (aStopAtThemed && frame->IsThemed()) {
+ return {NS_TRANSPARENT, true};
+ }
+
+ if (frame->IsCanvasFrame()) {
+ if (aPreferBodyToCanvas && !GetPageSequenceForCanvas(frame)) {
+ if (auto* body = pc->Document()->GetBodyElement()) {
+ if (nsIFrame* f = body->GetPrimaryFrame()) {
+ if (auto bg = BgColorIfNotTransparent(f)) {
+ return {*bg};
+ }
+ }
+ }
+ }
+ if (nsIFrame* bgFrame = FindBackgroundFrame(frame)) {
+ if (auto bg = BgColorIfNotTransparent(bgFrame)) {
+ return {*bg};
+ }
+ }
+ }
+ }
+
+ return {pc->DefaultBackgroundColor()};
+}
+
+nsIFrame* nsCSSRendering::FindBackgroundStyleFrame(nsIFrame* aForFrame) {
+ const nsStyleBackground* result = aForFrame->StyleBackground();
+
+ // Check if we need to do propagation from BODY rather than HTML.
+ if (!result->IsTransparent(aForFrame)) {
+ return aForFrame;
+ }
+
+ nsIContent* content = aForFrame->GetContent();
+ // The root element content can't be null. We wouldn't know what
+ // frame to create for aFrame.
+ // Use |OwnerDoc| so it works during destruction.
+ if (!content) {
+ return aForFrame;
+ }
+
+ Document* document = content->OwnerDoc();
+
+ dom::Element* bodyContent = document->GetBodyElement();
+ // We need to null check the body node (bug 118829) since
+ // there are cases, thanks to the fix for bug 5569, where we
+ // will reflow a document with no body. In particular, if a
+ // SCRIPT element in the head blocks the parser and then has a
+ // SCRIPT that does "document.location.href = 'foo'", then
+ // nsParser::Terminate will call |DidBuildModel| methods
+ // through to the content sink, which will call |StartLayout|
+ // and thus |Initialize| on the pres shell. See bug 119351
+ // for the ugly details.
+ if (!bodyContent || aForFrame->StyleDisplay()->IsContainAny()) {
+ return aForFrame;
+ }
+
+ nsIFrame* bodyFrame = bodyContent->GetPrimaryFrame();
+ if (!bodyFrame || bodyFrame->StyleDisplay()->IsContainAny()) {
+ return aForFrame;
+ }
+
+ return nsLayoutUtils::GetStyleFrame(bodyFrame);
+}
+
+/**
+ * |FindBackground| finds the correct style data to use to paint the
+ * background. It is responsible for handling the following two
+ * statements in section 14.2 of CSS2:
+ *
+ * The background of the box generated by the root element covers the
+ * entire canvas.
+ *
+ * For HTML documents, however, we recommend that authors specify the
+ * background for the BODY element rather than the HTML element. User
+ * agents should observe the following precedence rules to fill in the
+ * background: if the value of the 'background' property for the HTML
+ * element is different from 'transparent' then use it, else use the
+ * value of the 'background' property for the BODY element. If the
+ * resulting value is 'transparent', the rendering is undefined.
+ *
+ * Thus, in our implementation, it is responsible for ensuring that:
+ * + we paint the correct background on the |nsCanvasFrame| or |nsPageFrame|,
+ * + we don't paint the background on the root element, and
+ * + we don't paint the background on the BODY element in *some* cases,
+ * and for SGML-based HTML documents only.
+ *
+ * |FindBackground| checks whether a background should be painted. If yes, it
+ * returns the resulting ComputedStyle to use for the background information;
+ * Otherwise, it returns nullptr.
+ */
+ComputedStyle* nsCSSRendering::FindRootFrameBackground(nsIFrame* aForFrame) {
+ return FindBackgroundStyleFrame(aForFrame)->Style();
+}
+
+static nsIFrame* FindCanvasBackgroundFrame(const nsIFrame* aForFrame,
+ nsIFrame* aRootElementFrame) {
+ MOZ_ASSERT(aForFrame->IsCanvasFrame(), "not a canvas frame");
+ if (auto* ps = GetPageSequenceForCanvas(aForFrame)) {
+ return ps;
+ }
+ if (aRootElementFrame) {
+ return nsCSSRendering::FindBackgroundStyleFrame(aRootElementFrame);
+ }
+ // This should always give transparent, so we'll fill it in with the default
+ // color if needed. This seems to happen a bit while a page is being loaded.
+ return const_cast<nsIFrame*>(aForFrame);
+}
+
+// Helper for FindBackgroundFrame. Returns true if aForFrame has a meaningful
+// background that it should draw (i.e. that it hasn't propagated to another
+// frame). See documentation for FindBackground.
+inline bool FrameHasMeaningfulBackground(const nsIFrame* aForFrame,
+ nsIFrame* aRootElementFrame) {
+ MOZ_ASSERT(!aForFrame->IsCanvasFrame(),
+ "FindBackgroundFrame handles canvas frames before calling us, "
+ "so we don't need to consider them here");
+
+ if (aForFrame == aRootElementFrame) {
+ // We must have propagated our background to the viewport or canvas. Abort.
+ return false;
+ }
+
+ // Return true unless the frame is for a BODY element whose background
+ // was propagated to the viewport.
+
+ nsIContent* content = aForFrame->GetContent();
+ if (!content || content->NodeInfo()->NameAtom() != nsGkAtoms::body) {
+ return true; // not frame for a "body" element
+ }
+ // It could be a non-HTML "body" element but that's OK, we'd fail the
+ // bodyContent check below
+
+ if (aForFrame->Style()->GetPseudoType() != PseudoStyleType::NotPseudo ||
+ aForFrame->StyleDisplay()->IsContainAny()) {
+ return true; // A pseudo-element frame, or contained.
+ }
+
+ // We should only look at the <html> background if we're in an HTML document
+ Document* document = content->OwnerDoc();
+
+ dom::Element* bodyContent = document->GetBodyElement();
+ if (bodyContent != content) {
+ return true; // this wasn't the background that was propagated
+ }
+
+ // This can be called even when there's no root element yet, during frame
+ // construction, via nsLayoutUtils::FrameHasTransparency and
+ // nsContainerFrame::SyncFrameViewProperties.
+ if (!aRootElementFrame || aRootElementFrame->StyleDisplay()->IsContainAny()) {
+ return true;
+ }
+
+ const nsStyleBackground* htmlBG = aRootElementFrame->StyleBackground();
+ return !htmlBG->IsTransparent(aRootElementFrame);
+}
+
+nsIFrame* nsCSSRendering::FindBackgroundFrame(const nsIFrame* aForFrame) {
+ nsIFrame* rootElementFrame =
+ aForFrame->PresShell()->FrameConstructor()->GetRootElementStyleFrame();
+ if (aForFrame->IsCanvasFrame()) {
+ return FindCanvasBackgroundFrame(aForFrame, rootElementFrame);
+ }
+
+ if (FrameHasMeaningfulBackground(aForFrame, rootElementFrame)) {
+ return const_cast<nsIFrame*>(aForFrame);
+ }
+
+ return nullptr;
+}
+
+ComputedStyle* nsCSSRendering::FindBackground(const nsIFrame* aForFrame) {
+ if (auto* backgroundFrame = FindBackgroundFrame(aForFrame)) {
+ return backgroundFrame->Style();
+ }
+ return nullptr;
+}
+
+void nsCSSRendering::BeginFrameTreesLocked() { ++gFrameTreeLockCount; }
+
+void nsCSSRendering::EndFrameTreesLocked() {
+ NS_ASSERTION(gFrameTreeLockCount > 0, "Unbalanced EndFrameTreeLocked");
+ --gFrameTreeLockCount;
+ if (gFrameTreeLockCount == 0) {
+ gInlineBGData->Reset();
+ }
+}
+
+bool nsCSSRendering::HasBoxShadowNativeTheme(nsIFrame* aFrame,
+ bool& aMaybeHasBorderRadius) {
+ const nsStyleDisplay* styleDisplay = aFrame->StyleDisplay();
+ nsITheme::Transparency transparency;
+ if (aFrame->IsThemed(styleDisplay, &transparency)) {
+ aMaybeHasBorderRadius = false;
+ // For opaque (rectangular) theme widgets we can take the generic
+ // border-box path with border-radius disabled.
+ return transparency != nsITheme::eOpaque;
+ }
+
+ aMaybeHasBorderRadius = true;
+ return false;
+}
+
+gfx::sRGBColor nsCSSRendering::GetShadowColor(const StyleSimpleShadow& aShadow,
+ nsIFrame* aFrame,
+ float aOpacity) {
+ // Get the shadow color; if not specified, use the foreground color
+ nscolor shadowColor = aShadow.color.CalcColor(aFrame);
+ sRGBColor color = sRGBColor::FromABGR(shadowColor);
+ color.a *= aOpacity;
+ return color;
+}
+
+nsRect nsCSSRendering::GetShadowRect(const nsRect& aFrameArea,
+ bool aNativeTheme, nsIFrame* aForFrame) {
+ nsRect frameRect = aNativeTheme ? aForFrame->InkOverflowRectRelativeToSelf() +
+ aFrameArea.TopLeft()
+ : aFrameArea;
+ Sides skipSides = aForFrame->GetSkipSides();
+ frameRect = BoxDecorationRectForBorder(aForFrame, frameRect, skipSides);
+
+ // Explicitly do not need to account for the spread radius here
+ // Webrender does it for us or PaintBoxShadow will for non-WR
+ return frameRect;
+}
+
+bool nsCSSRendering::GetBorderRadii(const nsRect& aFrameRect,
+ const nsRect& aBorderRect, nsIFrame* aFrame,
+ RectCornerRadii& aOutRadii) {
+ const nscoord oneDevPixel = aFrame->PresContext()->DevPixelsToAppUnits(1);
+ nscoord twipsRadii[8];
+ NS_ASSERTION(
+ aBorderRect.Size() == aFrame->VisualBorderRectRelativeToSelf().Size(),
+ "unexpected size");
+ nsSize sz = aFrameRect.Size();
+ bool hasBorderRadius = aFrame->GetBorderRadii(sz, sz, Sides(), twipsRadii);
+ if (hasBorderRadius) {
+ ComputePixelRadii(twipsRadii, oneDevPixel, &aOutRadii);
+ }
+
+ return hasBorderRadius;
+}
+
+void nsCSSRendering::PaintBoxShadowOuter(nsPresContext* aPresContext,
+ gfxContext& aRenderingContext,
+ nsIFrame* aForFrame,
+ const nsRect& aFrameArea,
+ const nsRect& aDirtyRect,
+ float aOpacity) {
+ DrawTarget& aDrawTarget = *aRenderingContext.GetDrawTarget();
+ auto shadows = aForFrame->StyleEffects()->mBoxShadow.AsSpan();
+ if (shadows.IsEmpty()) {
+ return;
+ }
+
+ bool hasBorderRadius;
+ // mutually exclusive with hasBorderRadius
+ bool nativeTheme = HasBoxShadowNativeTheme(aForFrame, hasBorderRadius);
+ const nsStyleDisplay* styleDisplay = aForFrame->StyleDisplay();
+
+ nsRect frameRect = GetShadowRect(aFrameArea, nativeTheme, aForFrame);
+
+ // Get any border radius, since box-shadow must also have rounded corners if
+ // the frame does.
+ RectCornerRadii borderRadii;
+ const nscoord oneDevPixel = aPresContext->DevPixelsToAppUnits(1);
+ if (hasBorderRadius) {
+ nscoord twipsRadii[8];
+ NS_ASSERTION(
+ aFrameArea.Size() == aForFrame->VisualBorderRectRelativeToSelf().Size(),
+ "unexpected size");
+ nsSize sz = frameRect.Size();
+ hasBorderRadius = aForFrame->GetBorderRadii(sz, sz, Sides(), twipsRadii);
+ if (hasBorderRadius) {
+ ComputePixelRadii(twipsRadii, oneDevPixel, &borderRadii);
+ }
+ }
+
+ // We don't show anything that intersects with the frame we're blurring on. So
+ // tell the blurrer not to do unnecessary work there.
+ gfxRect skipGfxRect = ThebesRect(NSRectToRect(frameRect, oneDevPixel));
+ skipGfxRect.Round();
+ bool useSkipGfxRect = true;
+ if (nativeTheme) {
+ // Optimize non-leaf native-themed frames by skipping computing pixels
+ // in the padding-box. We assume the padding-box is going to be painted
+ // opaquely for non-leaf frames.
+ // XXX this may not be a safe assumption; we should make this go away
+ // by optimizing box-shadow drawing more for the cases where we don't have a
+ // skip-rect.
+ useSkipGfxRect = !aForFrame->IsLeaf();
+ nsRect paddingRect =
+ aForFrame->GetPaddingRectRelativeToSelf() + aFrameArea.TopLeft();
+ skipGfxRect = nsLayoutUtils::RectToGfxRect(paddingRect, oneDevPixel);
+ } else if (hasBorderRadius) {
+ skipGfxRect.Deflate(gfxMargin(
+ std::max(borderRadii[C_TL].height, borderRadii[C_TR].height), 0,
+ std::max(borderRadii[C_BL].height, borderRadii[C_BR].height), 0));
+ }
+
+ for (const StyleBoxShadow& shadow : Reversed(shadows)) {
+ if (shadow.inset) {
+ continue;
+ }
+
+ nsRect shadowRect = frameRect;
+ nsPoint shadowOffset(shadow.base.horizontal.ToAppUnits(),
+ shadow.base.vertical.ToAppUnits());
+ shadowRect.MoveBy(shadowOffset);
+ nscoord shadowSpread = shadow.spread.ToAppUnits();
+ if (!nativeTheme) {
+ shadowRect.Inflate(shadowSpread);
+ }
+
+ // shadowRect won't include the blur, so make an extra rect here that
+ // includes the blur for use in the even-odd rule below.
+ nsRect shadowRectPlusBlur = shadowRect;
+ nscoord blurRadius = shadow.base.blur.ToAppUnits();
+ shadowRectPlusBlur.Inflate(
+ nsContextBoxBlur::GetBlurRadiusMargin(blurRadius, oneDevPixel));
+
+ Rect shadowGfxRectPlusBlur = NSRectToRect(shadowRectPlusBlur, oneDevPixel);
+ shadowGfxRectPlusBlur.RoundOut();
+ MaybeSnapToDevicePixels(shadowGfxRectPlusBlur, aDrawTarget, true);
+
+ sRGBColor gfxShadowColor = GetShadowColor(shadow.base, aForFrame, aOpacity);
+
+ if (nativeTheme) {
+ nsContextBoxBlur blurringArea;
+
+ // When getting the widget shape from the native theme, we're going
+ // to draw the widget into the shadow surface to create a mask.
+ // We need to ensure that there actually *is* a shadow surface
+ // and that we're not going to draw directly into aRenderingContext.
+ gfxContext* shadowContext = blurringArea.Init(
+ shadowRect, shadowSpread, blurRadius, oneDevPixel, &aRenderingContext,
+ aDirtyRect, useSkipGfxRect ? &skipGfxRect : nullptr,
+ nsContextBoxBlur::FORCE_MASK);
+ if (!shadowContext) continue;
+
+ MOZ_ASSERT(shadowContext == blurringArea.GetContext());
+
+ aRenderingContext.Save();
+ aRenderingContext.SetColor(gfxShadowColor);
+
+ // Draw the shape of the frame so it can be blurred. Recall how
+ // nsContextBoxBlur doesn't make any temporary surfaces if blur is 0 and
+ // it just returns the original surface? If we have no blur, we're
+ // painting this fill on the actual content surface (aRenderingContext ==
+ // shadowContext) which is why we set up the color and clip before doing
+ // this.
+
+ // We don't clip the border-box from the shadow, nor any other box.
+ // We assume that the native theme is going to paint over the shadow.
+
+ // Draw the widget shape
+ gfxContextMatrixAutoSaveRestore save(shadowContext);
+ gfxPoint devPixelOffset = nsLayoutUtils::PointToGfxPoint(
+ shadowOffset, aPresContext->AppUnitsPerDevPixel());
+ shadowContext->SetMatrixDouble(
+ shadowContext->CurrentMatrixDouble().PreTranslate(devPixelOffset));
+
+ nsRect nativeRect = aDirtyRect;
+ nativeRect.MoveBy(-shadowOffset);
+ nativeRect.IntersectRect(frameRect, nativeRect);
+ aPresContext->Theme()->DrawWidgetBackground(
+ shadowContext, aForFrame, styleDisplay->EffectiveAppearance(),
+ aFrameArea, nativeRect, nsITheme::DrawOverflow::No);
+
+ blurringArea.DoPaint();
+ aRenderingContext.Restore();
+ } else {
+ aRenderingContext.Save();
+
+ {
+ Rect innerClipRect = NSRectToRect(frameRect, oneDevPixel);
+ if (!MaybeSnapToDevicePixels(innerClipRect, aDrawTarget, true)) {
+ innerClipRect.Round();
+ }
+
+ // Clip out the interior of the frame's border edge so that the shadow
+ // is only painted outside that area.
+ RefPtr<PathBuilder> builder =
+ aDrawTarget.CreatePathBuilder(FillRule::FILL_EVEN_ODD);
+ AppendRectToPath(builder, shadowGfxRectPlusBlur);
+ if (hasBorderRadius) {
+ AppendRoundedRectToPath(builder, innerClipRect, borderRadii);
+ } else {
+ AppendRectToPath(builder, innerClipRect);
+ }
+ RefPtr<Path> path = builder->Finish();
+ aRenderingContext.Clip(path);
+ }
+
+ // Clip the shadow so that we only get the part that applies to aForFrame.
+ nsRect fragmentClip = shadowRectPlusBlur;
+ Sides skipSides = aForFrame->GetSkipSides();
+ if (!skipSides.IsEmpty()) {
+ if (skipSides.Left()) {
+ nscoord xmost = fragmentClip.XMost();
+ fragmentClip.x = aFrameArea.x;
+ fragmentClip.width = xmost - fragmentClip.x;
+ }
+ if (skipSides.Right()) {
+ nscoord xmost = fragmentClip.XMost();
+ nscoord overflow = xmost - aFrameArea.XMost();
+ if (overflow > 0) {
+ fragmentClip.width -= overflow;
+ }
+ }
+ if (skipSides.Top()) {
+ nscoord ymost = fragmentClip.YMost();
+ fragmentClip.y = aFrameArea.y;
+ fragmentClip.height = ymost - fragmentClip.y;
+ }
+ if (skipSides.Bottom()) {
+ nscoord ymost = fragmentClip.YMost();
+ nscoord overflow = ymost - aFrameArea.YMost();
+ if (overflow > 0) {
+ fragmentClip.height -= overflow;
+ }
+ }
+ }
+ fragmentClip = fragmentClip.Intersect(aDirtyRect);
+ aRenderingContext.Clip(NSRectToSnappedRect(
+ fragmentClip, aForFrame->PresContext()->AppUnitsPerDevPixel(),
+ aDrawTarget));
+
+ RectCornerRadii clipRectRadii;
+ if (hasBorderRadius) {
+ Float spreadDistance = Float(shadowSpread / oneDevPixel);
+
+ Float borderSizes[4];
+
+ borderSizes[eSideLeft] = spreadDistance;
+ borderSizes[eSideTop] = spreadDistance;
+ borderSizes[eSideRight] = spreadDistance;
+ borderSizes[eSideBottom] = spreadDistance;
+
+ nsCSSBorderRenderer::ComputeOuterRadii(borderRadii, borderSizes,
+ &clipRectRadii);
+ }
+ nsContextBoxBlur::BlurRectangle(
+ &aRenderingContext, shadowRect, oneDevPixel,
+ hasBorderRadius ? &clipRectRadii : nullptr, blurRadius,
+ gfxShadowColor, aDirtyRect, skipGfxRect);
+ aRenderingContext.Restore();
+ }
+ }
+}
+
+nsRect nsCSSRendering::GetBoxShadowInnerPaddingRect(nsIFrame* aFrame,
+ const nsRect& aFrameArea) {
+ Sides skipSides = aFrame->GetSkipSides();
+ nsRect frameRect = BoxDecorationRectForBorder(aFrame, aFrameArea, skipSides);
+
+ nsRect paddingRect = frameRect;
+ nsMargin border = aFrame->GetUsedBorder();
+ paddingRect.Deflate(border);
+ return paddingRect;
+}
+
+bool nsCSSRendering::ShouldPaintBoxShadowInner(nsIFrame* aFrame) {
+ const Span<const StyleBoxShadow> shadows =
+ aFrame->StyleEffects()->mBoxShadow.AsSpan();
+ if (shadows.IsEmpty()) {
+ return false;
+ }
+
+ if (aFrame->IsThemed() && aFrame->GetContent() &&
+ !nsContentUtils::IsChromeDoc(aFrame->GetContent()->GetComposedDoc())) {
+ // There's no way of getting hold of a shape corresponding to a
+ // "padding-box" for native-themed widgets, so just don't draw
+ // inner box-shadows for them. But we allow chrome to paint inner
+ // box shadows since chrome can be aware of the platform theme.
+ return false;
+ }
+
+ return true;
+}
+
+bool nsCSSRendering::GetShadowInnerRadii(nsIFrame* aFrame,
+ const nsRect& aFrameArea,
+ RectCornerRadii& aOutInnerRadii) {
+ // Get any border radius, since box-shadow must also have rounded corners
+ // if the frame does.
+ nscoord twipsRadii[8];
+ nsRect frameRect =
+ BoxDecorationRectForBorder(aFrame, aFrameArea, aFrame->GetSkipSides());
+ nsSize sz = frameRect.Size();
+ nsMargin border = aFrame->GetUsedBorder();
+ aFrame->GetBorderRadii(sz, sz, Sides(), twipsRadii);
+ const nscoord oneDevPixel = aFrame->PresContext()->DevPixelsToAppUnits(1);
+
+ RectCornerRadii borderRadii;
+
+ const bool hasBorderRadius =
+ GetBorderRadii(frameRect, aFrameArea, aFrame, borderRadii);
+
+ if (hasBorderRadius) {
+ ComputePixelRadii(twipsRadii, oneDevPixel, &borderRadii);
+
+ Float borderSizes[4] = {
+ Float(border.top) / oneDevPixel, Float(border.right) / oneDevPixel,
+ Float(border.bottom) / oneDevPixel, Float(border.left) / oneDevPixel};
+ nsCSSBorderRenderer::ComputeInnerRadii(borderRadii, borderSizes,
+ &aOutInnerRadii);
+ }
+
+ return hasBorderRadius;
+}
+
+void nsCSSRendering::PaintBoxShadowInner(nsPresContext* aPresContext,
+ gfxContext& aRenderingContext,
+ nsIFrame* aForFrame,
+ const nsRect& aFrameArea) {
+ if (!ShouldPaintBoxShadowInner(aForFrame)) {
+ return;
+ }
+
+ const Span<const StyleBoxShadow> shadows =
+ aForFrame->StyleEffects()->mBoxShadow.AsSpan();
+ NS_ASSERTION(
+ aForFrame->IsFieldSetFrame() || aFrameArea.Size() == aForFrame->GetSize(),
+ "unexpected size");
+
+ nsRect paddingRect = GetBoxShadowInnerPaddingRect(aForFrame, aFrameArea);
+
+ RectCornerRadii innerRadii;
+ bool hasBorderRadius = GetShadowInnerRadii(aForFrame, aFrameArea, innerRadii);
+
+ const nscoord oneDevPixel = aPresContext->DevPixelsToAppUnits(1);
+
+ for (const StyleBoxShadow& shadow : Reversed(shadows)) {
+ if (!shadow.inset) {
+ continue;
+ }
+
+ // shadowPaintRect: the area to paint on the temp surface
+ // shadowClipRect: the area on the temporary surface within shadowPaintRect
+ // that we will NOT paint in
+ nscoord blurRadius = shadow.base.blur.ToAppUnits();
+ nsMargin blurMargin =
+ nsContextBoxBlur::GetBlurRadiusMargin(blurRadius, oneDevPixel);
+ nsRect shadowPaintRect = paddingRect;
+ shadowPaintRect.Inflate(blurMargin);
+
+ // Round the spread radius to device pixels (by truncation).
+ // This mostly matches what we do for borders, except that we don't round
+ // up values between zero and one device pixels to one device pixel.
+ // This way of rounding is symmetric around zero, which makes sense for
+ // the spread radius.
+ int32_t spreadDistance = shadow.spread.ToAppUnits() / oneDevPixel;
+ nscoord spreadDistanceAppUnits =
+ aPresContext->DevPixelsToAppUnits(spreadDistance);
+
+ nsRect shadowClipRect = paddingRect;
+ shadowClipRect.MoveBy(shadow.base.horizontal.ToAppUnits(),
+ shadow.base.vertical.ToAppUnits());
+ shadowClipRect.Deflate(spreadDistanceAppUnits, spreadDistanceAppUnits);
+
+ Rect shadowClipGfxRect = NSRectToRect(shadowClipRect, oneDevPixel);
+ shadowClipGfxRect.Round();
+
+ RectCornerRadii clipRectRadii;
+ if (hasBorderRadius) {
+ // Calculate the radii the inner clipping rect will have
+ Float borderSizes[4] = {0, 0, 0, 0};
+
+ // See PaintBoxShadowOuter and bug 514670
+ if (innerRadii[C_TL].width > 0 || innerRadii[C_BL].width > 0) {
+ borderSizes[eSideLeft] = spreadDistance;
+ }
+
+ if (innerRadii[C_TL].height > 0 || innerRadii[C_TR].height > 0) {
+ borderSizes[eSideTop] = spreadDistance;
+ }
+
+ if (innerRadii[C_TR].width > 0 || innerRadii[C_BR].width > 0) {
+ borderSizes[eSideRight] = spreadDistance;
+ }
+
+ if (innerRadii[C_BL].height > 0 || innerRadii[C_BR].height > 0) {
+ borderSizes[eSideBottom] = spreadDistance;
+ }
+
+ nsCSSBorderRenderer::ComputeInnerRadii(innerRadii, borderSizes,
+ &clipRectRadii);
+ }
+
+ // Set the "skip rect" to the area within the frame that we don't paint in,
+ // including after blurring.
+ nsRect skipRect = shadowClipRect;
+ skipRect.Deflate(blurMargin);
+ gfxRect skipGfxRect = nsLayoutUtils::RectToGfxRect(skipRect, oneDevPixel);
+ if (hasBorderRadius) {
+ skipGfxRect.Deflate(gfxMargin(
+ std::max(clipRectRadii[C_TL].height, clipRectRadii[C_TR].height), 0,
+ std::max(clipRectRadii[C_BL].height, clipRectRadii[C_BR].height), 0));
+ }
+
+ // When there's a blur radius, gfxAlphaBoxBlur leaves the skiprect area
+ // unchanged. And by construction the gfxSkipRect is not touched by the
+ // rendered shadow (even after blurring), so those pixels must be completely
+ // transparent in the shadow, so drawing them changes nothing.
+ DrawTarget* drawTarget = aRenderingContext.GetDrawTarget();
+
+ // Clip the context to the area of the frame's padding rect, so no part of
+ // the shadow is painted outside. Also cut out anything beyond where the
+ // inset shadow will be.
+ Rect shadowGfxRect = NSRectToRect(paddingRect, oneDevPixel);
+ shadowGfxRect.Round();
+
+ sRGBColor shadowColor = GetShadowColor(shadow.base, aForFrame, 1.0);
+ aRenderingContext.Save();
+
+ // This clips the outside border radius.
+ // clipRectRadii is the border radius inside the inset shadow.
+ if (hasBorderRadius) {
+ RefPtr<Path> roundedRect =
+ MakePathForRoundedRect(*drawTarget, shadowGfxRect, innerRadii);
+ aRenderingContext.Clip(roundedRect);
+ } else {
+ aRenderingContext.Clip(shadowGfxRect);
+ }
+
+ nsContextBoxBlur insetBoxBlur;
+ gfxRect destRect =
+ nsLayoutUtils::RectToGfxRect(shadowPaintRect, oneDevPixel);
+ Point shadowOffset(shadow.base.horizontal.ToAppUnits() / oneDevPixel,
+ shadow.base.vertical.ToAppUnits() / oneDevPixel);
+
+ insetBoxBlur.InsetBoxBlur(
+ &aRenderingContext, ToRect(destRect), shadowClipGfxRect, shadowColor,
+ blurRadius, spreadDistanceAppUnits, oneDevPixel, hasBorderRadius,
+ clipRectRadii, ToRect(skipGfxRect), shadowOffset);
+ aRenderingContext.Restore();
+ }
+}
+
+/* static */
+nsCSSRendering::PaintBGParams nsCSSRendering::PaintBGParams::ForAllLayers(
+ nsPresContext& aPresCtx, const nsRect& aDirtyRect,
+ const nsRect& aBorderArea, nsIFrame* aFrame, uint32_t aPaintFlags,
+ float aOpacity) {
+ MOZ_ASSERT(aFrame);
+
+ PaintBGParams result(aPresCtx, aDirtyRect, aBorderArea, aFrame, aPaintFlags,
+ -1, CompositionOp::OP_OVER, aOpacity);
+
+ return result;
+}
+
+/* static */
+nsCSSRendering::PaintBGParams nsCSSRendering::PaintBGParams::ForSingleLayer(
+ nsPresContext& aPresCtx, const nsRect& aDirtyRect,
+ const nsRect& aBorderArea, nsIFrame* aFrame, uint32_t aPaintFlags,
+ int32_t aLayer, CompositionOp aCompositionOp, float aOpacity) {
+ MOZ_ASSERT(aFrame && (aLayer != -1));
+
+ PaintBGParams result(aPresCtx, aDirtyRect, aBorderArea, aFrame, aPaintFlags,
+ aLayer, aCompositionOp, aOpacity);
+
+ return result;
+}
+
+ImgDrawResult nsCSSRendering::PaintStyleImageLayer(const PaintBGParams& aParams,
+ gfxContext& aRenderingCtx) {
+ AUTO_PROFILER_LABEL("nsCSSRendering::PaintStyleImageLayer", GRAPHICS);
+
+ MOZ_ASSERT(aParams.frame,
+ "Frame is expected to be provided to PaintStyleImageLayer");
+
+ const ComputedStyle* sc = FindBackground(aParams.frame);
+ if (!sc) {
+ // We don't want to bail out if moz-appearance is set on a root
+ // node. If it has a parent content node, bail because it's not
+ // a root, otherwise keep going in order to let the theme stuff
+ // draw the background. The canvas really should be drawing the
+ // bg, but there's no way to hook that up via css.
+ if (!aParams.frame->StyleDisplay()->HasAppearance()) {
+ return ImgDrawResult::SUCCESS;
+ }
+
+ nsIContent* content = aParams.frame->GetContent();
+ if (!content || content->GetParent()) {
+ return ImgDrawResult::SUCCESS;
+ }
+
+ sc = aParams.frame->Style();
+ }
+
+ return PaintStyleImageLayerWithSC(aParams, aRenderingCtx, sc,
+ *aParams.frame->StyleBorder());
+}
+
+bool nsCSSRendering::CanBuildWebRenderDisplayItemsForStyleImageLayer(
+ WebRenderLayerManager* aManager, nsPresContext& aPresCtx, nsIFrame* aFrame,
+ const nsStyleBackground* aBackgroundStyle, int32_t aLayer,
+ uint32_t aPaintFlags) {
+ if (!aBackgroundStyle) {
+ return false;
+ }
+
+ MOZ_ASSERT(aFrame && aLayer >= 0 &&
+ (uint32_t)aLayer < aBackgroundStyle->mImage.mLayers.Length());
+
+ // We cannot draw native themed backgrounds
+ StyleAppearance appearance = aFrame->StyleDisplay()->EffectiveAppearance();
+ if (appearance != StyleAppearance::None) {
+ nsITheme* theme = aPresCtx.Theme();
+ if (theme->ThemeSupportsWidget(&aPresCtx, aFrame, appearance)) {
+ return false;
+ }
+ }
+
+ // We only support painting gradients and image for a single style image
+ // layer, and we don't support crop-rects.
+ const auto& styleImage =
+ aBackgroundStyle->mImage.mLayers[aLayer].mImage.FinalImage();
+ if (styleImage.IsImageRequestType()) {
+ if (styleImage.IsRect()) {
+ return false;
+ }
+
+ imgRequestProxy* requestProxy = styleImage.GetImageRequest();
+ if (!requestProxy) {
+ return false;
+ }
+
+ uint32_t imageFlags = imgIContainer::FLAG_NONE;
+ if (aPaintFlags & nsCSSRendering::PAINTBG_SYNC_DECODE_IMAGES) {
+ imageFlags |= imgIContainer::FLAG_SYNC_DECODE;
+ }
+
+ nsCOMPtr<imgIContainer> srcImage;
+ requestProxy->GetImage(getter_AddRefs(srcImage));
+ if (!srcImage ||
+ !srcImage->IsImageContainerAvailable(aManager, imageFlags)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ if (styleImage.IsGradient()) {
+ return true;
+ }
+
+ return false;
+}
+
+ImgDrawResult nsCSSRendering::BuildWebRenderDisplayItemsForStyleImageLayer(
+ const PaintBGParams& aParams, mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const mozilla::layers::StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager, nsDisplayItem* aItem) {
+ MOZ_ASSERT(aParams.frame,
+ "Frame is expected to be provided to "
+ "BuildWebRenderDisplayItemsForStyleImageLayer");
+
+ ComputedStyle* sc = FindBackground(aParams.frame);
+ if (!sc) {
+ // We don't want to bail out if moz-appearance is set on a root
+ // node. If it has a parent content node, bail because it's not
+ // a root, otherwise keep going in order to let the theme stuff
+ // draw the background. The canvas really should be drawing the
+ // bg, but there's no way to hook that up via css.
+ if (!aParams.frame->StyleDisplay()->HasAppearance()) {
+ return ImgDrawResult::SUCCESS;
+ }
+
+ nsIContent* content = aParams.frame->GetContent();
+ if (!content || content->GetParent()) {
+ return ImgDrawResult::SUCCESS;
+ }
+
+ sc = aParams.frame->Style();
+ }
+ return BuildWebRenderDisplayItemsForStyleImageLayerWithSC(
+ aParams, aBuilder, aResources, aSc, aManager, aItem, sc,
+ *aParams.frame->StyleBorder());
+}
+
+static bool IsOpaqueBorderEdge(const nsStyleBorder& aBorder,
+ mozilla::Side aSide) {
+ if (aBorder.GetComputedBorder().Side(aSide) == 0) return true;
+ switch (aBorder.GetBorderStyle(aSide)) {
+ case StyleBorderStyle::Solid:
+ case StyleBorderStyle::Groove:
+ case StyleBorderStyle::Ridge:
+ case StyleBorderStyle::Inset:
+ case StyleBorderStyle::Outset:
+ break;
+ default:
+ return false;
+ }
+
+ // If we're using a border image, assume it's not fully opaque,
+ // because we may not even have the image loaded at this point, and
+ // even if we did, checking whether the relevant tile is fully
+ // opaque would be too much work.
+ if (!aBorder.mBorderImageSource.IsNone()) {
+ return false;
+ }
+
+ StyleColor color = aBorder.BorderColorFor(aSide);
+ // We don't know the foreground color here, so if it's being used
+ // we must assume it might be transparent.
+ return !color.MaybeTransparent();
+}
+
+/**
+ * Returns true if all border edges are either missing or opaque.
+ */
+static bool IsOpaqueBorder(const nsStyleBorder& aBorder) {
+ for (const auto i : mozilla::AllPhysicalSides()) {
+ if (!IsOpaqueBorderEdge(aBorder, i)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+static inline void SetupDirtyRects(const nsRect& aBGClipArea,
+ const nsRect& aCallerDirtyRect,
+ nscoord aAppUnitsPerPixel,
+ /* OUT: */
+ nsRect* aDirtyRect, gfxRect* aDirtyRectGfx) {
+ aDirtyRect->IntersectRect(aBGClipArea, aCallerDirtyRect);
+
+ // Compute the Thebes equivalent of the dirtyRect.
+ *aDirtyRectGfx = nsLayoutUtils::RectToGfxRect(*aDirtyRect, aAppUnitsPerPixel);
+ NS_WARNING_ASSERTION(aDirtyRect->IsEmpty() || !aDirtyRectGfx->IsEmpty(),
+ "converted dirty rect should not be empty");
+ MOZ_ASSERT(!aDirtyRect->IsEmpty() || aDirtyRectGfx->IsEmpty(),
+ "second should be empty if first is");
+}
+
+static bool IsSVGStyleGeometryBox(StyleGeometryBox aBox) {
+ return (aBox == StyleGeometryBox::FillBox ||
+ aBox == StyleGeometryBox::StrokeBox ||
+ aBox == StyleGeometryBox::ViewBox);
+}
+
+static bool IsHTMLStyleGeometryBox(StyleGeometryBox aBox) {
+ return (aBox == StyleGeometryBox::ContentBox ||
+ aBox == StyleGeometryBox::PaddingBox ||
+ aBox == StyleGeometryBox::BorderBox ||
+ aBox == StyleGeometryBox::MarginBox);
+}
+
+static StyleGeometryBox ComputeBoxValue(nsIFrame* aForFrame,
+ StyleGeometryBox aBox) {
+ if (!aForFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) {
+ // For elements with associated CSS layout box, the values fill-box,
+ // stroke-box and view-box compute to the initial value of mask-clip.
+ if (IsSVGStyleGeometryBox(aBox)) {
+ return StyleGeometryBox::BorderBox;
+ }
+ } else {
+ // For SVG elements without associated CSS layout box, the values
+ // content-box, padding-box, border-box and margin-box compute to fill-box.
+ if (IsHTMLStyleGeometryBox(aBox)) {
+ return StyleGeometryBox::FillBox;
+ }
+ }
+
+ return aBox;
+}
+
+bool nsCSSRendering::ImageLayerClipState::IsValid() const {
+ // mDirtyRectInDevPx comes from mDirtyRectInAppUnits. mDirtyRectInAppUnits
+ // can not be empty if mDirtyRectInDevPx is not.
+ if (!mDirtyRectInDevPx.IsEmpty() && mDirtyRectInAppUnits.IsEmpty()) {
+ return false;
+ }
+
+ if (mHasRoundedCorners == mClippedRadii.IsEmpty()) {
+ return false;
+ }
+
+ return true;
+}
+
+/* static */
+void nsCSSRendering::GetImageLayerClip(
+ const nsStyleImageLayers::Layer& aLayer, nsIFrame* aForFrame,
+ const nsStyleBorder& aBorder, const nsRect& aBorderArea,
+ const nsRect& aCallerDirtyRect, bool aWillPaintBorder,
+ nscoord aAppUnitsPerPixel,
+ /* out */ ImageLayerClipState* aClipState) {
+ StyleGeometryBox layerClip = ComputeBoxValue(aForFrame, aLayer.mClip);
+ if (IsSVGStyleGeometryBox(layerClip)) {
+ MOZ_ASSERT(aForFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT));
+
+ // The coordinate space of clipArea is svg user space.
+ nsRect clipArea = nsLayoutUtils::ComputeGeometryBox(aForFrame, layerClip);
+
+ nsRect strokeBox = (layerClip == StyleGeometryBox::StrokeBox)
+ ? clipArea
+ : nsLayoutUtils::ComputeGeometryBox(
+ aForFrame, StyleGeometryBox::StrokeBox);
+ nsRect clipAreaRelativeToStrokeBox = clipArea - strokeBox.TopLeft();
+
+ // aBorderArea is the stroke-box area in a coordinate space defined by
+ // the caller. This coordinate space can be svg user space of aForFrame,
+ // the space of aForFrame's reference-frame, or anything else.
+ //
+ // Which coordinate space chosen for aBorderArea is not matter. What
+ // matter is to ensure returning aClipState->mBGClipArea in the consistent
+ // coordiante space with aBorderArea. So we evaluate the position of clip
+ // area base on the position of aBorderArea here.
+ aClipState->mBGClipArea =
+ clipAreaRelativeToStrokeBox + aBorderArea.TopLeft();
+
+ SetupDirtyRects(aClipState->mBGClipArea, aCallerDirtyRect,
+ aAppUnitsPerPixel, &aClipState->mDirtyRectInAppUnits,
+ &aClipState->mDirtyRectInDevPx);
+ MOZ_ASSERT(aClipState->IsValid());
+ return;
+ }
+
+ if (layerClip == StyleGeometryBox::NoClip) {
+ aClipState->mBGClipArea = aCallerDirtyRect;
+
+ SetupDirtyRects(aClipState->mBGClipArea, aCallerDirtyRect,
+ aAppUnitsPerPixel, &aClipState->mDirtyRectInAppUnits,
+ &aClipState->mDirtyRectInDevPx);
+ MOZ_ASSERT(aClipState->IsValid());
+ return;
+ }
+
+ MOZ_ASSERT(!aForFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT));
+
+ // Compute the outermost boundary of the area that might be painted.
+ // Same coordinate space as aBorderArea.
+ Sides skipSides = aForFrame->GetSkipSides();
+ nsRect clipBorderArea =
+ BoxDecorationRectForBorder(aForFrame, aBorderArea, skipSides, &aBorder);
+
+ bool haveRoundedCorners = false;
+ LayoutFrameType fType = aForFrame->Type();
+ if (fType != LayoutFrameType::TableColGroup &&
+ fType != LayoutFrameType::TableCol &&
+ fType != LayoutFrameType::TableRow &&
+ fType != LayoutFrameType::TableRowGroup) {
+ haveRoundedCorners = GetRadii(aForFrame, aBorder, aBorderArea,
+ clipBorderArea, aClipState->mRadii);
+ }
+ bool isSolidBorder = aWillPaintBorder && IsOpaqueBorder(aBorder);
+ if (isSolidBorder && layerClip == StyleGeometryBox::BorderBox) {
+ // If we have rounded corners, we need to inflate the background
+ // drawing area a bit to avoid seams between the border and
+ // background.
+ layerClip = haveRoundedCorners ? StyleGeometryBox::MozAlmostPadding
+ : StyleGeometryBox::PaddingBox;
+ }
+
+ aClipState->mBGClipArea = clipBorderArea;
+
+ if (aForFrame->IsScrollFrame() &&
+ StyleImageLayerAttachment::Local == aLayer.mAttachment) {
+ // As of this writing, this is still in discussion in the CSS Working Group
+ // http://lists.w3.org/Archives/Public/www-style/2013Jul/0250.html
+
+ // The rectangle for 'background-clip' scrolls with the content,
+ // but the background is also clipped at a non-scrolling 'padding-box'
+ // like the content. (See below.)
+ // Therefore, only 'content-box' makes a difference here.
+ if (layerClip == StyleGeometryBox::ContentBox) {
+ nsIScrollableFrame* scrollableFrame = do_QueryFrame(aForFrame);
+ // Clip at a rectangle attached to the scrolled content.
+ aClipState->mHasAdditionalBGClipArea = true;
+ aClipState->mAdditionalBGClipArea =
+ nsRect(aClipState->mBGClipArea.TopLeft() +
+ scrollableFrame->GetScrolledFrame()->GetPosition()
+ // For the dir=rtl case:
+ + scrollableFrame->GetScrollRange().TopLeft(),
+ scrollableFrame->GetScrolledRect().Size());
+ nsMargin padding = aForFrame->GetUsedPadding();
+ // padding-bottom is ignored on scrollable frames:
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=748518
+ padding.bottom = 0;
+ padding.ApplySkipSides(skipSides);
+ aClipState->mAdditionalBGClipArea.Deflate(padding);
+ }
+
+ // Also clip at a non-scrolling, rounded-corner 'padding-box',
+ // same as the scrolled content because of the 'overflow' property.
+ layerClip = StyleGeometryBox::PaddingBox;
+ }
+
+ // See the comment of StyleGeometryBox::Margin.
+ // Hitting this assertion means we decide to turn on margin-box support for
+ // positioned mask from CSS parser and style system. In this case, you
+ // should *inflate* mBGClipArea by the margin returning from
+ // aForFrame->GetUsedMargin() in the code chunk bellow.
+ MOZ_ASSERT(layerClip != StyleGeometryBox::MarginBox,
+ "StyleGeometryBox::MarginBox rendering is not supported yet.\n");
+
+ if (layerClip != StyleGeometryBox::BorderBox &&
+ layerClip != StyleGeometryBox::Text) {
+ nsMargin border = aForFrame->GetUsedBorder();
+ if (layerClip == StyleGeometryBox::MozAlmostPadding) {
+ // Reduce |border| by 1px (device pixels) on all sides, if
+ // possible, so that we don't get antialiasing seams between the
+ // {background|mask} and border.
+ border.top = std::max(0, border.top - aAppUnitsPerPixel);
+ border.right = std::max(0, border.right - aAppUnitsPerPixel);
+ border.bottom = std::max(0, border.bottom - aAppUnitsPerPixel);
+ border.left = std::max(0, border.left - aAppUnitsPerPixel);
+ } else if (layerClip != StyleGeometryBox::PaddingBox) {
+ NS_ASSERTION(layerClip == StyleGeometryBox::ContentBox,
+ "unexpected background-clip");
+ border += aForFrame->GetUsedPadding();
+ }
+ border.ApplySkipSides(skipSides);
+ aClipState->mBGClipArea.Deflate(border);
+
+ if (haveRoundedCorners) {
+ nsIFrame::AdjustBorderRadii(aClipState->mRadii, -border);
+ }
+ }
+
+ if (haveRoundedCorners) {
+ auto d2a = aForFrame->PresContext()->AppUnitsPerDevPixel();
+ nsCSSRendering::ComputePixelRadii(aClipState->mRadii, d2a,
+ &aClipState->mClippedRadii);
+ aClipState->mHasRoundedCorners = !aClipState->mClippedRadii.IsEmpty();
+ }
+
+ if (!haveRoundedCorners && aClipState->mHasAdditionalBGClipArea) {
+ // Do the intersection here to account for the fast path(?) below.
+ aClipState->mBGClipArea =
+ aClipState->mBGClipArea.Intersect(aClipState->mAdditionalBGClipArea);
+ aClipState->mHasAdditionalBGClipArea = false;
+ }
+
+ SetupDirtyRects(aClipState->mBGClipArea, aCallerDirtyRect, aAppUnitsPerPixel,
+ &aClipState->mDirtyRectInAppUnits,
+ &aClipState->mDirtyRectInDevPx);
+
+ MOZ_ASSERT(aClipState->IsValid());
+}
+
+static void SetupImageLayerClip(nsCSSRendering::ImageLayerClipState& aClipState,
+ gfxContext* aCtx, nscoord aAppUnitsPerPixel,
+ gfxContextAutoSaveRestore* aAutoSR) {
+ if (aClipState.mDirtyRectInDevPx.IsEmpty()) {
+ // Our caller won't draw anything under this condition, so no need
+ // to set more up.
+ return;
+ }
+
+ if (aClipState.mCustomClip) {
+ // We don't support custom clips and rounded corners, arguably a bug, but
+ // table painting seems to depend on it.
+ return;
+ }
+
+ // If we have rounded corners, clip all subsequent drawing to the
+ // rounded rectangle defined by bgArea and bgRadii (we don't know
+ // whether the rounded corners intrude on the dirtyRect or not).
+ // Do not do this if we have a caller-provided clip rect --
+ // as above with bgArea, arguably a bug, but table painting seems
+ // to depend on it.
+
+ if (aClipState.mHasAdditionalBGClipArea) {
+ gfxRect bgAreaGfx = nsLayoutUtils::RectToGfxRect(
+ aClipState.mAdditionalBGClipArea, aAppUnitsPerPixel);
+ bgAreaGfx.Round();
+ gfxUtils::ConditionRect(bgAreaGfx);
+
+ aAutoSR->EnsureSaved(aCtx);
+ aCtx->SnappedClip(bgAreaGfx);
+ }
+
+ if (aClipState.mHasRoundedCorners) {
+ Rect bgAreaGfx = NSRectToRect(aClipState.mBGClipArea, aAppUnitsPerPixel);
+ bgAreaGfx.Round();
+
+ if (bgAreaGfx.IsEmpty()) {
+ // I think it's become possible to hit this since
+ // https://hg.mozilla.org/mozilla-central/rev/50e934e4979b landed.
+ NS_WARNING("converted background area should not be empty");
+ // Make our caller not do anything.
+ aClipState.mDirtyRectInDevPx.SizeTo(gfxSize(0.0, 0.0));
+ return;
+ }
+
+ aAutoSR->EnsureSaved(aCtx);
+
+ RefPtr<Path> roundedRect = MakePathForRoundedRect(
+ *aCtx->GetDrawTarget(), bgAreaGfx, aClipState.mClippedRadii);
+ aCtx->Clip(roundedRect);
+ }
+}
+
+static void DrawBackgroundColor(nsCSSRendering::ImageLayerClipState& aClipState,
+ gfxContext* aCtx, nscoord aAppUnitsPerPixel) {
+ if (aClipState.mDirtyRectInDevPx.IsEmpty()) {
+ // Our caller won't draw anything under this condition, so no need
+ // to set more up.
+ return;
+ }
+
+ DrawTarget* drawTarget = aCtx->GetDrawTarget();
+
+ // We don't support custom clips and rounded corners, arguably a bug, but
+ // table painting seems to depend on it.
+ if (!aClipState.mHasRoundedCorners || aClipState.mCustomClip) {
+ aCtx->NewPath();
+ aCtx->SnappedRectangle(aClipState.mDirtyRectInDevPx);
+ aCtx->Fill();
+ return;
+ }
+
+ Rect bgAreaGfx = NSRectToRect(aClipState.mBGClipArea, aAppUnitsPerPixel);
+ bgAreaGfx.Round();
+
+ if (bgAreaGfx.IsEmpty()) {
+ // I think it's become possible to hit this since
+ // https://hg.mozilla.org/mozilla-central/rev/50e934e4979b landed.
+ NS_WARNING("converted background area should not be empty");
+ // Make our caller not do anything.
+ aClipState.mDirtyRectInDevPx.SizeTo(gfxSize(0.0, 0.0));
+ return;
+ }
+
+ aCtx->Save();
+ gfxRect dirty = ThebesRect(bgAreaGfx).Intersect(aClipState.mDirtyRectInDevPx);
+
+ aCtx->SnappedClip(dirty);
+
+ if (aClipState.mHasAdditionalBGClipArea) {
+ gfxRect bgAdditionalAreaGfx = nsLayoutUtils::RectToGfxRect(
+ aClipState.mAdditionalBGClipArea, aAppUnitsPerPixel);
+ bgAdditionalAreaGfx.Round();
+ gfxUtils::ConditionRect(bgAdditionalAreaGfx);
+ aCtx->SnappedClip(bgAdditionalAreaGfx);
+ }
+
+ RefPtr<Path> roundedRect =
+ MakePathForRoundedRect(*drawTarget, bgAreaGfx, aClipState.mClippedRadii);
+ aCtx->SetPath(roundedRect);
+ aCtx->Fill();
+ aCtx->Restore();
+}
+
+enum class ScrollbarColorKind {
+ Thumb,
+ Track,
+};
+
+static Maybe<nscolor> CalcScrollbarColor(nsIFrame* aFrame,
+ ScrollbarColorKind aKind) {
+ ComputedStyle* scrollbarStyle = nsLayoutUtils::StyleForScrollbar(aFrame);
+ const auto& colors = scrollbarStyle->StyleUI()->mScrollbarColor;
+ if (colors.IsAuto()) {
+ return Nothing();
+ }
+ const auto& color = aKind == ScrollbarColorKind::Thumb
+ ? colors.AsColors().thumb
+ : colors.AsColors().track;
+ return Some(color.CalcColor(*scrollbarStyle));
+}
+
+static nscolor GetBackgroundColor(nsIFrame* aFrame,
+ const ComputedStyle* aStyle) {
+ switch (aStyle->StyleDisplay()->EffectiveAppearance()) {
+ case StyleAppearance::ScrollbarthumbVertical:
+ case StyleAppearance::ScrollbarthumbHorizontal: {
+ if (Maybe<nscolor> overrideColor =
+ CalcScrollbarColor(aFrame, ScrollbarColorKind::Thumb)) {
+ return *overrideColor;
+ }
+ break;
+ }
+ case StyleAppearance::ScrollbarVertical:
+ case StyleAppearance::ScrollbarHorizontal:
+ case StyleAppearance::Scrollcorner: {
+ if (Maybe<nscolor> overrideColor =
+ CalcScrollbarColor(aFrame, ScrollbarColorKind::Track)) {
+ return *overrideColor;
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ return aStyle->GetVisitedDependentColor(&nsStyleBackground::mBackgroundColor);
+}
+
+nscolor nsCSSRendering::DetermineBackgroundColor(nsPresContext* aPresContext,
+ const ComputedStyle* aStyle,
+ nsIFrame* aFrame,
+ bool& aDrawBackgroundImage,
+ bool& aDrawBackgroundColor) {
+ auto shouldPaint = aFrame->ComputeShouldPaintBackground();
+ aDrawBackgroundImage = shouldPaint.mImage;
+ aDrawBackgroundColor = shouldPaint.mColor;
+
+ const nsStyleBackground* bg = aStyle->StyleBackground();
+ nscolor bgColor;
+ if (aDrawBackgroundColor) {
+ bgColor = GetBackgroundColor(aFrame, aStyle);
+ if (NS_GET_A(bgColor) == 0) {
+ aDrawBackgroundColor = false;
+ }
+ } else {
+ // If GetBackgroundColorDraw() is false, we are still expected to
+ // draw color in the background of any frame that's not completely
+ // transparent, but we are expected to use white instead of whatever
+ // color was specified.
+ bgColor = NS_RGB(255, 255, 255);
+ if (aDrawBackgroundImage || !bg->IsTransparent(aStyle)) {
+ aDrawBackgroundColor = true;
+ } else {
+ bgColor = NS_RGBA(0, 0, 0, 0);
+ }
+ }
+
+ // We can skip painting the background color if a background image is opaque.
+ nsStyleImageLayers::Repeat repeat = bg->BottomLayer().mRepeat;
+ bool xFullRepeat = repeat.mXRepeat == StyleImageLayerRepeat::Repeat ||
+ repeat.mXRepeat == StyleImageLayerRepeat::Round;
+ bool yFullRepeat = repeat.mYRepeat == StyleImageLayerRepeat::Repeat ||
+ repeat.mYRepeat == StyleImageLayerRepeat::Round;
+ if (aDrawBackgroundColor && xFullRepeat && yFullRepeat &&
+ bg->BottomLayer().mImage.IsOpaque() &&
+ bg->BottomLayer().mBlendMode == StyleBlend::Normal) {
+ aDrawBackgroundColor = false;
+ }
+
+ return bgColor;
+}
+
+static CompositionOp DetermineCompositionOp(
+ const nsCSSRendering::PaintBGParams& aParams,
+ const nsStyleImageLayers& aLayers, uint32_t aLayerIndex) {
+ if (aParams.layer >= 0) {
+ // When drawing a single layer, use the specified composition op.
+ return aParams.compositionOp;
+ }
+
+ const nsStyleImageLayers::Layer& layer = aLayers.mLayers[aLayerIndex];
+ // When drawing all layers, get the compositon op from each image layer.
+ if (aParams.paintFlags & nsCSSRendering::PAINTBG_MASK_IMAGE) {
+ // Always using OP_OVER mode while drawing the bottom mask layer.
+ if (aLayerIndex == (aLayers.mImageCount - 1)) {
+ return CompositionOp::OP_OVER;
+ }
+
+ return nsCSSRendering::GetGFXCompositeMode(layer.mComposite);
+ }
+
+ return nsCSSRendering::GetGFXBlendMode(layer.mBlendMode);
+}
+
+ImgDrawResult nsCSSRendering::PaintStyleImageLayerWithSC(
+ const PaintBGParams& aParams, gfxContext& aRenderingCtx,
+ const ComputedStyle* aBackgroundSC, const nsStyleBorder& aBorder) {
+ MOZ_ASSERT(aParams.frame,
+ "Frame is expected to be provided to PaintStyleImageLayerWithSC");
+
+ // If we're drawing all layers, aCompositonOp is ignored, so make sure that
+ // it was left at its default value.
+ MOZ_ASSERT(aParams.layer != -1 ||
+ aParams.compositionOp == CompositionOp::OP_OVER);
+
+ // Check to see if we have an appearance defined. If so, we let the theme
+ // renderer draw the background and bail out.
+ // XXXzw this ignores aParams.bgClipRect.
+ StyleAppearance appearance =
+ aParams.frame->StyleDisplay()->EffectiveAppearance();
+ if (appearance != StyleAppearance::None) {
+ nsITheme* theme = aParams.presCtx.Theme();
+ if (theme->ThemeSupportsWidget(&aParams.presCtx, aParams.frame,
+ appearance)) {
+ nsRect drawing(aParams.borderArea);
+ theme->GetWidgetOverflow(aParams.presCtx.DeviceContext(), aParams.frame,
+ appearance, &drawing);
+ drawing.IntersectRect(drawing, aParams.dirtyRect);
+ theme->DrawWidgetBackground(&aRenderingCtx, aParams.frame, appearance,
+ aParams.borderArea, drawing);
+ return ImgDrawResult::SUCCESS;
+ }
+ }
+
+ // For canvas frames (in the CSS sense) we draw the background color using
+ // a solid color item that gets added in nsLayoutUtils::PaintFrame,
+ // or nsSubDocumentFrame::BuildDisplayList (bug 488242). (The solid
+ // color may be moved into nsDisplayCanvasBackground by
+ // PresShell::AddCanvasBackgroundColorItem(), and painted by
+ // nsDisplayCanvasBackground directly.) Either way we don't need to
+ // paint the background color here.
+ bool isCanvasFrame = aParams.frame->IsCanvasFrame();
+ const bool paintMask = aParams.paintFlags & PAINTBG_MASK_IMAGE;
+
+ // Determine whether we are drawing background images and/or
+ // background colors.
+ bool drawBackgroundImage = true;
+ bool drawBackgroundColor = !paintMask;
+ nscolor bgColor = NS_RGBA(0, 0, 0, 0);
+ if (!paintMask) {
+ bgColor =
+ DetermineBackgroundColor(&aParams.presCtx, aBackgroundSC, aParams.frame,
+ drawBackgroundImage, drawBackgroundColor);
+ }
+
+ // Masks shouldn't be suppressed for print.
+ MOZ_ASSERT_IF(paintMask, drawBackgroundImage);
+
+ const nsStyleImageLayers& layers =
+ paintMask ? aBackgroundSC->StyleSVGReset()->mMask
+ : aBackgroundSC->StyleBackground()->mImage;
+ // If we're drawing a specific layer, we don't want to draw the
+ // background color.
+ if (drawBackgroundColor && aParams.layer >= 0) {
+ drawBackgroundColor = false;
+ }
+
+ // At this point, drawBackgroundImage and drawBackgroundColor are
+ // true if and only if we are actually supposed to paint an image or
+ // color into aDirtyRect, respectively.
+ if (!drawBackgroundImage && !drawBackgroundColor) {
+ return ImgDrawResult::SUCCESS;
+ }
+
+ // The 'bgClipArea' (used only by the image tiling logic, far below)
+ // is the caller-provided aParams.bgClipRect if any, or else the area
+ // determined by the value of 'background-clip' in
+ // SetupCurrentBackgroundClip. (Arguably it should be the
+ // intersection, but that breaks the table painter -- in particular,
+ // taking the intersection breaks reftests/bugs/403249-1[ab].)
+ nscoord appUnitsPerPixel = aParams.presCtx.AppUnitsPerDevPixel();
+ ImageLayerClipState clipState;
+ if (aParams.bgClipRect) {
+ clipState.mBGClipArea = *aParams.bgClipRect;
+ clipState.mCustomClip = true;
+ clipState.mHasRoundedCorners = false;
+ SetupDirtyRects(clipState.mBGClipArea, aParams.dirtyRect, appUnitsPerPixel,
+ &clipState.mDirtyRectInAppUnits,
+ &clipState.mDirtyRectInDevPx);
+ } else {
+ GetImageLayerClip(layers.BottomLayer(), aParams.frame, aBorder,
+ aParams.borderArea, aParams.dirtyRect,
+ (aParams.paintFlags & PAINTBG_WILL_PAINT_BORDER),
+ appUnitsPerPixel, &clipState);
+ }
+
+ // If we might be using a background color, go ahead and set it now.
+ if (drawBackgroundColor && !isCanvasFrame) {
+ aRenderingCtx.SetColor(sRGBColor::FromABGR(bgColor));
+ }
+
+ // If there is no background image, draw a color. (If there is
+ // neither a background image nor a color, we wouldn't have gotten
+ // this far.)
+ if (!drawBackgroundImage) {
+ if (!isCanvasFrame) {
+ DrawBackgroundColor(clipState, &aRenderingCtx, appUnitsPerPixel);
+ }
+ return ImgDrawResult::SUCCESS;
+ }
+
+ if (layers.mImageCount < 1) {
+ // Return if there are no background layers, all work from this point
+ // onwards happens iteratively on these.
+ return ImgDrawResult::SUCCESS;
+ }
+
+ MOZ_ASSERT((aParams.layer < 0) ||
+ (layers.mImageCount > uint32_t(aParams.layer)));
+
+ // The background color is rendered over the entire dirty area,
+ // even if the image isn't.
+ if (drawBackgroundColor && !isCanvasFrame) {
+ DrawBackgroundColor(clipState, &aRenderingCtx, appUnitsPerPixel);
+ }
+
+ // Compute the outermost boundary of the area that might be painted.
+ // Same coordinate space as aParams.borderArea & aParams.bgClipRect.
+ Sides skipSides = aParams.frame->GetSkipSides();
+ nsRect paintBorderArea = BoxDecorationRectForBackground(
+ aParams.frame, aParams.borderArea, skipSides, &aBorder);
+ nsRect clipBorderArea = BoxDecorationRectForBorder(
+ aParams.frame, aParams.borderArea, skipSides, &aBorder);
+
+ ImgDrawResult result = ImgDrawResult::SUCCESS;
+ StyleGeometryBox currentBackgroundClip = StyleGeometryBox::BorderBox;
+ const bool drawAllLayers = (aParams.layer < 0);
+ uint32_t count = drawAllLayers
+ ? layers.mImageCount // iterate all image layers.
+ : layers.mImageCount -
+ aParams.layer; // iterate from the bottom layer to
+ // the 'aParams.layer-th' layer.
+ NS_FOR_VISIBLE_IMAGE_LAYERS_BACK_TO_FRONT_WITH_RANGE(
+ i, layers, layers.mImageCount - 1, count) {
+ // NOTE: no Save() yet, we do that later by calling autoSR.EnsureSaved(ctx)
+ // in the cases we need it.
+ gfxContextAutoSaveRestore autoSR;
+ const nsStyleImageLayers::Layer& layer = layers.mLayers[i];
+
+ ImageLayerClipState currentLayerClipState = clipState;
+ if (!aParams.bgClipRect) {
+ bool isBottomLayer = (i == layers.mImageCount - 1);
+ if (currentBackgroundClip != layer.mClip || isBottomLayer) {
+ currentBackgroundClip = layer.mClip;
+ if (!isBottomLayer) {
+ currentLayerClipState = {};
+ // For the bottom layer, we already called GetImageLayerClip above
+ // and it stored its results in clipState.
+ GetImageLayerClip(layer, aParams.frame, aBorder, aParams.borderArea,
+ aParams.dirtyRect,
+ (aParams.paintFlags & PAINTBG_WILL_PAINT_BORDER),
+ appUnitsPerPixel, &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,
+ currentLayerClipState.mBGClipArea, layer, nullptr);
+ result &= state.mImageRenderer.PrepareResult();
+
+ // Skip the layer painting code if we found the dirty region is empty.
+ if (currentLayerClipState.mDirtyRectInDevPx.IsEmpty()) {
+ continue;
+ }
+
+ if (!state.mFillArea.IsEmpty()) {
+ CompositionOp co = DetermineCompositionOp(aParams, layers, i);
+ if (co != CompositionOp::OP_OVER) {
+ NS_ASSERTION(aRenderingCtx.CurrentOp() == CompositionOp::OP_OVER,
+ "It is assumed the initial op is OP_OVER, when it is "
+ "restored later");
+ aRenderingCtx.SetOp(co);
+ }
+
+ result &= state.mImageRenderer.DrawLayer(
+ &aParams.presCtx, aRenderingCtx, state.mDestArea, state.mFillArea,
+ state.mAnchor + paintBorderArea.TopLeft(),
+ currentLayerClipState.mDirtyRectInAppUnits, state.mRepeatSize,
+ aParams.opacity);
+
+ if (co != CompositionOp::OP_OVER) {
+ aRenderingCtx.SetOp(CompositionOp::OP_OVER);
+ }
+ }
+ }
+
+ return result;
+}
+
+ImgDrawResult
+nsCSSRendering::BuildWebRenderDisplayItemsForStyleImageLayerWithSC(
+ const PaintBGParams& aParams, mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const mozilla::layers::StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager, nsDisplayItem* aItem,
+ ComputedStyle* aBackgroundSC, const nsStyleBorder& aBorder) {
+ MOZ_ASSERT(!(aParams.paintFlags & PAINTBG_MASK_IMAGE));
+
+ nscoord appUnitsPerPixel = aParams.presCtx.AppUnitsPerDevPixel();
+ ImageLayerClipState clipState;
+
+ clipState.mBGClipArea = *aParams.bgClipRect;
+ clipState.mCustomClip = true;
+ clipState.mHasRoundedCorners = false;
+ SetupDirtyRects(clipState.mBGClipArea, aParams.dirtyRect, appUnitsPerPixel,
+ &clipState.mDirtyRectInAppUnits,
+ &clipState.mDirtyRectInDevPx);
+
+ // Compute the outermost boundary of the area that might be painted.
+ // Same coordinate space as aParams.borderArea & aParams.bgClipRect.
+ Sides skipSides = aParams.frame->GetSkipSides();
+ nsRect paintBorderArea = BoxDecorationRectForBackground(
+ aParams.frame, aParams.borderArea, skipSides, &aBorder);
+
+ const nsStyleImageLayers& layers = aBackgroundSC->StyleBackground()->mImage;
+ const nsStyleImageLayers::Layer& layer = layers.mLayers[aParams.layer];
+
+ // Skip the following layer painting code if we found the dirty region is
+ // empty or the current layer is not selected for drawing.
+ if (clipState.mDirtyRectInDevPx.IsEmpty()) {
+ return ImgDrawResult::SUCCESS;
+ }
+
+ ImgDrawResult result = ImgDrawResult::SUCCESS;
+ nsBackgroundLayerState state =
+ PrepareImageLayer(&aParams.presCtx, aParams.frame, aParams.paintFlags,
+ paintBorderArea, clipState.mBGClipArea, layer, nullptr);
+ result &= state.mImageRenderer.PrepareResult();
+
+ if (!state.mFillArea.IsEmpty()) {
+ result &= state.mImageRenderer.BuildWebRenderDisplayItemsForLayer(
+ &aParams.presCtx, aBuilder, aResources, aSc, aManager, aItem,
+ state.mDestArea, state.mFillArea,
+ state.mAnchor + paintBorderArea.TopLeft(),
+ clipState.mDirtyRectInAppUnits, state.mRepeatSize, aParams.opacity);
+ }
+
+ return result;
+}
+
+nsRect nsCSSRendering::ComputeImageLayerPositioningArea(
+ nsPresContext* aPresContext, nsIFrame* aForFrame, const nsRect& aBorderArea,
+ const nsStyleImageLayers::Layer& aLayer, nsIFrame** aAttachedToFrame,
+ bool* aOutIsTransformedFixed) {
+ // Compute {background|mask} origin area relative to aBorderArea now as we
+ // may need it to compute the effective image size for a CSS gradient.
+ nsRect positionArea;
+
+ StyleGeometryBox layerOrigin = ComputeBoxValue(aForFrame, aLayer.mOrigin);
+
+ if (IsSVGStyleGeometryBox(layerOrigin)) {
+ MOZ_ASSERT(aForFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT));
+ *aAttachedToFrame = aForFrame;
+
+ positionArea = nsLayoutUtils::ComputeGeometryBox(aForFrame, layerOrigin);
+
+ nsPoint toStrokeBoxOffset = nsPoint(0, 0);
+ if (layerOrigin != StyleGeometryBox::StrokeBox) {
+ nsRect strokeBox = nsLayoutUtils::ComputeGeometryBox(
+ aForFrame, StyleGeometryBox::StrokeBox);
+ toStrokeBoxOffset = positionArea.TopLeft() - strokeBox.TopLeft();
+ }
+
+ // For SVG frames, the return value is relative to the stroke box
+ return nsRect(toStrokeBoxOffset, positionArea.Size());
+ }
+
+ MOZ_ASSERT(!aForFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT));
+
+ LayoutFrameType frameType = aForFrame->Type();
+ nsIFrame* geometryFrame = aForFrame;
+ if (MOZ_UNLIKELY(frameType == LayoutFrameType::Scroll &&
+ StyleImageLayerAttachment::Local == aLayer.mAttachment)) {
+ nsIScrollableFrame* scrollableFrame = do_QueryFrame(aForFrame);
+ positionArea = nsRect(scrollableFrame->GetScrolledFrame()->GetPosition()
+ // For the dir=rtl case:
+ + scrollableFrame->GetScrollRange().TopLeft(),
+ scrollableFrame->GetScrolledRect().Size());
+ // The ScrolledRect’s size does not include the borders or scrollbars,
+ // reverse the handling of background-origin
+ // compared to the common case below.
+ if (layerOrigin == StyleGeometryBox::BorderBox) {
+ nsMargin border = geometryFrame->GetUsedBorder();
+ border.ApplySkipSides(geometryFrame->GetSkipSides());
+ positionArea.Inflate(border);
+ positionArea.Inflate(scrollableFrame->GetActualScrollbarSizes());
+ } else if (layerOrigin != StyleGeometryBox::PaddingBox) {
+ nsMargin padding = geometryFrame->GetUsedPadding();
+ padding.ApplySkipSides(geometryFrame->GetSkipSides());
+ positionArea.Deflate(padding);
+ NS_ASSERTION(layerOrigin == StyleGeometryBox::ContentBox,
+ "unknown background-origin value");
+ }
+ *aAttachedToFrame = aForFrame;
+ return positionArea;
+ }
+
+ if (MOZ_UNLIKELY(frameType == LayoutFrameType::Canvas)) {
+ geometryFrame = aForFrame->PrincipalChildList().FirstChild();
+ // geometryFrame might be null if this canvas is a page created
+ // as an overflow container (e.g. the in-flow content has already
+ // finished and this page only displays the continuations of
+ // absolutely positioned content).
+ if (geometryFrame) {
+ positionArea =
+ nsPlaceholderFrame::GetRealFrameFor(geometryFrame)->GetRect();
+ }
+ } else {
+ positionArea = nsRect(nsPoint(0, 0), aBorderArea.Size());
+ }
+
+ // See the comment of StyleGeometryBox::MarginBox.
+ // Hitting this assertion means we decide to turn on margin-box support for
+ // positioned mask from CSS parser and style system. In this case, you
+ // should *inflate* positionArea by the margin returning from
+ // geometryFrame->GetUsedMargin() in the code chunk bellow.
+ MOZ_ASSERT(aLayer.mOrigin != StyleGeometryBox::MarginBox,
+ "StyleGeometryBox::MarginBox rendering is not supported yet.\n");
+
+ // {background|mask} images are tiled over the '{background|mask}-clip' area
+ // but the origin of the tiling is based on the '{background|mask}-origin'
+ // area.
+ if (layerOrigin != StyleGeometryBox::BorderBox && geometryFrame) {
+ nsMargin border = geometryFrame->GetUsedBorder();
+ if (layerOrigin != StyleGeometryBox::PaddingBox) {
+ border += geometryFrame->GetUsedPadding();
+ NS_ASSERTION(layerOrigin == StyleGeometryBox::ContentBox,
+ "unknown background-origin value");
+ }
+ positionArea.Deflate(border);
+ }
+
+ nsIFrame* attachedToFrame = aForFrame;
+ if (StyleImageLayerAttachment::Fixed == aLayer.mAttachment) {
+ // If it's a fixed background attachment, then the image is placed
+ // relative to the viewport, which is the area of the root frame
+ // in a screen context or the page content frame in a print context.
+ attachedToFrame = aPresContext->PresShell()->GetRootFrame();
+ NS_ASSERTION(attachedToFrame, "no root frame");
+ nsIFrame* pageContentFrame = nullptr;
+ if (aPresContext->IsPaginated()) {
+ pageContentFrame = nsLayoutUtils::GetClosestFrameOfType(
+ aForFrame, LayoutFrameType::PageContent);
+ if (pageContentFrame) {
+ attachedToFrame = pageContentFrame;
+ }
+ // else this is an embedded shell and its root frame is what we want
+ }
+
+ // If the background is affected by a transform, treat is as if it
+ // wasn't fixed.
+ if (nsLayoutUtils::IsTransformed(aForFrame, attachedToFrame)) {
+ attachedToFrame = aForFrame;
+ *aOutIsTransformedFixed = true;
+ } else {
+ // Set the background positioning area to the viewport's area
+ // (relative to aForFrame)
+ positionArea = nsRect(-aForFrame->GetOffsetTo(attachedToFrame),
+ attachedToFrame->GetSize());
+
+ if (!pageContentFrame) {
+ // Subtract the size of scrollbars.
+ nsIScrollableFrame* scrollableFrame =
+ aPresContext->PresShell()->GetRootScrollFrameAsScrollable();
+ if (scrollableFrame) {
+ nsMargin scrollbars = scrollableFrame->GetActualScrollbarSizes();
+ positionArea.Deflate(scrollbars);
+ }
+ }
+
+ // If we have the dynamic toolbar, we need to expand the image area to
+ // include the region under the dynamic toolbar, otherwise we will see a
+ // blank space under the toolbar.
+ if (aPresContext->IsRootContentDocumentCrossProcess() &&
+ aPresContext->HasDynamicToolbar()) {
+ positionArea.SizeTo(nsLayoutUtils::ExpandHeightForDynamicToolbar(
+ aPresContext, positionArea.Size()));
+ }
+ }
+ }
+ *aAttachedToFrame = attachedToFrame;
+
+ return positionArea;
+}
+
+/* static */
+nscoord nsCSSRendering::ComputeRoundedSize(nscoord aCurrentSize,
+ nscoord aPositioningSize) {
+ float repeatCount = NS_roundf(float(aPositioningSize) / float(aCurrentSize));
+ if (repeatCount < 1.0f) {
+ return aPositioningSize;
+ }
+ return nscoord(NS_lround(float(aPositioningSize) / repeatCount));
+}
+
+// Apply the CSS image sizing algorithm as it applies to background images.
+// See http://www.w3.org/TR/css3-background/#the-background-size .
+// aIntrinsicSize is the size that the background image 'would like to be'.
+// It can be found by calling nsImageRenderer::ComputeIntrinsicSize.
+static nsSize ComputeDrawnSizeForBackground(
+ const CSSSizeOrRatio& aIntrinsicSize, const nsSize& aBgPositioningArea,
+ const StyleBackgroundSize& aLayerSize, StyleImageLayerRepeat aXRepeat,
+ StyleImageLayerRepeat aYRepeat) {
+ nsSize imageSize;
+
+ // Size is dictated by cover or contain rules.
+ if (aLayerSize.IsContain() || aLayerSize.IsCover()) {
+ nsImageRenderer::FitType fitType = aLayerSize.IsCover()
+ ? nsImageRenderer::COVER
+ : nsImageRenderer::CONTAIN;
+ imageSize = nsImageRenderer::ComputeConstrainedSize(
+ aBgPositioningArea, aIntrinsicSize.mRatio, fitType);
+ } else {
+ MOZ_ASSERT(aLayerSize.IsExplicitSize());
+ const auto& width = aLayerSize.explicit_size.width;
+ const auto& height = aLayerSize.explicit_size.height;
+ // No cover/contain constraint, use default algorithm.
+ CSSSizeOrRatio specifiedSize;
+ if (width.IsLengthPercentage()) {
+ specifiedSize.SetWidth(
+ width.AsLengthPercentage().Resolve(aBgPositioningArea.width));
+ }
+ if (height.IsLengthPercentage()) {
+ specifiedSize.SetHeight(
+ height.AsLengthPercentage().Resolve(aBgPositioningArea.height));
+ }
+
+ imageSize = nsImageRenderer::ComputeConcreteSize(
+ specifiedSize, aIntrinsicSize, aBgPositioningArea);
+ }
+
+ // See https://www.w3.org/TR/css3-background/#background-size .
+ // "If 'background-repeat' is 'round' for one (or both) dimensions, there is a
+ // second
+ // step. The UA must scale the image in that dimension (or both dimensions)
+ // so that it fits a whole number of times in the background positioning
+ // area."
+ // "If 'background-repeat' is 'round' for one dimension only and if
+ // 'background-size'
+ // is 'auto' for the other dimension, then there is a third step: that other
+ // dimension is scaled so that the original aspect ratio is restored."
+ bool isRepeatRoundInBothDimensions =
+ aXRepeat == StyleImageLayerRepeat::Round &&
+ aYRepeat == StyleImageLayerRepeat::Round;
+
+ // Calculate the rounded size only if the background-size computation
+ // returned a correct size for the image.
+ if (imageSize.width && aXRepeat == StyleImageLayerRepeat::Round) {
+ imageSize.width = nsCSSRendering::ComputeRoundedSize(
+ imageSize.width, aBgPositioningArea.width);
+ if (!isRepeatRoundInBothDimensions && aLayerSize.IsExplicitSize() &&
+ aLayerSize.explicit_size.height.IsAuto()) {
+ // Restore intrinsic ratio
+ if (aIntrinsicSize.mRatio) {
+ imageSize.height =
+ aIntrinsicSize.mRatio.Inverted().ApplyTo(imageSize.width);
+ }
+ }
+ }
+
+ // Calculate the rounded size only if the background-size computation
+ // returned a correct size for the image.
+ if (imageSize.height && aYRepeat == StyleImageLayerRepeat::Round) {
+ imageSize.height = nsCSSRendering::ComputeRoundedSize(
+ imageSize.height, aBgPositioningArea.height);
+ if (!isRepeatRoundInBothDimensions && aLayerSize.IsExplicitSize() &&
+ aLayerSize.explicit_size.width.IsAuto()) {
+ // Restore intrinsic ratio
+ if (aIntrinsicSize.mRatio) {
+ imageSize.width = aIntrinsicSize.mRatio.ApplyTo(imageSize.height);
+ }
+ }
+ }
+
+ return imageSize;
+}
+
+/* ComputeSpacedRepeatSize
+ * aImageDimension: the image width/height
+ * aAvailableSpace: the background positioning area width/height
+ * aRepeat: determine whether the image is repeated
+ * Returns the image size plus gap size of app units for use as spacing
+ */
+static nscoord ComputeSpacedRepeatSize(nscoord aImageDimension,
+ nscoord aAvailableSpace, bool& aRepeat) {
+ float ratio = static_cast<float>(aAvailableSpace) / aImageDimension;
+
+ if (ratio < 2.0f) { // If you can't repeat at least twice, then don't repeat.
+ aRepeat = false;
+ return aImageDimension;
+ }
+
+ aRepeat = true;
+ return (aAvailableSpace - aImageDimension) / (NSToIntFloor(ratio) - 1);
+}
+
+/* static */
+nscoord nsCSSRendering::ComputeBorderSpacedRepeatSize(nscoord aImageDimension,
+ nscoord aAvailableSpace,
+ nscoord& aSpace) {
+ int32_t count = aImageDimension ? (aAvailableSpace / aImageDimension) : 0;
+ aSpace = (aAvailableSpace - aImageDimension * count) / (count + 1);
+ return aSpace + aImageDimension;
+}
+
+nsBackgroundLayerState nsCSSRendering::PrepareImageLayer(
+ nsPresContext* aPresContext, nsIFrame* aForFrame, uint32_t aFlags,
+ const nsRect& aBorderArea, const nsRect& aBGClipRect,
+ const nsStyleImageLayers::Layer& aLayer, bool* aOutIsTransformedFixed) {
+ /*
+ * The properties we need to keep in mind when drawing style image
+ * layers are:
+ *
+ * background-image/ mask-image
+ * background-repeat/ mask-repeat
+ * background-attachment
+ * background-position/ mask-position
+ * background-clip/ mask-clip
+ * background-origin/ mask-origin
+ * background-size/ mask-size
+ * background-blend-mode
+ * box-decoration-break
+ * mask-mode
+ * mask-composite
+ *
+ * (background-color applies to the entire element and not to individual
+ * layers, so it is irrelevant to this method.)
+ *
+ * These properties have the following dependencies upon each other when
+ * determining rendering:
+ *
+ * background-image/ mask-image
+ * no dependencies
+ * background-repeat/ mask-repeat
+ * no dependencies
+ * background-attachment
+ * no dependencies
+ * background-position/ mask-position
+ * depends upon background-size/mask-size (for the image's scaled size)
+ * and background-break (for the background positioning area)
+ * background-clip/ mask-clip
+ * no dependencies
+ * background-origin/ mask-origin
+ * depends upon background-attachment (only in the case where that value
+ * is 'fixed')
+ * background-size/ mask-size
+ * depends upon box-decoration-break (for the background positioning area
+ * for resolving percentages), background-image (for the image's intrinsic
+ * size), background-repeat (if that value is 'round'), and
+ * background-origin (for the background painting area, when
+ * background-repeat is 'round')
+ * background-blend-mode
+ * no dependencies
+ * mask-mode
+ * no dependencies
+ * mask-composite
+ * no dependencies
+ * box-decoration-break
+ * no dependencies
+ *
+ * As a result of only-if dependencies we don't strictly do a topological
+ * sort of the above properties when processing, but it's pretty close to one:
+ *
+ * background-clip/mask-clip (by caller)
+ * background-image/ mask-image
+ * box-decoration-break, background-origin/ mask origin
+ * background-attachment (postfix for background-origin if 'fixed')
+ * background-size/ mask-size
+ * background-position/ mask-position
+ * background-repeat/ mask-repeat
+ */
+
+ uint32_t irFlags = 0;
+ if (aFlags & nsCSSRendering::PAINTBG_SYNC_DECODE_IMAGES) {
+ irFlags |= nsImageRenderer::FLAG_SYNC_DECODE_IMAGES;
+ }
+ if (aFlags & nsCSSRendering::PAINTBG_TO_WINDOW) {
+ irFlags |= nsImageRenderer::FLAG_PAINTING_TO_WINDOW;
+ }
+ if (aFlags & nsCSSRendering::PAINTBG_HIGH_QUALITY_SCALING) {
+ irFlags |= nsImageRenderer::FLAG_HIGH_QUALITY_SCALING;
+ }
+ // Only do partial bg image drawing in content documents: non-content
+ // documents are viewed as UI of the browser and a partial draw of a bg image
+ // might look weird in that context.
+ if (StaticPrefs::layout_display_partial_background_images() &&
+ XRE_IsContentProcess() && !aPresContext->IsChrome()) {
+ irFlags |= nsImageRenderer::FLAG_DRAW_PARTIAL_FRAMES;
+ }
+
+ nsBackgroundLayerState state(aForFrame, &aLayer.mImage, irFlags);
+ if (!state.mImageRenderer.PrepareImage()) {
+ // There's no image or it's not ready to be painted.
+ if (aOutIsTransformedFixed &&
+ StyleImageLayerAttachment::Fixed == aLayer.mAttachment) {
+ nsIFrame* attachedToFrame = aPresContext->PresShell()->GetRootFrame();
+ NS_ASSERTION(attachedToFrame, "no root frame");
+ nsIFrame* pageContentFrame = nullptr;
+ if (aPresContext->IsPaginated()) {
+ pageContentFrame = nsLayoutUtils::GetClosestFrameOfType(
+ aForFrame, LayoutFrameType::PageContent);
+ if (pageContentFrame) {
+ attachedToFrame = pageContentFrame;
+ }
+ // else this is an embedded shell and its root frame is what we want
+ }
+
+ *aOutIsTransformedFixed =
+ nsLayoutUtils::IsTransformed(aForFrame, attachedToFrame);
+ }
+ return state;
+ }
+
+ // The frame to which the background is attached
+ nsIFrame* attachedToFrame = aForFrame;
+ // Is the background marked 'fixed', but affected by a transform?
+ bool transformedFixed = false;
+ // Compute background origin area relative to aBorderArea now as we may need
+ // it to compute the effective image size for a CSS gradient.
+ nsRect positionArea = ComputeImageLayerPositioningArea(
+ aPresContext, aForFrame, aBorderArea, aLayer, &attachedToFrame,
+ &transformedFixed);
+ if (aOutIsTransformedFixed) {
+ *aOutIsTransformedFixed = transformedFixed;
+ }
+
+ // For background-attachment:fixed backgrounds, we'll override the area
+ // where the background can be drawn to the viewport.
+ nsRect bgClipRect = aBGClipRect;
+
+ if (StyleImageLayerAttachment::Fixed == aLayer.mAttachment &&
+ !transformedFixed && (aFlags & nsCSSRendering::PAINTBG_TO_WINDOW)) {
+ bgClipRect = positionArea + aBorderArea.TopLeft();
+ }
+
+ StyleImageLayerRepeat repeatX = aLayer.mRepeat.mXRepeat;
+ StyleImageLayerRepeat repeatY = aLayer.mRepeat.mYRepeat;
+
+ // Scale the image as specified for background-size and background-repeat.
+ // Also as required for proper background positioning when background-position
+ // is defined with percentages.
+ CSSSizeOrRatio intrinsicSize = state.mImageRenderer.ComputeIntrinsicSize();
+ nsSize bgPositionSize = positionArea.Size();
+ nsSize imageSize = ComputeDrawnSizeForBackground(
+ intrinsicSize, bgPositionSize, aLayer.mSize, repeatX, repeatY);
+
+ if (imageSize.width <= 0 || imageSize.height <= 0) return state;
+
+ state.mImageRenderer.SetPreferredSize(intrinsicSize, imageSize);
+
+ // Compute the anchor point.
+ //
+ // relative to aBorderArea.TopLeft() (which is where the top-left
+ // of aForFrame's border-box will be rendered)
+ nsPoint imageTopLeft;
+
+ // Compute the position of the background now that the background's size is
+ // determined.
+ nsImageRenderer::ComputeObjectAnchorPoint(aLayer.mPosition, bgPositionSize,
+ imageSize, &imageTopLeft,
+ &state.mAnchor);
+ state.mRepeatSize = imageSize;
+ if (repeatX == StyleImageLayerRepeat::Space) {
+ bool isRepeat;
+ state.mRepeatSize.width = ComputeSpacedRepeatSize(
+ imageSize.width, bgPositionSize.width, isRepeat);
+ if (isRepeat) {
+ imageTopLeft.x = 0;
+ state.mAnchor.x = 0;
+ } else {
+ repeatX = StyleImageLayerRepeat::NoRepeat;
+ }
+ }
+
+ if (repeatY == StyleImageLayerRepeat::Space) {
+ bool isRepeat;
+ state.mRepeatSize.height = ComputeSpacedRepeatSize(
+ imageSize.height, bgPositionSize.height, isRepeat);
+ if (isRepeat) {
+ imageTopLeft.y = 0;
+ state.mAnchor.y = 0;
+ } else {
+ repeatY = StyleImageLayerRepeat::NoRepeat;
+ }
+ }
+
+ imageTopLeft += positionArea.TopLeft();
+ state.mAnchor += positionArea.TopLeft();
+ state.mDestArea = nsRect(imageTopLeft + aBorderArea.TopLeft(), imageSize);
+ state.mFillArea = state.mDestArea;
+
+ ExtendMode repeatMode = ExtendMode::CLAMP;
+ if (repeatX == StyleImageLayerRepeat::Repeat ||
+ repeatX == StyleImageLayerRepeat::Round ||
+ repeatX == StyleImageLayerRepeat::Space) {
+ state.mFillArea.x = bgClipRect.x;
+ state.mFillArea.width = bgClipRect.width;
+ repeatMode = ExtendMode::REPEAT_X;
+ }
+ if (repeatY == StyleImageLayerRepeat::Repeat ||
+ repeatY == StyleImageLayerRepeat::Round ||
+ repeatY == StyleImageLayerRepeat::Space) {
+ state.mFillArea.y = bgClipRect.y;
+ state.mFillArea.height = bgClipRect.height;
+
+ /***
+ * We're repeating on the X axis already,
+ * so if we have to repeat in the Y axis,
+ * we really need to repeat in both directions.
+ */
+ if (repeatMode == ExtendMode::REPEAT_X) {
+ repeatMode = ExtendMode::REPEAT;
+ } else {
+ repeatMode = ExtendMode::REPEAT_Y;
+ }
+ }
+ state.mImageRenderer.SetExtendMode(repeatMode);
+ state.mImageRenderer.SetMaskOp(aLayer.mMaskMode);
+
+ state.mFillArea.IntersectRect(state.mFillArea, bgClipRect);
+
+ return state;
+}
+
+nsRect nsCSSRendering::GetBackgroundLayerRect(
+ nsPresContext* aPresContext, nsIFrame* aForFrame, const nsRect& aBorderArea,
+ const nsRect& aClipRect, const nsStyleImageLayers::Layer& aLayer,
+ uint32_t aFlags) {
+ Sides skipSides = aForFrame->GetSkipSides();
+ nsRect borderArea =
+ BoxDecorationRectForBackground(aForFrame, aBorderArea, skipSides);
+ nsBackgroundLayerState state = PrepareImageLayer(
+ aPresContext, aForFrame, aFlags, borderArea, aClipRect, aLayer);
+ return state.mFillArea;
+}
+
+// Begin table border-collapsing section
+// These functions were written to not disrupt the normal ones and yet satisfy
+// some additional requirements At some point, all functions should be unified
+// to include the additional functionality that these provide
+
+static nscoord RoundIntToPixel(nscoord aValue, nscoord aOneDevPixel,
+ bool aRoundDown = false) {
+ if (aOneDevPixel <= 0) {
+ // We must be rendering to a device that has a resolution greater than
+ // one device pixel!
+ // In that case, aValue is as accurate as it's going to get.
+ return aValue;
+ }
+
+ nscoord halfPixel = NSToCoordRound(aOneDevPixel / 2.0f);
+ nscoord extra = aValue % aOneDevPixel;
+ nscoord finalValue = (!aRoundDown && (extra >= halfPixel))
+ ? aValue + (aOneDevPixel - extra)
+ : aValue - extra;
+ return finalValue;
+}
+
+static nscoord RoundFloatToPixel(float aValue, nscoord aOneDevPixel,
+ bool aRoundDown = false) {
+ return RoundIntToPixel(NSToCoordRound(aValue), aOneDevPixel, aRoundDown);
+}
+
+static void SetPoly(const Rect& aRect, Point* poly) {
+ poly[0].x = aRect.x;
+ poly[0].y = aRect.y;
+ poly[1].x = aRect.x + aRect.width;
+ poly[1].y = aRect.y;
+ poly[2].x = aRect.x + aRect.width;
+ poly[2].y = aRect.y + aRect.height;
+ poly[3].x = aRect.x;
+ poly[3].y = aRect.y + aRect.height;
+}
+
+static void DrawDashedSegment(DrawTarget& aDrawTarget, nsRect aRect,
+ nscoord aDashLength, nscolor aColor,
+ int32_t aAppUnitsPerDevPixel, bool aHorizontal) {
+ ColorPattern color(ToDeviceColor(aColor));
+ DrawOptions drawOptions(1.f, CompositionOp::OP_OVER, AntialiasMode::NONE);
+ StrokeOptions strokeOptions;
+
+ Float dash[2];
+ dash[0] = Float(aDashLength) / aAppUnitsPerDevPixel;
+ dash[1] = dash[0];
+
+ strokeOptions.mDashPattern = dash;
+ strokeOptions.mDashLength = MOZ_ARRAY_LENGTH(dash);
+
+ if (aHorizontal) {
+ nsPoint left = (aRect.TopLeft() + aRect.BottomLeft()) / 2;
+ nsPoint right = (aRect.TopRight() + aRect.BottomRight()) / 2;
+ strokeOptions.mLineWidth = Float(aRect.height) / aAppUnitsPerDevPixel;
+ StrokeLineWithSnapping(left, right, aAppUnitsPerDevPixel, aDrawTarget,
+ color, strokeOptions, drawOptions);
+ } else {
+ nsPoint top = (aRect.TopLeft() + aRect.TopRight()) / 2;
+ nsPoint bottom = (aRect.BottomLeft() + aRect.BottomRight()) / 2;
+ strokeOptions.mLineWidth = Float(aRect.width) / aAppUnitsPerDevPixel;
+ StrokeLineWithSnapping(top, bottom, aAppUnitsPerDevPixel, aDrawTarget,
+ color, strokeOptions, drawOptions);
+ }
+}
+
+static void DrawSolidBorderSegment(
+ DrawTarget& aDrawTarget, nsRect aRect, nscolor aColor,
+ int32_t aAppUnitsPerDevPixel,
+ mozilla::Side aStartBevelSide = mozilla::eSideTop,
+ nscoord aStartBevelOffset = 0,
+ mozilla::Side aEndBevelSide = mozilla::eSideTop,
+ nscoord aEndBevelOffset = 0) {
+ ColorPattern color(ToDeviceColor(aColor));
+ DrawOptions drawOptions(1.f, CompositionOp::OP_OVER, AntialiasMode::NONE);
+
+ nscoord oneDevPixel = NSIntPixelsToAppUnits(1, aAppUnitsPerDevPixel);
+ // We don't need to bevel single pixel borders
+ if ((aRect.width == oneDevPixel) || (aRect.height == oneDevPixel) ||
+ ((0 == aStartBevelOffset) && (0 == aEndBevelOffset))) {
+ // simple rectangle
+ aDrawTarget.FillRect(
+ NSRectToSnappedRect(aRect, aAppUnitsPerDevPixel, aDrawTarget), color,
+ drawOptions);
+ } else {
+ // polygon with beveling
+ Point poly[4];
+ SetPoly(NSRectToSnappedRect(aRect, aAppUnitsPerDevPixel, aDrawTarget),
+ poly);
+
+ Float startBevelOffset =
+ NSAppUnitsToFloatPixels(aStartBevelOffset, aAppUnitsPerDevPixel);
+ switch (aStartBevelSide) {
+ case eSideTop:
+ poly[0].x += startBevelOffset;
+ break;
+ case eSideBottom:
+ poly[3].x += startBevelOffset;
+ break;
+ case eSideRight:
+ poly[1].y += startBevelOffset;
+ break;
+ case eSideLeft:
+ poly[0].y += startBevelOffset;
+ }
+
+ Float endBevelOffset =
+ NSAppUnitsToFloatPixels(aEndBevelOffset, aAppUnitsPerDevPixel);
+ switch (aEndBevelSide) {
+ case eSideTop:
+ poly[1].x -= endBevelOffset;
+ break;
+ case eSideBottom:
+ poly[2].x -= endBevelOffset;
+ break;
+ case eSideRight:
+ poly[2].y -= endBevelOffset;
+ break;
+ case eSideLeft:
+ poly[3].y -= endBevelOffset;
+ }
+
+ RefPtr<PathBuilder> builder = aDrawTarget.CreatePathBuilder();
+ builder->MoveTo(poly[0]);
+ builder->LineTo(poly[1]);
+ builder->LineTo(poly[2]);
+ builder->LineTo(poly[3]);
+ builder->Close();
+ RefPtr<Path> path = builder->Finish();
+ aDrawTarget.Fill(path, color, drawOptions);
+ }
+}
+
+static void GetDashInfo(nscoord aBorderLength, nscoord aDashLength,
+ nscoord aOneDevPixel, int32_t& aNumDashSpaces,
+ nscoord& aStartDashLength, nscoord& aEndDashLength) {
+ aNumDashSpaces = 0;
+ if (aStartDashLength + aDashLength + aEndDashLength >= aBorderLength) {
+ aStartDashLength = aBorderLength;
+ aEndDashLength = 0;
+ } else {
+ aNumDashSpaces =
+ (aBorderLength - aDashLength) / (2 * aDashLength); // round down
+ nscoord extra = aBorderLength - aStartDashLength - aEndDashLength -
+ (((2 * aNumDashSpaces) - 1) * aDashLength);
+ if (extra > 0) {
+ nscoord half = RoundIntToPixel(extra / 2, aOneDevPixel);
+ aStartDashLength += half;
+ aEndDashLength += (extra - half);
+ }
+ }
+}
+
+void nsCSSRendering::DrawTableBorderSegment(
+ DrawTarget& aDrawTarget, StyleBorderStyle aBorderStyle,
+ nscolor aBorderColor, const nsRect& aBorder, int32_t aAppUnitsPerDevPixel,
+ mozilla::Side aStartBevelSide, nscoord aStartBevelOffset,
+ mozilla::Side aEndBevelSide, nscoord aEndBevelOffset) {
+ bool horizontal =
+ ((eSideTop == aStartBevelSide) || (eSideBottom == aStartBevelSide));
+ nscoord oneDevPixel = NSIntPixelsToAppUnits(1, aAppUnitsPerDevPixel);
+
+ if ((oneDevPixel >= aBorder.width) || (oneDevPixel >= aBorder.height) ||
+ (StyleBorderStyle::Dashed == aBorderStyle) ||
+ (StyleBorderStyle::Dotted == aBorderStyle)) {
+ // no beveling for 1 pixel border, dash or dot
+ aStartBevelOffset = 0;
+ aEndBevelOffset = 0;
+ }
+
+ switch (aBorderStyle) {
+ case StyleBorderStyle::None:
+ case StyleBorderStyle::Hidden:
+ // NS_ASSERTION(false, "style of none or hidden");
+ break;
+ case StyleBorderStyle::Dotted:
+ case StyleBorderStyle::Dashed: {
+ nscoord dashLength =
+ (StyleBorderStyle::Dashed == aBorderStyle) ? DASH_LENGTH : DOT_LENGTH;
+ // make the dash length proportional to the border thickness
+ dashLength *= (horizontal) ? aBorder.height : aBorder.width;
+ // make the min dash length for the ends 1/2 the dash length
+ nscoord minDashLength =
+ (StyleBorderStyle::Dashed == aBorderStyle)
+ ? RoundFloatToPixel(((float)dashLength) / 2.0f,
+ aAppUnitsPerDevPixel)
+ : dashLength;
+ minDashLength = std::max(minDashLength, oneDevPixel);
+ nscoord numDashSpaces = 0;
+ nscoord startDashLength = minDashLength;
+ nscoord endDashLength = minDashLength;
+ if (horizontal) {
+ GetDashInfo(aBorder.width, dashLength, aAppUnitsPerDevPixel,
+ numDashSpaces, startDashLength, endDashLength);
+ nsRect rect(aBorder.x, aBorder.y, startDashLength, aBorder.height);
+ DrawSolidBorderSegment(aDrawTarget, rect, aBorderColor,
+ aAppUnitsPerDevPixel);
+
+ rect.x += startDashLength + dashLength;
+ rect.width =
+ aBorder.width - (startDashLength + endDashLength + dashLength);
+ DrawDashedSegment(aDrawTarget, rect, dashLength, aBorderColor,
+ aAppUnitsPerDevPixel, horizontal);
+
+ rect.x += rect.width;
+ rect.width = endDashLength;
+ DrawSolidBorderSegment(aDrawTarget, rect, aBorderColor,
+ aAppUnitsPerDevPixel);
+ } else {
+ GetDashInfo(aBorder.height, dashLength, aAppUnitsPerDevPixel,
+ numDashSpaces, startDashLength, endDashLength);
+ nsRect rect(aBorder.x, aBorder.y, aBorder.width, startDashLength);
+ DrawSolidBorderSegment(aDrawTarget, rect, aBorderColor,
+ aAppUnitsPerDevPixel);
+
+ rect.y += rect.height + dashLength;
+ rect.height =
+ aBorder.height - (startDashLength + endDashLength + dashLength);
+ DrawDashedSegment(aDrawTarget, rect, dashLength, aBorderColor,
+ aAppUnitsPerDevPixel, horizontal);
+
+ rect.y += rect.height;
+ rect.height = endDashLength;
+ DrawSolidBorderSegment(aDrawTarget, rect, aBorderColor,
+ aAppUnitsPerDevPixel);
+ }
+ } break;
+ default:
+ AutoTArray<SolidBeveledBorderSegment, 3> segments;
+ GetTableBorderSolidSegments(
+ segments, aBorderStyle, aBorderColor, aBorder, aAppUnitsPerDevPixel,
+ aStartBevelSide, aStartBevelOffset, aEndBevelSide, aEndBevelOffset);
+ for (const auto& segment : segments) {
+ DrawSolidBorderSegment(
+ aDrawTarget, segment.mRect, segment.mColor, aAppUnitsPerDevPixel,
+ segment.mStartBevel.mSide, segment.mStartBevel.mOffset,
+ segment.mEndBevel.mSide, segment.mEndBevel.mOffset);
+ }
+ break;
+ }
+}
+
+void nsCSSRendering::GetTableBorderSolidSegments(
+ nsTArray<SolidBeveledBorderSegment>& aSegments,
+ StyleBorderStyle aBorderStyle, nscolor aBorderColor, const nsRect& aBorder,
+ int32_t aAppUnitsPerDevPixel, mozilla::Side aStartBevelSide,
+ nscoord aStartBevelOffset, mozilla::Side aEndBevelSide,
+ nscoord aEndBevelOffset) {
+ const bool horizontal =
+ eSideTop == aStartBevelSide || eSideBottom == aStartBevelSide;
+ const nscoord oneDevPixel = NSIntPixelsToAppUnits(1, aAppUnitsPerDevPixel);
+
+ switch (aBorderStyle) {
+ case StyleBorderStyle::None:
+ case StyleBorderStyle::Hidden:
+ return;
+ case StyleBorderStyle::Dotted:
+ case StyleBorderStyle::Dashed:
+ MOZ_ASSERT_UNREACHABLE("Caller should have checked");
+ return;
+ case StyleBorderStyle::Groove:
+ case StyleBorderStyle::Ridge:
+ if ((horizontal && (oneDevPixel >= aBorder.height)) ||
+ (!horizontal && (oneDevPixel >= aBorder.width))) {
+ aSegments.AppendElement(
+ SolidBeveledBorderSegment{aBorder,
+ aBorderColor,
+ {aStartBevelSide, aStartBevelOffset},
+ {aEndBevelSide, aEndBevelOffset}});
+ } else {
+ nscoord startBevel =
+ (aStartBevelOffset > 0)
+ ? RoundFloatToPixel(0.5f * (float)aStartBevelOffset,
+ aAppUnitsPerDevPixel, true)
+ : 0;
+ nscoord endBevel =
+ (aEndBevelOffset > 0)
+ ? RoundFloatToPixel(0.5f * (float)aEndBevelOffset,
+ aAppUnitsPerDevPixel, true)
+ : 0;
+ mozilla::Side ridgeGrooveSide = (horizontal) ? eSideTop : eSideLeft;
+ // FIXME: In theory, this should use the visited-dependent
+ // background color, but I don't care.
+ nscolor bevelColor =
+ MakeBevelColor(ridgeGrooveSide, aBorderStyle, aBorderColor);
+ nsRect rect(aBorder);
+ nscoord half;
+ if (horizontal) { // top, bottom
+ half = RoundFloatToPixel(0.5f * (float)aBorder.height,
+ aAppUnitsPerDevPixel);
+ rect.height = half;
+ if (eSideTop == aStartBevelSide) {
+ rect.x += startBevel;
+ rect.width -= startBevel;
+ }
+ if (eSideTop == aEndBevelSide) {
+ rect.width -= endBevel;
+ }
+ aSegments.AppendElement(
+ SolidBeveledBorderSegment{rect,
+ bevelColor,
+ {aStartBevelSide, startBevel},
+ {aEndBevelSide, endBevel}});
+ } else { // left, right
+ half = RoundFloatToPixel(0.5f * (float)aBorder.width,
+ aAppUnitsPerDevPixel);
+ rect.width = half;
+ if (eSideLeft == aStartBevelSide) {
+ rect.y += startBevel;
+ rect.height -= startBevel;
+ }
+ if (eSideLeft == aEndBevelSide) {
+ rect.height -= endBevel;
+ }
+ aSegments.AppendElement(
+ SolidBeveledBorderSegment{rect,
+ bevelColor,
+ {aStartBevelSide, startBevel},
+ {aEndBevelSide, endBevel}});
+ }
+
+ rect = aBorder;
+ ridgeGrooveSide =
+ (eSideTop == ridgeGrooveSide) ? eSideBottom : eSideRight;
+ // FIXME: In theory, this should use the visited-dependent
+ // background color, but I don't care.
+ bevelColor =
+ MakeBevelColor(ridgeGrooveSide, aBorderStyle, aBorderColor);
+ if (horizontal) {
+ rect.y = rect.y + half;
+ rect.height = aBorder.height - half;
+ if (eSideBottom == aStartBevelSide) {
+ rect.x += startBevel;
+ rect.width -= startBevel;
+ }
+ if (eSideBottom == aEndBevelSide) {
+ rect.width -= endBevel;
+ }
+ aSegments.AppendElement(
+ SolidBeveledBorderSegment{rect,
+ bevelColor,
+ {aStartBevelSide, startBevel},
+ {aEndBevelSide, endBevel}});
+ } else {
+ rect.x = rect.x + half;
+ rect.width = aBorder.width - half;
+ if (eSideRight == aStartBevelSide) {
+ rect.y += aStartBevelOffset - startBevel;
+ rect.height -= startBevel;
+ }
+ if (eSideRight == aEndBevelSide) {
+ rect.height -= endBevel;
+ }
+ aSegments.AppendElement(
+ SolidBeveledBorderSegment{rect,
+ bevelColor,
+ {aStartBevelSide, startBevel},
+ {aEndBevelSide, endBevel}});
+ }
+ }
+ break;
+ case StyleBorderStyle::Double:
+ // We can only do "double" borders if the thickness of the border
+ // is more than 2px. Otherwise, we fall through to painting a
+ // solid border.
+ if ((aBorder.width > 2 * oneDevPixel || horizontal) &&
+ (aBorder.height > 2 * oneDevPixel || !horizontal)) {
+ nscoord startBevel =
+ (aStartBevelOffset > 0)
+ ? RoundFloatToPixel(0.333333f * (float)aStartBevelOffset,
+ aAppUnitsPerDevPixel)
+ : 0;
+ nscoord endBevel =
+ (aEndBevelOffset > 0)
+ ? RoundFloatToPixel(0.333333f * (float)aEndBevelOffset,
+ aAppUnitsPerDevPixel)
+ : 0;
+ if (horizontal) { // top, bottom
+ nscoord thirdHeight = RoundFloatToPixel(
+ 0.333333f * (float)aBorder.height, aAppUnitsPerDevPixel);
+
+ // draw the top line or rect
+ nsRect topRect(aBorder.x, aBorder.y, aBorder.width, thirdHeight);
+ if (eSideTop == aStartBevelSide) {
+ topRect.x += aStartBevelOffset - startBevel;
+ topRect.width -= aStartBevelOffset - startBevel;
+ }
+ if (eSideTop == aEndBevelSide) {
+ topRect.width -= aEndBevelOffset - endBevel;
+ }
+
+ aSegments.AppendElement(
+ SolidBeveledBorderSegment{topRect,
+ aBorderColor,
+ {aStartBevelSide, startBevel},
+ {aEndBevelSide, endBevel}});
+
+ // draw the botom line or rect
+ nscoord heightOffset = aBorder.height - thirdHeight;
+ nsRect bottomRect(aBorder.x, aBorder.y + heightOffset, aBorder.width,
+ aBorder.height - heightOffset);
+ if (eSideBottom == aStartBevelSide) {
+ bottomRect.x += aStartBevelOffset - startBevel;
+ bottomRect.width -= aStartBevelOffset - startBevel;
+ }
+ if (eSideBottom == aEndBevelSide) {
+ bottomRect.width -= aEndBevelOffset - endBevel;
+ }
+ aSegments.AppendElement(
+ SolidBeveledBorderSegment{bottomRect,
+ aBorderColor,
+ {aStartBevelSide, startBevel},
+ {aEndBevelSide, endBevel}});
+ } else { // left, right
+ nscoord thirdWidth = RoundFloatToPixel(
+ 0.333333f * (float)aBorder.width, aAppUnitsPerDevPixel);
+
+ nsRect leftRect(aBorder.x, aBorder.y, thirdWidth, aBorder.height);
+ if (eSideLeft == aStartBevelSide) {
+ leftRect.y += aStartBevelOffset - startBevel;
+ leftRect.height -= aStartBevelOffset - startBevel;
+ }
+ if (eSideLeft == aEndBevelSide) {
+ leftRect.height -= aEndBevelOffset - endBevel;
+ }
+
+ aSegments.AppendElement(
+ SolidBeveledBorderSegment{leftRect,
+ aBorderColor,
+ {aStartBevelSide, startBevel},
+ {aEndBevelSide, endBevel}});
+
+ nscoord widthOffset = aBorder.width - thirdWidth;
+ nsRect rightRect(aBorder.x + widthOffset, aBorder.y,
+ aBorder.width - widthOffset, aBorder.height);
+ if (eSideRight == aStartBevelSide) {
+ rightRect.y += aStartBevelOffset - startBevel;
+ rightRect.height -= aStartBevelOffset - startBevel;
+ }
+ if (eSideRight == aEndBevelSide) {
+ rightRect.height -= aEndBevelOffset - endBevel;
+ }
+ aSegments.AppendElement(
+ SolidBeveledBorderSegment{rightRect,
+ aBorderColor,
+ {aStartBevelSide, startBevel},
+ {aEndBevelSide, endBevel}});
+ }
+ break;
+ }
+ // else fall through to solid
+ [[fallthrough]];
+ case StyleBorderStyle::Solid:
+ aSegments.AppendElement(
+ SolidBeveledBorderSegment{aBorder,
+ aBorderColor,
+ {aStartBevelSide, aStartBevelOffset},
+ {aEndBevelSide, aEndBevelOffset}});
+ break;
+ case StyleBorderStyle::Outset:
+ case StyleBorderStyle::Inset:
+ MOZ_ASSERT_UNREACHABLE(
+ "inset, outset should have been converted to groove, ridge");
+ break;
+ }
+}
+
+// End table border-collapsing section
+
+Rect nsCSSRendering::ExpandPaintingRectForDecorationLine(
+ nsIFrame* aFrame, const StyleTextDecorationStyle aStyle,
+ const Rect& aClippedRect, const Float aICoordInFrame,
+ const Float aCycleLength, bool aVertical) {
+ switch (aStyle) {
+ case StyleTextDecorationStyle::Dotted:
+ case StyleTextDecorationStyle::Dashed:
+ case StyleTextDecorationStyle::Wavy:
+ break;
+ default:
+ NS_ERROR("Invalid style was specified");
+ return aClippedRect;
+ }
+
+ nsBlockFrame* block = nullptr;
+ // Note that when we paint the decoration lines in relative positioned
+ // box, we should paint them like all of the boxes are positioned as static.
+ nscoord framePosInBlockAppUnits = 0;
+ for (nsIFrame* f = aFrame; f; f = f->GetParent()) {
+ block = do_QueryFrame(f);
+ if (block) {
+ break;
+ }
+ framePosInBlockAppUnits +=
+ aVertical ? f->GetNormalPosition().y : f->GetNormalPosition().x;
+ }
+
+ NS_ENSURE_TRUE(block, aClippedRect);
+
+ nsPresContext* pc = aFrame->PresContext();
+ Float framePosInBlock =
+ Float(pc->AppUnitsToGfxUnits(framePosInBlockAppUnits));
+ int32_t rectPosInBlock = int32_t(NS_round(framePosInBlock + aICoordInFrame));
+ int32_t extraStartEdge =
+ rectPosInBlock - (rectPosInBlock / int32_t(aCycleLength) * aCycleLength);
+ Rect rect(aClippedRect);
+ if (aVertical) {
+ rect.y -= extraStartEdge;
+ rect.height += extraStartEdge;
+ } else {
+ rect.x -= extraStartEdge;
+ rect.width += extraStartEdge;
+ }
+ return rect;
+}
+
+// Converts a GfxFont to an SkFont
+// Either returns true if it was successful, or false if something went wrong
+static bool GetSkFontFromGfxFont(DrawTarget& aDrawTarget, gfxFont* aFont,
+ SkFont& aSkFont) {
+ RefPtr<ScaledFont> scaledFont = aFont->GetScaledFont(&aDrawTarget);
+ if (!scaledFont) {
+ return false;
+ }
+
+ ScaledFontBase* fontBase = static_cast<ScaledFontBase*>(scaledFont.get());
+
+ SkTypeface* typeface = fontBase->GetSkTypeface();
+ if (!typeface) {
+ return false;
+ }
+
+ aSkFont = SkFont(sk_ref_sp(typeface), SkFloatToScalar(fontBase->GetSize()));
+ return true;
+}
+
+// Computes data used to position the decoration line within a
+// SkTextBlob, data is returned through aBounds
+static void GetPositioning(
+ const nsCSSRendering::PaintDecorationLineParams& aParams, const Rect& aRect,
+ Float aOneCSSPixel, Float aCenterBaselineOffset, SkScalar aBounds[]) {
+ /**
+ * How Positioning in Skia Works
+ * Take the letter "n" for example
+ * We set textPos as 0, 0
+ * This is represented in Skia like so (not to scale)
+ * ^
+ * -10px | _ __
+ * | | '_ \
+ * -5px | | | | |
+ * y-axis | |_| |_|
+ * (0,0) ----------------------->
+ * | 5px 10px
+ * 5px |
+ * |
+ * 10px |
+ * v
+ * 0 on the x axis is a line that touches the bottom of the n
+ * (0,0) is the bottom left-hand corner of the n character
+ * Moving "up" from the n is going in a negative y direction
+ * Moving "down" from the n is going in a positive y direction
+ *
+ * The intercepts that are returned in this arrangement will be
+ * offset by the original point it starts at. (This happens in
+ * the SkipInk function below).
+ *
+ * In Skia, text MUST be laid out such that the next character
+ * in the RunBuffer is further along the x-axis than the previous
+ * character, otherwise there is undefined/strange behavior.
+ */
+
+ Float rectThickness = aParams.vertical ? aRect.Width() : aRect.Height();
+
+ // the upper and lower lines/edges of the under or over line
+ SkScalar upperLine, lowerLine;
+ if (aParams.decoration == mozilla::StyleTextDecorationLine::OVERLINE) {
+ lowerLine =
+ -aParams.offset + aParams.defaultLineThickness - aCenterBaselineOffset;
+ upperLine = lowerLine - rectThickness;
+ } else {
+ // underlines in vertical text are offset from the center of
+ // the text, and not the baseline
+ // Skia sets the text at it's baseline so we have to offset it
+ // for text in vertical-* writing modes
+ upperLine = -aParams.offset - aCenterBaselineOffset;
+ lowerLine = upperLine + rectThickness;
+ }
+
+ // set up the bounds, add in a little padding to the thickness of the line
+ // (unless the line is <= 1 CSS pixel thick)
+ Float lineThicknessPadding = aParams.lineSize.height > aOneCSSPixel
+ ? 0.25f * aParams.lineSize.height
+ : 0;
+ // don't allow padding greater than 0.75 CSS pixel
+ lineThicknessPadding = std::min(lineThicknessPadding, 0.75f * aOneCSSPixel);
+ aBounds[0] = upperLine - lineThicknessPadding;
+ aBounds[1] = lowerLine + lineThicknessPadding;
+}
+
+// positions an individual glyph according to the given offset
+static SkPoint GlyphPosition(const gfxTextRun::DetailedGlyph& aGlyph,
+ const SkPoint& aTextPos,
+ int32_t aAppUnitsPerDevPixel) {
+ SkPoint point = {aGlyph.mOffset.x, aGlyph.mOffset.y};
+
+ // convert to device pixels
+ point.fX /= (float)aAppUnitsPerDevPixel;
+ point.fY /= (float)aAppUnitsPerDevPixel;
+
+ // add offsets
+ point.fX += aTextPos.fX;
+ point.fY += aTextPos.fY;
+ return point;
+}
+
+// returns a count of all the glyphs that will be rendered
+// excludes ligature continuations, includes the number of individual
+// glyph records. This includes the number of DetailedGlyphs that a single
+// CompressedGlyph record points to. This function is necessary because Skia
+// needs the total length of glyphs to add to it's run buffer before it creates
+// the RunBuffer object, and this cannot be resized later.
+static uint32_t CountAllGlyphs(
+ const gfxTextRun* aTextRun,
+ const gfxTextRun::CompressedGlyph* aCompressedGlyph, uint32_t aStringStart,
+ uint32_t aStringEnd) {
+ uint32_t totalGlyphCount = 0;
+
+ for (const gfxTextRun::CompressedGlyph* cg = aCompressedGlyph + aStringStart;
+ cg < aCompressedGlyph + aStringEnd; ++cg) {
+ totalGlyphCount += cg->IsSimpleGlyph() ? 1 : cg->GetGlyphCount();
+ }
+
+ return totalGlyphCount;
+}
+
+static void AddDetailedGlyph(const SkTextBlobBuilder::RunBuffer& aRunBuffer,
+ const gfxTextRun::DetailedGlyph& aGlyph,
+ int aIndex, float aAppUnitsPerDevPixel,
+ SkPoint& aTextPos) {
+ // add glyph ID to the run buffer at i
+ aRunBuffer.glyphs[aIndex] = aGlyph.mGlyphID;
+
+ // position the glyph correctly using the detailed offsets
+ SkPoint position = GlyphPosition(aGlyph, aTextPos, aAppUnitsPerDevPixel);
+ aRunBuffer.pos[2 * aIndex] = position.fX;
+ aRunBuffer.pos[(2 * aIndex) + 1] = position.fY;
+
+ // increase aTextPos.fx by the advance
+ aTextPos.fX += ((float)aGlyph.mAdvance / aAppUnitsPerDevPixel);
+}
+
+static void AddSimpleGlyph(const SkTextBlobBuilder::RunBuffer& aRunBuffer,
+ const gfxTextRun::CompressedGlyph& aGlyph,
+ int aIndex, float aAppUnitsPerDevPixel,
+ SkPoint& aTextPos) {
+ aRunBuffer.glyphs[aIndex] = aGlyph.GetSimpleGlyph();
+
+ // simple glyphs are offset from 0, so we'll just use textPos
+ aRunBuffer.pos[2 * aIndex] = aTextPos.fX;
+ aRunBuffer.pos[(2 * aIndex) + 1] = aTextPos.fY;
+
+ // increase aTextPos.fX by the advance
+ aTextPos.fX += ((float)aGlyph.GetSimpleAdvance() / aAppUnitsPerDevPixel);
+}
+
+// Sets up a Skia TextBlob of the specified font, text position, and made up of
+// the glyphs between aStringStart and aStringEnd. Handles RTL and LTR text
+// and positions each glyph within the text blob
+static sk_sp<const SkTextBlob> CreateTextBlob(
+ const gfxTextRun* aTextRun,
+ const gfxTextRun::CompressedGlyph* aCompressedGlyph, const SkFont& aFont,
+ const gfxTextRun::PropertyProvider::Spacing* aSpacing,
+ uint32_t aStringStart, uint32_t aStringEnd, float aAppUnitsPerDevPixel,
+ SkPoint& aTextPos, int32_t& aSpacingOffset) {
+ // allocate space for the run buffer, then fill it with the glyphs
+ uint32_t len =
+ CountAllGlyphs(aTextRun, aCompressedGlyph, aStringStart, aStringEnd);
+ if (len <= 0) {
+ return nullptr;
+ }
+
+ SkTextBlobBuilder builder;
+ const SkTextBlobBuilder::RunBuffer& run = builder.allocRunPos(aFont, len);
+
+ // RTL text should be read in by glyph starting at aStringEnd - 1 down until
+ // aStringStart.
+ bool isRTL = aTextRun->IsRightToLeft();
+ uint32_t currIndex = isRTL ? aStringEnd - 1 : aStringStart; // textRun index
+ // currIndex will be advanced by |step| until it reaches |limit|, which is the
+ // final index to be handled (NOT one beyond the final index)
+ int step = isRTL ? -1 : 1;
+ uint32_t limit = isRTL ? aStringStart : aStringEnd - 1;
+
+ uint32_t i = 0; // index into the SkTextBlob we're building
+ while (true) {
+ // Loop exit test is below, just before we update currIndex.
+ aTextPos.fX +=
+ isRTL ? aSpacing[aSpacingOffset].mAfter / aAppUnitsPerDevPixel
+ : aSpacing[aSpacingOffset].mBefore / aAppUnitsPerDevPixel;
+
+ if (aCompressedGlyph[currIndex].IsSimpleGlyph()) {
+ MOZ_ASSERT(i < len, "glyph count error!");
+ AddSimpleGlyph(run, aCompressedGlyph[currIndex], i, aAppUnitsPerDevPixel,
+ aTextPos);
+ i++;
+ } else {
+ // if it's detailed, potentially add multiple into run.glyphs
+ uint32_t count = aCompressedGlyph[currIndex].GetGlyphCount();
+ if (count > 0) {
+ gfxTextRun::DetailedGlyph* detailGlyph =
+ aTextRun->GetDetailedGlyphs(currIndex);
+ for (uint32_t d = isRTL ? count - 1 : 0; count; count--, d += step) {
+ MOZ_ASSERT(i < len, "glyph count error!");
+ AddDetailedGlyph(run, detailGlyph[d], i, aAppUnitsPerDevPixel,
+ aTextPos);
+ i++;
+ }
+ }
+ }
+ aTextPos.fX += isRTL
+ ? aSpacing[aSpacingOffset].mBefore / aAppUnitsPerDevPixel
+ : aSpacing[aSpacingOffset].mAfter / aAppUnitsPerDevPixel;
+ aSpacingOffset += step;
+
+ if (currIndex == limit) {
+ break;
+ }
+ currIndex += step;
+ }
+
+ MOZ_ASSERT(i == len, "glyph count error!");
+
+ return builder.make();
+}
+
+// Given a TextBlob, the bounding lines, and the set of current intercepts this
+// function adds the intercepts for the current TextBlob into the given set of
+// previoulsy calculated intercepts. This set is either of length 0, or a
+// multiple of 2 (since every intersection with a piece of text results in two
+// intercepts: entering/exiting)
+static void GetTextIntercepts(const sk_sp<const SkTextBlob>& aBlob,
+ const SkScalar aBounds[],
+ nsTArray<SkScalar>& aIntercepts) {
+ // It's possible that we'll encounter a Windows exception deep inside
+ // Skia's DirectWrite code while trying to get the intercepts. To avoid
+ // crashing in this case, catch any such exception here and discard the
+ // newly-added (and incompletely filled in) elements.
+ int count = 0;
+ MOZ_SEH_TRY {
+ // https://skia.org/user/api/SkTextBlob_Reference#Text_Blob_Text_Intercepts
+ count = aBlob->getIntercepts(aBounds, nullptr);
+ if (count < 2) {
+ return;
+ }
+ aBlob->getIntercepts(aBounds, aIntercepts.AppendElements(count));
+ }
+ MOZ_SEH_EXCEPT(EXCEPTION_EXECUTE_HANDLER) {
+ gfxCriticalNote << "Exception occurred getting text intercepts";
+ aIntercepts.TruncateLength(aIntercepts.Length() - count);
+ }
+}
+
+// This function, given a set of intercepts that represent each intersection
+// between an under/overline and text, makes a series of calls to
+// PaintDecorationLineInternal that paints a series of clip rects which
+// implement the text-decoration-skip-ink property
+// Logic for where to place each clipped rect, and the length of each rect is
+// included here
+static void SkipInk(nsIFrame* aFrame, DrawTarget& aDrawTarget,
+ const nsCSSRendering::PaintDecorationLineParams& aParams,
+ const nsTArray<SkScalar>& aIntercepts, Float aPadding,
+ Rect& aRect) {
+ nsCSSRendering::PaintDecorationLineParams clipParams = aParams;
+ int length = aIntercepts.Length();
+
+ SkScalar startIntercept = 0;
+ SkScalar endIntercept = 0;
+
+ // keep track of the direction we are drawing the clipped rects in
+ // for sideways text, our intercepts from the first glyph are actually
+ // decreasing (towards the top edge of the page), so we use a negative
+ // direction
+ Float dir = 1.0f;
+ Float lineStart = aParams.vertical ? aParams.pt.y : aParams.pt.x;
+ Float lineEnd = lineStart + aParams.lineSize.width;
+ if (aParams.sidewaysLeft) {
+ dir = -1.0f;
+ std::swap(lineStart, lineEnd);
+ }
+
+ for (int i = 0; i <= length; i += 2) {
+ // handle start/end edge cases and set up general case
+ startIntercept = (i > 0) ? (dir * aIntercepts[i - 1]) + lineStart
+ : lineStart - (dir * aPadding);
+ endIntercept = (i < length) ? (dir * aIntercepts[i]) + lineStart
+ : lineEnd + (dir * aPadding);
+
+ // remove padding at both ends for width
+ // the start of the line is calculated so the padding removes just
+ // enough so that the line starts at its normal position
+ clipParams.lineSize.width =
+ (dir * (endIntercept - startIntercept)) - (2.0 * aPadding);
+
+ // Don't draw decoration lines that have a smaller width than 1, or half
+ // the line-end padding dimension.
+ if (clipParams.lineSize.width < std::max(aPadding * 0.5, 1.0)) {
+ continue;
+ }
+
+ // Start the line right after the intercept's location plus room for
+ // padding; snap the rect edges to device pixels for consistent rendering
+ // of dots across separate fragments of a dotted line.
+ if (aParams.vertical) {
+ clipParams.pt.y = aParams.sidewaysLeft ? endIntercept + aPadding
+ : startIntercept + aPadding;
+ aRect.y = std::floor(clipParams.pt.y + 0.5);
+ aRect.SetBottomEdge(
+ std::floor(clipParams.pt.y + clipParams.lineSize.width + 0.5));
+ } else {
+ clipParams.pt.x = startIntercept + aPadding;
+ aRect.x = std::floor(clipParams.pt.x + 0.5);
+ aRect.SetRightEdge(
+ std::floor(clipParams.pt.x + clipParams.lineSize.width + 0.5));
+ }
+
+ nsCSSRendering::PaintDecorationLineInternal(aFrame, aDrawTarget, clipParams,
+ aRect);
+ }
+}
+
+void nsCSSRendering::PaintDecorationLine(
+ nsIFrame* aFrame, DrawTarget& aDrawTarget,
+ const PaintDecorationLineParams& aParams) {
+ NS_ASSERTION(aParams.style != StyleTextDecorationStyle::None,
+ "aStyle is none");
+
+ Rect rect = ToRect(GetTextDecorationRectInternal(aParams.pt, aParams));
+ if (rect.IsEmpty() || !rect.Intersects(aParams.dirtyRect)) {
+ return;
+ }
+
+ if (aParams.decoration != StyleTextDecorationLine::UNDERLINE &&
+ aParams.decoration != StyleTextDecorationLine::OVERLINE &&
+ aParams.decoration != StyleTextDecorationLine::LINE_THROUGH) {
+ MOZ_ASSERT_UNREACHABLE("Invalid text decoration value");
+ return;
+ }
+
+ // Check if decoration line will skip past ascenders/descenders
+ // text-decoration-skip-ink only applies to overlines/underlines
+ mozilla::StyleTextDecorationSkipInk skipInk =
+ aFrame->StyleText()->mTextDecorationSkipInk;
+ bool skipInkEnabled =
+ skipInk != mozilla::StyleTextDecorationSkipInk::None &&
+ aParams.decoration != StyleTextDecorationLine::LINE_THROUGH;
+
+ if (!skipInkEnabled || aParams.glyphRange.Length() == 0) {
+ PaintDecorationLineInternal(aFrame, aDrawTarget, aParams, rect);
+ return;
+ }
+
+ // check if the frame is a text frame or not
+ nsTextFrame* textFrame = nullptr;
+ if (aFrame->IsTextFrame()) {
+ textFrame = static_cast<nsTextFrame*>(aFrame);
+ } else {
+ PaintDecorationLineInternal(aFrame, aDrawTarget, aParams, rect);
+ return;
+ }
+
+ // get text run and current text offset (for line wrapping)
+ gfxTextRun* textRun =
+ textFrame->GetTextRun(nsTextFrame::TextRunType::eInflated);
+
+ // used for conversions from app units to device pixels
+ int32_t appUnitsPerDevPixel = aFrame->PresContext()->AppUnitsPerDevPixel();
+
+ // pointer to the array of glyphs for this TextRun
+ gfxTextRun::CompressedGlyph* characterGlyphs = textRun->GetCharacterGlyphs();
+
+ // get positioning info
+ SkPoint textPos = {0, aParams.baselineOffset};
+ SkScalar bounds[] = {0, 0};
+ Float oneCSSPixel = aFrame->PresContext()->CSSPixelsToDevPixels(1.0f);
+ if (!textRun->UseCenterBaseline()) {
+ GetPositioning(aParams, rect, oneCSSPixel, 0, bounds);
+ }
+
+ // array for the text intercepts
+ AutoTArray<SkScalar, 256> intercepts;
+
+ // array for spacing data
+ AutoTArray<gfxTextRun::PropertyProvider::Spacing, 64> spacing;
+ spacing.SetLength(aParams.glyphRange.Length());
+ if (aParams.provider != nullptr) {
+ aParams.provider->GetSpacing(aParams.glyphRange, spacing.Elements());
+ }
+
+ // loop through each glyph run
+ // in most cases there will only be one
+ bool isRTL = textRun->IsRightToLeft();
+ int32_t spacingOffset = isRTL ? aParams.glyphRange.Length() - 1 : 0;
+ gfxTextRun::GlyphRunIterator iter(textRun, aParams.glyphRange, isRTL);
+
+ // For any glyph run where we don't actually do skipping, we'll need to
+ // advance the current position by its width.
+ // (For runs we do process, CreateTextBlob will update the position.)
+ auto currentGlyphRunAdvance = [&]() {
+ return textRun->GetAdvanceWidth(
+ gfxTextRun::Range(iter.StringStart(), iter.StringEnd()),
+ aParams.provider) /
+ appUnitsPerDevPixel;
+ };
+
+ for (; !iter.AtEnd(); iter.NextRun()) {
+ if (iter.GlyphRun()->mOrientation ==
+ mozilla::gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT ||
+ (iter.GlyphRun()->mIsCJK &&
+ skipInk == mozilla::StyleTextDecorationSkipInk::Auto)) {
+ // We don't support upright text in vertical modes currently
+ // (see https://bugzilla.mozilla.org/show_bug.cgi?id=1572294),
+ // but we do need to update textPos so that following runs will be
+ // correctly positioned.
+ // We also don't apply skip-ink to CJK text runs because many fonts
+ // have an underline that looks really bad if this is done
+ // (see https://bugzilla.mozilla.org/show_bug.cgi?id=1573249),
+ // when skip-ink is set to 'auto'.
+ textPos.fX += currentGlyphRunAdvance();
+ continue;
+ }
+
+ gfxFont* font = iter.GlyphRun()->mFont;
+ // Don't try to apply skip-ink to 'sbix' fonts like Apple Color Emoji,
+ // because old macOS (10.9) may crash trying to retrieve glyph paths
+ // that don't exist.
+ if (font->GetFontEntry()->HasFontTable(TRUETYPE_TAG('s', 'b', 'i', 'x'))) {
+ textPos.fX += currentGlyphRunAdvance();
+ continue;
+ }
+
+ // get a Skia version of the glyph run's font
+ SkFont skiafont;
+ if (!GetSkFontFromGfxFont(aDrawTarget, font, skiafont)) {
+ PaintDecorationLineInternal(aFrame, aDrawTarget, aParams, rect);
+ return;
+ }
+
+ // Create a text blob with correctly positioned glyphs. This also updates
+ // textPos.fX with the advance of the glyphs.
+ sk_sp<const SkTextBlob> textBlob =
+ CreateTextBlob(textRun, characterGlyphs, skiafont, spacing.Elements(),
+ iter.StringStart(), iter.StringEnd(),
+ (float)appUnitsPerDevPixel, textPos, spacingOffset);
+
+ if (!textBlob) {
+ textPos.fX += currentGlyphRunAdvance();
+ continue;
+ }
+
+ if (textRun->UseCenterBaseline()) {
+ // writing modes that use a center baseline need to be adjusted on a
+ // font-by-font basis since Skia lines up the text on a alphabetic
+ // baseline, but for some vertical-* writing modes the offset is from the
+ // center.
+ gfxFont::Metrics metrics = font->GetMetrics(nsFontMetrics::eHorizontal);
+ Float centerToBaseline = (metrics.emAscent - metrics.emDescent) / 2.0f;
+ GetPositioning(aParams, rect, oneCSSPixel, centerToBaseline, bounds);
+ }
+
+ // compute the text intercepts that need to be skipped
+ GetTextIntercepts(textBlob, bounds, intercepts);
+ }
+ bool needsSkipInk = intercepts.Length() > 0;
+
+ if (needsSkipInk) {
+ // Padding between glyph intercepts and the decoration line: we use the
+ // decoration line thickness, clamped to a minimum of 1px and a maximum
+ // of 0.2em.
+ Float padding =
+ std::min(std::max(aParams.lineSize.height, oneCSSPixel),
+ Float(textRun->GetFontGroup()->GetStyle()->size / 5.0));
+ SkipInk(aFrame, aDrawTarget, aParams, intercepts, padding, rect);
+ } else {
+ PaintDecorationLineInternal(aFrame, aDrawTarget, aParams, rect);
+ }
+}
+
+void nsCSSRendering::PaintDecorationLineInternal(
+ nsIFrame* aFrame, DrawTarget& aDrawTarget,
+ const PaintDecorationLineParams& aParams, Rect aRect) {
+ Float lineThickness = std::max(NS_round(aParams.lineSize.height), 1.0);
+
+ DeviceColor color = ToDeviceColor(aParams.color);
+ ColorPattern colorPat(color);
+ StrokeOptions strokeOptions(lineThickness);
+ DrawOptions drawOptions;
+
+ Float dash[2];
+
+ AutoPopClips autoPopClips(&aDrawTarget);
+
+ mozilla::layout::TextDrawTarget* textDrawer = nullptr;
+ if (aDrawTarget.GetBackendType() == BackendType::WEBRENDER_TEXT) {
+ textDrawer = static_cast<mozilla::layout::TextDrawTarget*>(&aDrawTarget);
+ }
+
+ switch (aParams.style) {
+ case StyleTextDecorationStyle::Solid:
+ case StyleTextDecorationStyle::Double:
+ break;
+ case StyleTextDecorationStyle::Dashed: {
+ autoPopClips.PushClipRect(aRect);
+ Float dashWidth = lineThickness * DOT_LENGTH * DASH_LENGTH;
+ dash[0] = dashWidth;
+ dash[1] = dashWidth;
+ strokeOptions.mDashPattern = dash;
+ strokeOptions.mDashLength = MOZ_ARRAY_LENGTH(dash);
+ strokeOptions.mLineCap = CapStyle::BUTT;
+ aRect = ExpandPaintingRectForDecorationLine(
+ aFrame, aParams.style, aRect, aParams.icoordInFrame, dashWidth * 2,
+ aParams.vertical);
+ // We should continue to draw the last dash even if it is not in the rect.
+ aRect.width += dashWidth;
+ break;
+ }
+ case StyleTextDecorationStyle::Dotted: {
+ autoPopClips.PushClipRect(aRect);
+ Float dashWidth = lineThickness * DOT_LENGTH;
+ if (lineThickness > 2.0) {
+ dash[0] = 0.f;
+ dash[1] = dashWidth * 2.f;
+ strokeOptions.mLineCap = CapStyle::ROUND;
+ } else {
+ dash[0] = dashWidth;
+ dash[1] = dashWidth;
+ }
+ strokeOptions.mDashPattern = dash;
+ strokeOptions.mDashLength = MOZ_ARRAY_LENGTH(dash);
+ aRect = ExpandPaintingRectForDecorationLine(
+ aFrame, aParams.style, aRect, aParams.icoordInFrame, dashWidth * 2,
+ aParams.vertical);
+ // We should continue to draw the last dot even if it is not in the rect.
+ aRect.width += dashWidth;
+ break;
+ }
+ case StyleTextDecorationStyle::Wavy:
+ autoPopClips.PushClipRect(aRect);
+ if (lineThickness > 2.0) {
+ drawOptions.mAntialiasMode = AntialiasMode::SUBPIXEL;
+ } else {
+ // Don't use anti-aliasing here. Because looks like lighter color wavy
+ // line at this case. And probably, users don't think the
+ // non-anti-aliased wavy line is not pretty.
+ drawOptions.mAntialiasMode = AntialiasMode::NONE;
+ }
+ break;
+ default:
+ NS_ERROR("Invalid style value!");
+ return;
+ }
+
+ // The block-direction position should be set to the middle of the line.
+ if (aParams.vertical) {
+ aRect.x += lineThickness / 2;
+ } else {
+ aRect.y += lineThickness / 2;
+ }
+
+ switch (aParams.style) {
+ case StyleTextDecorationStyle::Solid:
+ case StyleTextDecorationStyle::Dotted:
+ case StyleTextDecorationStyle::Dashed: {
+ Point p1 = aRect.TopLeft();
+ Point p2 = aParams.vertical ? aRect.BottomLeft() : aRect.TopRight();
+ if (textDrawer) {
+ textDrawer->AppendDecoration(p1, p2, lineThickness, aParams.vertical,
+ color, aParams.style);
+ } else {
+ aDrawTarget.StrokeLine(p1, p2, colorPat, strokeOptions, drawOptions);
+ }
+ return;
+ }
+ case StyleTextDecorationStyle::Double: {
+ /**
+ * We are drawing double line as:
+ *
+ * +-------------------------------------------+
+ * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| ^
+ * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| | lineThickness
+ * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| v
+ * | |
+ * | |
+ * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| ^
+ * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| | lineThickness
+ * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| v
+ * +-------------------------------------------+
+ */
+ Point p1a = aRect.TopLeft();
+ Point p2a = aParams.vertical ? aRect.BottomLeft() : aRect.TopRight();
+
+ if (aParams.vertical) {
+ aRect.width -= lineThickness;
+ } else {
+ aRect.height -= lineThickness;
+ }
+
+ Point p1b = aParams.vertical ? aRect.TopRight() : aRect.BottomLeft();
+ Point p2b = aRect.BottomRight();
+
+ if (textDrawer) {
+ textDrawer->AppendDecoration(p1a, p2a, lineThickness, aParams.vertical,
+ color, StyleTextDecorationStyle::Solid);
+ textDrawer->AppendDecoration(p1b, p2b, lineThickness, aParams.vertical,
+ color, StyleTextDecorationStyle::Solid);
+ } else {
+ aDrawTarget.StrokeLine(p1a, p2a, colorPat, strokeOptions, drawOptions);
+ aDrawTarget.StrokeLine(p1b, p2b, colorPat, strokeOptions, drawOptions);
+ }
+ return;
+ }
+ case StyleTextDecorationStyle::Wavy: {
+ /**
+ * We are drawing wavy line as:
+ *
+ * P: Path, X: Painted pixel
+ *
+ * +---------------------------------------+
+ * XX|X XXXXXX XXXXXX |
+ * PP|PX XPPPPPPX XPPPPPPX | ^
+ * XX|XPX XPXXXXXXPX XPXXXXXXPX| |
+ * | XPX XPX XPX XPX XP|X |adv
+ * | XPXXXXXXPX XPXXXXXXPX X|PX |
+ * | XPPPPPPX XPPPPPPX |XPX v
+ * | XXXXXX XXXXXX | XX
+ * +---------------------------------------+
+ * <---><---> ^
+ * adv flatLengthAtVertex rightMost
+ *
+ * 1. Always starts from top-left of the drawing area, however, we need
+ * to draw the line from outside of the rect. Because the start
+ * point of the line is not good style if we draw from inside it.
+ * 2. First, draw horizontal line from outside the rect to top-left of
+ * the rect;
+ * 3. Goes down to bottom of the area at 45 degrees.
+ * 4. Slides to right horizontaly, see |flatLengthAtVertex|.
+ * 5. Goes up to top of the area at 45 degrees.
+ * 6. Slides to right horizontaly.
+ * 7. Repeat from 2 until reached to right-most edge of the area.
+ *
+ * In the vertical case, swap horizontal and vertical coordinates and
+ * directions in the above description.
+ */
+
+ Float& rectICoord = aParams.vertical ? aRect.y : aRect.x;
+ Float& rectISize = aParams.vertical ? aRect.height : aRect.width;
+ const Float rectBSize = aParams.vertical ? aRect.width : aRect.height;
+
+ const Float adv = rectBSize - lineThickness;
+ const Float flatLengthAtVertex =
+ std::max((lineThickness - 1.0) * 2.0, 1.0);
+
+ // Align the start of wavy lines to the nearest ancestor block.
+ const Float cycleLength = 2 * (adv + flatLengthAtVertex);
+ aRect = ExpandPaintingRectForDecorationLine(
+ aFrame, aParams.style, aRect, aParams.icoordInFrame, cycleLength,
+ aParams.vertical);
+
+ if (textDrawer) {
+ // Undo attempted centering
+ Float& rectBCoord = aParams.vertical ? aRect.x : aRect.y;
+ rectBCoord -= lineThickness / 2;
+
+ textDrawer->AppendWavyDecoration(aRect, lineThickness, aParams.vertical,
+ color);
+ return;
+ }
+
+ // figure out if we can trim whole cycles from the left and right edges
+ // of the line, to try and avoid creating an unnecessarily long and
+ // complex path (but don't do this for webrender, )
+ const Float dirtyRectICoord =
+ aParams.vertical ? aParams.dirtyRect.y : aParams.dirtyRect.x;
+ int32_t skipCycles = floor((dirtyRectICoord - rectICoord) / cycleLength);
+ if (skipCycles > 0) {
+ rectICoord += skipCycles * cycleLength;
+ rectISize -= skipCycles * cycleLength;
+ }
+
+ rectICoord += lineThickness / 2.0;
+
+ Point pt(aRect.TopLeft());
+ Float& ptICoord = aParams.vertical ? pt.y.value : pt.x.value;
+ Float& ptBCoord = aParams.vertical ? pt.x.value : pt.y.value;
+ if (aParams.vertical) {
+ ptBCoord += adv;
+ }
+ Float iCoordLimit = ptICoord + rectISize + lineThickness;
+
+ const Float dirtyRectIMost = aParams.vertical ? aParams.dirtyRect.YMost()
+ : aParams.dirtyRect.XMost();
+ skipCycles = floor((iCoordLimit - dirtyRectIMost) / cycleLength);
+ if (skipCycles > 0) {
+ iCoordLimit -= skipCycles * cycleLength;
+ }
+
+ RefPtr<PathBuilder> builder = aDrawTarget.CreatePathBuilder();
+ RefPtr<Path> path;
+
+ ptICoord -= lineThickness;
+ builder->MoveTo(pt); // 1
+
+ ptICoord = rectICoord;
+ builder->LineTo(pt); // 2
+
+ // In vertical mode, to go "down" relative to the text we need to
+ // decrease the block coordinate, whereas in horizontal we increase
+ // it. So the sense of this flag is effectively inverted.
+ bool goDown = !aParams.vertical;
+ uint32_t iter = 0;
+ while (ptICoord < iCoordLimit) {
+ if (++iter > 1000) {
+ // stroke the current path and start again, to avoid pathological
+ // behavior in cairo with huge numbers of path segments
+ path = builder->Finish();
+ aDrawTarget.Stroke(path, colorPat, strokeOptions, drawOptions);
+ builder = aDrawTarget.CreatePathBuilder();
+ builder->MoveTo(pt);
+ iter = 0;
+ }
+ ptICoord += adv;
+ ptBCoord += goDown ? adv : -adv;
+
+ builder->LineTo(pt); // 3 and 5
+
+ ptICoord += flatLengthAtVertex;
+ builder->LineTo(pt); // 4 and 6
+
+ goDown = !goDown;
+ }
+ path = builder->Finish();
+ aDrawTarget.Stroke(path, colorPat, strokeOptions, drawOptions);
+ return;
+ }
+ default:
+ NS_ERROR("Invalid style value!");
+ }
+}
+
+Rect nsCSSRendering::DecorationLineToPath(
+ const PaintDecorationLineParams& aParams) {
+ NS_ASSERTION(aParams.style != StyleTextDecorationStyle::None,
+ "aStyle is none");
+
+ Rect path; // To benefit from RVO, we return this from all return points
+
+ Rect rect = ToRect(GetTextDecorationRectInternal(aParams.pt, aParams));
+ if (rect.IsEmpty() || !rect.Intersects(aParams.dirtyRect)) {
+ return path;
+ }
+
+ if (aParams.decoration != StyleTextDecorationLine::UNDERLINE &&
+ aParams.decoration != StyleTextDecorationLine::OVERLINE &&
+ aParams.decoration != StyleTextDecorationLine::LINE_THROUGH) {
+ MOZ_ASSERT_UNREACHABLE("Invalid text decoration value");
+ return path;
+ }
+
+ if (aParams.style != StyleTextDecorationStyle::Solid) {
+ // For the moment, we support only solid text decorations.
+ return path;
+ }
+
+ Float lineThickness = std::max(NS_round(aParams.lineSize.height), 1.0);
+
+ // The block-direction position should be set to the middle of the line.
+ if (aParams.vertical) {
+ rect.x += lineThickness / 2;
+ path = Rect(rect.TopLeft() - Point(lineThickness / 2, 0.0),
+ Size(lineThickness, rect.Height()));
+ } else {
+ rect.y += lineThickness / 2;
+ path = Rect(rect.TopLeft() - Point(0.0, lineThickness / 2),
+ Size(rect.Width(), lineThickness));
+ }
+
+ return path;
+}
+
+nsRect nsCSSRendering::GetTextDecorationRect(
+ nsPresContext* aPresContext, const DecorationRectParams& aParams) {
+ NS_ASSERTION(aPresContext, "aPresContext is null");
+ NS_ASSERTION(aParams.style != StyleTextDecorationStyle::None,
+ "aStyle is none");
+
+ gfxRect rect = GetTextDecorationRectInternal(Point(0, 0), aParams);
+ // The rect values are already rounded to nearest device pixels.
+ nsRect r;
+ r.x = aPresContext->GfxUnitsToAppUnits(rect.X());
+ r.y = aPresContext->GfxUnitsToAppUnits(rect.Y());
+ r.width = aPresContext->GfxUnitsToAppUnits(rect.Width());
+ r.height = aPresContext->GfxUnitsToAppUnits(rect.Height());
+ return r;
+}
+
+gfxRect nsCSSRendering::GetTextDecorationRectInternal(
+ const Point& aPt, const DecorationRectParams& aParams) {
+ NS_ASSERTION(aParams.style <= StyleTextDecorationStyle::Wavy,
+ "Invalid aStyle value");
+
+ if (aParams.style == StyleTextDecorationStyle::None) {
+ return gfxRect(0, 0, 0, 0);
+ }
+
+ bool canLiftUnderline = aParams.descentLimit >= 0.0;
+
+ gfxFloat iCoord = aParams.vertical ? aPt.y : aPt.x;
+ gfxFloat bCoord = aParams.vertical ? aPt.x : aPt.y;
+
+ // 'left' and 'right' are relative to the line, so for vertical writing modes
+ // they will actually become top and bottom of the rendered line.
+ // Similarly, aLineSize.width and .height are actually length and thickness
+ // of the line, which runs horizontally or vertically according to aVertical.
+ const gfxFloat left = floor(iCoord + 0.5),
+ right = floor(iCoord + aParams.lineSize.width + 0.5);
+
+ // We compute |r| as if for a horizontal text run, and then swap vertical
+ // and horizontal coordinates at the end if vertical was requested.
+ gfxRect r(left, 0, right - left, 0);
+
+ gfxFloat lineThickness = NS_round(aParams.lineSize.height);
+ lineThickness = std::max(lineThickness, 1.0);
+ gfxFloat defaultLineThickness = NS_round(aParams.defaultLineThickness);
+ defaultLineThickness = std::max(defaultLineThickness, 1.0);
+
+ gfxFloat ascent = NS_round(aParams.ascent);
+ gfxFloat descentLimit = floor(aParams.descentLimit);
+
+ gfxFloat suggestedMaxRectHeight =
+ std::max(std::min(ascent, descentLimit), 1.0);
+ r.height = lineThickness;
+ if (aParams.style == StyleTextDecorationStyle::Double) {
+ /**
+ * We will draw double line as:
+ *
+ * +-------------------------------------------+
+ * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| ^
+ * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| | lineThickness
+ * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| v
+ * | | ^
+ * | | | gap
+ * | | v
+ * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| ^
+ * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| | lineThickness
+ * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| v
+ * +-------------------------------------------+
+ */
+ gfxFloat gap = NS_round(lineThickness / 2.0);
+ gap = std::max(gap, 1.0);
+ r.height = lineThickness * 2.0 + gap;
+ if (canLiftUnderline) {
+ if (r.Height() > suggestedMaxRectHeight) {
+ // Don't shrink the line height, because the thickness has some meaning.
+ // We can just shrink the gap at this time.
+ r.height = std::max(suggestedMaxRectHeight, lineThickness * 2.0 + 1.0);
+ }
+ }
+ } else if (aParams.style == StyleTextDecorationStyle::Wavy) {
+ /**
+ * We will draw wavy line as:
+ *
+ * +-------------------------------------------+
+ * |XXXXX XXXXXX XXXXXX | ^
+ * |XXXXXX XXXXXXXX XXXXXXXX | | lineThickness
+ * |XXXXXXX XXXXXXXXXX XXXXXXXXXX| v
+ * | XXX XXX XXX XXX XX|
+ * | XXXXXXXXXX XXXXXXXXXX X|
+ * | XXXXXXXX XXXXXXXX |
+ * | XXXXXX XXXXXX |
+ * +-------------------------------------------+
+ */
+ r.height = lineThickness > 2.0 ? lineThickness * 4.0 : lineThickness * 3.0;
+ if (canLiftUnderline) {
+ if (r.Height() > suggestedMaxRectHeight) {
+ // Don't shrink the line height even if there is not enough space,
+ // because the thickness has some meaning. E.g., the 1px wavy line and
+ // 2px wavy line can be used for different meaning in IME selections
+ // at same time.
+ r.height = std::max(suggestedMaxRectHeight, lineThickness * 2.0);
+ }
+ }
+ }
+
+ gfxFloat baseline = floor(bCoord + aParams.ascent + 0.5);
+
+ // Calculate adjusted offset based on writing-mode/orientation and thickness
+ // of decoration line. The input value aParams.offset is the nominal position
+ // (offset from baseline) where we would draw a single, infinitely-thin line;
+ // but for a wavy or double line, we'll need to move the bounding rect of the
+ // decoration outwards from the baseline so that an underline remains below
+ // the glyphs, and an overline above them, despite the increased block-dir
+ // extent of the decoration.
+ //
+ // So adjustments by r.Height() are used to make the wider line styles (wavy
+ // and double) "grow" in the appropriate direction compared to the basic
+ // single line.
+ //
+ // Note that at this point, the decoration rect is being calculated in line-
+ // relative coordinates, where 'x' is line-rightwards, and 'y' is line-
+ // upwards. We'll swap them to be physical coords at the end.
+ gfxFloat offset = 0.0;
+
+ if (aParams.decoration == StyleTextDecorationLine::UNDERLINE) {
+ offset = aParams.offset;
+ if (canLiftUnderline) {
+ if (descentLimit < -offset + r.Height()) {
+ // If we can ignore the offset and the decoration line is overflowing,
+ // we should align the bottom edge of the decoration line rect if it's
+ // possible. Otherwise, we should lift up the top edge of the rect as
+ // far as possible.
+ gfxFloat offsetBottomAligned = -descentLimit + r.Height();
+ gfxFloat offsetTopAligned = 0.0;
+ offset = std::min(offsetBottomAligned, offsetTopAligned);
+ }
+ }
+ } else if (aParams.decoration == StyleTextDecorationLine::OVERLINE) {
+ // For overline, we adjust the offset by defaultlineThickness (the default
+ // thickness of a single decoration line) because empirically it looks
+ // better to draw the overline just inside rather than outside the font's
+ // ascent, which is what nsTextFrame passes as aParams.offset (as fonts
+ // don't provide an explicit overline-offset).
+ offset = aParams.offset - defaultLineThickness + r.Height();
+ } else if (aParams.decoration == StyleTextDecorationLine::LINE_THROUGH) {
+ // To maintain a consistent mid-point for line-through decorations,
+ // we adjust the offset by half of the decoration rect's height.
+ gfxFloat extra = floor(r.Height() / 2.0 + 0.5);
+ extra = std::max(extra, lineThickness);
+ // computes offset for when user specifies a decoration width since
+ // aParams.offset is derived from the font metric's line height
+ gfxFloat decorationThicknessOffset =
+ (lineThickness - defaultLineThickness) / 2.0;
+ offset = aParams.offset - lineThickness + extra + decorationThicknessOffset;
+ } else {
+ MOZ_ASSERT_UNREACHABLE("Invalid text decoration value");
+ }
+
+ // Convert line-relative coordinate system (x = line-right, y = line-up)
+ // to physical coords, and move the decoration rect to the calculated
+ // offset from baseline.
+ if (aParams.vertical) {
+ std::swap(r.x, r.y);
+ std::swap(r.width, r.height);
+ // line-upwards in vertical mode = physical-right, so we /add/ offset
+ // to baseline. Except in sideways-lr mode, where line-upwards will be
+ // physical leftwards.
+ if (aParams.sidewaysLeft) {
+ r.x = baseline - floor(offset + 0.5);
+ } else {
+ r.x = baseline + floor(offset - r.Width() + 0.5);
+ }
+ } else {
+ // line-upwards in horizontal mode = physical-up, but our physical coord
+ // system works downwards, so we /subtract/ offset from baseline.
+ r.y = baseline - floor(offset + 0.5);
+ }
+
+ return r;
+}
+
+#define MAX_BLUR_RADIUS 300
+#define MAX_SPREAD_RADIUS 50
+
+static inline gfxPoint ComputeBlurStdDev(nscoord aBlurRadius,
+ int32_t aAppUnitsPerDevPixel,
+ gfxFloat aScaleX, gfxFloat aScaleY) {
+ // http://dev.w3.org/csswg/css3-background/#box-shadow says that the
+ // standard deviation of the blur should be half the given blur value.
+ gfxFloat blurStdDev = gfxFloat(aBlurRadius) / gfxFloat(aAppUnitsPerDevPixel);
+
+ return gfxPoint(
+ std::min((blurStdDev * aScaleX), gfxFloat(MAX_BLUR_RADIUS)) / 2.0,
+ std::min((blurStdDev * aScaleY), gfxFloat(MAX_BLUR_RADIUS)) / 2.0);
+}
+
+static inline IntSize ComputeBlurRadius(nscoord aBlurRadius,
+ int32_t aAppUnitsPerDevPixel,
+ gfxFloat aScaleX = 1.0,
+ gfxFloat aScaleY = 1.0) {
+ gfxPoint scaledBlurStdDev =
+ ComputeBlurStdDev(aBlurRadius, aAppUnitsPerDevPixel, aScaleX, aScaleY);
+ return gfxAlphaBoxBlur::CalculateBlurRadius(scaledBlurStdDev);
+}
+
+// -----
+// nsContextBoxBlur
+// -----
+gfxContext* nsContextBoxBlur::Init(const nsRect& aRect, nscoord aSpreadRadius,
+ nscoord aBlurRadius,
+ int32_t aAppUnitsPerDevPixel,
+ gfxContext* aDestinationCtx,
+ const nsRect& aDirtyRect,
+ const gfxRect* aSkipRect, uint32_t aFlags) {
+ if (aRect.IsEmpty()) {
+ mContext = nullptr;
+ return nullptr;
+ }
+
+ IntSize blurRadius;
+ IntSize spreadRadius;
+ GetBlurAndSpreadRadius(aDestinationCtx->GetDrawTarget(), aAppUnitsPerDevPixel,
+ aBlurRadius, aSpreadRadius, blurRadius, spreadRadius);
+
+ mDestinationCtx = aDestinationCtx;
+
+ // If not blurring, draw directly onto the destination device
+ if (blurRadius.width <= 0 && blurRadius.height <= 0 &&
+ spreadRadius.width <= 0 && spreadRadius.height <= 0 &&
+ !(aFlags & FORCE_MASK)) {
+ mContext = aDestinationCtx;
+ return mContext;
+ }
+
+ // Convert from app units to device pixels
+ gfxRect rect = nsLayoutUtils::RectToGfxRect(aRect, aAppUnitsPerDevPixel);
+
+ gfxRect dirtyRect =
+ nsLayoutUtils::RectToGfxRect(aDirtyRect, aAppUnitsPerDevPixel);
+ dirtyRect.RoundOut();
+
+ gfxMatrix transform = aDestinationCtx->CurrentMatrixDouble();
+ rect = transform.TransformBounds(rect);
+
+ mPreTransformed = !transform.IsIdentity();
+
+ // Create the temporary surface for blurring
+ dirtyRect = transform.TransformBounds(dirtyRect);
+ bool useHardwareAccel = !(aFlags & DISABLE_HARDWARE_ACCELERATION_BLUR);
+ if (aSkipRect) {
+ gfxRect skipRect = transform.TransformBounds(*aSkipRect);
+ mOwnedContext =
+ mAlphaBoxBlur.Init(aDestinationCtx, rect, spreadRadius, blurRadius,
+ &dirtyRect, &skipRect, useHardwareAccel);
+ } else {
+ mOwnedContext =
+ mAlphaBoxBlur.Init(aDestinationCtx, rect, spreadRadius, blurRadius,
+ &dirtyRect, nullptr, useHardwareAccel);
+ }
+ mContext = mOwnedContext.get();
+
+ if (mContext) {
+ // we don't need to blur if skipRect is equal to rect
+ // and mContext will be nullptr
+ mContext->Multiply(transform);
+ }
+ return mContext;
+}
+
+void nsContextBoxBlur::DoPaint() {
+ if (mContext == mDestinationCtx) {
+ return;
+ }
+
+ gfxContextMatrixAutoSaveRestore saveMatrix(mDestinationCtx);
+
+ if (mPreTransformed) {
+ mDestinationCtx->SetMatrix(Matrix());
+ }
+
+ mAlphaBoxBlur.Paint(mDestinationCtx);
+}
+
+gfxContext* nsContextBoxBlur::GetContext() { return mContext; }
+
+/* static */
+nsMargin nsContextBoxBlur::GetBlurRadiusMargin(nscoord aBlurRadius,
+ int32_t aAppUnitsPerDevPixel) {
+ IntSize blurRadius = ComputeBlurRadius(aBlurRadius, aAppUnitsPerDevPixel);
+
+ nsMargin result;
+ result.top = result.bottom = blurRadius.height * aAppUnitsPerDevPixel;
+ result.left = result.right = blurRadius.width * aAppUnitsPerDevPixel;
+ return result;
+}
+
+/* static */
+void nsContextBoxBlur::BlurRectangle(
+ gfxContext* aDestinationCtx, const nsRect& aRect,
+ int32_t aAppUnitsPerDevPixel, RectCornerRadii* aCornerRadii,
+ nscoord aBlurRadius, const sRGBColor& aShadowColor,
+ const nsRect& aDirtyRect, const gfxRect& aSkipRect) {
+ DrawTarget& aDestDrawTarget = *aDestinationCtx->GetDrawTarget();
+
+ if (aRect.IsEmpty()) {
+ return;
+ }
+
+ Rect shadowGfxRect = NSRectToRect(aRect, aAppUnitsPerDevPixel);
+
+ if (aBlurRadius <= 0) {
+ ColorPattern color(ToDeviceColor(aShadowColor));
+ if (aCornerRadii) {
+ RefPtr<Path> roundedRect =
+ MakePathForRoundedRect(aDestDrawTarget, shadowGfxRect, *aCornerRadii);
+ aDestDrawTarget.Fill(roundedRect, color);
+ } else {
+ aDestDrawTarget.FillRect(shadowGfxRect, color);
+ }
+ return;
+ }
+
+ gfxFloat scaleX = 1;
+ gfxFloat scaleY = 1;
+
+ // Do blurs in device space when possible.
+ // Chrome/Skia always does the blurs in device space
+ // and will sometimes get incorrect results (e.g. rotated blurs)
+ gfxMatrix transform = aDestinationCtx->CurrentMatrixDouble();
+ // XXX: we could probably handle negative scales but for now it's easier just
+ // to fallback
+ if (!transform.HasNonAxisAlignedTransform() && transform._11 > 0.0 &&
+ transform._22 > 0.0) {
+ scaleX = transform._11;
+ scaleY = transform._22;
+ aDestinationCtx->SetMatrix(Matrix());
+ } else {
+ transform = gfxMatrix();
+ }
+
+ gfxPoint blurStdDev =
+ ComputeBlurStdDev(aBlurRadius, aAppUnitsPerDevPixel, scaleX, scaleY);
+
+ gfxRect dirtyRect =
+ nsLayoutUtils::RectToGfxRect(aDirtyRect, aAppUnitsPerDevPixel);
+ dirtyRect.RoundOut();
+
+ gfxRect shadowThebesRect =
+ transform.TransformBounds(ThebesRect(shadowGfxRect));
+ dirtyRect = transform.TransformBounds(dirtyRect);
+ gfxRect skipRect = transform.TransformBounds(aSkipRect);
+
+ if (aCornerRadii) {
+ aCornerRadii->Scale(scaleX, scaleY);
+ }
+
+ gfxAlphaBoxBlur::BlurRectangle(aDestinationCtx, shadowThebesRect,
+ aCornerRadii, blurStdDev, aShadowColor,
+ dirtyRect, skipRect);
+}
+
+/* static */
+void nsContextBoxBlur::GetBlurAndSpreadRadius(
+ DrawTarget* aDestDrawTarget, int32_t aAppUnitsPerDevPixel,
+ nscoord aBlurRadius, nscoord aSpreadRadius, IntSize& aOutBlurRadius,
+ IntSize& aOutSpreadRadius, bool aConstrainSpreadRadius) {
+ // Do blurs in device space when possible.
+ // Chrome/Skia always does the blurs in device space
+ // and will sometimes get incorrect results (e.g. rotated blurs)
+ Matrix transform = aDestDrawTarget->GetTransform();
+ // XXX: we could probably handle negative scales but for now it's easier just
+ // to fallback
+ gfxFloat scaleX, scaleY;
+ if (transform.HasNonAxisAlignedTransform() || transform._11 <= 0.0 ||
+ transform._22 <= 0.0) {
+ scaleX = 1;
+ scaleY = 1;
+ } else {
+ scaleX = transform._11;
+ scaleY = transform._22;
+ }
+
+ // compute a large or smaller blur radius
+ aOutBlurRadius =
+ ComputeBlurRadius(aBlurRadius, aAppUnitsPerDevPixel, scaleX, scaleY);
+ aOutSpreadRadius =
+ IntSize(int32_t(aSpreadRadius * scaleX / aAppUnitsPerDevPixel),
+ int32_t(aSpreadRadius * scaleY / aAppUnitsPerDevPixel));
+
+ if (aConstrainSpreadRadius) {
+ aOutSpreadRadius.width =
+ std::min(aOutSpreadRadius.width, int32_t(MAX_SPREAD_RADIUS));
+ aOutSpreadRadius.height =
+ std::min(aOutSpreadRadius.height, int32_t(MAX_SPREAD_RADIUS));
+ }
+}
+
+/* static */
+bool nsContextBoxBlur::InsetBoxBlur(
+ gfxContext* aDestinationCtx, Rect aDestinationRect, Rect aShadowClipRect,
+ sRGBColor& aShadowColor, nscoord aBlurRadiusAppUnits,
+ nscoord aSpreadDistanceAppUnits, int32_t aAppUnitsPerDevPixel,
+ bool aHasBorderRadius, RectCornerRadii& aInnerClipRectRadii, Rect aSkipRect,
+ Point aShadowOffset) {
+ if (aDestinationRect.IsEmpty()) {
+ mContext = nullptr;
+ return false;
+ }
+
+ gfxContextAutoSaveRestore autoRestore(aDestinationCtx);
+
+ IntSize blurRadius;
+ IntSize spreadRadius;
+ // Convert the blur and spread radius to device pixels
+ bool constrainSpreadRadius = false;
+ GetBlurAndSpreadRadius(aDestinationCtx->GetDrawTarget(), aAppUnitsPerDevPixel,
+ aBlurRadiusAppUnits, aSpreadDistanceAppUnits,
+ blurRadius, spreadRadius, constrainSpreadRadius);
+
+ // The blur and spread radius are scaled already, so scale all
+ // input data to the blur. This way, we don't have to scale the min
+ // inset blur to the invert of the dest context, then rescale it back
+ // when we draw to the destination surface.
+ auto scale = aDestinationCtx->CurrentMatrix().ScaleFactors();
+ Matrix transform = aDestinationCtx->CurrentMatrix();
+
+ // XXX: we could probably handle negative scales but for now it's easier just
+ // to fallback
+ if (!transform.HasNonAxisAlignedTransform() && transform._11 > 0.0 &&
+ transform._22 > 0.0) {
+ // If we don't have a rotation, we're pre-transforming all the rects.
+ aDestinationCtx->SetMatrix(Matrix());
+ } else {
+ // Don't touch anything, we have a rotation.
+ transform = Matrix();
+ }
+
+ Rect transformedDestRect = transform.TransformBounds(aDestinationRect);
+ Rect transformedShadowClipRect = transform.TransformBounds(aShadowClipRect);
+ Rect transformedSkipRect = transform.TransformBounds(aSkipRect);
+
+ transformedDestRect.Round();
+ transformedShadowClipRect.Round();
+ transformedSkipRect.RoundIn();
+
+ for (size_t i = 0; i < 4; i++) {
+ aInnerClipRectRadii[i].width =
+ std::floor(scale.xScale * aInnerClipRectRadii[i].width);
+ aInnerClipRectRadii[i].height =
+ std::floor(scale.yScale * aInnerClipRectRadii[i].height);
+ }
+
+ mAlphaBoxBlur.BlurInsetBox(aDestinationCtx, transformedDestRect,
+ transformedShadowClipRect, blurRadius,
+ aShadowColor,
+ aHasBorderRadius ? &aInnerClipRectRadii : nullptr,
+ transformedSkipRect, aShadowOffset);
+ return true;
+}
diff --git a/layout/painting/nsCSSRendering.h b/layout/painting/nsCSSRendering.h
new file mode 100644
index 0000000000..2e813ff1db
--- /dev/null
+++ b/layout/painting/nsCSSRendering.h
@@ -0,0 +1,924 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* utility functions for drawing borders and backgrounds */
+
+#ifndef nsCSSRendering_h___
+#define nsCSSRendering_h___
+
+#include "gfxBlur.h"
+#include "gfxContext.h"
+#include "mozilla/gfx/PathHelpers.h"
+#include "mozilla/gfx/Rect.h"
+#include "mozilla/TypedEnumBits.h"
+#include "nsStyleStruct.h"
+#include "nsIFrame.h"
+#include "nsImageRenderer.h"
+#include "nsCSSRenderingBorders.h"
+#include "gfxTextRun.h"
+
+class gfxContext;
+class nsPresContext;
+
+namespace mozilla {
+
+class ComputedStyle;
+
+namespace gfx {
+struct sRGBColor;
+class DrawTarget;
+} // namespace gfx
+
+namespace layers {
+class ImageContainer;
+class StackingContextHelper;
+class WebRenderParentCommand;
+class WebRenderLayerManager;
+class RenderRootStateManager;
+} // namespace layers
+
+namespace wr {
+class DisplayListBuilder;
+} // namespace wr
+
+enum class PaintBorderFlags : uint8_t { SyncDecodeImages = 1 << 0 };
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(PaintBorderFlags)
+
+} // namespace mozilla
+
+/**
+ * A struct representing all the information needed to paint a background
+ * image to some target, taking into account all CSS background-* properties.
+ * See PrepareImageLayer.
+ */
+struct nsBackgroundLayerState {
+ typedef mozilla::gfx::CompositionOp CompositionOp;
+ typedef mozilla::nsImageRenderer nsImageRenderer;
+
+ /**
+ * @param aFlags some combination of nsCSSRendering::PAINTBG_* flags
+ */
+ nsBackgroundLayerState(nsIFrame* aForFrame, const mozilla::StyleImage* aImage,
+ uint32_t aFlags)
+ : mImageRenderer(aForFrame, aImage, aFlags) {}
+
+ /**
+ * The nsImageRenderer that will be used to draw the background.
+ */
+ nsImageRenderer mImageRenderer;
+ /**
+ * A rectangle that one copy of the image tile is mapped onto. Same
+ * coordinate system as aBorderArea/aBGClipRect passed into
+ * PrepareImageLayer.
+ */
+ nsRect mDestArea;
+ /**
+ * The actual rectangle that should be filled with (complete or partial)
+ * image tiles. Same coordinate system as aBorderArea/aBGClipRect passed into
+ * PrepareImageLayer.
+ */
+ nsRect mFillArea;
+ /**
+ * The anchor point that should be snapped to a pixel corner. Same
+ * coordinate system as aBorderArea/aBGClipRect passed into
+ * PrepareImageLayer.
+ */
+ nsPoint mAnchor;
+ /**
+ * The background-repeat property space keyword computes the
+ * repeat size which is image size plus spacing.
+ */
+ nsSize mRepeatSize;
+};
+
+struct nsCSSRendering {
+ typedef mozilla::gfx::sRGBColor sRGBColor;
+ typedef mozilla::gfx::CompositionOp CompositionOp;
+ typedef mozilla::gfx::DrawTarget DrawTarget;
+ typedef mozilla::gfx::Float Float;
+ typedef mozilla::gfx::Point Point;
+ typedef mozilla::gfx::Rect Rect;
+ typedef mozilla::gfx::Size Size;
+ typedef mozilla::gfx::RectCornerRadii RectCornerRadii;
+ typedef mozilla::layers::WebRenderLayerManager WebRenderLayerManager;
+ typedef mozilla::image::ImgDrawResult ImgDrawResult;
+ typedef nsIFrame::Sides Sides;
+
+ /**
+ * Initialize any static variables used by nsCSSRendering.
+ */
+ static void Init();
+
+ /**
+ * Clean up any static variables used by nsCSSRendering.
+ */
+ static void Shutdown();
+
+ static bool IsBoxDecorationSlice(const nsStyleBorder& aStyleBorder);
+ static nsRect BoxDecorationRectForBorder(
+ nsIFrame* aFrame, const nsRect& aBorderArea, Sides aSkipSides,
+ const nsStyleBorder* aStyleBorder = nullptr);
+ static nsRect BoxDecorationRectForBackground(
+ nsIFrame* aFrame, const nsRect& aBorderArea, Sides aSkipSides,
+ const nsStyleBorder* aStyleBorder = nullptr);
+
+ static bool GetShadowInnerRadii(nsIFrame* aFrame, const nsRect& aFrameArea,
+ RectCornerRadii& aOutInnerRadii);
+ static nsRect GetBoxShadowInnerPaddingRect(nsIFrame* aFrame,
+ const nsRect& aFrameArea);
+ static bool ShouldPaintBoxShadowInner(nsIFrame* aFrame);
+ static void PaintBoxShadowInner(nsPresContext* aPresContext,
+ gfxContext& aRenderingContext,
+ nsIFrame* aForFrame,
+ const nsRect& aFrameArea);
+
+ static bool GetBorderRadii(const nsRect& aFrameRect,
+ const nsRect& aBorderRect, nsIFrame* aFrame,
+ RectCornerRadii& aOutRadii);
+ static nsRect GetShadowRect(const nsRect& aFrameArea, bool aNativeTheme,
+ nsIFrame* aForFrame);
+ static mozilla::gfx::sRGBColor GetShadowColor(
+ const mozilla::StyleSimpleShadow&, nsIFrame* aFrame, float aOpacity);
+ // Returns if the frame has a themed frame.
+ // aMaybeHasBorderRadius will return false if we can early detect
+ // that we don't have a border radius.
+ static bool HasBoxShadowNativeTheme(nsIFrame* aFrame,
+ bool& aMaybeHasBorderRadius);
+ static void PaintBoxShadowOuter(nsPresContext* aPresContext,
+ gfxContext& aRenderingContext,
+ nsIFrame* aForFrame, const nsRect& aFrameArea,
+ const nsRect& aDirtyRect,
+ float aOpacity = 1.0);
+
+ static void ComputePixelRadii(const nscoord* aAppUnitsRadii,
+ nscoord aAppUnitsPerPixel,
+ RectCornerRadii* oBorderRadii);
+
+ /**
+ * Render the border for an element using css rendering rules
+ * for borders. aSkipSides says which sides to skip
+ * when rendering, the default is to skip none.
+ */
+ static ImgDrawResult PaintBorder(
+ nsPresContext* aPresContext, gfxContext& aRenderingContext,
+ nsIFrame* aForFrame, const nsRect& aDirtyRect, const nsRect& aBorderArea,
+ mozilla::ComputedStyle* aStyle, mozilla::PaintBorderFlags aFlags,
+ Sides aSkipSides = Sides());
+
+ /**
+ * Like PaintBorder, but taking an nsStyleBorder argument instead of
+ * getting it from aStyle. aSkipSides says which sides to skip
+ * when rendering, the default is to skip none.
+ */
+ static ImgDrawResult PaintBorderWithStyleBorder(
+ nsPresContext* aPresContext, gfxContext& aRenderingContext,
+ nsIFrame* aForFrame, const nsRect& aDirtyRect, const nsRect& aBorderArea,
+ const nsStyleBorder& aBorderStyle, mozilla::ComputedStyle* aStyle,
+ mozilla::PaintBorderFlags aFlags, Sides aSkipSides = Sides());
+
+ static mozilla::Maybe<nsCSSBorderRenderer> CreateBorderRenderer(
+ nsPresContext* aPresContext, DrawTarget* aDrawTarget, nsIFrame* aForFrame,
+ const nsRect& aDirtyRect, const nsRect& aBorderArea,
+ mozilla::ComputedStyle* aStyle, bool* aOutBorderIsEmpty,
+ Sides aSkipSides = Sides());
+
+ static mozilla::Maybe<nsCSSBorderRenderer>
+ CreateBorderRendererWithStyleBorder(
+ nsPresContext* aPresContext, DrawTarget* aDrawTarget, nsIFrame* aForFrame,
+ const nsRect& aDirtyRect, const nsRect& aBorderArea,
+ const nsStyleBorder& aBorderStyle, mozilla::ComputedStyle* aStyle,
+ bool* aOutBorderIsEmpty, Sides aSkipSides = Sides());
+
+ static mozilla::Maybe<nsCSSBorderRenderer>
+ CreateNullBorderRendererWithStyleBorder(
+ nsPresContext* aPresContext, DrawTarget* aDrawTarget, nsIFrame* aForFrame,
+ const nsRect& aDirtyRect, const nsRect& aBorderArea,
+ const nsStyleBorder& aBorderStyle, mozilla::ComputedStyle* aStyle,
+ bool* aOutBorderIsEmpty, Sides aSkipSides = Sides());
+
+ static mozilla::Maybe<nsCSSBorderRenderer>
+ CreateBorderRendererForNonThemedOutline(nsPresContext* aPresContext,
+ DrawTarget* aDrawTarget,
+ nsIFrame* aForFrame,
+ const nsRect& aDirtyRect,
+ const nsRect& aInnerRect,
+ mozilla::ComputedStyle* aStyle);
+
+ static ImgDrawResult CreateWebRenderCommandsForBorder(
+ mozilla::nsDisplayItem* aItem, nsIFrame* aForFrame,
+ const nsRect& aBorderArea, mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const mozilla::layers::StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager,
+ mozilla::nsDisplayListBuilder* aDisplayListBuilder);
+
+ static void CreateWebRenderCommandsForNullBorder(
+ mozilla::nsDisplayItem* aItem, nsIFrame* aForFrame,
+ const nsRect& aBorderArea, mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const mozilla::layers::StackingContextHelper& aSc,
+ const nsStyleBorder& aStyleBorder);
+
+ static ImgDrawResult CreateWebRenderCommandsForBorderWithStyleBorder(
+ mozilla::nsDisplayItem* aItem, nsIFrame* aForFrame,
+ const nsRect& aBorderArea, mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const mozilla::layers::StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager,
+ mozilla::nsDisplayListBuilder* aDisplayListBuilder,
+ const nsStyleBorder& aStyleBorder);
+
+ /**
+ * Render the outline for an element using css rendering rules for borders.
+ */
+ static void PaintNonThemedOutline(nsPresContext* aPresContext,
+ gfxContext& aRenderingContext,
+ nsIFrame* aForFrame,
+ const nsRect& aDirtyRect,
+ const nsRect& aInnerRect,
+ mozilla::ComputedStyle* aStyle);
+
+ /**
+ * Render keyboard focus on an element.
+ * |aFocusRect| is the outer rectangle of the focused element.
+ * Uses a fixed style equivalent to "1px dotted |aColor|".
+ * Not used for controls, because the native theme may differ.
+ */
+ static void PaintFocus(nsPresContext* aPresContext, DrawTarget* aDrawTarget,
+ const nsRect& aFocusRect, nscolor aColor);
+
+ /**
+ * Render a gradient for an element.
+ * aDest is the rect for a single tile of the gradient on the destination.
+ * aFill is the rect on the destination to be covered by repeated tiling of
+ * the gradient.
+ * aSrc is the part of the gradient to be rendered into a tile (aDest), if
+ * aSrc and aDest are different sizes, the image will be scaled to map aSrc
+ * onto aDest.
+ * aIntrinsicSize is the size of the source gradient.
+ */
+ static void PaintGradient(nsPresContext* aPresContext, gfxContext& aContext,
+ const mozilla::StyleGradient& aGradient,
+ const nsRect& aDirtyRect, const nsRect& aDest,
+ const nsRect& aFill, const nsSize& aRepeatSize,
+ const mozilla::CSSIntRect& aSrc,
+ const nsSize& aIntrinsiceSize,
+ float aOpacity = 1.0);
+
+ /**
+ * Find the frame whose background style should be used to draw the
+ * canvas background. aForFrame must be the frame for the root element
+ * whose background style should be used. This function will return
+ * aForFrame unless the <body> background should be propagated, in
+ * which case we return the frame associated with the <body>'s background.
+ */
+ static nsIFrame* FindBackgroundStyleFrame(nsIFrame* aForFrame);
+
+ /**
+ * Returns the ComputedStyle to be used to paint the background for the given
+ * frame, if its element has a meaningful background. This applies the rules
+ * for propagating backgrounds between BODY, the root element, and the
+ * canvas.
+ *
+ * @return the ComputedStyle (if any) to be used for painting aForFrame's
+ * background.
+ */
+ static mozilla::ComputedStyle* FindBackground(const nsIFrame* aForFrame);
+ static nsIFrame* FindBackgroundFrame(const nsIFrame* aForFrame);
+
+ /**
+ * As FindBackground, but the passed-in frame is known to be a root frame
+ * (returned from nsCSSFrameConstructor::GetRootElementStyleFrame())
+ * and there is always some meaningful background returned.
+ */
+ static mozilla::ComputedStyle* FindRootFrameBackground(nsIFrame* aForFrame);
+
+ /**
+ * Find a non-transparent background color on an ancestor, for various
+ * contrast checks. Note that this only accounts for background-color and
+ * might stop at themed frames (depending on the argument), so it might not be
+ * what you want. Note that if we stop at themed frames we might, in fact, end
+ * up returning a transparent color (but then mIsThemed will be set to true).
+ *
+ * For semi-transparent colors, right now we blend with the default
+ * background-color rather than with all ancestor backgrounds.
+ *
+ * If aPreferBodyToCanvas is true, we prefer the background color of the
+ * <body> frame, even though we found a canvas background, because the body
+ * background color is most likely what will be visible as the background
+ * color of the page, even if the html element has a different background
+ * color which prevents that of the body frame to propagate to the viewport.
+ */
+ struct EffectiveBackgroundColor {
+ nscolor mColor = 0;
+ bool mIsThemed = false;
+ };
+ static EffectiveBackgroundColor FindEffectiveBackgroundColor(
+ nsIFrame* aFrame, bool aStopAtThemed = true,
+ bool aPreferBodyToCanvas = false);
+
+ /**
+ * Determine the background color to draw taking into account print settings.
+ */
+ static nscolor DetermineBackgroundColor(nsPresContext* aPresContext,
+ const mozilla::ComputedStyle* aStyle,
+ nsIFrame* aFrame,
+ bool& aDrawBackgroundImage,
+ bool& aDrawBackgroundColor);
+
+ static nsRect ComputeImageLayerPositioningArea(
+ nsPresContext* aPresContext, nsIFrame* aForFrame,
+ const nsRect& aBorderArea, const nsStyleImageLayers::Layer& aLayer,
+ nsIFrame** aAttachedToFrame, bool* aOutTransformedFixed);
+
+ // Implementation of the formula for computation of background-repeat round
+ // See http://dev.w3.org/csswg/css3-background/#the-background-size
+ // This function returns the adjusted size of the background image.
+ static nscoord ComputeRoundedSize(nscoord aCurrentSize,
+ nscoord aPositioningSize);
+
+ /* ComputeBorderSpacedRepeatSize
+ * aImageDimension: the image width/height
+ * aAvailableSpace: the background positioning area width/height
+ * aSpace: the space between each image
+ * Returns the image size plus gap size of app units for use as spacing
+ */
+ static nscoord ComputeBorderSpacedRepeatSize(nscoord aImageDimension,
+ nscoord aAvailableSpace,
+ nscoord& aSpace);
+
+ static nsBackgroundLayerState PrepareImageLayer(
+ nsPresContext* aPresContext, nsIFrame* aForFrame, uint32_t aFlags,
+ const nsRect& aBorderArea, const nsRect& aBGClipRect,
+ const nsStyleImageLayers::Layer& aLayer,
+ bool* aOutIsTransformedFixed = nullptr);
+
+ struct ImageLayerClipState {
+ nsRect mBGClipArea; // Affected by mClippedRadii
+ nsRect mAdditionalBGClipArea; // Not affected by mClippedRadii
+ nsRect mDirtyRectInAppUnits;
+ gfxRect mDirtyRectInDevPx;
+
+ nscoord mRadii[8];
+ RectCornerRadii mClippedRadii;
+ bool mHasRoundedCorners;
+ bool mHasAdditionalBGClipArea;
+
+ // Whether we are being asked to draw with a caller provided background
+ // clipping area. If this is true we also disable rounded corners.
+ bool mCustomClip;
+
+ ImageLayerClipState()
+ : mHasRoundedCorners(false),
+ mHasAdditionalBGClipArea(false),
+ mCustomClip(false) {
+ memset(mRadii, 0, sizeof(nscoord) * 8);
+ }
+
+ bool IsValid() const;
+ };
+
+ static void GetImageLayerClip(const nsStyleImageLayers::Layer& aLayer,
+ nsIFrame* aForFrame,
+ const nsStyleBorder& aBorder,
+ const nsRect& aBorderArea,
+ const nsRect& aCallerDirtyRect,
+ bool aWillPaintBorder,
+ nscoord aAppUnitsPerPixel,
+ /* out */ ImageLayerClipState* aClipState);
+
+ /**
+ * Render the background for an element using css rendering rules
+ * for backgrounds or mask.
+ */
+ enum {
+ /**
+ * When this flag is passed, the element's nsDisplayBorder will be
+ * painted immediately on top of this background.
+ */
+ PAINTBG_WILL_PAINT_BORDER = 0x01,
+ /**
+ * When this flag is passed, images are synchronously decoded.
+ */
+ PAINTBG_SYNC_DECODE_IMAGES = 0x02,
+ /**
+ * When this flag is passed, painting will go to the screen so we can
+ * take advantage of the fact that it will be clipped to the viewport.
+ */
+ PAINTBG_TO_WINDOW = 0x04,
+ /**
+ * When this flag is passed, painting will read properties of mask-image
+ * style, instead of background-image.
+ */
+ PAINTBG_MASK_IMAGE = 0x08,
+ /**
+ * When this flag is passed, images are downscaled during decode. This
+ * is also implied by PAINTBG_TO_WINDOW.
+ */
+ PAINTBG_HIGH_QUALITY_SCALING = 0x10,
+ };
+
+ struct PaintBGParams {
+ nsPresContext& presCtx;
+ nsRect dirtyRect;
+ nsRect borderArea;
+ nsIFrame* frame;
+ uint32_t paintFlags;
+ nsRect* bgClipRect = nullptr;
+ int32_t layer; // -1 means painting all layers; other
+ // value means painting one specific
+ // layer only.
+ CompositionOp compositionOp;
+ float opacity;
+
+ static PaintBGParams ForAllLayers(nsPresContext& aPresCtx,
+ const nsRect& aDirtyRect,
+ const nsRect& aBorderArea,
+ nsIFrame* aFrame, uint32_t aPaintFlags,
+ float aOpacity = 1.0);
+ static PaintBGParams ForSingleLayer(
+ nsPresContext& aPresCtx, const nsRect& aDirtyRect,
+ const nsRect& aBorderArea, nsIFrame* aFrame, uint32_t aPaintFlags,
+ int32_t aLayer, CompositionOp aCompositionOp = CompositionOp::OP_OVER,
+ float aOpacity = 1.0);
+
+ private:
+ PaintBGParams(nsPresContext& aPresCtx, const nsRect& aDirtyRect,
+ const nsRect& aBorderArea, nsIFrame* aFrame,
+ uint32_t aPaintFlags, int32_t aLayer,
+ CompositionOp aCompositionOp, float aOpacity)
+ : presCtx(aPresCtx),
+ dirtyRect(aDirtyRect),
+ borderArea(aBorderArea),
+ frame(aFrame),
+ paintFlags(aPaintFlags),
+ layer(aLayer),
+ compositionOp(aCompositionOp),
+ opacity(aOpacity) {}
+ };
+
+ static ImgDrawResult PaintStyleImageLayer(const PaintBGParams& aParams,
+ gfxContext& aRenderingCtx);
+
+ /**
+ * Same as |PaintStyleImageLayer|, except using the provided style structs.
+ * This short-circuits the code that ensures that the root element's
+ * {background|mask} is drawn on the canvas.
+ * The aLayer parameter allows you to paint a single layer of the
+ * {background|mask}.
+ * The default value for aLayer, -1, means that all layers will be painted.
+ * The background color will only be painted if the back-most layer is also
+ * being painted and (aParams.paintFlags & PAINTBG_MASK_IMAGE) is false.
+ * aCompositionOp is only respected if a single layer is specified (aLayer !=
+ * -1). If all layers are painted, the image layer's blend mode (or the mask
+ * layer's composition mode) will be used.
+ */
+ static ImgDrawResult PaintStyleImageLayerWithSC(
+ const PaintBGParams& aParams, gfxContext& aRenderingCtx,
+ const mozilla::ComputedStyle* aBackgroundSC,
+ const nsStyleBorder& aBorder);
+
+ static bool CanBuildWebRenderDisplayItemsForStyleImageLayer(
+ WebRenderLayerManager* aManager, nsPresContext& aPresCtx,
+ nsIFrame* aFrame, const nsStyleBackground* aBackgroundStyle,
+ int32_t aLayer, uint32_t aPaintFlags);
+ static ImgDrawResult BuildWebRenderDisplayItemsForStyleImageLayer(
+ const PaintBGParams& aParams, mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const mozilla::layers::StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager,
+ mozilla::nsDisplayItem* aItem);
+
+ static ImgDrawResult BuildWebRenderDisplayItemsForStyleImageLayerWithSC(
+ const PaintBGParams& aParams, mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const mozilla::layers::StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager,
+ mozilla::nsDisplayItem* aItem, mozilla::ComputedStyle* mBackgroundSC,
+ const nsStyleBorder& aBorder);
+
+ /**
+ * Returns the rectangle covered by the given background layer image, taking
+ * into account background positioning, sizing, and repetition, but not
+ * clipping.
+ */
+ static nsRect GetBackgroundLayerRect(nsPresContext* aPresContext,
+ nsIFrame* aForFrame,
+ const nsRect& aBorderArea,
+ const nsRect& aClipRect,
+ const nsStyleImageLayers::Layer& aLayer,
+ uint32_t aFlags);
+
+ /**
+ * Called when we start creating a display list. The frame tree will not
+ * change until a matching EndFrameTreeLocked is called.
+ */
+ static void BeginFrameTreesLocked();
+ /**
+ * Called when we've finished using a display list. When all
+ * BeginFrameTreeLocked calls have been balanced by an EndFrameTreeLocked,
+ * the frame tree may start changing again.
+ */
+ static void EndFrameTreesLocked();
+
+ // Draw a border segment in the table collapsing border model with beveling
+ // corners.
+ static void DrawTableBorderSegment(
+ DrawTarget& aDrawTarget, mozilla::StyleBorderStyle aBorderStyle,
+ nscolor aBorderColor, const nsRect& aBorderRect,
+ int32_t aAppUnitsPerDevPixel, mozilla::Side aStartBevelSide,
+ nscoord aStartBevelOffset, mozilla::Side aEndBevelSide,
+ nscoord aEndBevelOffset);
+
+ // A single border bevel.
+ struct Bevel {
+ mozilla::Side mSide;
+ nscoord mOffset;
+ };
+
+ // A single solid beveled border segment.
+ struct SolidBeveledBorderSegment {
+ nsRect mRect;
+ nscolor mColor;
+ Bevel mStartBevel;
+ Bevel mEndBevel;
+ };
+
+ // Collect the table border segments with beveling. Can't be called with
+ // dashed / dotted borders, since we don't support beveling those.
+ static void GetTableBorderSolidSegments(
+ nsTArray<SolidBeveledBorderSegment>& aSegments,
+ mozilla::StyleBorderStyle aBorderStyle, nscolor aBorderColor,
+ const nsRect& aBorderRect, int32_t aAppUnitsPerDevPixel,
+ mozilla::Side aStartBevelSide, nscoord aStartBevelOffset,
+ mozilla::Side aEndBevelSide, nscoord aEndBevelOffset);
+
+ // NOTE: pt, dirtyRect, lineSize, ascent, offset in the following
+ // structs are non-rounded device pixels, not app units.
+ struct DecorationRectParams {
+ // The width [length] and the height [thickness] of the decoration
+ // line. This is a "logical" size in textRun orientation, so that
+ // for a vertical textrun, width will actually be a physical height;
+ // and conversely, height will be a physical width.
+ Size lineSize;
+ // The default height [thickness] of the line given by the font metrics.
+ // This is used for obtaining the correct offset for the decoration line
+ // when CSS specifies a unique thickness for a text-decoration,
+ // since the offset given by the font is derived from the font metric's
+ // assumed line height
+ Float defaultLineThickness = 0.0f;
+ // The ascent of the text.
+ Float ascent = 0.0f;
+ // The offset of the decoration line from the baseline of the text
+ // (if the value is positive, the line is lifted up).
+ Float offset = 0.0f;
+ // If descentLimit is zero or larger and the underline overflows
+ // from the descent space, the underline should be lifted up as far
+ // as possible. Note that this does not mean the underline never
+ // overflows from this limitation, because if the underline is
+ // positioned to the baseline or upper, it causes unreadability.
+ // Note that if this is zero or larger, the underline rect may be
+ // shrunken if it's possible. Therefore, this value is used for
+ // strikeout line and overline too.
+ Float descentLimit = -1.0f;
+ // Which line will be painted. The value can be
+ // UNDERLINE or OVERLINE or LINE_THROUGH.
+ mozilla::StyleTextDecorationLine decoration =
+ mozilla::StyleTextDecorationLine::UNDERLINE;
+ // The style of the decoration line
+ mozilla::StyleTextDecorationStyle style =
+ mozilla::StyleTextDecorationStyle::None;
+ bool vertical = false;
+ bool sidewaysLeft = false;
+ gfxTextRun::Range glyphRange;
+ gfxTextRun::PropertyProvider* provider;
+ };
+
+ struct PaintDecorationLineParams : DecorationRectParams {
+ // No need to paint outside this rect.
+ Rect dirtyRect;
+ // The top/left edge of the text.
+ Point pt;
+ // The color of the decoration line.
+ nscolor color = NS_RGBA(0, 0, 0, 0);
+ // The distance between the left edge of the given frame and the
+ // position of the text as positioned without offset of the shadow.
+ Float icoordInFrame = 0.0f;
+ // Baseline offset being applied to this text (block-direction adjustment
+ // applied to glyph positions when computing skip-ink intercepts).
+ Float baselineOffset = 0.0f;
+ };
+
+ /**
+ * Function for painting the clipped decoration lines for the text.
+ * Takes into account the rect clipping that occurs when
+ * text-decoration-skip-ink is being used for underlines or overlines
+ *
+ * input:
+ * @param aFrame the frame which needs the decoration line
+ * @param aDrawTarget the target/backend being drawn to
+ * @param aParams the parameters for the decoration line
+ * being drawn
+ * @param aRect the rect representing the decoration line
+ */
+ static void PaintDecorationLineInternal(
+ nsIFrame* aFrame, DrawTarget& aDrawTarget,
+ const PaintDecorationLineParams& aParams, Rect aRect);
+
+ /**
+ * Function for painting the decoration lines for the text.
+ *
+ * input:
+ * @param aFrame the frame which needs the decoration line
+ * @param aDrawTarget the target/backend being drawn to
+ * @param aParams the parameters for the decoration line
+ * being drawn
+ */
+ static void PaintDecorationLine(nsIFrame* aFrame, DrawTarget& aDrawTarget,
+ const PaintDecorationLineParams& aParams);
+
+ /**
+ * Returns a Rect corresponding to the outline of the decoration line for the
+ * given text metrics. Arguments have the same meaning as for
+ * PaintDecorationLine. Currently this only works for solid
+ * decorations; for other decoration styles the returned Rect will be empty.
+ */
+ static Rect DecorationLineToPath(const PaintDecorationLineParams& aParams);
+
+ /**
+ * Function for getting the decoration line rect for the text.
+ * NOTE: aLineSize, aAscent and aOffset are non-rounded device pixels,
+ * not app units.
+ * input:
+ * @param aPresContext
+ * output:
+ * @return the decoration line rect for the input,
+ * the each values are app units.
+ */
+ static nsRect GetTextDecorationRect(nsPresContext* aPresContext,
+ const DecorationRectParams& aParams);
+
+ static CompositionOp GetGFXBlendMode(mozilla::StyleBlend aBlendMode) {
+ switch (aBlendMode) {
+ case mozilla::StyleBlend::Normal:
+ return CompositionOp::OP_OVER;
+ case mozilla::StyleBlend::Multiply:
+ return CompositionOp::OP_MULTIPLY;
+ case mozilla::StyleBlend::Screen:
+ return CompositionOp::OP_SCREEN;
+ case mozilla::StyleBlend::Overlay:
+ return CompositionOp::OP_OVERLAY;
+ case mozilla::StyleBlend::Darken:
+ return CompositionOp::OP_DARKEN;
+ case mozilla::StyleBlend::Lighten:
+ return CompositionOp::OP_LIGHTEN;
+ case mozilla::StyleBlend::ColorDodge:
+ return CompositionOp::OP_COLOR_DODGE;
+ case mozilla::StyleBlend::ColorBurn:
+ return CompositionOp::OP_COLOR_BURN;
+ case mozilla::StyleBlend::HardLight:
+ return CompositionOp::OP_HARD_LIGHT;
+ case mozilla::StyleBlend::SoftLight:
+ return CompositionOp::OP_SOFT_LIGHT;
+ case mozilla::StyleBlend::Difference:
+ return CompositionOp::OP_DIFFERENCE;
+ case mozilla::StyleBlend::Exclusion:
+ return CompositionOp::OP_EXCLUSION;
+ case mozilla::StyleBlend::Hue:
+ return CompositionOp::OP_HUE;
+ case mozilla::StyleBlend::Saturation:
+ return CompositionOp::OP_SATURATION;
+ case mozilla::StyleBlend::Color:
+ return CompositionOp::OP_COLOR;
+ case mozilla::StyleBlend::Luminosity:
+ return CompositionOp::OP_LUMINOSITY;
+ case mozilla::StyleBlend::PlusLighter:
+ return CompositionOp::OP_ADD;
+ default:
+ MOZ_ASSERT(false);
+ return CompositionOp::OP_OVER;
+ }
+ }
+
+ static CompositionOp GetGFXCompositeMode(
+ mozilla::StyleMaskComposite aCompositeMode) {
+ switch (aCompositeMode) {
+ case mozilla::StyleMaskComposite::Add:
+ return CompositionOp::OP_OVER;
+ case mozilla::StyleMaskComposite::Subtract:
+ return CompositionOp::OP_OUT;
+ case mozilla::StyleMaskComposite::Intersect:
+ return CompositionOp::OP_IN;
+ case mozilla::StyleMaskComposite::Exclude:
+ return CompositionOp::OP_XOR;
+ default:
+ MOZ_ASSERT(false);
+ return CompositionOp::OP_OVER;
+ }
+ }
+
+ protected:
+ static gfxRect GetTextDecorationRectInternal(
+ const Point& aPt, const DecorationRectParams& aParams);
+
+ /**
+ * Returns inflated rect for painting a decoration line.
+ * Complex style decoration lines should be painted from leftmost of nearest
+ * ancestor block box because that makes better look of connection of lines
+ * for different nodes. ExpandPaintingRectForDecorationLine() returns
+ * a rect for actual painting rect for the clipped rect.
+ *
+ * input:
+ * @param aFrame the frame which needs the decoration line.
+ * @param aStyle the style of the complex decoration line
+ * @param aClippedRect the clipped rect for the decoration line.
+ * in other words, visible area of the line.
+ * @param aICoordInFrame the distance between inline-start edge of aFrame
+ * and aClippedRect.pos.
+ * @param aCycleLength the width of one cycle of the line style.
+ */
+ static Rect ExpandPaintingRectForDecorationLine(
+ nsIFrame* aFrame, const mozilla::StyleTextDecorationStyle aStyle,
+ const Rect& aClippedRect, const Float aICoordInFrame,
+ const Float aCycleLength, bool aVertical);
+};
+
+/*
+ * nsContextBoxBlur
+ * Creates an 8-bit alpha channel context for callers to draw in, blurs the
+ * contents of that context and applies it as a 1-color mask on a
+ * different existing context. Uses gfxAlphaBoxBlur as its back end.
+ *
+ * You must call Init() first to create a suitable temporary surface to draw
+ * on. You must then draw any desired content onto the given context, then
+ * call DoPaint() to apply the blurred content as a single-color mask. You
+ * can only call Init() once, so objects cannot be reused.
+ *
+ * This is very useful for creating drop shadows or silhouettes.
+ */
+class nsContextBoxBlur {
+ typedef mozilla::gfx::sRGBColor sRGBColor;
+ typedef mozilla::gfx::DrawTarget DrawTarget;
+ typedef mozilla::gfx::RectCornerRadii RectCornerRadii;
+
+ public:
+ enum { FORCE_MASK = 0x01, DISABLE_HARDWARE_ACCELERATION_BLUR = 0x02 };
+ /**
+ * Prepares a gfxContext to draw on. Do not call this twice; if you want
+ * to get the gfxContext again use GetContext().
+ *
+ * @param aRect The coordinates of the surface to create.
+ * All coordinates must be in app units.
+ * This must not include the blur radius, pass
+ * it as the second parameter and everything
+ * is taken care of.
+ *
+ * @param aBlurRadius The blur radius in app units.
+ *
+ * @param aAppUnitsPerDevPixel The number of app units in a device pixel,
+ * for conversion. Most of the time you'll
+ * pass this from the current PresContext if
+ * available.
+ *
+ * @param aDestinationCtx The graphics context to apply the blurred
+ * mask to when you call DoPaint(). Make sure
+ * it is not destroyed before you call
+ * DoPaint(). To set the color of the
+ * resulting blurred graphic mask, you must
+ * set the color on this context before
+ * calling Init().
+ *
+ * @param aDirtyRect The absolute dirty rect in app units. Used to
+ * optimize the temporary surface size and speed
+ * up blur.
+ *
+ * @param aSkipRect An area in device pixels (NOT app units!) to
+ * avoid blurring over, to prevent unnecessary work.
+ *
+ * @param aFlags FORCE_MASK to ensure that the content drawn to
+ * the returned gfxContext is used as a mask, and not drawn directly to
+ * aDestinationCtx.
+ *
+ * @return A blank 8-bit alpha-channel-only graphics context to
+ * draw on, or null on error. Must not be freed. The
+ * context has a device offset applied to it given by
+ * aRect. This means you can use coordinates as if it
+ * were at the desired position at aRect and you don't
+ * need to worry about translating any coordinates to
+ * draw on this temporary surface.
+ *
+ * If aBlurRadius is 0, the returned context is aDestinationCtx and
+ * DoPaint() does nothing, because no blurring is required. Therefore, you
+ * should prepare the destination context as if you were going to draw
+ * directly on it instead of any temporary surface created in this class.
+ */
+ gfxContext* Init(const nsRect& aRect, nscoord aSpreadRadius,
+ nscoord aBlurRadius, int32_t aAppUnitsPerDevPixel,
+ gfxContext* aDestinationCtx, const nsRect& aDirtyRect,
+ const gfxRect* aSkipRect, uint32_t aFlags = 0);
+
+ /**
+ * Does the actual blurring and mask applying. Users of this object *must*
+ * have called Init() first, then have drawn whatever they want to be
+ * blurred onto the internal gfxContext before calling this.
+ */
+ void DoPaint();
+
+ /**
+ * Gets the internal gfxContext at any time. Must not be freed. Avoid
+ * calling this before calling Init() since the context would not be
+ * constructed at that point.
+ */
+ gfxContext* GetContext();
+
+ /**
+ * Get the margin associated with the given blur radius, i.e., the
+ * additional area that might be painted as a result of it. (The
+ * margin for a spread radius is itself, on all sides.)
+ */
+ static nsMargin GetBlurRadiusMargin(nscoord aBlurRadius,
+ int32_t aAppUnitsPerDevPixel);
+
+ /**
+ * Blurs a coloured rectangle onto aDestinationCtx. This is equivalent
+ * to calling Init(), drawing a rectangle onto the returned surface
+ * and then calling DoPaint, but may let us optimize better in the
+ * backend.
+ *
+ * @param aDestinationCtx The destination to blur to.
+ * @param aRect The rectangle to blur in app units.
+ * @param aAppUnitsPerDevPixel The number of app units in a device pixel,
+ * for conversion. Most of the time you'll
+ * pass this from the current PresContext if
+ * available.
+ * @param aCornerRadii Corner radii for aRect, if it is a rounded
+ * rectangle.
+ * @param aBlurRadius The blur radius in app units.
+ * @param aShadowColor The color to draw the blurred shadow.
+ * @param aDirtyRect The absolute dirty rect in app units. Used to
+ * optimize the temporary surface size and speed
+ * up blur.
+ * @param aSkipRect An area in device pixels (NOT app units!) to
+ * avoid blurring over, to prevent unnecessary work.
+ */
+ static void BlurRectangle(gfxContext* aDestinationCtx, const nsRect& aRect,
+ int32_t aAppUnitsPerDevPixel,
+ RectCornerRadii* aCornerRadii, nscoord aBlurRadius,
+ const sRGBColor& aShadowColor,
+ const nsRect& aDirtyRect, const gfxRect& aSkipRect);
+
+ /**
+ * Draws a blurred inset box shadow shape onto the destination surface.
+ * Like BlurRectangle, this is equivalent to calling Init(),
+ * drawing a rectangle onto the returned surface
+ * and then calling DoPaint, but may let us optimize better in the
+ * backend.
+ *
+ * @param aDestinationCtx The destination to blur to.
+ * @param aDestinationRect The rectangle to blur in app units.
+ * @param aShadowClipRect The inside clip rect that creates the path.
+ * @param aShadowColor The color of the blur
+ * @param aBlurRadiusAppUnits The blur radius in app units
+ * @param aSpreadRadiusAppUnits The spread radius in app units.
+ * @param aAppUnitsPerDevPixel The number of app units in a device pixel,
+ * for conversion. Most of the time you'll
+ * pass this from the current PresContext if
+ * available.
+ * @param aHasBorderRadius If this inset box blur has a border radius
+ * @param aInnerClipRectRadii The clip rect radii used for the inside rect's
+ * path.
+ * @param aSkipRect An area in device pixels (NOT app units!) to
+ * avoid blurring over, to prevent unnecessary work.
+ */
+ bool InsetBoxBlur(gfxContext* aDestinationCtx,
+ mozilla::gfx::Rect aDestinationRect,
+ mozilla::gfx::Rect aShadowClipRect,
+ mozilla::gfx::sRGBColor& aShadowColor,
+ nscoord aBlurRadiusAppUnits, nscoord aSpreadRadiusAppUnits,
+ int32_t aAppUnitsPerDevPixel, bool aHasBorderRadius,
+ RectCornerRadii& aInnerClipRectRadii,
+ mozilla::gfx::Rect aSkipRect,
+ mozilla::gfx::Point aShadowOffset);
+
+ protected:
+ static void GetBlurAndSpreadRadius(DrawTarget* aDestDrawTarget,
+ int32_t aAppUnitsPerDevPixel,
+ nscoord aBlurRadius, nscoord aSpreadRadius,
+ mozilla::gfx::IntSize& aOutBlurRadius,
+ mozilla::gfx::IntSize& aOutSpreadRadius,
+ bool aConstrainSpreadRadius = true);
+
+ gfxAlphaBoxBlur mAlphaBoxBlur;
+ mozilla::UniquePtr<gfxContext> mOwnedContext;
+ gfxContext* mContext; // may be either mOwnedContext or mDestinationContext
+ gfxContext* mDestinationCtx;
+
+ /* This is true if the blur already has it's content transformed
+ * by mDestinationCtx's transform */
+ bool mPreTransformed;
+};
+
+#endif /* nsCSSRendering_h___ */
diff --git a/layout/painting/nsCSSRenderingBorders.cpp b/layout/painting/nsCSSRenderingBorders.cpp
new file mode 100644
index 0000000000..3f0bee7f85
--- /dev/null
+++ b/layout/painting/nsCSSRenderingBorders.cpp
@@ -0,0 +1,3891 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsCSSRenderingBorders.h"
+
+#include "gfxUtils.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/Helpers.h"
+#include "mozilla/gfx/PathHelpers.h"
+#include "BorderConsts.h"
+#include "DashedCornerFinder.h"
+#include "DottedCornerFinder.h"
+#include "ImageRegion.h"
+#include "nsLayoutUtils.h"
+#include "nsStyleConsts.h"
+#include "nsContentUtils.h"
+#include "nsCSSColorUtils.h"
+#include "nsCSSRendering.h"
+#include "nsCSSRenderingGradients.h"
+#include "nsDisplayList.h"
+#include "nsExpirationTracker.h"
+#include "nsIScriptError.h"
+#include "nsClassHashtable.h"
+#include "nsPresContext.h"
+#include "nsStyleStruct.h"
+#include "gfx2DGlue.h"
+#include "gfxGradientCache.h"
+#include "mozilla/image/WebRenderImageProvider.h"
+#include "mozilla/layers/StackingContextHelper.h"
+#include "mozilla/layers/RenderRootStateManager.h"
+#include "mozilla/layers/WebRenderLayerManager.h"
+#include "mozilla/ProfilerLabels.h"
+#include "mozilla/Range.h"
+#include <algorithm>
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+using namespace mozilla::image;
+
+#define MAX_COMPOSITE_BORDER_WIDTH LayoutDeviceIntCoord(10000)
+
+/**
+ * nsCSSRendering::PaintBorder
+ * nsCSSRendering::PaintOutline
+ * -> DrawBorders
+ *
+ * DrawBorders
+ * -> Ability to use specialized approach?
+ * |- Draw using specialized function
+ * |- separate corners?
+ * |- dashed side mask
+ * |
+ * -> can border be drawn in 1 pass? (e.g., solid border same color all
+ * around)
+ * |- DrawBorderSides with all 4 sides
+ * -> more than 1 pass?
+ * |- for each corner
+ * |- clip to DoCornerClipSubPath
+ * |- for each side adjacent to corner
+ * |- clip to GetSideClipSubPath
+ * |- DrawBorderSides with one side
+ * |- for each side
+ * |- GetSideClipWithoutCornersRect
+ * |- DrawDashedOrDottedSide || DrawBorderSides with one side
+ */
+
+static void ComputeBorderCornerDimensions(const Float* aBorderWidths,
+ const RectCornerRadii& aRadii,
+ RectCornerRadii* aDimsResult);
+
+// given a side index, get the previous and next side index
+#define NEXT_SIDE(_s) mozilla::Side(((_s) + 1) & 3)
+#define PREV_SIDE(_s) mozilla::Side(((_s) + 3) & 3)
+
+// given a corner index, get the previous and next corner index
+#define NEXT_CORNER(_s) Corner(((_s) + 1) & 3)
+#define PREV_CORNER(_s) Corner(((_s) + 3) & 3)
+
+// from the given base color and the background color, turn
+// color into a color for the given border pattern style
+static sRGBColor MakeBorderColor(nscolor aColor,
+ BorderColorStyle aBorderColorStyle);
+
+// Given a line index (an index starting from the outside of the
+// border going inwards) and an array of line styles, calculate the
+// color that that stripe of the border should be rendered in.
+static sRGBColor ComputeColorForLine(uint32_t aLineIndex,
+ const BorderColorStyle* aBorderColorStyle,
+ uint32_t aBorderColorStyleCount,
+ nscolor aBorderColor);
+
+// little helper function to check if the array of 4 floats given are
+// equal to the given value
+static bool CheckFourFloatsEqual(const Float* vals, Float k) {
+ return (vals[0] == k && vals[1] == k && vals[2] == k && vals[3] == k);
+}
+
+static bool IsZeroSize(const Size& sz) {
+ return sz.width == 0.0 || sz.height == 0.0;
+}
+
+/* static */
+bool nsCSSBorderRenderer::AllCornersZeroSize(const RectCornerRadii& corners) {
+ return IsZeroSize(corners[eCornerTopLeft]) &&
+ IsZeroSize(corners[eCornerTopRight]) &&
+ IsZeroSize(corners[eCornerBottomRight]) &&
+ IsZeroSize(corners[eCornerBottomLeft]);
+}
+
+static mozilla::Side GetHorizontalSide(Corner aCorner) {
+ return (aCorner == C_TL || aCorner == C_TR) ? eSideTop : eSideBottom;
+}
+
+static mozilla::Side GetVerticalSide(Corner aCorner) {
+ return (aCorner == C_TL || aCorner == C_BL) ? eSideLeft : eSideRight;
+}
+
+static Corner GetCWCorner(mozilla::Side aSide) {
+ return Corner(NEXT_SIDE(aSide));
+}
+
+static Corner GetCCWCorner(mozilla::Side aSide) { return Corner(aSide); }
+
+static bool IsSingleSide(mozilla::SideBits aSides) {
+ return aSides == SideBits::eTop || aSides == SideBits::eRight ||
+ aSides == SideBits::eBottom || aSides == SideBits::eLeft;
+}
+
+static bool IsHorizontalSide(mozilla::Side aSide) {
+ return aSide == eSideTop || aSide == eSideBottom;
+}
+
+typedef enum {
+ // Normal solid square corner. Will be rectangular, the size of the
+ // adjacent sides. If the corner has a border radius, the corner
+ // will always be solid, since we don't do dotted/dashed etc.
+ CORNER_NORMAL,
+
+ // Paint the corner in whatever style is not dotted/dashed of the
+ // adjacent corners.
+ CORNER_SOLID,
+
+ // Paint the corner as a dot, the size of the bigger of the adjacent
+ // sides.
+ CORNER_DOT
+} CornerStyle;
+
+nsCSSBorderRenderer::nsCSSBorderRenderer(
+ nsPresContext* aPresContext, DrawTarget* aDrawTarget,
+ const Rect& aDirtyRect, Rect& aOuterRect,
+ const StyleBorderStyle* aBorderStyles, const Float* aBorderWidths,
+ RectCornerRadii& aBorderRadii, const nscolor* aBorderColors,
+ bool aBackfaceIsVisible, const Maybe<Rect>& aClipRect)
+ : mPresContext(aPresContext),
+ mDrawTarget(aDrawTarget),
+ mDirtyRect(aDirtyRect),
+ mOuterRect(aOuterRect),
+ mBorderRadii(aBorderRadii),
+ mBackfaceIsVisible(aBackfaceIsVisible),
+ mLocalClip(aClipRect) {
+ PodCopy(mBorderStyles, aBorderStyles, 4);
+ PodCopy(mBorderWidths, aBorderWidths, 4);
+ PodCopy(mBorderColors, aBorderColors, 4);
+ mInnerRect = mOuterRect;
+ mInnerRect.Deflate(Margin(
+ mBorderStyles[0] != StyleBorderStyle::None ? mBorderWidths[0] : 0,
+ mBorderStyles[1] != StyleBorderStyle::None ? mBorderWidths[1] : 0,
+ mBorderStyles[2] != StyleBorderStyle::None ? mBorderWidths[2] : 0,
+ mBorderStyles[3] != StyleBorderStyle::None ? mBorderWidths[3] : 0));
+
+ ComputeBorderCornerDimensions(mBorderWidths, mBorderRadii,
+ &mBorderCornerDimensions);
+
+ mOneUnitBorder = CheckFourFloatsEqual(mBorderWidths, 1.0);
+ mNoBorderRadius = AllCornersZeroSize(mBorderRadii);
+ mAllBordersSameStyle = AreBorderSideFinalStylesSame(SideBits::eAll);
+ mAllBordersSameWidth = AllBordersSameWidth();
+ mAvoidStroke = false;
+}
+
+/* static */
+void nsCSSBorderRenderer::ComputeInnerRadii(const RectCornerRadii& aRadii,
+ const Float* aBorderSizes,
+ RectCornerRadii* aInnerRadiiRet) {
+ RectCornerRadii& iRadii = *aInnerRadiiRet;
+
+ iRadii[C_TL].width =
+ std::max(0.f, aRadii[C_TL].width - aBorderSizes[eSideLeft]);
+ iRadii[C_TL].height =
+ std::max(0.f, aRadii[C_TL].height - aBorderSizes[eSideTop]);
+
+ iRadii[C_TR].width =
+ std::max(0.f, aRadii[C_TR].width - aBorderSizes[eSideRight]);
+ iRadii[C_TR].height =
+ std::max(0.f, aRadii[C_TR].height - aBorderSizes[eSideTop]);
+
+ iRadii[C_BR].width =
+ std::max(0.f, aRadii[C_BR].width - aBorderSizes[eSideRight]);
+ iRadii[C_BR].height =
+ std::max(0.f, aRadii[C_BR].height - aBorderSizes[eSideBottom]);
+
+ iRadii[C_BL].width =
+ std::max(0.f, aRadii[C_BL].width - aBorderSizes[eSideLeft]);
+ iRadii[C_BL].height =
+ std::max(0.f, aRadii[C_BL].height - aBorderSizes[eSideBottom]);
+}
+
+/* static */
+void nsCSSBorderRenderer::ComputeOuterRadii(const RectCornerRadii& aRadii,
+ const Float* aBorderSizes,
+ RectCornerRadii* aOuterRadiiRet) {
+ RectCornerRadii& oRadii = *aOuterRadiiRet;
+
+ // default all corners to sharp corners
+ oRadii = RectCornerRadii(0.f);
+
+ // round the edges that have radii > 0.0 to start with
+ if (aRadii[C_TL].width > 0.f && aRadii[C_TL].height > 0.f) {
+ oRadii[C_TL].width =
+ std::max(0.f, aRadii[C_TL].width + aBorderSizes[eSideLeft]);
+ oRadii[C_TL].height =
+ std::max(0.f, aRadii[C_TL].height + aBorderSizes[eSideTop]);
+ }
+
+ if (aRadii[C_TR].width > 0.f && aRadii[C_TR].height > 0.f) {
+ oRadii[C_TR].width =
+ std::max(0.f, aRadii[C_TR].width + aBorderSizes[eSideRight]);
+ oRadii[C_TR].height =
+ std::max(0.f, aRadii[C_TR].height + aBorderSizes[eSideTop]);
+ }
+
+ if (aRadii[C_BR].width > 0.f && aRadii[C_BR].height > 0.f) {
+ oRadii[C_BR].width =
+ std::max(0.f, aRadii[C_BR].width + aBorderSizes[eSideRight]);
+ oRadii[C_BR].height =
+ std::max(0.f, aRadii[C_BR].height + aBorderSizes[eSideBottom]);
+ }
+
+ if (aRadii[C_BL].width > 0.f && aRadii[C_BL].height > 0.f) {
+ oRadii[C_BL].width =
+ std::max(0.f, aRadii[C_BL].width + aBorderSizes[eSideLeft]);
+ oRadii[C_BL].height =
+ std::max(0.f, aRadii[C_BL].height + aBorderSizes[eSideBottom]);
+ }
+}
+
+/*static*/ void ComputeBorderCornerDimensions(const Float* aBorderWidths,
+ const RectCornerRadii& aRadii,
+ RectCornerRadii* aDimsRet) {
+ Float leftWidth = aBorderWidths[eSideLeft];
+ Float topWidth = aBorderWidths[eSideTop];
+ Float rightWidth = aBorderWidths[eSideRight];
+ Float bottomWidth = aBorderWidths[eSideBottom];
+
+ if (nsCSSBorderRenderer::AllCornersZeroSize(aRadii)) {
+ // These will always be in pixel units from CSS
+ (*aDimsRet)[C_TL] = Size(leftWidth, topWidth);
+ (*aDimsRet)[C_TR] = Size(rightWidth, topWidth);
+ (*aDimsRet)[C_BR] = Size(rightWidth, bottomWidth);
+ (*aDimsRet)[C_BL] = Size(leftWidth, bottomWidth);
+ } else {
+ // Always round up to whole pixels for the corners; it's safe to
+ // make the corners bigger than necessary, and this way we ensure
+ // that we avoid seams.
+ (*aDimsRet)[C_TL] = Size(ceil(std::max(leftWidth, aRadii[C_TL].width)),
+ ceil(std::max(topWidth, aRadii[C_TL].height)));
+ (*aDimsRet)[C_TR] = Size(ceil(std::max(rightWidth, aRadii[C_TR].width)),
+ ceil(std::max(topWidth, aRadii[C_TR].height)));
+ (*aDimsRet)[C_BR] = Size(ceil(std::max(rightWidth, aRadii[C_BR].width)),
+ ceil(std::max(bottomWidth, aRadii[C_BR].height)));
+ (*aDimsRet)[C_BL] = Size(ceil(std::max(leftWidth, aRadii[C_BL].width)),
+ ceil(std::max(bottomWidth, aRadii[C_BL].height)));
+ }
+}
+
+bool nsCSSBorderRenderer::AreBorderSideFinalStylesSame(
+ mozilla::SideBits aSides) {
+ NS_ASSERTION(aSides != SideBits::eNone &&
+ (aSides & ~SideBits::eAll) == SideBits::eNone,
+ "AreBorderSidesSame: invalid whichSides!");
+
+ /* First check if the specified styles and colors are the same for all sides
+ */
+ int firstStyle = 0;
+ for (const auto i : mozilla::AllPhysicalSides()) {
+ if (firstStyle == i) {
+ if ((static_cast<mozilla::SideBits>(1 << i) & aSides) ==
+ SideBits::eNone) {
+ firstStyle++;
+ }
+ continue;
+ }
+
+ if ((static_cast<mozilla::SideBits>(1 << i) & aSides) == SideBits::eNone) {
+ continue;
+ }
+
+ if (mBorderStyles[firstStyle] != mBorderStyles[i] ||
+ mBorderColors[firstStyle] != mBorderColors[i]) {
+ return false;
+ }
+ }
+
+ /* Then if it's one of the two-tone styles and we're not
+ * just comparing the TL or BR sides */
+ switch (mBorderStyles[firstStyle]) {
+ case StyleBorderStyle::Groove:
+ case StyleBorderStyle::Ridge:
+ case StyleBorderStyle::Inset:
+ case StyleBorderStyle::Outset:
+ return ((aSides & ~(SideBits::eTop | SideBits::eLeft)) ==
+ SideBits::eNone ||
+ (aSides & ~(SideBits::eBottom | SideBits::eRight)) ==
+ SideBits::eNone);
+ default:
+ return true;
+ }
+}
+
+bool nsCSSBorderRenderer::IsSolidCornerStyle(StyleBorderStyle aStyle,
+ Corner aCorner) {
+ switch (aStyle) {
+ case StyleBorderStyle::Solid:
+ return true;
+
+ case StyleBorderStyle::Inset:
+ case StyleBorderStyle::Outset:
+ return (aCorner == eCornerTopLeft || aCorner == eCornerBottomRight);
+
+ case StyleBorderStyle::Groove:
+ case StyleBorderStyle::Ridge:
+ return mOneUnitBorder &&
+ (aCorner == eCornerTopLeft || aCorner == eCornerBottomRight);
+
+ case StyleBorderStyle::Double:
+ return mOneUnitBorder;
+
+ default:
+ return false;
+ }
+}
+
+bool nsCSSBorderRenderer::IsCornerMergeable(Corner aCorner) {
+ // Corner between dotted borders with same width and small radii is
+ // merged into single dot.
+ //
+ // widthH / 2.0
+ // |<---------->|
+ // | |
+ // |radius.width|
+ // |<--->| |
+ // | | |
+ // | _+------+------------+-----
+ // | / ###|### |
+ // |/ #######|####### |
+ // + #########|######### |
+ // | ##########|########## |
+ // | ###########|########### |
+ // | ###########|########### |
+ // |############|############|
+ // +------------+############|
+ // |#########################|
+ // | ####################### |
+ // | ####################### |
+ // | ##################### |
+ // | ################### |
+ // | ############### |
+ // | ####### |
+ // +-------------------------+----
+ // | |
+ // | |
+ mozilla::Side sideH(GetHorizontalSide(aCorner));
+ mozilla::Side sideV(GetVerticalSide(aCorner));
+ StyleBorderStyle styleH = mBorderStyles[sideH];
+ StyleBorderStyle styleV = mBorderStyles[sideV];
+ if (styleH != styleV || styleH != StyleBorderStyle::Dotted) {
+ return false;
+ }
+
+ Float widthH = mBorderWidths[sideH];
+ Float widthV = mBorderWidths[sideV];
+ if (widthH != widthV) {
+ return false;
+ }
+
+ Size radius = mBorderRadii[aCorner];
+ return IsZeroSize(radius) ||
+ (radius.width < widthH / 2.0f && radius.height < widthH / 2.0f);
+}
+
+BorderColorStyle nsCSSBorderRenderer::BorderColorStyleForSolidCorner(
+ StyleBorderStyle aStyle, Corner aCorner) {
+ // note that this function assumes that the corner is already solid,
+ // as per the earlier function
+ switch (aStyle) {
+ case StyleBorderStyle::Solid:
+ case StyleBorderStyle::Double:
+ return BorderColorStyleSolid;
+
+ case StyleBorderStyle::Inset:
+ case StyleBorderStyle::Groove:
+ if (aCorner == eCornerTopLeft) {
+ return BorderColorStyleDark;
+ } else if (aCorner == eCornerBottomRight) {
+ return BorderColorStyleLight;
+ }
+ break;
+
+ case StyleBorderStyle::Outset:
+ case StyleBorderStyle::Ridge:
+ if (aCorner == eCornerTopLeft) {
+ return BorderColorStyleLight;
+ } else if (aCorner == eCornerBottomRight) {
+ return BorderColorStyleDark;
+ }
+ break;
+ default:
+ return BorderColorStyleNone;
+ }
+
+ return BorderColorStyleNone;
+}
+
+Rect nsCSSBorderRenderer::GetCornerRect(Corner aCorner) {
+ Point offset(0.f, 0.f);
+
+ if (aCorner == C_TR || aCorner == C_BR)
+ offset.x = mOuterRect.Width() - mBorderCornerDimensions[aCorner].width;
+ if (aCorner == C_BR || aCorner == C_BL)
+ offset.y = mOuterRect.Height() - mBorderCornerDimensions[aCorner].height;
+
+ return Rect(mOuterRect.TopLeft() + offset, mBorderCornerDimensions[aCorner]);
+}
+
+Rect nsCSSBorderRenderer::GetSideClipWithoutCornersRect(mozilla::Side aSide) {
+ Point offset(0.f, 0.f);
+
+ // The offset from the outside rect to the start of this side's
+ // box. For the top and bottom sides, the height of the box
+ // must be the border height; the x start must take into account
+ // the corner size (which may be bigger than the right or left
+ // side's width). The same applies to the right and left sides.
+ if (aSide == eSideTop) {
+ offset.x = mBorderCornerDimensions[C_TL].width;
+ } else if (aSide == eSideRight) {
+ offset.x = mOuterRect.Width() - mBorderWidths[eSideRight];
+ offset.y = mBorderCornerDimensions[C_TR].height;
+ } else if (aSide == eSideBottom) {
+ offset.x = mBorderCornerDimensions[C_BL].width;
+ offset.y = mOuterRect.Height() - mBorderWidths[eSideBottom];
+ } else if (aSide == eSideLeft) {
+ offset.y = mBorderCornerDimensions[C_TL].height;
+ }
+
+ // The sum of the width & height of the corners adjacent to the
+ // side. This relies on the relationship between side indexing and
+ // corner indexing; that is, 0 == SIDE_TOP and 0 == CORNER_TOP_LEFT,
+ // with both proceeding clockwise.
+ Size sideCornerSum = mBorderCornerDimensions[GetCCWCorner(aSide)] +
+ mBorderCornerDimensions[GetCWCorner(aSide)];
+ Rect rect(mOuterRect.TopLeft() + offset, mOuterRect.Size() - sideCornerSum);
+
+ if (IsHorizontalSide(aSide))
+ rect.height = mBorderWidths[aSide];
+ else
+ rect.width = mBorderWidths[aSide];
+
+ return rect;
+}
+
+// The side border type and the adjacent border types are
+// examined and one of the different types of clipping (listed
+// below) is selected.
+
+typedef enum {
+ // clip to the trapezoid formed by the corners of the
+ // inner and outer rectangles for the given side
+ //
+ // +---------------
+ // |\%%%%%%%%%%%%%%
+ // | \%%%%%%%%%%%%
+ // | \%%%%%%%%%%%
+ // | \%%%%%%%%%
+ // | +--------
+ // | |
+ // | |
+ SIDE_CLIP_TRAPEZOID,
+
+ // clip to the trapezoid formed by the outer rectangle
+ // corners and the center of the region, making sure
+ // that diagonal lines all go directly from the outside
+ // corner to the inside corner, but that they then continue on
+ // to the middle.
+ //
+ // This is needed for correctly clipping rounded borders,
+ // which might extend past the SIDE_CLIP_TRAPEZOID trap.
+ //
+ // +-------__--+---
+ // \%%%%_-%%%%%%%%
+ // \+-%%%%%%%%%%
+ // / \%%%%%%%%%%
+ // / \%%%%%%%%%
+ // | +%%_-+---
+ // | +%%%%%%
+ // | / \%%%%%
+ // + + \%%%
+ // | | +-
+ SIDE_CLIP_TRAPEZOID_FULL,
+
+ // clip to the rectangle formed by the given side including corner.
+ // This is used by the non-dotted side next to dotted side.
+ //
+ // +---------------
+ // |%%%%%%%%%%%%%%%
+ // |%%%%%%%%%%%%%%%
+ // |%%%%%%%%%%%%%%%
+ // |%%%%%%%%%%%%%%%
+ // +------+--------
+ // | |
+ // | |
+ SIDE_CLIP_RECTANGLE_CORNER,
+
+ // clip to the rectangle formed by the given side excluding corner.
+ // This is used by the dotted side next to non-dotted side.
+ //
+ // +------+--------
+ // | |%%%%%%%%
+ // | |%%%%%%%%
+ // | |%%%%%%%%
+ // | |%%%%%%%%
+ // | +--------
+ // | |
+ // | |
+ SIDE_CLIP_RECTANGLE_NO_CORNER,
+} SideClipType;
+
+// Given three points, p0, p1, and midPoint, move p1 further in to the
+// rectangle (of which aMidPoint is the center) so that it reaches the
+// closer of the horizontal or vertical lines intersecting the midpoint,
+// while maintaing the slope of the line. If p0 and p1 are the same,
+// just move p1 to midPoint (since there's no slope to maintain).
+// FIXME: Extending only to the midpoint isn't actually sufficient for
+// boxes with asymmetric radii.
+static void MaybeMoveToMidPoint(Point& aP0, Point& aP1,
+ const Point& aMidPoint) {
+ Point ps = aP1 - aP0;
+
+ if (ps.x == 0.0) {
+ if (ps.y == 0.0) {
+ aP1 = aMidPoint;
+ } else {
+ aP1.y = aMidPoint.y;
+ }
+ } else {
+ if (ps.y == 0.0) {
+ aP1.x = aMidPoint.x;
+ } else {
+ Float k =
+ std::min((aMidPoint.x - aP0.x) / ps.x, (aMidPoint.y - aP0.y) / ps.y);
+ aP1 = aP0 + ps * k;
+ }
+ }
+}
+
+already_AddRefed<Path> nsCSSBorderRenderer::GetSideClipSubPath(
+ mozilla::Side aSide) {
+ // the clip proceeds clockwise from the top left corner;
+ // so "start" in each case is the start of the region from that side.
+ //
+ // the final path will be formed like:
+ // s0 ------- e0
+ // | /
+ // s1 ----- e1
+ //
+ // that is, the second point will always be on the inside
+
+ Point start[2];
+ Point end[2];
+
+#define IS_DOTTED(_s) ((_s) == StyleBorderStyle::Dotted)
+ bool isDotted = IS_DOTTED(mBorderStyles[aSide]);
+ bool startIsDotted = IS_DOTTED(mBorderStyles[PREV_SIDE(aSide)]);
+ bool endIsDotted = IS_DOTTED(mBorderStyles[NEXT_SIDE(aSide)]);
+#undef IS_DOTTED
+
+ SideClipType startType = SIDE_CLIP_TRAPEZOID;
+ SideClipType endType = SIDE_CLIP_TRAPEZOID;
+
+ if (!IsZeroSize(mBorderRadii[GetCCWCorner(aSide)])) {
+ startType = SIDE_CLIP_TRAPEZOID_FULL;
+ } else if (startIsDotted && !isDotted) {
+ startType = SIDE_CLIP_RECTANGLE_CORNER;
+ } else if (!startIsDotted && isDotted) {
+ startType = SIDE_CLIP_RECTANGLE_NO_CORNER;
+ }
+
+ if (!IsZeroSize(mBorderRadii[GetCWCorner(aSide)])) {
+ endType = SIDE_CLIP_TRAPEZOID_FULL;
+ } else if (endIsDotted && !isDotted) {
+ endType = SIDE_CLIP_RECTANGLE_CORNER;
+ } else if (!endIsDotted && isDotted) {
+ endType = SIDE_CLIP_RECTANGLE_NO_CORNER;
+ }
+
+ Point midPoint = mInnerRect.Center();
+
+ start[0] = mOuterRect.CCWCorner(aSide);
+ start[1] = mInnerRect.CCWCorner(aSide);
+
+ end[0] = mOuterRect.CWCorner(aSide);
+ end[1] = mInnerRect.CWCorner(aSide);
+
+ if (startType == SIDE_CLIP_TRAPEZOID_FULL) {
+ MaybeMoveToMidPoint(start[0], start[1], midPoint);
+ } else if (startType == SIDE_CLIP_RECTANGLE_CORNER) {
+ if (IsHorizontalSide(aSide)) {
+ start[1] =
+ Point(mOuterRect.CCWCorner(aSide).x, mInnerRect.CCWCorner(aSide).y);
+ } else {
+ start[1] =
+ Point(mInnerRect.CCWCorner(aSide).x, mOuterRect.CCWCorner(aSide).y);
+ }
+ } else if (startType == SIDE_CLIP_RECTANGLE_NO_CORNER) {
+ if (IsHorizontalSide(aSide)) {
+ start[0] =
+ Point(mInnerRect.CCWCorner(aSide).x, mOuterRect.CCWCorner(aSide).y);
+ } else {
+ start[0] =
+ Point(mOuterRect.CCWCorner(aSide).x, mInnerRect.CCWCorner(aSide).y);
+ }
+ }
+
+ if (endType == SIDE_CLIP_TRAPEZOID_FULL) {
+ MaybeMoveToMidPoint(end[0], end[1], midPoint);
+ } else if (endType == SIDE_CLIP_RECTANGLE_CORNER) {
+ if (IsHorizontalSide(aSide)) {
+ end[1] =
+ Point(mOuterRect.CWCorner(aSide).x, mInnerRect.CWCorner(aSide).y);
+ } else {
+ end[1] =
+ Point(mInnerRect.CWCorner(aSide).x, mOuterRect.CWCorner(aSide).y);
+ }
+ } else if (endType == SIDE_CLIP_RECTANGLE_NO_CORNER) {
+ if (IsHorizontalSide(aSide)) {
+ end[0] =
+ Point(mInnerRect.CWCorner(aSide).x, mOuterRect.CWCorner(aSide).y);
+ } else {
+ end[0] =
+ Point(mOuterRect.CWCorner(aSide).x, mInnerRect.CWCorner(aSide).y);
+ }
+ }
+
+ RefPtr<PathBuilder> builder = mDrawTarget->CreatePathBuilder();
+ builder->MoveTo(start[0]);
+ builder->LineTo(end[0]);
+ builder->LineTo(end[1]);
+ builder->LineTo(start[1]);
+ builder->Close();
+ return builder->Finish();
+}
+
+Point nsCSSBorderRenderer::GetStraightBorderPoint(mozilla::Side aSide,
+ Corner aCorner,
+ bool* aIsUnfilled,
+ Float aDotOffset) {
+ // Calculate the end point of the side for dashed/dotted border, that is also
+ // the end point of the corner curve. The point is specified by aSide and
+ // aCorner. (e.g. eSideTop and C_TL means the left end of border-top)
+ //
+ //
+ // aCorner aSide
+ // +--------------------
+ // |
+ // |
+ // | +----------
+ // | the end point
+ // |
+ // | +----------
+ // | |
+ // | |
+ // | |
+ //
+ // The position of the point depends on the border-style, border-width, and
+ // border-radius of the side, corner, and the adjacent side beyond the corner,
+ // to make those sides (and corner) interact well.
+ //
+ // If the style of aSide is dotted and the dot at the point should be
+ // unfilled, true is stored to *aIsUnfilled, otherwise false is stored.
+
+ const Float signsList[4][2] = {
+ {+1.0f, +1.0f}, {-1.0f, +1.0f}, {-1.0f, -1.0f}, {+1.0f, -1.0f}};
+ const Float(&signs)[2] = signsList[aCorner];
+
+ *aIsUnfilled = false;
+
+ Point P = mOuterRect.AtCorner(aCorner);
+ StyleBorderStyle style = mBorderStyles[aSide];
+ Float borderWidth = mBorderWidths[aSide];
+ Size dim = mBorderCornerDimensions[aCorner];
+ bool isHorizontal = IsHorizontalSide(aSide);
+ //
+ // aCorner aSide
+ // +--------------
+ // |
+ // | +----------
+ // | |
+ // otherSide | |
+ // | |
+ mozilla::Side otherSide = ((uint8_t)aSide == (uint8_t)aCorner)
+ ? PREV_SIDE(aSide)
+ : NEXT_SIDE(aSide);
+ StyleBorderStyle otherStyle = mBorderStyles[otherSide];
+ Float otherBorderWidth = mBorderWidths[otherSide];
+ Size radius = mBorderRadii[aCorner];
+ if (IsZeroSize(radius)) {
+ radius.width = 0.0f;
+ radius.height = 0.0f;
+ }
+ if (style == StyleBorderStyle::Dotted) {
+ // Offset the dot's location along the side toward the corner by a
+ // multiple of its width.
+ if (isHorizontal) {
+ P.x -= signs[0] * aDotOffset * borderWidth;
+ } else {
+ P.y -= signs[1] * aDotOffset * borderWidth;
+ }
+ }
+ if (style == StyleBorderStyle::Dotted &&
+ otherStyle == StyleBorderStyle::Dotted) {
+ if (borderWidth == otherBorderWidth) {
+ if (radius.width < borderWidth / 2.0f &&
+ radius.height < borderWidth / 2.0f) {
+ // Two dots are merged into one and placed at the corner.
+ //
+ // borderWidth / 2.0
+ // |<---------->|
+ // | |
+ // |radius.width|
+ // |<--->| |
+ // | | |
+ // | _+------+------------+-----
+ // | / ###|### |
+ // |/ #######|####### |
+ // + #########|######### |
+ // | ##########|########## |
+ // | ###########|########### |
+ // | ###########|########### |
+ // |############|############|
+ // +------------+############|
+ // |########### P ###########|
+ // | ####################### |
+ // | ####################### |
+ // | ##################### |
+ // | ################### |
+ // | ############### |
+ // | ####### |
+ // +-------------------------+----
+ // | |
+ // | |
+ P.x += signs[0] * borderWidth / 2.0f;
+ P.y += signs[1] * borderWidth / 2.0f;
+ } else {
+ // Two dots are drawn separately.
+ //
+ // borderWidth * 1.5
+ // |<------------>|
+ // | |
+ // |radius.width |
+ // |<----->| |
+ // | | |
+ // | _--+-+----+---
+ // | _- | ##|##
+ // | / | ###|###
+ // |/ |####|####
+ // | |####+####
+ // | |### P ###
+ // + | ###|###
+ // | | ##|##
+ // +---------+----+---
+ // | ##### |
+ // | ####### |
+ // |#########|
+ // +----+----+
+ // |#########|
+ // | ####### |
+ // | ##### |
+ // | |
+ //
+ // There should be enough gap between 2 dots even if radius.width is
+ // small but larger than borderWidth / 2.0. borderWidth * 1.5 is the
+ // value that there's imaginally unfilled dot at the corner. The
+ // unfilled dot may overflow from the outer curve, but filled dots
+ // doesn't, so this could be acceptable solution at least for now.
+ // We may have to find better model/value.
+ //
+ // imaginally unfilled dot at the corner
+ // |
+ // v +----+---
+ // ***** | ##|##
+ // ******* | ###|###
+ // *********|####|####
+ // *********|####+####
+ // *********|### P ###
+ // ******* | ###|###
+ // ***** | ##|##
+ // +---------+----+---
+ // | ##### |
+ // | ####### |
+ // |#########|
+ // +----+----+
+ // |#########|
+ // | ####### |
+ // | ##### |
+ // | |
+ Float minimum = borderWidth * 1.5f;
+ if (isHorizontal) {
+ P.x += signs[0] * std::max(radius.width, minimum);
+ P.y += signs[1] * borderWidth / 2.0f;
+ } else {
+ P.x += signs[0] * borderWidth / 2.0f;
+ P.y += signs[1] * std::max(radius.height, minimum);
+ }
+ }
+
+ return P;
+ }
+
+ if (borderWidth < otherBorderWidth) {
+ // This side is smaller than other side, other side draws the corner.
+ //
+ // otherBorderWidth + borderWidth / 2.0
+ // |<---------->|
+ // | |
+ // +---------+--+--------
+ // | ##### | *|* ###
+ // | ####### |**|**#####
+ // |#########|**+**##+##
+ // |####+####|* P *#####
+ // |#########| *** ###
+ // | ####### +-----------
+ // | ##### | ^
+ // | | |
+ // | | first dot is not filled
+ // | |
+ //
+ // radius.width
+ // |<----------------->|
+ // | |
+ // | ___---+-------------
+ // | __-- #|# ###
+ // | _- ##|## #####
+ // | / ##+## ##+##
+ // | / # P # #####
+ // | | #|# ###
+ // | | __--+-------------
+ // || _- ^
+ // || / |
+ // | / first dot is filled
+ // | |
+ // | |
+ // | ##### |
+ // | ####### |
+ // |#########|
+ // +----+----+
+ // |#########|
+ // | ####### |
+ // | ##### |
+ Float minimum = otherBorderWidth + borderWidth / 2.0f;
+ if (isHorizontal) {
+ if (radius.width < minimum) {
+ *aIsUnfilled = true;
+ P.x += signs[0] * minimum;
+ } else {
+ P.x += signs[0] * radius.width;
+ }
+ P.y += signs[1] * borderWidth / 2.0f;
+ } else {
+ P.x += signs[0] * borderWidth / 2.0f;
+ if (radius.height < minimum) {
+ *aIsUnfilled = true;
+ P.y += signs[1] * minimum;
+ } else {
+ P.y += signs[1] * radius.height;
+ }
+ }
+
+ return P;
+ }
+
+ // This side is larger than other side, this side draws the corner.
+ //
+ // borderWidth / 2.0
+ // |<-->|
+ // | |
+ // +----+---------------------
+ // | ##|## #####
+ // | ###|### #######
+ // |####|#### #########
+ // |####+#### ####+####
+ // |### P ### #########
+ // | ####### #######
+ // | ##### #####
+ // +-----+---------------------
+ // | *** |
+ // |*****|
+ // |**+**| <-- first dot in other side is not filled
+ // |*****|
+ // | *** |
+ // | ### |
+ // |#####|
+ // |##+##|
+ // |#####|
+ // | ### |
+ // | |
+ if (isHorizontal) {
+ P.x += signs[0] * std::max(radius.width, borderWidth / 2.0f);
+ P.y += signs[1] * borderWidth / 2.0f;
+ } else {
+ P.x += signs[0] * borderWidth / 2.0f;
+ P.y += signs[1] * std::max(radius.height, borderWidth / 2.0f);
+ }
+ return P;
+ }
+
+ if (style == StyleBorderStyle::Dotted) {
+ // If only this side is dotted, other side draws the corner.
+ //
+ // otherBorderWidth + borderWidth / 2.0
+ // |<------->|
+ // | |
+ // +------+--+--------
+ // |## ##| *|* ###
+ // |## ##|**|**#####
+ // |## ##|**+**##+##
+ // |## ##|* P *#####
+ // |## ##| *** ###
+ // |## ##+-----------
+ // |## ##| ^
+ // |## ##| |
+ // |## ##| first dot is not filled
+ // |## ##|
+ //
+ // radius.width
+ // |<----------------->|
+ // | |
+ // | ___---+-------------
+ // | __-- #|# ###
+ // | _- ##|## #####
+ // | / ##+## ##+##
+ // | / # P # #####
+ // | | #|# ###
+ // | | __--+-------------
+ // || _- ^
+ // || / |
+ // | / first dot is filled
+ // | |
+ // | |
+ // | |
+ // | |
+ // | |
+ // +------+
+ // |## ##|
+ // |## ##|
+ // |## ##|
+ Float minimum = otherBorderWidth + borderWidth / 2.0f;
+ if (isHorizontal) {
+ if (radius.width < minimum) {
+ *aIsUnfilled = true;
+ P.x += signs[0] * minimum;
+ } else {
+ P.x += signs[0] * radius.width;
+ }
+ P.y += signs[1] * borderWidth / 2.0f;
+ } else {
+ P.x += signs[0] * borderWidth / 2.0f;
+ if (radius.height < minimum) {
+ *aIsUnfilled = true;
+ P.y += signs[1] * minimum;
+ } else {
+ P.y += signs[1] * radius.height;
+ }
+ }
+ return P;
+ }
+
+ if (otherStyle == StyleBorderStyle::Dotted && IsZeroSize(radius)) {
+ // If other side is dotted and radius=0, draw side to the end of corner.
+ //
+ // +-------------------------------
+ // |########## ##########
+ // P +########## ##########
+ // |########## ##########
+ // +-----+-------------------------
+ // | *** |
+ // |*****|
+ // |**+**| <-- first dot in other side is not filled
+ // |*****|
+ // | *** |
+ // | ### |
+ // |#####|
+ // |##+##|
+ // |#####|
+ // | ### |
+ // | |
+ if (isHorizontal) {
+ P.y += signs[1] * borderWidth / 2.0f;
+ } else {
+ P.x += signs[0] * borderWidth / 2.0f;
+ }
+ return P;
+ }
+
+ // Other cases.
+ //
+ // dim.width
+ // |<----------------->|
+ // | |
+ // | ___---+------------------
+ // | __-- |####### ###
+ // | _- P +####### ###
+ // | / |####### ###
+ // | / __---+------------------
+ // | | __--
+ // | | /
+ // || /
+ // || |
+ // | |
+ // | |
+ // | |
+ // | |
+ // +-+-+
+ // |###|
+ // |###|
+ // |###|
+ // |###|
+ // |###|
+ // | |
+ // | |
+ if (isHorizontal) {
+ P.x += signs[0] * dim.width;
+ P.y += signs[1] * borderWidth / 2.0f;
+ } else {
+ P.x += signs[0] * borderWidth / 2.0f;
+ P.y += signs[1] * dim.height;
+ }
+
+ return P;
+}
+
+void nsCSSBorderRenderer::GetOuterAndInnerBezier(Bezier* aOuterBezier,
+ Bezier* aInnerBezier,
+ Corner aCorner) {
+ // Return bezier control points for outer and inner curve for given corner.
+ //
+ // ___---+ outer curve
+ // __-- |
+ // _- |
+ // / |
+ // / |
+ // | |
+ // | __--+ inner curve
+ // | _-
+ // | /
+ // | /
+ // | |
+ // | |
+ // | |
+ // | |
+ // | |
+ // +---------+
+
+ mozilla::Side sideH(GetHorizontalSide(aCorner));
+ mozilla::Side sideV(GetVerticalSide(aCorner));
+
+ Size outerCornerSize(ceil(mBorderRadii[aCorner].width),
+ ceil(mBorderRadii[aCorner].height));
+ Size innerCornerSize(
+ ceil(std::max(0.0f, mBorderRadii[aCorner].width - mBorderWidths[sideV])),
+ ceil(
+ std::max(0.0f, mBorderRadii[aCorner].height - mBorderWidths[sideH])));
+
+ GetBezierPointsForCorner(aOuterBezier, aCorner, mOuterRect.AtCorner(aCorner),
+ outerCornerSize);
+
+ GetBezierPointsForCorner(aInnerBezier, aCorner, mInnerRect.AtCorner(aCorner),
+ innerCornerSize);
+}
+
+void nsCSSBorderRenderer::FillSolidBorder(const Rect& aOuterRect,
+ const Rect& aInnerRect,
+ const RectCornerRadii& aBorderRadii,
+ const Float* aBorderSizes,
+ SideBits aSides,
+ const ColorPattern& aColor) {
+ // Note that this function is allowed to draw more than just the
+ // requested sides.
+
+ // If we have a border radius, do full rounded rectangles
+ // and fill, regardless of what sides we're asked to draw.
+ if (!AllCornersZeroSize(aBorderRadii)) {
+ RefPtr<PathBuilder> builder = mDrawTarget->CreatePathBuilder();
+
+ RectCornerRadii innerRadii;
+ ComputeInnerRadii(aBorderRadii, aBorderSizes, &innerRadii);
+
+ // do the outer border
+ AppendRoundedRectToPath(builder, aOuterRect, aBorderRadii, true);
+
+ // then do the inner border CCW
+ AppendRoundedRectToPath(builder, aInnerRect, innerRadii, false);
+
+ RefPtr<Path> path = builder->Finish();
+
+ mDrawTarget->Fill(path, aColor);
+ return;
+ }
+
+ // If we're asked to draw all sides of an equal-sized border,
+ // stroking is fastest. This is a fairly common path, but partial
+ // sides is probably second in the list -- there are a bunch of
+ // common border styles, such as inset and outset, that are
+ // top-left/bottom-right split.
+ if (aSides == SideBits::eAll &&
+ CheckFourFloatsEqual(aBorderSizes, aBorderSizes[0]) && !mAvoidStroke) {
+ Float strokeWidth = aBorderSizes[0];
+ Rect r(aOuterRect);
+ r.Deflate(strokeWidth / 2.f);
+ mDrawTarget->StrokeRect(r, aColor, StrokeOptions(strokeWidth));
+ return;
+ }
+
+ // Otherwise, we have unequal sized borders or we're only
+ // drawing some sides; create rectangles for each side
+ // and fill them.
+
+ Rect r[4];
+
+ // compute base rects for each side
+ if (aSides & SideBits::eTop) {
+ r[eSideTop] = Rect(aOuterRect.X(), aOuterRect.Y(), aOuterRect.Width(),
+ aBorderSizes[eSideTop]);
+ }
+
+ if (aSides & SideBits::eBottom) {
+ r[eSideBottom] =
+ Rect(aOuterRect.X(), aOuterRect.YMost() - aBorderSizes[eSideBottom],
+ aOuterRect.Width(), aBorderSizes[eSideBottom]);
+ }
+
+ if (aSides & SideBits::eLeft) {
+ r[eSideLeft] = Rect(aOuterRect.X(), aOuterRect.Y(), aBorderSizes[eSideLeft],
+ aOuterRect.Height());
+ }
+
+ if (aSides & SideBits::eRight) {
+ r[eSideRight] =
+ Rect(aOuterRect.XMost() - aBorderSizes[eSideRight], aOuterRect.Y(),
+ aBorderSizes[eSideRight], aOuterRect.Height());
+ }
+
+ // If two sides meet at a corner that we're rendering, then
+ // make sure that we adjust one of the sides to avoid overlap.
+ // This is especially important in the case of colors with
+ // an alpha channel.
+
+ if ((aSides & (SideBits::eTop | SideBits::eLeft)) ==
+ (SideBits::eTop | SideBits::eLeft)) {
+ // adjust the left's top down a bit
+ r[eSideLeft].y += aBorderSizes[eSideTop];
+ r[eSideLeft].height -= aBorderSizes[eSideTop];
+ }
+
+ if ((aSides & (SideBits::eTop | SideBits::eRight)) ==
+ (SideBits::eTop | SideBits::eRight)) {
+ // adjust the top's left a bit
+ r[eSideTop].width -= aBorderSizes[eSideRight];
+ }
+
+ if ((aSides & (SideBits::eBottom | SideBits::eRight)) ==
+ (SideBits::eBottom | SideBits::eRight)) {
+ // adjust the right's bottom a bit
+ r[eSideRight].height -= aBorderSizes[eSideBottom];
+ }
+
+ if ((aSides & (SideBits::eBottom | SideBits::eLeft)) ==
+ (SideBits::eBottom | SideBits::eLeft)) {
+ // adjust the bottom's left a bit
+ r[eSideBottom].x += aBorderSizes[eSideLeft];
+ r[eSideBottom].width -= aBorderSizes[eSideLeft];
+ }
+
+ // Filling these one by one is faster than filling them all at once.
+ for (uint32_t i = 0; i < 4; i++) {
+ if (aSides & static_cast<mozilla::SideBits>(1 << i)) {
+ MaybeSnapToDevicePixels(r[i], *mDrawTarget, true);
+ mDrawTarget->FillRect(r[i], aColor);
+ }
+ }
+}
+
+sRGBColor MakeBorderColor(nscolor aColor, BorderColorStyle aBorderColorStyle) {
+ nscolor colors[2];
+ int k = 0;
+
+ switch (aBorderColorStyle) {
+ case BorderColorStyleNone:
+ return sRGBColor(0.f, 0.f, 0.f, 0.f); // transparent black
+
+ case BorderColorStyleLight:
+ k = 1;
+ [[fallthrough]];
+ case BorderColorStyleDark:
+ NS_GetSpecial3DColors(colors, aColor);
+ return sRGBColor::FromABGR(colors[k]);
+
+ case BorderColorStyleSolid:
+ default:
+ return sRGBColor::FromABGR(aColor);
+ }
+}
+
+sRGBColor ComputeColorForLine(uint32_t aLineIndex,
+ const BorderColorStyle* aBorderColorStyle,
+ uint32_t aBorderColorStyleCount,
+ nscolor aBorderColor) {
+ NS_ASSERTION(aLineIndex < aBorderColorStyleCount, "Invalid lineIndex given");
+
+ return MakeBorderColor(aBorderColor, aBorderColorStyle[aLineIndex]);
+}
+
+void nsCSSBorderRenderer::DrawBorderSides(mozilla::SideBits aSides) {
+ if (aSides == SideBits::eNone ||
+ (aSides & ~SideBits::eAll) != SideBits::eNone) {
+ NS_WARNING("DrawBorderSides: invalid sides!");
+ return;
+ }
+
+ StyleBorderStyle borderRenderStyle = StyleBorderStyle::None;
+ nscolor borderRenderColor;
+
+ uint32_t borderColorStyleCount = 0;
+ BorderColorStyle borderColorStyleTopLeft[3], borderColorStyleBottomRight[3];
+ BorderColorStyle* borderColorStyle = nullptr;
+
+ for (const auto i : mozilla::AllPhysicalSides()) {
+ if ((aSides & static_cast<mozilla::SideBits>(1 << i)) == SideBits::eNone) {
+ continue;
+ }
+ borderRenderStyle = mBorderStyles[i];
+ borderRenderColor = mBorderColors[i];
+ break;
+ }
+
+ if (borderRenderStyle == StyleBorderStyle::None ||
+ borderRenderStyle == StyleBorderStyle::Hidden) {
+ return;
+ }
+
+ if (borderRenderStyle == StyleBorderStyle::Dashed ||
+ borderRenderStyle == StyleBorderStyle::Dotted) {
+ // Draw each corner separately, with the given side's color.
+ if (aSides & SideBits::eTop) {
+ DrawDashedOrDottedCorner(eSideTop, C_TL);
+ } else if (aSides & SideBits::eLeft) {
+ DrawDashedOrDottedCorner(eSideLeft, C_TL);
+ }
+
+ if (aSides & SideBits::eTop) {
+ DrawDashedOrDottedCorner(eSideTop, C_TR);
+ } else if (aSides & SideBits::eRight) {
+ DrawDashedOrDottedCorner(eSideRight, C_TR);
+ }
+
+ if (aSides & SideBits::eBottom) {
+ DrawDashedOrDottedCorner(eSideBottom, C_BL);
+ } else if (aSides & SideBits::eLeft) {
+ DrawDashedOrDottedCorner(eSideLeft, C_BL);
+ }
+
+ if (aSides & SideBits::eBottom) {
+ DrawDashedOrDottedCorner(eSideBottom, C_BR);
+ } else if (aSides & SideBits::eRight) {
+ DrawDashedOrDottedCorner(eSideRight, C_BR);
+ }
+ return;
+ }
+
+ // The borderColorStyle array goes from the outer to the inner style.
+ //
+ // If the border width is 1, we need to change the borderRenderStyle
+ // a bit to make sure that we get the right colors -- e.g. 'ridge'
+ // with a 1px border needs to look like solid, not like 'outset'.
+ if (mOneUnitBorder && (borderRenderStyle == StyleBorderStyle::Ridge ||
+ borderRenderStyle == StyleBorderStyle::Groove ||
+ borderRenderStyle == StyleBorderStyle::Double)) {
+ borderRenderStyle = StyleBorderStyle::Solid;
+ }
+
+ switch (borderRenderStyle) {
+ case StyleBorderStyle::Solid:
+ borderColorStyleTopLeft[0] = BorderColorStyleSolid;
+
+ borderColorStyleBottomRight[0] = BorderColorStyleSolid;
+
+ borderColorStyleCount = 1;
+ break;
+
+ case StyleBorderStyle::Groove:
+ borderColorStyleTopLeft[0] = BorderColorStyleDark;
+ borderColorStyleTopLeft[1] = BorderColorStyleLight;
+
+ borderColorStyleBottomRight[0] = BorderColorStyleLight;
+ borderColorStyleBottomRight[1] = BorderColorStyleDark;
+
+ borderColorStyleCount = 2;
+ break;
+
+ case StyleBorderStyle::Ridge:
+ borderColorStyleTopLeft[0] = BorderColorStyleLight;
+ borderColorStyleTopLeft[1] = BorderColorStyleDark;
+
+ borderColorStyleBottomRight[0] = BorderColorStyleDark;
+ borderColorStyleBottomRight[1] = BorderColorStyleLight;
+
+ borderColorStyleCount = 2;
+ break;
+
+ case StyleBorderStyle::Double:
+ borderColorStyleTopLeft[0] = BorderColorStyleSolid;
+ borderColorStyleTopLeft[1] = BorderColorStyleNone;
+ borderColorStyleTopLeft[2] = BorderColorStyleSolid;
+
+ borderColorStyleBottomRight[0] = BorderColorStyleSolid;
+ borderColorStyleBottomRight[1] = BorderColorStyleNone;
+ borderColorStyleBottomRight[2] = BorderColorStyleSolid;
+
+ borderColorStyleCount = 3;
+ break;
+
+ case StyleBorderStyle::Inset:
+ borderColorStyleTopLeft[0] = BorderColorStyleDark;
+ borderColorStyleBottomRight[0] = BorderColorStyleLight;
+
+ borderColorStyleCount = 1;
+ break;
+
+ case StyleBorderStyle::Outset:
+ borderColorStyleTopLeft[0] = BorderColorStyleLight;
+ borderColorStyleBottomRight[0] = BorderColorStyleDark;
+
+ borderColorStyleCount = 1;
+ break;
+
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unhandled border style!!");
+ break;
+ }
+
+ // The only way to get to here is by having a
+ // borderColorStyleCount < 1 or > 3; this should never happen,
+ // since -moz-border-colors doesn't get handled here.
+ NS_ASSERTION(borderColorStyleCount > 0 && borderColorStyleCount < 4,
+ "Non-border-colors case with borderColorStyleCount < 1 or > 3; "
+ "what happened?");
+
+ // The caller should never give us anything with a mix
+ // of TL/BR if the border style would require a
+ // TL/BR split.
+ if (aSides & (SideBits::eBottom | SideBits::eRight)) {
+ borderColorStyle = borderColorStyleBottomRight;
+ } else {
+ borderColorStyle = borderColorStyleTopLeft;
+ }
+
+ // Distribute the border across the available space.
+ Float borderWidths[3][4];
+
+ if (borderColorStyleCount == 1) {
+ for (const auto i : mozilla::AllPhysicalSides()) {
+ borderWidths[0][i] = mBorderWidths[i];
+ }
+ } else if (borderColorStyleCount == 2) {
+ // with 2 color styles, any extra pixel goes to the outside
+ for (const auto i : mozilla::AllPhysicalSides()) {
+ borderWidths[0][i] =
+ int32_t(mBorderWidths[i]) / 2 + int32_t(mBorderWidths[i]) % 2;
+ borderWidths[1][i] = int32_t(mBorderWidths[i]) / 2;
+ }
+ } else if (borderColorStyleCount == 3) {
+ // with 3 color styles, any extra pixel (or lack of extra pixel)
+ // goes to the middle
+ for (const auto i : mozilla::AllPhysicalSides()) {
+ if (mBorderWidths[i] == 1.0) {
+ borderWidths[0][i] = 1.f;
+ borderWidths[1][i] = borderWidths[2][i] = 0.f;
+ } else {
+ int32_t rest = int32_t(mBorderWidths[i]) % 3;
+ borderWidths[0][i] = borderWidths[2][i] = borderWidths[1][i] =
+ (int32_t(mBorderWidths[i]) - rest) / 3;
+
+ if (rest == 1) {
+ borderWidths[1][i] += 1.f;
+ } else if (rest == 2) {
+ borderWidths[0][i] += 1.f;
+ borderWidths[2][i] += 1.f;
+ }
+ }
+ }
+ }
+
+ // make a copy that we can modify
+ RectCornerRadii radii = mBorderRadii;
+
+ Rect soRect(mOuterRect);
+ Rect siRect(mOuterRect);
+
+ // If adjacent side is dotted and radius=0, draw side to the end of corner.
+ //
+ // +--------------------------------
+ // |################################
+ // |
+ // |################################
+ // +-----+--------------------------
+ // | |
+ // | |
+ // | |
+ // | |
+ // | |
+ // | ### |
+ // |#####|
+ // |#####|
+ // |#####|
+ // | ### |
+ // | |
+ bool noMarginTop = false;
+ bool noMarginRight = false;
+ bool noMarginBottom = false;
+ bool noMarginLeft = false;
+
+ // If there is at least one dotted side, every side is rendered separately.
+ if (IsSingleSide(aSides)) {
+ if (aSides == SideBits::eTop) {
+ if (mBorderStyles[eSideRight] == StyleBorderStyle::Dotted &&
+ IsZeroSize(mBorderRadii[C_TR])) {
+ noMarginRight = true;
+ }
+ if (mBorderStyles[eSideLeft] == StyleBorderStyle::Dotted &&
+ IsZeroSize(mBorderRadii[C_TL])) {
+ noMarginLeft = true;
+ }
+ } else if (aSides == SideBits::eRight) {
+ if (mBorderStyles[eSideTop] == StyleBorderStyle::Dotted &&
+ IsZeroSize(mBorderRadii[C_TR])) {
+ noMarginTop = true;
+ }
+ if (mBorderStyles[eSideBottom] == StyleBorderStyle::Dotted &&
+ IsZeroSize(mBorderRadii[C_BR])) {
+ noMarginBottom = true;
+ }
+ } else if (aSides == SideBits::eBottom) {
+ if (mBorderStyles[eSideRight] == StyleBorderStyle::Dotted &&
+ IsZeroSize(mBorderRadii[C_BR])) {
+ noMarginRight = true;
+ }
+ if (mBorderStyles[eSideLeft] == StyleBorderStyle::Dotted &&
+ IsZeroSize(mBorderRadii[C_BL])) {
+ noMarginLeft = true;
+ }
+ } else {
+ if (mBorderStyles[eSideTop] == StyleBorderStyle::Dotted &&
+ IsZeroSize(mBorderRadii[C_TL])) {
+ noMarginTop = true;
+ }
+ if (mBorderStyles[eSideBottom] == StyleBorderStyle::Dotted &&
+ IsZeroSize(mBorderRadii[C_BL])) {
+ noMarginBottom = true;
+ }
+ }
+ }
+
+ for (unsigned int i = 0; i < borderColorStyleCount; i++) {
+ // walk siRect inwards at the start of the loop to get the
+ // correct inner rect.
+ //
+ // If noMarginTop is false:
+ // --------------------+
+ // /|
+ // / |
+ // L |
+ // ----------------+ |
+ // | |
+ // | |
+ //
+ // If noMarginTop is true:
+ // ----------------+<--+
+ // | |
+ // | |
+ // | |
+ // | |
+ // | |
+ // | |
+ siRect.Deflate(Margin(noMarginTop ? 0 : borderWidths[i][0],
+ noMarginRight ? 0 : borderWidths[i][1],
+ noMarginBottom ? 0 : borderWidths[i][2],
+ noMarginLeft ? 0 : borderWidths[i][3]));
+
+ if (borderColorStyle[i] != BorderColorStyleNone) {
+ sRGBColor c = ComputeColorForLine(
+ i, borderColorStyle, borderColorStyleCount, borderRenderColor);
+ ColorPattern color(ToDeviceColor(c));
+
+ FillSolidBorder(soRect, siRect, radii, borderWidths[i], aSides, color);
+ }
+
+ ComputeInnerRadii(radii, borderWidths[i], &radii);
+
+ // And now soRect is the same as siRect, for the next line in.
+ soRect = siRect;
+ }
+}
+
+void nsCSSBorderRenderer::SetupDashedOptions(StrokeOptions* aStrokeOptions,
+ Float aDash[2],
+ mozilla::Side aSide,
+ Float aBorderLength,
+ bool isCorner) {
+ MOZ_ASSERT(mBorderStyles[aSide] == StyleBorderStyle::Dashed ||
+ mBorderStyles[aSide] == StyleBorderStyle::Dotted,
+ "Style should be dashed or dotted.");
+
+ StyleBorderStyle style = mBorderStyles[aSide];
+ Float borderWidth = mBorderWidths[aSide];
+
+ // Dashed line starts and ends with half segment in most case.
+ //
+ // __--+---+---+---+---+---+---+---+---+--__
+ // |###| | |###|###| | |###|
+ // |###| | |###|###| | |###|
+ // |###| | |###|###| | |###|
+ // __--+---+---+---+---+---+---+---+---+--__
+ //
+ // If radius=0 and other side is either dotted or 0-width, it starts or ends
+ // with full segment.
+ //
+ // +---+---+---+---+---+---+---+---+---+---+
+ // |###|###| | |###|###| | |###|###|
+ // |###|###| | |###|###| | |###|###|
+ // |###|###| | |###|###| | |###|###|
+ // +---++--+---+---+---+---+---+---+--++---+
+ // | | | |
+ // | | | |
+ // | | | |
+ // | | | |
+ // | ## | | ## |
+ // |####| |####|
+ // |####| |####|
+ // | ## | | ## |
+ // | | | |
+ bool fullStart = false, fullEnd = false;
+ Float halfDash;
+ if (style == StyleBorderStyle::Dashed) {
+ // If either end of the side is not connecting onto a corner then we want a
+ // full dash at that end.
+ //
+ // Note that in the case that a corner is empty, either the adjacent side
+ // has zero width, or else DrawBorders() set the corner to be empty
+ // (it does that if the adjacent side has zero length and the border widths
+ // of this and the adjacent sides are thin enough that the corner will be
+ // insignificantly small).
+
+ if (mBorderRadii[GetCCWCorner(aSide)].IsEmpty() &&
+ (mBorderCornerDimensions[GetCCWCorner(aSide)].IsEmpty() ||
+ mBorderStyles[PREV_SIDE(aSide)] == StyleBorderStyle::Dotted ||
+ // XXX why this <=1 check?
+ borderWidth <= 1.0f)) {
+ fullStart = true;
+ }
+
+ if (mBorderRadii[GetCWCorner(aSide)].IsEmpty() &&
+ (mBorderCornerDimensions[GetCWCorner(aSide)].IsEmpty() ||
+ mBorderStyles[NEXT_SIDE(aSide)] == StyleBorderStyle::Dotted)) {
+ fullEnd = true;
+ }
+
+ halfDash = borderWidth * DOT_LENGTH * DASH_LENGTH / 2.0f;
+ } else {
+ halfDash = borderWidth * DOT_LENGTH / 2.0f;
+ }
+
+ if (style == StyleBorderStyle::Dashed && aBorderLength > 0.0f) {
+ // The number of half segments, with maximum dash length.
+ int32_t count = floor(aBorderLength / halfDash);
+ Float minHalfDash = borderWidth * DOT_LENGTH / 2.0f;
+
+ if (fullStart && fullEnd) {
+ // count should be 4n + 2
+ //
+ // 1 + 4 + 4 + 1
+ //
+ // | | | | |
+ // +---+---+---+---+---+---+---+---+---+---+
+ // |###|###| | |###|###| | |###|###|
+ // |###|###| | |###|###| | |###|###|
+ // |###|###| | |###|###| | |###|###|
+ // +---+---+---+---+---+---+---+---+---+---+
+
+ // If border is too short, draw solid line.
+ if (aBorderLength < 6.0f * minHalfDash) {
+ return;
+ }
+
+ if (count % 4 == 0) {
+ count += 2;
+ } else if (count % 4 == 1) {
+ count += 1;
+ } else if (count % 4 == 3) {
+ count += 3;
+ }
+ } else if (fullStart || fullEnd) {
+ // count should be 4n + 1
+ //
+ // 1 + 4 + 4
+ //
+ // | | | |
+ // +---+---+---+---+---+---+---+---+---+
+ // |###|###| | |###|###| | |###|
+ // |###|###| | |###|###| | |###|
+ // |###|###| | |###|###| | |###|
+ // +---+---+---+---+---+---+---+---+---+
+ //
+ // 4 + 4 + 1
+ //
+ // | | | |
+ // +---+---+---+---+---+---+---+---+---+
+ // |###| | |###|###| | |###|###|
+ // |###| | |###|###| | |###|###|
+ // |###| | |###|###| | |###|###|
+ // +---+---+---+---+---+---+---+---+---+
+
+ // If border is too short, draw solid line.
+ if (aBorderLength < 5.0f * minHalfDash) {
+ return;
+ }
+
+ if (count % 4 == 0) {
+ count += 1;
+ } else if (count % 4 == 2) {
+ count += 3;
+ } else if (count % 4 == 3) {
+ count += 2;
+ }
+ } else {
+ // count should be 4n
+ //
+ // 4 + 4
+ //
+ // | | |
+ // +---+---+---+---+---+---+---+---+
+ // |###| | |###|###| | |###|
+ // |###| | |###|###| | |###|
+ // |###| | |###|###| | |###|
+ // +---+---+---+---+---+---+---+---+
+
+ // If border is too short, draw solid line.
+ if (aBorderLength < 4.0f * minHalfDash) {
+ return;
+ }
+
+ if (count % 4 == 1) {
+ count += 3;
+ } else if (count % 4 == 2) {
+ count += 2;
+ } else if (count % 4 == 3) {
+ count += 1;
+ }
+ }
+ halfDash = aBorderLength / count;
+ }
+
+ Float fullDash = halfDash * 2.0f;
+
+ aDash[0] = fullDash;
+ aDash[1] = fullDash;
+
+ if (style == StyleBorderStyle::Dashed && fullDash > 1.0f) {
+ if (!fullStart) {
+ // Draw half segments on both ends.
+ aStrokeOptions->mDashOffset = halfDash;
+ }
+ } else if (style != StyleBorderStyle::Dotted && isCorner) {
+ // If side ends with filled full segment, corner should start with unfilled
+ // full segment. Not needed for dotted corners, as they overlap one dot with
+ // the side's end.
+ //
+ // corner side
+ // ------------>|<---------------------------
+ // |
+ // __+---+---+---+---+---+---+---+---+
+ // _+- | |###|###| | |###|###| |
+ // /##| | |###|###| | |###|###| |
+ // +####| | |###|###| | |###|###| |
+ // /#\####| _+--+---+---+---+---+---+---+---+
+ // |####\##+-
+ // |#####+-
+ // +--###/
+ // | --+
+ aStrokeOptions->mDashOffset = fullDash;
+ }
+
+ aStrokeOptions->mDashPattern = aDash;
+ aStrokeOptions->mDashLength = 2;
+
+ PrintAsFormatString("dash: %f %f\n", aDash[0], aDash[1]);
+}
+
+static Float GetBorderLength(mozilla::Side aSide, const Point& aStart,
+ const Point& aEnd) {
+ if (aSide == eSideTop) {
+ return aEnd.x - aStart.x;
+ }
+ if (aSide == eSideRight) {
+ return aEnd.y - aStart.y;
+ }
+ if (aSide == eSideBottom) {
+ return aStart.x - aEnd.x;
+ }
+ return aStart.y - aEnd.y;
+}
+
+void nsCSSBorderRenderer::DrawDashedOrDottedSide(mozilla::Side aSide) {
+ // Draw dashed/dotted side with following approach.
+ //
+ // dashed side
+ // Draw dashed line along the side, with appropriate dash length and gap
+ // to make the side symmetric as far as possible. Dash length equals to
+ // the gap, and the ratio of the dash length to border-width is the maximum
+ // value in in [1, 3] range.
+ // In most case, line ends with half segment, to joint with corner easily.
+ // If adjacent side is dotted or 0px and border-radius for the corner
+ // between them is 0, the line ends with full segment.
+ // (see comment for GetStraightBorderPoint for more detail)
+ //
+ // dotted side
+ // If border-width <= 2.0, draw 1:1 dashed line.
+ // Otherwise, draw circles along the side, with appropriate gap that makes
+ // the side symmetric as far as possible. The ratio of the gap to
+ // border-width is the maximum value in [0.5, 1] range in most case.
+ // if the side is too short and there's only 2 dots, it can be more smaller.
+ // If there's no space to place 2 dots at the side, draw single dot at the
+ // middle of the side.
+ // In most case, line ends with filled dot, to joint with corner easily,
+ // If adjacent side is dotted with larger border-width, or other style,
+ // the line ends with unfilled dot.
+ // (see comment for GetStraightBorderPoint for more detail)
+
+ NS_ASSERTION(mBorderStyles[aSide] == StyleBorderStyle::Dashed ||
+ mBorderStyles[aSide] == StyleBorderStyle::Dotted,
+ "Style should be dashed or dotted.");
+
+ Float borderWidth = mBorderWidths[aSide];
+ if (borderWidth == 0.0f) {
+ return;
+ }
+
+ if (mBorderStyles[aSide] == StyleBorderStyle::Dotted && borderWidth > 2.0f) {
+ DrawDottedSideSlow(aSide);
+ return;
+ }
+
+ nscolor borderColor = mBorderColors[aSide];
+ bool ignored;
+ // Get the start and end points of the side, ensuring that any dot origins get
+ // pushed outward to account for stroking.
+ Point start =
+ GetStraightBorderPoint(aSide, GetCCWCorner(aSide), &ignored, 0.5f);
+ Point end = GetStraightBorderPoint(aSide, GetCWCorner(aSide), &ignored, 0.5f);
+ if (borderWidth < 2.0f) {
+ // Round start to draw dot on each pixel.
+ if (IsHorizontalSide(aSide)) {
+ start.x = round(start.x);
+ } else {
+ start.y = round(start.y);
+ }
+ }
+
+ Float borderLength = GetBorderLength(aSide, start, end);
+ if (borderLength < 0.0f) {
+ return;
+ }
+
+ StrokeOptions strokeOptions(borderWidth);
+ Float dash[2];
+ SetupDashedOptions(&strokeOptions, dash, aSide, borderLength, false);
+
+ // For dotted sides that can merge with their prior dotted sides, advance the
+ // dash offset to measure the distance around the combined path. This prevents
+ // two dots from bunching together at a corner.
+ mozilla::Side mergeSide = aSide;
+ while (IsCornerMergeable(GetCCWCorner(mergeSide))) {
+ mergeSide = PREV_SIDE(mergeSide);
+ // If we looped all the way around, measure starting at the top side, since
+ // we need to pick a fixed location to start measuring distance from still.
+ if (mergeSide == aSide) {
+ mergeSide = eSideTop;
+ break;
+ }
+ }
+ while (mergeSide != aSide) {
+ // Measure the length of the merged side starting from a possibly
+ // unmergeable corner up to the merged corner. A merged corner effectively
+ // has no border radius, so we can just use the cheaper AtCorner to find the
+ // end point.
+ Float mergeLength =
+ GetBorderLength(mergeSide,
+ GetStraightBorderPoint(
+ mergeSide, GetCCWCorner(mergeSide), &ignored, 0.5f),
+ mOuterRect.AtCorner(GetCWCorner(mergeSide)));
+ // Add in the merged side length. Also offset the dash progress by an extra
+ // dot's width to avoid drawing a dot that would overdraw where the merged
+ // side would have ended in a gap, i.e. O_O_
+ // O
+ strokeOptions.mDashOffset += mergeLength + borderWidth;
+ mergeSide = NEXT_SIDE(mergeSide);
+ }
+
+ DrawOptions drawOptions;
+ if (mBorderStyles[aSide] == StyleBorderStyle::Dotted) {
+ drawOptions.mAntialiasMode = AntialiasMode::NONE;
+ }
+
+ mDrawTarget->StrokeLine(start, end, ColorPattern(ToDeviceColor(borderColor)),
+ strokeOptions, drawOptions);
+}
+
+void nsCSSBorderRenderer::DrawDottedSideSlow(mozilla::Side aSide) {
+ // Draw each circles separately for dotted with borderWidth > 2.0.
+ // Dashed line with CapStyle::ROUND doesn't render perfect circles.
+
+ NS_ASSERTION(mBorderStyles[aSide] == StyleBorderStyle::Dotted,
+ "Style should be dotted.");
+
+ Float borderWidth = mBorderWidths[aSide];
+ if (borderWidth == 0.0f) {
+ return;
+ }
+
+ nscolor borderColor = mBorderColors[aSide];
+ bool isStartUnfilled, isEndUnfilled;
+ Point start =
+ GetStraightBorderPoint(aSide, GetCCWCorner(aSide), &isStartUnfilled);
+ Point end = GetStraightBorderPoint(aSide, GetCWCorner(aSide), &isEndUnfilled);
+ enum {
+ // Corner is not mergeable.
+ NO_MERGE,
+
+ // Corner between different colors.
+ // Two dots are merged into one, and both side draw half dot.
+ MERGE_HALF,
+
+ // Corner between same colors, CCW corner of the side.
+ // Two dots are merged into one, and this side draw entire dot.
+ //
+ // MERGE_ALL MERGE_NONE
+ // | |
+ // v v
+ // +-----------------------+----+
+ // | ## ## ## | ## |
+ // |#### #### #### |####|
+ // |#### #### #### |####|
+ // | ## ## ## | ## |
+ // +----+------------------+ |
+ // | | | |
+ // | | | |
+ // | | | |
+ // | ## | | ## |
+ // |####| |####|
+ MERGE_ALL,
+
+ // Corner between same colors, CW corner of the side.
+ // Two dots are merged into one, and this side doesn't draw dot.
+ MERGE_NONE
+ } mergeStart = NO_MERGE,
+ mergeEnd = NO_MERGE;
+
+ if (IsCornerMergeable(GetCCWCorner(aSide))) {
+ if (borderColor == mBorderColors[PREV_SIDE(aSide)]) {
+ mergeStart = MERGE_ALL;
+ } else {
+ mergeStart = MERGE_HALF;
+ }
+ }
+
+ if (IsCornerMergeable(GetCWCorner(aSide))) {
+ if (borderColor == mBorderColors[NEXT_SIDE(aSide)]) {
+ mergeEnd = MERGE_NONE;
+ } else {
+ mergeEnd = MERGE_HALF;
+ }
+ }
+
+ Float borderLength = GetBorderLength(aSide, start, end);
+ if (borderLength < 0.0f) {
+ if (isStartUnfilled || isEndUnfilled) {
+ return;
+ }
+ borderLength = 0.0f;
+ start = end = (start + end) / 2.0f;
+ }
+
+ Float dotWidth = borderWidth * DOT_LENGTH;
+ Float radius = borderWidth / 2.0f;
+ if (borderLength < dotWidth) {
+ // If dots on start and end may overlap, draw a dot at the middle of them.
+ //
+ // ___---+-------+---___
+ // __-- | ##### | --__
+ // #|#######|#
+ // ##|#######|##
+ // ###|#######|###
+ // ###+###+###+###
+ // start ## end #
+ // ##|#######|##
+ // #|#######|#
+ // | ##### |
+ // __--+-------+--__
+ // _- -_
+ //
+ // If that circle overflows from outer rect, do not draw it.
+ //
+ // +-------+
+ // | ##### |
+ // #|#######|#
+ // ##|#######|##
+ // ###|#######|###
+ // ###|###+###|###
+ // ###|#######|###
+ // ##|#######|##
+ // #|#######|#
+ // | ##### |
+ // +--+-+--+
+ // | | | |
+ // | | | |
+ if (!mOuterRect.Contains(Rect(start.x - radius, start.y - radius,
+ borderWidth, borderWidth))) {
+ return;
+ }
+
+ if (isStartUnfilled || isEndUnfilled) {
+ return;
+ }
+
+ Point P = (start + end) / 2;
+ RefPtr<PathBuilder> builder = mDrawTarget->CreatePathBuilder();
+ builder->MoveTo(Point(P.x + radius, P.y));
+ builder->Arc(P, radius, 0.0f, Float(2.0 * M_PI));
+ RefPtr<Path> path = builder->Finish();
+ mDrawTarget->Fill(path, ColorPattern(ToDeviceColor(borderColor)));
+ return;
+ }
+
+ if (mergeStart == MERGE_HALF || mergeEnd == MERGE_HALF) {
+ // MERGE_HALF
+ // Eo
+ // -------+----+
+ // ##### /
+ // ######/
+ // ######/
+ // ####+
+ // ##/ end
+ // /
+ // /
+ // --+
+ // Ei
+ //
+ // other (NO_MERGE, MERGE_ALL, MERGE_NONE)
+ // Eo
+ // ------------+
+ // ##### |
+ // ####### |
+ // #########|
+ // ####+####|
+ // ## end ##|
+ // ####### |
+ // ##### |
+ // ------------+
+ // Ei
+
+ Point I(0.0f, 0.0f), J(0.0f, 0.0f);
+ if (aSide == eSideTop) {
+ I.x = 1.0f;
+ J.y = 1.0f;
+ } else if (aSide == eSideRight) {
+ I.y = 1.0f;
+ J.x = -1.0f;
+ } else if (aSide == eSideBottom) {
+ I.x = -1.0f;
+ J.y = -1.0f;
+ } else if (aSide == eSideLeft) {
+ I.y = -1.0f;
+ J.x = 1.0f;
+ }
+
+ Point So, Si, Eo, Ei;
+
+ So = (start + (-I + -J) * borderWidth / 2.0f);
+ Si = (mergeStart == MERGE_HALF) ? (start + (I + J) * borderWidth / 2.0f)
+ : (start + (-I + J) * borderWidth / 2.0f);
+ Eo = (end + (I - J) * borderWidth / 2.0f);
+ Ei = (mergeEnd == MERGE_HALF) ? (end + (-I + J) * borderWidth / 2.0f)
+ : (end + (I + J) * borderWidth / 2.0f);
+
+ RefPtr<PathBuilder> builder = mDrawTarget->CreatePathBuilder();
+ builder->MoveTo(So);
+ builder->LineTo(Eo);
+ builder->LineTo(Ei);
+ builder->LineTo(Si);
+ builder->Close();
+ RefPtr<Path> path = builder->Finish();
+
+ mDrawTarget->PushClip(path);
+ }
+
+ size_t count = round(borderLength / dotWidth);
+ if (isStartUnfilled == isEndUnfilled) {
+ // Split into 2n segments.
+ if (count % 2) {
+ count++;
+ }
+ } else {
+ // Split into 2n+1 segments.
+ if (count % 2 == 0) {
+ count++;
+ }
+ }
+
+ // A: radius == borderWidth / 2.0
+ // B: borderLength / count == borderWidth * (1 - overlap)
+ //
+ // A B B B B A
+ // |<-->|<------>|<------>|<------>|<------>|<-->|
+ // | | | | | | |
+ // +----+--------+--------+--------+--------+----+
+ // | ##|## **|** ##|## **|** ##|## |
+ // | ###|### ***|*** ###|### ***|*** ###|### |
+ // |####|####****|****####|####****|****####|####|
+ // |####+####****+****####+####****+****####+####|
+ // |# start #****|****####|####****|****## end ##|
+ // | ###|### ***|*** ###|### ***|*** ###|### |
+ // | ##|## **|** ##|## **|** ##|## |
+ // +----+----+---+--------+--------+---+----+----+
+ // | | | |
+ // | | | |
+
+ // If isStartUnfilled is true, draw dots on 2j+1 points, if not, draw dots on
+ // 2j points.
+ size_t from = isStartUnfilled ? 1 : 0;
+
+ // If mergeEnd == MERGE_NONE, last dot is drawn by next side.
+ size_t to = count;
+ if (mergeEnd == MERGE_NONE) {
+ if (to > 2) {
+ to -= 2;
+ } else {
+ to = 0;
+ }
+ }
+
+ Point fromP = (start * (count - from) + end * from) / count;
+ Point toP = (start * (count - to) + end * to) / count;
+ // Extend dirty rect to avoid clipping pixel for anti-aliasing.
+ const Float AA_MARGIN = 2.0f;
+
+ // The following algorithm assumes the border's rect and the dirty rect
+ // intersect.
+ MOZ_ASSERT(mDirtyRect.Intersects(mOuterRect));
+
+ if (aSide == eSideTop) {
+ // Tweak |from| and |to| to fit into |mDirtyRect + radius margin|,
+ // to render only paths that may overlap mDirtyRect.
+ //
+ // mDirtyRect + radius margin
+ // +--+---------------------+--+
+ // | |
+ // | mDirtyRect |
+ // + +---------------------+ +
+ // from ===> |from to | <=== to
+ // +-----+-----+-----+-----+-----+-----+-----+-----+
+ // ### |### ### ###| ###
+ // ##### ##### ##### ##### #####
+ // ##### ##### ##### ##### #####
+ // ##### ##### ##### ##### #####
+ // ### |### ### ###| ###
+ // | | | |
+ // + +---------------------+ +
+ // | |
+ // | |
+ // +--+---------------------+--+
+
+ Float left = mDirtyRect.x - radius - AA_MARGIN;
+ if (fromP.x < left) {
+ size_t tmp = ceil(count * (left - start.x) / (end.x - start.x));
+ if (tmp > from) {
+ // We increment by 2, so odd/even should match between before/after.
+ if ((tmp & 1) != (from & 1)) {
+ from = tmp - 1;
+ } else {
+ from = tmp;
+ }
+ }
+ }
+ Float right = mDirtyRect.x + mDirtyRect.width + radius + AA_MARGIN;
+ if (toP.x > right) {
+ size_t tmp = floor(count * (right - start.x) / (end.x - start.x));
+ if (tmp < to) {
+ if ((tmp & 1) != (to & 1)) {
+ to = tmp + 1;
+ } else {
+ to = tmp;
+ }
+ }
+ }
+ } else if (aSide == eSideRight) {
+ Float top = mDirtyRect.y - radius - AA_MARGIN;
+ if (fromP.y < top) {
+ size_t tmp = ceil(count * (top - start.y) / (end.y - start.y));
+ if (tmp > from) {
+ if ((tmp & 1) != (from & 1)) {
+ from = tmp - 1;
+ } else {
+ from = tmp;
+ }
+ }
+ }
+ Float bottom = mDirtyRect.y + mDirtyRect.height + radius + AA_MARGIN;
+ if (toP.y > bottom) {
+ size_t tmp = floor(count * (bottom - start.y) / (end.y - start.y));
+ if (tmp < to) {
+ if ((tmp & 1) != (to & 1)) {
+ to = tmp + 1;
+ } else {
+ to = tmp;
+ }
+ }
+ }
+ } else if (aSide == eSideBottom) {
+ Float right = mDirtyRect.x + mDirtyRect.width + radius + AA_MARGIN;
+ if (fromP.x > right) {
+ size_t tmp = ceil(count * (right - start.x) / (end.x - start.x));
+ if (tmp > from) {
+ if ((tmp & 1) != (from & 1)) {
+ from = tmp - 1;
+ } else {
+ from = tmp;
+ }
+ }
+ }
+ Float left = mDirtyRect.x - radius - AA_MARGIN;
+ if (toP.x < left) {
+ size_t tmp = floor(count * (left - start.x) / (end.x - start.x));
+ if (tmp < to) {
+ if ((tmp & 1) != (to & 1)) {
+ to = tmp + 1;
+ } else {
+ to = tmp;
+ }
+ }
+ }
+ } else if (aSide == eSideLeft) {
+ Float bottom = mDirtyRect.y + mDirtyRect.height + radius + AA_MARGIN;
+ if (fromP.y > bottom) {
+ size_t tmp = ceil(count * (bottom - start.y) / (end.y - start.y));
+ if (tmp > from) {
+ if ((tmp & 1) != (from & 1)) {
+ from = tmp - 1;
+ } else {
+ from = tmp;
+ }
+ }
+ }
+ Float top = mDirtyRect.y - radius - AA_MARGIN;
+ if (toP.y < top) {
+ size_t tmp = floor(count * (top - start.y) / (end.y - start.y));
+ if (tmp < to) {
+ if ((tmp & 1) != (to & 1)) {
+ to = tmp + 1;
+ } else {
+ to = tmp;
+ }
+ }
+ }
+ }
+
+ RefPtr<PathBuilder> builder = mDrawTarget->CreatePathBuilder();
+ size_t segmentCount = 0;
+ for (size_t i = from; i <= to; i += 2) {
+ if (segmentCount > BORDER_SEGMENT_COUNT_MAX) {
+ RefPtr<Path> path = builder->Finish();
+ mDrawTarget->Fill(path, ColorPattern(ToDeviceColor(borderColor)));
+ builder = mDrawTarget->CreatePathBuilder();
+ segmentCount = 0;
+ }
+
+ Point P = (start * (count - i) + end * i) / count;
+ builder->MoveTo(Point(P.x + radius, P.y));
+ builder->Arc(P, radius, 0.0f, Float(2.0 * M_PI));
+ segmentCount++;
+ }
+ RefPtr<Path> path = builder->Finish();
+ mDrawTarget->Fill(path, ColorPattern(ToDeviceColor(borderColor)));
+
+ if (mergeStart == MERGE_HALF || mergeEnd == MERGE_HALF) {
+ mDrawTarget->PopClip();
+ }
+}
+
+void nsCSSBorderRenderer::DrawDashedOrDottedCorner(mozilla::Side aSide,
+ Corner aCorner) {
+ // Draw dashed/dotted corner with following approach.
+ //
+ // dashed corner
+ // If both side has same border-width and border-width <= 2.0, draw dashed
+ // line along the corner, with appropriate dash length and gap to make the
+ // corner symmetric as far as possible. Dash length equals to the gap, and
+ // the ratio of the dash length to border-width is the maximum value in in
+ // [1, 3] range.
+ // Otherwise, draw dashed segments along the corner, keeping same dash
+ // length ratio to border-width at that point.
+ // (see DashedCornerFinder.h for more detail)
+ // Line ends with half segments, to joint with both side easily.
+ //
+ // dotted corner
+ // If both side has same border-width and border-width <= 2.0, draw 1:1
+ // dashed line along the corner.
+ // Otherwise Draw circles along the corner, with appropriate gap that makes
+ // the corner symmetric as far as possible. The size of the circle may
+ // change along the corner, that is tangent to the outer curver and the
+ // inner curve. The ratio of the gap to circle diameter is the maximum
+ // value in [0.5, 1] range.
+ // (see DottedCornerFinder.h for more detail)
+ // Corner ends with filled dots but those dots are drawn by
+ // DrawDashedOrDottedSide. So this may draw no circles if there's no space
+ // between 2 dots at both ends.
+
+ NS_ASSERTION(mBorderStyles[aSide] == StyleBorderStyle::Dashed ||
+ mBorderStyles[aSide] == StyleBorderStyle::Dotted,
+ "Style should be dashed or dotted.");
+
+ if (IsCornerMergeable(aCorner)) {
+ // DrawDashedOrDottedSide will draw corner.
+ return;
+ }
+
+ mozilla::Side sideH(GetHorizontalSide(aCorner));
+ mozilla::Side sideV(GetVerticalSide(aCorner));
+ Float borderWidthH = mBorderWidths[sideH];
+ Float borderWidthV = mBorderWidths[sideV];
+ if (borderWidthH == 0.0f && borderWidthV == 0.0f) {
+ return;
+ }
+
+ StyleBorderStyle styleH = mBorderStyles[sideH];
+ StyleBorderStyle styleV = mBorderStyles[sideV];
+
+ // Corner between dotted and others with radius=0 is drawn by side.
+ if (IsZeroSize(mBorderRadii[aCorner]) &&
+ (styleV == StyleBorderStyle::Dotted ||
+ styleH == StyleBorderStyle::Dotted)) {
+ return;
+ }
+
+ Float maxRadius =
+ std::max(mBorderRadii[aCorner].width, mBorderRadii[aCorner].height);
+ if (maxRadius > BORDER_DOTTED_CORNER_MAX_RADIUS) {
+ DrawFallbackSolidCorner(aSide, aCorner);
+ return;
+ }
+
+ if (borderWidthH != borderWidthV || borderWidthH > 2.0f) {
+ StyleBorderStyle style = mBorderStyles[aSide];
+ if (style == StyleBorderStyle::Dotted) {
+ DrawDottedCornerSlow(aSide, aCorner);
+ } else {
+ DrawDashedCornerSlow(aSide, aCorner);
+ }
+ return;
+ }
+
+ nscolor borderColor = mBorderColors[aSide];
+ Point points[4];
+ bool ignored;
+ // Get the start and end points of the corner arc, ensuring that any dot
+ // origins get pushed backwards towards the edges of the corner rect to
+ // account for stroking.
+ points[0] = GetStraightBorderPoint(sideH, aCorner, &ignored, -0.5f);
+ points[3] = GetStraightBorderPoint(sideV, aCorner, &ignored, -0.5f);
+ // Round points to draw dot on each pixel.
+ if (borderWidthH < 2.0f) {
+ points[0].x = round(points[0].x);
+ }
+ if (borderWidthV < 2.0f) {
+ points[3].y = round(points[3].y);
+ }
+ points[1] = points[0];
+ points[1].x += kKappaFactor * (points[3].x - points[0].x);
+ points[2] = points[3];
+ points[2].y += kKappaFactor * (points[0].y - points[3].y);
+
+ Float len = GetQuarterEllipticArcLength(fabs(points[0].x - points[3].x),
+ fabs(points[0].y - points[3].y));
+
+ Float dash[2];
+ StrokeOptions strokeOptions(borderWidthH);
+ SetupDashedOptions(&strokeOptions, dash, aSide, len, true);
+
+ RefPtr<PathBuilder> builder = mDrawTarget->CreatePathBuilder();
+ builder->MoveTo(points[0]);
+ builder->BezierTo(points[1], points[2], points[3]);
+ RefPtr<Path> path = builder->Finish();
+ mDrawTarget->Stroke(path, ColorPattern(ToDeviceColor(borderColor)),
+ strokeOptions);
+}
+
+void nsCSSBorderRenderer::DrawDottedCornerSlow(mozilla::Side aSide,
+ Corner aCorner) {
+ NS_ASSERTION(mBorderStyles[aSide] == StyleBorderStyle::Dotted,
+ "Style should be dotted.");
+
+ mozilla::Side sideH(GetHorizontalSide(aCorner));
+ mozilla::Side sideV(GetVerticalSide(aCorner));
+ Float R0 = mBorderWidths[sideH] / 2.0f;
+ Float Rn = mBorderWidths[sideV] / 2.0f;
+ if (R0 == 0.0f && Rn == 0.0f) {
+ return;
+ }
+
+ nscolor borderColor = mBorderColors[aSide];
+ Bezier outerBezier;
+ Bezier innerBezier;
+ GetOuterAndInnerBezier(&outerBezier, &innerBezier, aCorner);
+
+ bool ignored;
+ Point C0 = GetStraightBorderPoint(sideH, aCorner, &ignored);
+ Point Cn = GetStraightBorderPoint(sideV, aCorner, &ignored);
+ DottedCornerFinder finder(outerBezier, innerBezier, aCorner,
+ mBorderRadii[aCorner].width,
+ mBorderRadii[aCorner].height, C0, R0, Cn, Rn,
+ mBorderCornerDimensions[aCorner]);
+
+ RefPtr<PathBuilder> builder = mDrawTarget->CreatePathBuilder();
+ size_t segmentCount = 0;
+ const Float AA_MARGIN = 2.0f;
+ Rect marginedDirtyRect = mDirtyRect;
+ marginedDirtyRect.Inflate(std::max(R0, Rn) + AA_MARGIN);
+ bool entered = false;
+ while (finder.HasMore()) {
+ if (segmentCount > BORDER_SEGMENT_COUNT_MAX) {
+ RefPtr<Path> path = builder->Finish();
+ mDrawTarget->Fill(path, ColorPattern(ToDeviceColor(borderColor)));
+ builder = mDrawTarget->CreatePathBuilder();
+ segmentCount = 0;
+ }
+
+ DottedCornerFinder::Result result = finder.Next();
+
+ if (marginedDirtyRect.Contains(result.C) && result.r > 0) {
+ entered = true;
+ builder->MoveTo(Point(result.C.x + result.r, result.C.y));
+ builder->Arc(result.C, result.r, 0, Float(2.0 * M_PI));
+ segmentCount++;
+ } else if (entered) {
+ break;
+ }
+ }
+ RefPtr<Path> path = builder->Finish();
+ mDrawTarget->Fill(path, ColorPattern(ToDeviceColor(borderColor)));
+}
+
+static inline bool DashedPathOverlapsRect(Rect& pathRect,
+ const Rect& marginedDirtyRect,
+ DashedCornerFinder::Result& result) {
+ // Calculate a rect that contains all control points of the |result| path,
+ // and check if it intersects with |marginedDirtyRect|.
+ pathRect.SetRect(result.outerSectionBezier.mPoints[0].x,
+ result.outerSectionBezier.mPoints[0].y, 0, 0);
+ pathRect.ExpandToEnclose(result.outerSectionBezier.mPoints[1]);
+ pathRect.ExpandToEnclose(result.outerSectionBezier.mPoints[2]);
+ pathRect.ExpandToEnclose(result.outerSectionBezier.mPoints[3]);
+ pathRect.ExpandToEnclose(result.innerSectionBezier.mPoints[0]);
+ pathRect.ExpandToEnclose(result.innerSectionBezier.mPoints[1]);
+ pathRect.ExpandToEnclose(result.innerSectionBezier.mPoints[2]);
+ pathRect.ExpandToEnclose(result.innerSectionBezier.mPoints[3]);
+
+ return pathRect.Intersects(marginedDirtyRect);
+}
+
+void nsCSSBorderRenderer::DrawDashedCornerSlow(mozilla::Side aSide,
+ Corner aCorner) {
+ NS_ASSERTION(mBorderStyles[aSide] == StyleBorderStyle::Dashed,
+ "Style should be dashed.");
+
+ mozilla::Side sideH(GetHorizontalSide(aCorner));
+ mozilla::Side sideV(GetVerticalSide(aCorner));
+ Float borderWidthH = mBorderWidths[sideH];
+ Float borderWidthV = mBorderWidths[sideV];
+ if (borderWidthH == 0.0f && borderWidthV == 0.0f) {
+ return;
+ }
+
+ nscolor borderColor = mBorderColors[aSide];
+ Bezier outerBezier;
+ Bezier innerBezier;
+ GetOuterAndInnerBezier(&outerBezier, &innerBezier, aCorner);
+
+ DashedCornerFinder finder(outerBezier, innerBezier, borderWidthH,
+ borderWidthV, mBorderCornerDimensions[aCorner]);
+
+ RefPtr<PathBuilder> builder = mDrawTarget->CreatePathBuilder();
+ size_t segmentCount = 0;
+ const Float AA_MARGIN = 2.0f;
+ Rect marginedDirtyRect = mDirtyRect;
+ marginedDirtyRect.Inflate(AA_MARGIN);
+ Rect pathRect;
+ bool entered = false;
+ while (finder.HasMore()) {
+ if (segmentCount > BORDER_SEGMENT_COUNT_MAX) {
+ RefPtr<Path> path = builder->Finish();
+ mDrawTarget->Fill(path, ColorPattern(ToDeviceColor(borderColor)));
+ builder = mDrawTarget->CreatePathBuilder();
+ segmentCount = 0;
+ }
+
+ DashedCornerFinder::Result result = finder.Next();
+
+ if (DashedPathOverlapsRect(pathRect, marginedDirtyRect, result)) {
+ entered = true;
+ builder->MoveTo(result.outerSectionBezier.mPoints[0]);
+ builder->BezierTo(result.outerSectionBezier.mPoints[1],
+ result.outerSectionBezier.mPoints[2],
+ result.outerSectionBezier.mPoints[3]);
+ builder->LineTo(result.innerSectionBezier.mPoints[3]);
+ builder->BezierTo(result.innerSectionBezier.mPoints[2],
+ result.innerSectionBezier.mPoints[1],
+ result.innerSectionBezier.mPoints[0]);
+ builder->LineTo(result.outerSectionBezier.mPoints[0]);
+ segmentCount++;
+ } else if (entered) {
+ break;
+ }
+ }
+
+ if (outerBezier.mPoints[0].x != innerBezier.mPoints[0].x) {
+ // Fill gap before the first section.
+ //
+ // outnerPoint[0]
+ // |
+ // v
+ // _+-----------+--
+ // / \##########|
+ // / \#########|
+ // + \########|
+ // |\ \######|
+ // | \ \#####|
+ // | \ \####|
+ // | \ \##|
+ // | \ \#|
+ // | \ \|
+ // | \ _-+--
+ // +--------------+ ^
+ // | | |
+ // | | innerPoint[0]
+ // | |
+ builder->MoveTo(outerBezier.mPoints[0]);
+ builder->LineTo(innerBezier.mPoints[0]);
+ builder->LineTo(Point(innerBezier.mPoints[0].x, outerBezier.mPoints[0].y));
+ builder->LineTo(outerBezier.mPoints[0]);
+ }
+
+ if (outerBezier.mPoints[3].y != innerBezier.mPoints[3].y) {
+ // Fill gap after the last section.
+ //
+ // outnerPoint[3]
+ // |
+ // |
+ // | _+-----------+--
+ // | / \ |
+ // v/ \ |
+ // + \ |
+ // |\ \ |
+ // |##\ \ |
+ // |####\ \ |
+ // |######\ \ |
+ // |########\ \ |
+ // |##########\ \|
+ // |############\ _-+--
+ // +--------------+<-- innerPoint[3]
+ // | |
+ // | |
+ // | |
+ builder->MoveTo(outerBezier.mPoints[3]);
+ builder->LineTo(innerBezier.mPoints[3]);
+ builder->LineTo(Point(outerBezier.mPoints[3].x, innerBezier.mPoints[3].y));
+ builder->LineTo(outerBezier.mPoints[3]);
+ }
+
+ RefPtr<Path> path = builder->Finish();
+ mDrawTarget->Fill(path, ColorPattern(ToDeviceColor(borderColor)));
+}
+
+void nsCSSBorderRenderer::DrawFallbackSolidCorner(mozilla::Side aSide,
+ Corner aCorner) {
+ // Render too large dashed or dotted corner with solid style, to avoid hangup
+ // inside DashedCornerFinder and DottedCornerFinder.
+
+ NS_ASSERTION(mBorderStyles[aSide] == StyleBorderStyle::Dashed ||
+ mBorderStyles[aSide] == StyleBorderStyle::Dotted,
+ "Style should be dashed or dotted.");
+
+ nscolor borderColor = mBorderColors[aSide];
+ Bezier outerBezier;
+ Bezier innerBezier;
+ GetOuterAndInnerBezier(&outerBezier, &innerBezier, aCorner);
+
+ RefPtr<PathBuilder> builder = mDrawTarget->CreatePathBuilder();
+
+ builder->MoveTo(outerBezier.mPoints[0]);
+ builder->BezierTo(outerBezier.mPoints[1], outerBezier.mPoints[2],
+ outerBezier.mPoints[3]);
+ builder->LineTo(innerBezier.mPoints[3]);
+ builder->BezierTo(innerBezier.mPoints[2], innerBezier.mPoints[1],
+ innerBezier.mPoints[0]);
+ builder->LineTo(outerBezier.mPoints[0]);
+
+ RefPtr<Path> path = builder->Finish();
+ mDrawTarget->Fill(path, ColorPattern(ToDeviceColor(borderColor)));
+
+ if (!mPresContext->HasWarnedAboutTooLargeDashedOrDottedRadius()) {
+ mPresContext->SetHasWarnedAboutTooLargeDashedOrDottedRadius();
+ nsContentUtils::ReportToConsole(
+ nsIScriptError::warningFlag, "CSS"_ns, mPresContext->Document(),
+ nsContentUtils::eCSS_PROPERTIES,
+ mBorderStyles[aSide] == StyleBorderStyle::Dashed
+ ? "TooLargeDashedRadius"
+ : "TooLargeDottedRadius");
+ }
+}
+
+bool nsCSSBorderRenderer::AllBordersSameWidth() {
+ if (mBorderWidths[0] == mBorderWidths[1] &&
+ mBorderWidths[0] == mBorderWidths[2] &&
+ mBorderWidths[0] == mBorderWidths[3]) {
+ return true;
+ }
+
+ return false;
+}
+
+bool nsCSSBorderRenderer::AllBordersSolid() {
+ for (const auto i : mozilla::AllPhysicalSides()) {
+ if (mBorderStyles[i] == StyleBorderStyle::Solid ||
+ mBorderStyles[i] == StyleBorderStyle::None ||
+ mBorderStyles[i] == StyleBorderStyle::Hidden) {
+ continue;
+ }
+ return false;
+ }
+
+ return true;
+}
+
+static bool IsVisible(StyleBorderStyle aStyle) {
+ if (aStyle != StyleBorderStyle::None && aStyle != StyleBorderStyle::Hidden) {
+ return true;
+ }
+ return false;
+}
+
+struct twoFloats {
+ Float a, b;
+
+ twoFloats operator*(const Size& aSize) const {
+ return {a * aSize.width, b * aSize.height};
+ }
+
+ twoFloats operator*(Float aScale) const { return {a * aScale, b * aScale}; }
+
+ twoFloats operator+(const Point& aPoint) const {
+ return {a + aPoint.x, b + aPoint.y};
+ }
+
+ operator Point() const { return Point(a, b); }
+};
+
+void nsCSSBorderRenderer::DrawSingleWidthSolidBorder() {
+ // Easy enough to deal with.
+ Rect rect = mOuterRect;
+ rect.Deflate(0.5);
+
+ const twoFloats cornerAdjusts[4] = {
+ {+0.5, 0}, {0, +0.5}, {-0.5, 0}, {0, -0.5}};
+ for (const auto side : mozilla::AllPhysicalSides()) {
+ Point firstCorner = rect.CCWCorner(side) + cornerAdjusts[side];
+ Point secondCorner = rect.CWCorner(side) + cornerAdjusts[side];
+
+ ColorPattern color(ToDeviceColor(mBorderColors[side]));
+
+ mDrawTarget->StrokeLine(firstCorner, secondCorner, color);
+ }
+}
+
+// Intersect a ray from the inner corner to the outer corner
+// with the border radius, yielding the intersection point.
+static Point IntersectBorderRadius(const Point& aCenter, const Size& aRadius,
+ const Point& aInnerCorner,
+ const Point& aCornerDirection) {
+ Point toCorner = aCornerDirection;
+ // transform to-corner ray to unit-circle space
+ toCorner.x /= aRadius.width;
+ toCorner.y /= aRadius.height;
+ // normalize to-corner ray
+ Float cornerDist = toCorner.Length();
+ if (cornerDist < 1.0e-6f) {
+ return aInnerCorner;
+ }
+ toCorner = toCorner / cornerDist;
+ // ray from inner corner to border radius center
+ Point toCenter = aCenter - aInnerCorner;
+ // transform to-center ray to unit-circle space
+ toCenter.x /= aRadius.width;
+ toCenter.y /= aRadius.height;
+ // compute offset of intersection with border radius unit circle
+ Float offset = toCenter.DotProduct(toCorner);
+ // compute discriminant to check for intersections
+ Float discrim = 1.0f - toCenter.DotProduct(toCenter) + offset * offset;
+ // choose farthest intersection
+ offset += sqrtf(std::max(discrim, 0.0f));
+ // transform to-corner ray back out of unit-circle space
+ toCorner.x *= aRadius.width;
+ toCorner.y *= aRadius.height;
+ return aInnerCorner + toCorner * offset;
+}
+
+// Calculate the split point and split angle for a border radius with
+// differing sides.
+static inline void SplitBorderRadius(const Point& aCenter, const Size& aRadius,
+ const Point& aOuterCorner,
+ const Point& aInnerCorner,
+ const twoFloats& aCornerMults,
+ Float aStartAngle, Point& aSplit,
+ Float& aSplitAngle) {
+ Point cornerDir = aOuterCorner - aInnerCorner;
+ if (cornerDir.x == cornerDir.y && aRadius.IsSquare()) {
+ // optimize 45-degree intersection with circle since we can assume
+ // the circle center lies along the intersection edge
+ aSplit = aCenter - aCornerMults * (aRadius * Float(1.0f / M_SQRT2));
+ aSplitAngle = aStartAngle + 0.5f * M_PI / 2.0f;
+ } else {
+ aSplit = IntersectBorderRadius(aCenter, aRadius, aInnerCorner, cornerDir);
+ aSplitAngle = atan2f((aSplit.y - aCenter.y) / aRadius.height,
+ (aSplit.x - aCenter.x) / aRadius.width);
+ }
+}
+
+// Compute the size of the skirt needed, given the color alphas
+// of each corner side and the slope between them.
+static void ComputeCornerSkirtSize(Float aAlpha1, Float aAlpha2, Float aSlopeY,
+ Float aSlopeX, Float& aSizeResult,
+ Float& aSlopeResult) {
+ // If either side is (almost) invisible or there is no diagonal edge,
+ // then don't try to render a skirt.
+ if (aAlpha1 < 0.01f || aAlpha2 < 0.01f) {
+ return;
+ }
+ aSlopeX = fabs(aSlopeX);
+ aSlopeY = fabs(aSlopeY);
+ if (aSlopeX < 1.0e-6f || aSlopeY < 1.0e-6f) {
+ return;
+ }
+
+ // If first and second color don't match, we need to split the corner in
+ // half. The diagonal edges created may not have full pixel coverage given
+ // anti-aliasing, so we need to compute a small subpixel skirt edge. This
+ // assumes each half has half coverage to start with, and that coverage
+ // increases as the skirt is pushed over, with the end result that we want
+ // to roughly preserve the alpha value along this edge.
+ // Given slope m, alphas a and A, use quadratic formula to solve for S in:
+ // a*(1 - 0.5*(1-S)*(1-mS))*(1 - 0.5*A) + 0.5*A = A
+ // yielding:
+ // S = ((1+m) - sqrt((1+m)*(1+m) + 4*m*(1 - A/(a*(1-0.5*A))))) / (2*m)
+ // and substitute k = (1+m)/(2*m):
+ // S = k - sqrt(k*k + (1 - A/(a*(1-0.5*A)))/m)
+ Float slope = aSlopeY / aSlopeX;
+ Float slopeScale = (1.0f + slope) / (2.0f * slope);
+ Float discrim = slopeScale * slopeScale +
+ (1 - aAlpha2 / (aAlpha1 * (1.0f - 0.49f * aAlpha2))) / slope;
+ if (discrim >= 0) {
+ aSizeResult = slopeScale - sqrtf(discrim);
+ aSlopeResult = slope;
+ }
+}
+
+// Draws a border radius with possibly different sides.
+// A skirt is drawn underneath the corner intersection to hide possible
+// seams when anti-aliased drawing is used.
+static void DrawBorderRadius(
+ DrawTarget* aDrawTarget, Corner c, const Point& aOuterCorner,
+ const Point& aInnerCorner, const twoFloats& aCornerMultPrev,
+ const twoFloats& aCornerMultNext, const Size& aCornerDims,
+ const Size& aOuterRadius, const Size& aInnerRadius,
+ const DeviceColor& aFirstColor, const DeviceColor& aSecondColor,
+ Float aSkirtSize, Float aSkirtSlope) {
+ // Connect edge to outer arc start point
+ Point outerCornerStart = aOuterCorner + aCornerMultPrev * aCornerDims;
+ // Connect edge to outer arc end point
+ Point outerCornerEnd = aOuterCorner + aCornerMultNext * aCornerDims;
+ // Connect edge to inner arc start point
+ Point innerCornerStart =
+ outerCornerStart + aCornerMultNext * (aCornerDims - aInnerRadius);
+ // Connect edge to inner arc end point
+ Point innerCornerEnd =
+ outerCornerEnd + aCornerMultPrev * (aCornerDims - aInnerRadius);
+
+ // Outer arc start point
+ Point outerArcStart = aOuterCorner + aCornerMultPrev * aOuterRadius;
+ // Outer arc end point
+ Point outerArcEnd = aOuterCorner + aCornerMultNext * aOuterRadius;
+ // Inner arc start point
+ Point innerArcStart = aInnerCorner + aCornerMultPrev * aInnerRadius;
+ // Inner arc end point
+ Point innerArcEnd = aInnerCorner + aCornerMultNext * aInnerRadius;
+
+ // Outer radius center
+ Point outerCenter =
+ aOuterCorner + (aCornerMultPrev + aCornerMultNext) * aOuterRadius;
+ // Inner radius center
+ Point innerCenter =
+ aInnerCorner + (aCornerMultPrev + aCornerMultNext) * aInnerRadius;
+
+ RefPtr<PathBuilder> builder;
+ RefPtr<Path> path;
+
+ if (aFirstColor.a > 0) {
+ builder = aDrawTarget->CreatePathBuilder();
+ builder->MoveTo(outerCornerStart);
+ }
+
+ if (aFirstColor != aSecondColor) {
+ // Start and end angles of corner quadrant
+ constexpr float PIf = M_PI;
+ Float startAngle = (static_cast<float>(c) * PIf) / 2.0f - PIf;
+ Float endAngle = startAngle + PIf / 2.0f;
+ Float outerSplitAngle, innerSplitAngle;
+ Point outerSplit, innerSplit;
+
+ // Outer half-way point
+ SplitBorderRadius(outerCenter, aOuterRadius, aOuterCorner, aInnerCorner,
+ aCornerMultPrev + aCornerMultNext, startAngle, outerSplit,
+ outerSplitAngle);
+ // Inner half-way point
+ if (aInnerRadius.IsEmpty()) {
+ innerSplit = aInnerCorner;
+ innerSplitAngle = endAngle;
+ } else {
+ SplitBorderRadius(innerCenter, aInnerRadius, aOuterCorner, aInnerCorner,
+ aCornerMultPrev + aCornerMultNext, startAngle,
+ innerSplit, innerSplitAngle);
+ }
+
+ // Draw first half with first color
+ if (aFirstColor.a > 0) {
+ AcuteArcToBezier(builder.get(), outerCenter, aOuterRadius, outerArcStart,
+ outerSplit, startAngle, outerSplitAngle);
+ // Draw skirt as part of first half
+ if (aSkirtSize > 0) {
+ builder->LineTo(outerSplit + aCornerMultNext * aSkirtSize);
+ builder->LineTo(innerSplit -
+ aCornerMultPrev * (aSkirtSize * aSkirtSlope));
+ }
+ AcuteArcToBezier(builder.get(), innerCenter, aInnerRadius, innerSplit,
+ innerArcStart, innerSplitAngle, startAngle);
+ if ((innerCornerStart - innerArcStart).DotProduct(aCornerMultPrev) > 0) {
+ builder->LineTo(innerCornerStart);
+ }
+ builder->Close();
+ path = builder->Finish();
+ aDrawTarget->Fill(path, ColorPattern(aFirstColor));
+ }
+
+ // Draw second half with second color
+ if (aSecondColor.a > 0) {
+ builder = aDrawTarget->CreatePathBuilder();
+ builder->MoveTo(outerCornerEnd);
+ if ((innerArcEnd - innerCornerEnd).DotProduct(aCornerMultNext) < 0) {
+ builder->LineTo(innerCornerEnd);
+ }
+ AcuteArcToBezier(builder.get(), innerCenter, aInnerRadius, innerArcEnd,
+ innerSplit, endAngle, innerSplitAngle);
+ AcuteArcToBezier(builder.get(), outerCenter, aOuterRadius, outerSplit,
+ outerArcEnd, outerSplitAngle, endAngle);
+ builder->Close();
+ path = builder->Finish();
+ aDrawTarget->Fill(path, ColorPattern(aSecondColor));
+ }
+ } else if (aFirstColor.a > 0) {
+ // Draw corner with single color
+ AcuteArcToBezier(builder.get(), outerCenter, aOuterRadius, outerArcStart,
+ outerArcEnd);
+ builder->LineTo(outerCornerEnd);
+ if ((innerArcEnd - innerCornerEnd).DotProduct(aCornerMultNext) < 0) {
+ builder->LineTo(innerCornerEnd);
+ }
+ AcuteArcToBezier(builder.get(), innerCenter, aInnerRadius, innerArcEnd,
+ innerArcStart, -kKappaFactor);
+ if ((innerCornerStart - innerArcStart).DotProduct(aCornerMultPrev) > 0) {
+ builder->LineTo(innerCornerStart);
+ }
+ builder->Close();
+ path = builder->Finish();
+ aDrawTarget->Fill(path, ColorPattern(aFirstColor));
+ }
+}
+
+// Draw a corner with possibly different sides.
+// A skirt is drawn underneath the corner intersection to hide possible
+// seams when anti-aliased drawing is used.
+static void DrawCorner(DrawTarget* aDrawTarget, const Point& aOuterCorner,
+ const Point& aInnerCorner,
+ const twoFloats& aCornerMultPrev,
+ const twoFloats& aCornerMultNext,
+ const Size& aCornerDims, const DeviceColor& aFirstColor,
+ const DeviceColor& aSecondColor, Float aSkirtSize,
+ Float aSkirtSlope) {
+ // Corner box start point
+ Point cornerStart = aOuterCorner + aCornerMultPrev * aCornerDims;
+ // Corner box end point
+ Point cornerEnd = aOuterCorner + aCornerMultNext * aCornerDims;
+
+ RefPtr<PathBuilder> builder;
+ RefPtr<Path> path;
+
+ if (aFirstColor.a > 0) {
+ builder = aDrawTarget->CreatePathBuilder();
+ builder->MoveTo(cornerStart);
+ }
+
+ if (aFirstColor != aSecondColor) {
+ // Draw first half with first color
+ if (aFirstColor.a > 0) {
+ builder->LineTo(aOuterCorner);
+ // Draw skirt as part of first half
+ if (aSkirtSize > 0) {
+ builder->LineTo(aOuterCorner + aCornerMultNext * aSkirtSize);
+ builder->LineTo(aInnerCorner -
+ aCornerMultPrev * (aSkirtSize * aSkirtSlope));
+ }
+ builder->LineTo(aInnerCorner);
+ builder->Close();
+ path = builder->Finish();
+ aDrawTarget->Fill(path, ColorPattern(aFirstColor));
+ }
+
+ // Draw second half with second color
+ if (aSecondColor.a > 0) {
+ builder = aDrawTarget->CreatePathBuilder();
+ builder->MoveTo(cornerEnd);
+ builder->LineTo(aInnerCorner);
+ builder->LineTo(aOuterCorner);
+ builder->Close();
+ path = builder->Finish();
+ aDrawTarget->Fill(path, ColorPattern(aSecondColor));
+ }
+ } else if (aFirstColor.a > 0) {
+ // Draw corner with single color
+ builder->LineTo(aOuterCorner);
+ builder->LineTo(cornerEnd);
+ builder->LineTo(aInnerCorner);
+ builder->Close();
+ path = builder->Finish();
+ aDrawTarget->Fill(path, ColorPattern(aFirstColor));
+ }
+}
+
+void nsCSSBorderRenderer::DrawSolidBorder() {
+ const twoFloats cornerMults[4] = {{-1, 0}, {0, -1}, {+1, 0}, {0, +1}};
+
+ const twoFloats centerAdjusts[4] = {
+ {0, +0.5}, {-0.5, 0}, {0, -0.5}, {+0.5, 0}};
+
+ RectCornerRadii innerRadii;
+ ComputeInnerRadii(mBorderRadii, mBorderWidths, &innerRadii);
+
+ Rect strokeRect = mOuterRect;
+ strokeRect.Deflate(Margin(mBorderWidths[0] / 2.0, mBorderWidths[1] / 2.0,
+ mBorderWidths[2] / 2.0, mBorderWidths[3] / 2.0));
+
+ for (const auto i : mozilla::AllPhysicalSides()) {
+ // We now draw the current side and the CW corner following it.
+ // The CCW corner of this side was already drawn in the previous iteration.
+ // The side will be drawn as an explicit stroke, and the CW corner will be
+ // filled separately.
+ // If the next side does not have a matching color, then we split the
+ // corner into two halves, one of each side's color and draw both.
+ // Thus, the CCW corner of the next side will end up drawn here.
+
+ // the corner index -- either 1 2 3 0 (cw) or 0 3 2 1 (ccw)
+ Corner c = Corner((i + 1) % 4);
+ Corner prevCorner = Corner(i);
+
+ // i+2 and i+3 respectively. These are used to index into the corner
+ // multiplier table, and were deduced by calculating out the long form
+ // of each corner and finding a pattern in the signs and values.
+ int i1 = (i + 1) % 4;
+ int i2 = (i + 2) % 4;
+ int i3 = (i + 3) % 4;
+
+ Float sideWidth = 0.0f;
+ DeviceColor firstColor, secondColor;
+ if (IsVisible(mBorderStyles[i]) && mBorderWidths[i]) {
+ // draw the side since it is visible
+ sideWidth = mBorderWidths[i];
+ firstColor = ToDeviceColor(mBorderColors[i]);
+ // if the next side is visible, use its color for corner
+ secondColor = IsVisible(mBorderStyles[i1]) && mBorderWidths[i1]
+ ? ToDeviceColor(mBorderColors[i1])
+ : firstColor;
+ } else if (IsVisible(mBorderStyles[i1]) && mBorderWidths[i1]) {
+ // assign next side's color to both corner sides
+ firstColor = ToDeviceColor(mBorderColors[i1]);
+ secondColor = firstColor;
+ } else {
+ // neither side is visible, so nothing to do
+ continue;
+ }
+
+ Point outerCorner = mOuterRect.AtCorner(c);
+ Point innerCorner = mInnerRect.AtCorner(c);
+
+ // start and end points of border side stroke between corners
+ Point sideStart = mOuterRect.AtCorner(prevCorner) +
+ cornerMults[i2] * mBorderCornerDimensions[prevCorner];
+ Point sideEnd = outerCorner + cornerMults[i] * mBorderCornerDimensions[c];
+ // check if the side is visible and not inverted
+ if (sideWidth > 0 && firstColor.a > 0 &&
+ -(sideEnd - sideStart).DotProduct(cornerMults[i]) > 0) {
+ mDrawTarget->StrokeLine(sideStart + centerAdjusts[i] * sideWidth,
+ sideEnd + centerAdjusts[i] * sideWidth,
+ ColorPattern(firstColor),
+ StrokeOptions(sideWidth));
+ }
+
+ Float skirtSize = 0.0f, skirtSlope = 0.0f;
+ // the sides don't match, so compute a skirt
+ if (firstColor != secondColor &&
+ mPresContext->Type() != nsPresContext::eContext_Print) {
+ Point cornerDir = outerCorner - innerCorner;
+ ComputeCornerSkirtSize(
+ firstColor.a, secondColor.a, cornerDir.DotProduct(cornerMults[i]),
+ cornerDir.DotProduct(cornerMults[i3]), skirtSize, skirtSlope);
+ }
+
+ if (!mBorderRadii[c].IsEmpty()) {
+ // the corner has a border radius
+ DrawBorderRadius(mDrawTarget, c, outerCorner, innerCorner, cornerMults[i],
+ cornerMults[i3], mBorderCornerDimensions[c],
+ mBorderRadii[c], innerRadii[c], firstColor, secondColor,
+ skirtSize, skirtSlope);
+ } else if (!mBorderCornerDimensions[c].IsEmpty()) {
+ // a corner with no border radius
+ DrawCorner(mDrawTarget, outerCorner, innerCorner, cornerMults[i],
+ cornerMults[i3], mBorderCornerDimensions[c], firstColor,
+ secondColor, skirtSize, skirtSlope);
+ }
+ }
+}
+
+void nsCSSBorderRenderer::DrawBorders() {
+ if (MOZ_UNLIKELY(!mDirtyRect.Intersects(mOuterRect))) {
+ return;
+ }
+
+ if (mAllBordersSameStyle && (mBorderStyles[0] == StyleBorderStyle::None ||
+ mBorderStyles[0] == StyleBorderStyle::Hidden ||
+ mBorderColors[0] == NS_RGBA(0, 0, 0, 0))) {
+ // All borders are the same style, and the style is either none or hidden,
+ // or the color is transparent.
+ return;
+ }
+
+ if (mAllBordersSameWidth && mBorderWidths[0] == 0.0) {
+ // Some of the mAllBordersSameWidth codepaths depend on the border
+ // width being greater than zero.
+ return;
+ }
+
+ AutoRestoreTransform autoRestoreTransform;
+ Matrix mat = mDrawTarget->GetTransform();
+
+ // Clamp the CTM to be pixel-aligned; we do this only
+ // for translation-only matrices now, but we could do it
+ // if the matrix has just a scale as well. We should not
+ // do it if there's a rotation.
+ if (mat.HasNonTranslation()) {
+ if (!mat.HasNonAxisAlignedTransform()) {
+ // Scale + transform. Avoid stroke fast-paths so that we have a chance
+ // of snapping to pixel boundaries.
+ mAvoidStroke = true;
+ }
+ } else {
+ mat._31 = floor(mat._31 + 0.5);
+ mat._32 = floor(mat._32 + 0.5);
+ autoRestoreTransform.Init(mDrawTarget);
+ mDrawTarget->SetTransform(mat);
+
+ // round mOuterRect and mInnerRect; they're already an integer
+ // number of pixels apart and should stay that way after
+ // rounding. We don't do this if there's a scale in the current transform
+ // since this loses information that might be relevant when we're scaling.
+ mOuterRect.Round();
+ mInnerRect.Round();
+ }
+
+ // Initial values only used when the border colors/widths are all the same:
+ ColorPattern color(ToDeviceColor(mBorderColors[eSideTop]));
+ StrokeOptions strokeOptions(mBorderWidths[eSideTop]); // stroke width
+
+ // First there's a couple of 'special cases' that have specifically optimized
+ // drawing paths, when none of these can be used we move on to the generalized
+ // border drawing code.
+ if (mAllBordersSameStyle && mAllBordersSameWidth &&
+ mBorderStyles[0] == StyleBorderStyle::Solid && mNoBorderRadius &&
+ !mAvoidStroke) {
+ // Very simple case.
+ Rect rect = mOuterRect;
+ rect.Deflate(mBorderWidths[0] / 2.0);
+ mDrawTarget->StrokeRect(rect, color, strokeOptions);
+ return;
+ }
+
+ if (mAllBordersSameStyle && mBorderStyles[0] == StyleBorderStyle::Solid &&
+ !mAvoidStroke && !mNoBorderRadius) {
+ // Relatively simple case.
+ RoundedRect borderInnerRect(mOuterRect, mBorderRadii);
+ borderInnerRect.Deflate(mBorderWidths[eSideTop], mBorderWidths[eSideBottom],
+ mBorderWidths[eSideLeft],
+ mBorderWidths[eSideRight]);
+
+ // Instead of stroking we just use two paths: an inner and an outer.
+ // This allows us to draw borders that we couldn't when stroking. For
+ // example, borders with a border width >= the border radius. (i.e. when
+ // there are square corners on the inside)
+ //
+ // Further, this approach can be more efficient because the backend
+ // doesn't need to compute an offset curve to stroke the path. We know that
+ // the rounded parts are elipses we can offset exactly and can just compute
+ // a new cubic approximation.
+ RefPtr<PathBuilder> builder = mDrawTarget->CreatePathBuilder();
+ AppendRoundedRectToPath(builder, mOuterRect, mBorderRadii, true);
+ AppendRoundedRectToPath(builder, borderInnerRect.rect,
+ borderInnerRect.corners, false);
+ RefPtr<Path> path = builder->Finish();
+ mDrawTarget->Fill(path, color);
+ return;
+ }
+
+ const bool allBordersSolid = AllBordersSolid();
+
+ // This leaves the border corners non-interpolated for single width borders.
+ // Doing this is slightly faster and shouldn't be a problem visually.
+ if (allBordersSolid && mAllBordersSameWidth && mBorderWidths[0] == 1 &&
+ mNoBorderRadius && !mAvoidStroke) {
+ DrawSingleWidthSolidBorder();
+ return;
+ }
+
+ if (allBordersSolid && !mAvoidStroke) {
+ DrawSolidBorder();
+ return;
+ }
+
+ PrintAsString(" mOuterRect: ");
+ PrintAsString(mOuterRect);
+ PrintAsStringNewline();
+ PrintAsString(" mInnerRect: ");
+ PrintAsString(mInnerRect);
+ PrintAsStringNewline();
+ PrintAsFormatString(" mBorderColors: 0x%08x 0x%08x 0x%08x 0x%08x\n",
+ mBorderColors[0], mBorderColors[1], mBorderColors[2],
+ mBorderColors[3]);
+
+ // if conditioning the outside rect failed, then bail -- the outside
+ // rect is supposed to enclose the entire border
+ {
+ gfxRect outerRect = ThebesRect(mOuterRect);
+ gfxUtils::ConditionRect(outerRect);
+ if (outerRect.IsEmpty()) {
+ return;
+ }
+ mOuterRect = ToRect(outerRect);
+
+ if (MOZ_UNLIKELY(!mDirtyRect.Intersects(mOuterRect))) {
+ return;
+ }
+
+ gfxRect innerRect = ThebesRect(mInnerRect);
+ gfxUtils::ConditionRect(innerRect);
+ mInnerRect = ToRect(innerRect);
+ }
+
+ SideBits dashedSides = SideBits::eNone;
+ bool forceSeparateCorners = false;
+
+ for (const auto i : mozilla::AllPhysicalSides()) {
+ StyleBorderStyle style = mBorderStyles[i];
+ if (style == StyleBorderStyle::Dashed ||
+ style == StyleBorderStyle::Dotted) {
+ // we need to draw things separately for dashed/dotting
+ forceSeparateCorners = true;
+ dashedSides |= static_cast<mozilla::SideBits>(1 << i);
+ }
+ }
+
+ PrintAsFormatString(" mAllBordersSameStyle: %d dashedSides: 0x%02x\n",
+ mAllBordersSameStyle,
+ static_cast<unsigned int>(dashedSides));
+
+ if (mAllBordersSameStyle && !forceSeparateCorners) {
+ /* Draw everything in one go */
+ DrawBorderSides(SideBits::eAll);
+ PrintAsStringNewline("---------------- (1)");
+ } else {
+ AUTO_PROFILER_LABEL("nsCSSBorderRenderer::DrawBorders:multipass", GRAPHICS);
+
+ /* We have more than one pass to go. Draw the corners separately from the
+ * sides. */
+
+ // The corner is going to have negligible size if its two adjacent border
+ // sides are only 1px wide and there is no border radius. In that case we
+ // skip the overhead of painting the corner by setting the width or height
+ // of the corner to zero, which effectively extends one of the corner's
+ // adjacent border sides. We extend the longer adjacent side so that
+ // opposite sides will be the same length, which is necessary for opposite
+ // dashed/dotted sides to be symmetrical.
+ //
+ // if width > height
+ // +--+--------------+--+ +--------------------+
+ // | | | | | |
+ // +--+--------------+--+ +--+--------------+--+
+ // | | | | | | | |
+ // | | | | => | | | |
+ // | | | | | | | |
+ // +--+--------------+--+ +--+--------------+--+
+ // | | | | | |
+ // +--+--------------+--+ +--------------------+
+ //
+ // if width <= height
+ // +--+--------+--+ +--+--------+--+
+ // | | | | | | | |
+ // +--+--------+--+ | +--------+ |
+ // | | | | | | | |
+ // | | | | | | | |
+ // | | | | | | | |
+ // | | | | => | | | |
+ // | | | | | | | |
+ // | | | | | | | |
+ // | | | | | | | |
+ // +--+--------+--+ | +--------+ |
+ // | | | | | | | |
+ // +--+--------+--+ +--+--------+--+
+ //
+ // Note that if we have different border widths we could end up with
+ // opposite sides of different length. For example, if the left and
+ // bottom borders are 2px wide instead of 1px, we will end up doing
+ // something like:
+ //
+ // +----+------------+--+ +----+---------------+
+ // | | | | | | |
+ // +----+------------+--+ +----+------------+--+
+ // | | | | | | | |
+ // | | | | => | | | |
+ // | | | | | | | |
+ // +----+------------+--+ +----+------------+--+
+ // | | | | | | | |
+ // | | | | | | | |
+ // +----+------------+--+ +----+------------+--+
+ //
+ // XXX Should we only do this optimization if |mAllBordersSameWidth| is
+ // true?
+ //
+ // XXX In fact is this optimization even worth the complexity it adds to
+ // the code? 1px wide dashed borders are not overly common, and drawing
+ // corners for them is not that expensive.
+ for (const auto corner : mozilla::AllPhysicalCorners()) {
+ const mozilla::Side sides[2] = {mozilla::Side(corner), PREV_SIDE(corner)};
+
+ if (!IsZeroSize(mBorderRadii[corner])) {
+ continue;
+ }
+
+ if (mBorderWidths[sides[0]] == 1.0 && mBorderWidths[sides[1]] == 1.0) {
+ if (mOuterRect.Width() > mOuterRect.Height()) {
+ mBorderCornerDimensions[corner].width = 0.0;
+ } else {
+ mBorderCornerDimensions[corner].height = 0.0;
+ }
+ }
+ }
+
+ // First, the corners
+ for (const auto corner : mozilla::AllPhysicalCorners()) {
+ // if there's no corner, don't do all this work for it
+ if (IsZeroSize(mBorderCornerDimensions[corner])) {
+ continue;
+ }
+
+ const int sides[2] = {corner, PREV_SIDE(corner)};
+ SideBits sideBits =
+ static_cast<SideBits>((1 << sides[0]) | (1 << sides[1]));
+
+ bool simpleCornerStyle = AreBorderSideFinalStylesSame(sideBits);
+
+ // If we don't have anything complex going on in this corner,
+ // then we can just fill the corner with a solid color, and avoid
+ // the potentially expensive clip.
+ if (simpleCornerStyle && IsZeroSize(mBorderRadii[corner]) &&
+ IsSolidCornerStyle(mBorderStyles[sides[0]], corner)) {
+ sRGBColor color = MakeBorderColor(
+ mBorderColors[sides[0]],
+ BorderColorStyleForSolidCorner(mBorderStyles[sides[0]], corner));
+ mDrawTarget->FillRect(GetCornerRect(corner),
+ ColorPattern(ToDeviceColor(color)));
+ continue;
+ }
+
+ // clip to the corner
+ mDrawTarget->PushClipRect(GetCornerRect(corner));
+
+ if (simpleCornerStyle) {
+ // we don't need a group for this corner, the sides are the same,
+ // but we weren't able to render just a solid block for the corner.
+ DrawBorderSides(sideBits);
+ } else {
+ // Sides are different. We could draw using OP_ADD to
+ // get correct color blending behaviour at the seam. We'd need
+ // to do it in an offscreen surface to ensure that we're
+ // always compositing on transparent black. If the colors
+ // don't have transparency and the current destination surface
+ // has an alpha channel, we could just clear the region and
+ // avoid the temporary, but that situation doesn't happen all
+ // that often in practice (we double buffer to no-alpha
+ // surfaces). We choose just to seam though, as the performance
+ // advantages outway the modest easthetic improvement.
+
+ for (int cornerSide = 0; cornerSide < 2; cornerSide++) {
+ mozilla::Side side = mozilla::Side(sides[cornerSide]);
+ StyleBorderStyle style = mBorderStyles[side];
+
+ PrintAsFormatString("corner: %d cornerSide: %d side: %d style: %d\n",
+ corner, cornerSide, side,
+ static_cast<int>(style));
+
+ RefPtr<Path> path = GetSideClipSubPath(side);
+ mDrawTarget->PushClip(path);
+
+ DrawBorderSides(static_cast<mozilla::SideBits>(1 << side));
+
+ mDrawTarget->PopClip();
+ }
+ }
+
+ mDrawTarget->PopClip();
+
+ PrintAsStringNewline();
+ }
+
+ // in the case of a single-unit border, we already munged the
+ // corners up above; so we can just draw the top left and bottom
+ // right sides separately, if they're the same.
+ //
+ // We need to check for mNoBorderRadius, because when there is
+ // one, FillSolidBorder always draws the full rounded rectangle
+ // and expects there to be a clip in place.
+ SideBits alreadyDrawnSides = SideBits::eNone;
+ if (mOneUnitBorder && mNoBorderRadius &&
+ (dashedSides & (SideBits::eTop | SideBits::eLeft)) == SideBits::eNone) {
+ bool tlBordersSameStyle =
+ AreBorderSideFinalStylesSame(SideBits::eTop | SideBits::eLeft);
+ bool brBordersSameStyle =
+ AreBorderSideFinalStylesSame(SideBits::eBottom | SideBits::eRight);
+
+ if (tlBordersSameStyle) {
+ DrawBorderSides(SideBits::eTop | SideBits::eLeft);
+ alreadyDrawnSides |= (SideBits::eTop | SideBits::eLeft);
+ }
+
+ if (brBordersSameStyle &&
+ (dashedSides & (SideBits::eBottom | SideBits::eRight)) ==
+ SideBits::eNone) {
+ DrawBorderSides(SideBits::eBottom | SideBits::eRight);
+ alreadyDrawnSides |= (SideBits::eBottom | SideBits::eRight);
+ }
+ }
+
+ // We're done with the corners, now draw the sides.
+ for (const auto side : mozilla::AllPhysicalSides()) {
+ // if we drew it above, skip it
+ if (alreadyDrawnSides & static_cast<mozilla::SideBits>(1 << side)) {
+ continue;
+ }
+
+ // If there's no border on this side, skip it
+ if (mBorderWidths[side] == 0.0 ||
+ mBorderStyles[side] == StyleBorderStyle::Hidden ||
+ mBorderStyles[side] == StyleBorderStyle::None) {
+ continue;
+ }
+
+ if (dashedSides & static_cast<mozilla::SideBits>(1 << side)) {
+ // Dashed sides will always draw just the part ignoring the
+ // corners for the side, so no need to clip.
+ DrawDashedOrDottedSide(side);
+
+ PrintAsStringNewline("---------------- (d)");
+ continue;
+ }
+
+ // Undashed sides will currently draw the entire side,
+ // including parts that would normally be covered by a corner,
+ // so we need to clip.
+ //
+ // XXX Optimization -- it would be good to make this work like
+ // DrawDashedOrDottedSide, and have a DrawOneSide function that just
+ // draws one side and not the corners, because then we can
+ // avoid the potentially expensive clip.
+ mDrawTarget->PushClipRect(GetSideClipWithoutCornersRect(side));
+
+ DrawBorderSides(static_cast<mozilla::SideBits>(1 << side));
+
+ mDrawTarget->PopClip();
+
+ PrintAsStringNewline("---------------- (*)");
+ }
+ }
+}
+
+void nsCSSBorderRenderer::CreateWebRenderCommands(
+ nsDisplayItem* aItem, wr::DisplayListBuilder& aBuilder,
+ wr::IpcResourceUpdateQueue& aResources,
+ const layers::StackingContextHelper& aSc) {
+ LayoutDeviceRect outerRect = LayoutDeviceRect::FromUnknownRect(mOuterRect);
+ wr::LayoutRect roundedRect = wr::ToLayoutRect(outerRect);
+ wr::LayoutRect clipRect = roundedRect;
+ wr::BorderSide side[4];
+ for (const auto i : mozilla::AllPhysicalSides()) {
+ side[i] =
+ wr::ToBorderSide(ToDeviceColor(mBorderColors[i]), mBorderStyles[i]);
+ }
+
+ wr::BorderRadius borderRadius = wr::ToBorderRadius(mBorderRadii);
+
+ if (mLocalClip) {
+ LayoutDeviceRect localClip =
+ LayoutDeviceRect::FromUnknownRect(mLocalClip.value());
+ clipRect = wr::ToLayoutRect(localClip.Intersect(outerRect));
+ }
+
+ Range<const wr::BorderSide> wrsides(side, 4);
+ aBuilder.PushBorder(roundedRect, clipRect, mBackfaceIsVisible,
+ wr::ToBorderWidths(mBorderWidths[0], mBorderWidths[1],
+ mBorderWidths[2], mBorderWidths[3]),
+ wrsides, borderRadius);
+}
+
+/* static */
+Maybe<nsCSSBorderImageRenderer>
+nsCSSBorderImageRenderer::CreateBorderImageRenderer(
+ nsPresContext* aPresContext, nsIFrame* aForFrame, const nsRect& aBorderArea,
+ const nsStyleBorder& aStyleBorder, const nsRect& aDirtyRect,
+ Sides aSkipSides, uint32_t aFlags, ImgDrawResult* aDrawResult) {
+ MOZ_ASSERT(aDrawResult);
+
+ if (aDirtyRect.IsEmpty()) {
+ *aDrawResult = ImgDrawResult::SUCCESS;
+ return Nothing();
+ }
+
+ nsImageRenderer imgRenderer(aForFrame, &aStyleBorder.mBorderImageSource,
+ aFlags);
+ if (!imgRenderer.PrepareImage()) {
+ *aDrawResult = imgRenderer.PrepareResult();
+ return Nothing();
+ }
+
+ // We should always get here with the frame's border, but we may construct an
+ // nsStyleBorder om the stack to deal with :visited and other shenaningans.
+ //
+ // We always copy the border image and such from the non-visited one, so
+ // there's no need to do anything with it.
+ MOZ_ASSERT(aStyleBorder.GetBorderImageRequest() ==
+ aForFrame->StyleBorder()->GetBorderImageRequest());
+
+ nsCSSBorderImageRenderer renderer(aForFrame, aBorderArea, aStyleBorder,
+ aSkipSides, imgRenderer);
+ *aDrawResult = ImgDrawResult::SUCCESS;
+ return Some(renderer);
+}
+
+ImgDrawResult nsCSSBorderImageRenderer::DrawBorderImage(
+ nsPresContext* aPresContext, gfxContext& aRenderingContext,
+ nsIFrame* aForFrame, const nsRect& aDirtyRect) {
+ // NOTE: no Save() yet, we do that later by calling autoSR.EnsureSaved()
+ // in case we need it.
+ gfxContextAutoSaveRestore autoSR;
+
+ if (!mClip.IsEmpty()) {
+ autoSR.EnsureSaved(&aRenderingContext);
+ aRenderingContext.Clip(NSRectToSnappedRect(
+ mClip, aForFrame->PresContext()->AppUnitsPerDevPixel(),
+ *aRenderingContext.GetDrawTarget()));
+ }
+
+ // intrinsicSize.CanComputeConcreteSize() return false means we can not
+ // read intrinsic size from aStyleBorder.mBorderImageSource.
+ // In this condition, we pass imageSize(a resolved size comes from
+ // default sizing algorithm) to renderer as the viewport size.
+ CSSSizeOrRatio intrinsicSize = mImageRenderer.ComputeIntrinsicSize();
+ Maybe<nsSize> svgViewportSize =
+ intrinsicSize.CanComputeConcreteSize() ? Nothing() : Some(mImageSize);
+ bool hasIntrinsicRatio = intrinsicSize.HasRatio();
+
+ // These helper tables recharacterize the 'slice' and 'width' margins
+ // in a more convenient form: they are the x/y/width/height coords
+ // required for various bands of the border, and they have been transformed
+ // to be relative to the innerRect (for 'slice') or the page (for 'border').
+ enum { LEFT, MIDDLE, RIGHT, TOP = LEFT, BOTTOM = RIGHT };
+ const nscoord borderX[3] = {
+ mArea.x + 0,
+ mArea.x + mWidths.left,
+ mArea.x + mArea.width - mWidths.right,
+ };
+ const nscoord borderY[3] = {
+ mArea.y + 0,
+ mArea.y + mWidths.top,
+ mArea.y + mArea.height - mWidths.bottom,
+ };
+ const nscoord borderWidth[3] = {
+ mWidths.left,
+ mArea.width - mWidths.left - mWidths.right,
+ mWidths.right,
+ };
+ const nscoord borderHeight[3] = {
+ mWidths.top,
+ mArea.height - mWidths.top - mWidths.bottom,
+ mWidths.bottom,
+ };
+ const int32_t sliceX[3] = {
+ 0,
+ mSlice.left,
+ mImageSize.width - mSlice.right,
+ };
+ const int32_t sliceY[3] = {
+ 0,
+ mSlice.top,
+ mImageSize.height - mSlice.bottom,
+ };
+ const int32_t sliceWidth[3] = {
+ mSlice.left,
+ std::max(mImageSize.width - mSlice.left - mSlice.right, 0),
+ mSlice.right,
+ };
+ const int32_t sliceHeight[3] = {
+ mSlice.top,
+ std::max(mImageSize.height - mSlice.top - mSlice.bottom, 0),
+ mSlice.bottom,
+ };
+
+ ImgDrawResult result = ImgDrawResult::SUCCESS;
+
+ for (int i = LEFT; i <= RIGHT; i++) {
+ for (int j = TOP; j <= BOTTOM; j++) {
+ StyleBorderImageRepeat fillStyleH, fillStyleV;
+ nsSize unitSize;
+
+ if (i == MIDDLE && j == MIDDLE) {
+ // Discard the middle portion unless set to fill.
+ if (!mFill) {
+ continue;
+ }
+
+ // css-background:
+ // The middle image's width is scaled by the same factor as the
+ // top image unless that factor is zero or infinity, in which
+ // case the scaling factor of the bottom is substituted, and
+ // failing that, the width is not scaled. The height of the
+ // middle image is scaled by the same factor as the left image
+ // unless that factor is zero or infinity, in which case the
+ // scaling factor of the right image is substituted, and failing
+ // that, the height is not scaled.
+ gfxFloat hFactor, vFactor;
+
+ if (0 < mWidths.left && 0 < mSlice.left) {
+ vFactor = gfxFloat(mWidths.left) / mSlice.left;
+ } else if (0 < mWidths.right && 0 < mSlice.right) {
+ vFactor = gfxFloat(mWidths.right) / mSlice.right;
+ } else {
+ vFactor = 1;
+ }
+
+ if (0 < mWidths.top && 0 < mSlice.top) {
+ hFactor = gfxFloat(mWidths.top) / mSlice.top;
+ } else if (0 < mWidths.bottom && 0 < mSlice.bottom) {
+ hFactor = gfxFloat(mWidths.bottom) / mSlice.bottom;
+ } else {
+ hFactor = 1;
+ }
+
+ unitSize.width = sliceWidth[i] * hFactor;
+ unitSize.height = sliceHeight[j] * vFactor;
+ fillStyleH = mRepeatModeHorizontal;
+ fillStyleV = mRepeatModeVertical;
+
+ } else if (i == MIDDLE) { // top, bottom
+ // Sides are always stretched to the thickness of their border,
+ // and stretched proportionately on the other axis.
+ gfxFloat factor;
+ if (0 < borderHeight[j] && 0 < sliceHeight[j]) {
+ factor = gfxFloat(borderHeight[j]) / sliceHeight[j];
+ } else {
+ factor = 1;
+ }
+
+ unitSize.width = sliceWidth[i] * factor;
+ unitSize.height = borderHeight[j];
+ fillStyleH = mRepeatModeHorizontal;
+ fillStyleV = StyleBorderImageRepeat::Stretch;
+
+ } else if (j == MIDDLE) { // left, right
+ gfxFloat factor;
+ if (0 < borderWidth[i] && 0 < sliceWidth[i]) {
+ factor = gfxFloat(borderWidth[i]) / sliceWidth[i];
+ } else {
+ factor = 1;
+ }
+
+ unitSize.width = borderWidth[i];
+ unitSize.height = sliceHeight[j] * factor;
+ fillStyleH = StyleBorderImageRepeat::Stretch;
+ fillStyleV = mRepeatModeVertical;
+
+ } else {
+ // Corners are always stretched to fit the corner.
+ unitSize.width = borderWidth[i];
+ unitSize.height = borderHeight[j];
+ fillStyleH = StyleBorderImageRepeat::Stretch;
+ fillStyleV = StyleBorderImageRepeat::Stretch;
+ }
+
+ nsRect destArea(borderX[i], borderY[j], borderWidth[i], borderHeight[j]);
+ nsRect subArea(sliceX[i], sliceY[j], sliceWidth[i], sliceHeight[j]);
+ if (subArea.IsEmpty()) continue;
+
+ nsIntRect intSubArea = subArea.ToOutsidePixels(AppUnitsPerCSSPixel());
+ result &= mImageRenderer.DrawBorderImageComponent(
+ aPresContext, aRenderingContext, aDirtyRect, destArea,
+ CSSIntRect(intSubArea.x, intSubArea.y, intSubArea.width,
+ intSubArea.height),
+ fillStyleH, fillStyleV, unitSize, j * (RIGHT + 1) + i,
+ svgViewportSize, hasIntrinsicRatio);
+ }
+ }
+
+ return result;
+}
+
+ImgDrawResult nsCSSBorderImageRenderer::CreateWebRenderCommands(
+ nsDisplayItem* aItem, nsIFrame* aForFrame,
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const mozilla::layers::StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) {
+ if (!mImageRenderer.IsReady()) {
+ return ImgDrawResult::NOT_READY;
+ }
+
+ float widths[4];
+ float slice[4];
+ const int32_t appUnitsPerDevPixel =
+ aForFrame->PresContext()->AppUnitsPerDevPixel();
+ for (const auto i : mozilla::AllPhysicalSides()) {
+ slice[i] = (float)(mSlice.Side(i)) / appUnitsPerDevPixel;
+ widths[i] = (float)(mWidths.Side(i)) / appUnitsPerDevPixel;
+ }
+
+ LayoutDeviceRect destRect =
+ LayoutDeviceRect::FromAppUnits(mArea, appUnitsPerDevPixel);
+ destRect.Round();
+ wr::LayoutRect dest = wr::ToLayoutRect(destRect);
+
+ wr::LayoutRect clip = dest;
+ if (!mClip.IsEmpty()) {
+ LayoutDeviceRect clipRect =
+ LayoutDeviceRect::FromAppUnits(mClip, appUnitsPerDevPixel);
+ clip = wr::ToLayoutRect(clipRect);
+ }
+
+ ImgDrawResult drawResult = ImgDrawResult::SUCCESS;
+ switch (mImageRenderer.GetType()) {
+ case StyleImage::Tag::Rect:
+ case StyleImage::Tag::Url: {
+ RefPtr<imgIContainer> img = mImageRenderer.GetImage();
+ if (!img || img->GetType() == imgIContainer::TYPE_VECTOR) {
+ // Vector images will redraw each segment of the border up to 8 times.
+ // We draw using a restricted region derived from the segment's clip and
+ // scale the image accordingly (see ClippedImage::Draw). If we follow
+ // this convention as is for WebRender, we will need to rasterize the
+ // entire vector image scaled up without the restriction region, which
+ // means our main thread CPU and memory footprints will be much higher.
+ // Ideally we would be able to provide a raster image for each segment
+ // of the border. For now we use fallback.
+ return ImgDrawResult::NOT_SUPPORTED;
+ }
+
+ uint32_t flags = aDisplayListBuilder->GetImageDecodeFlags();
+
+ LayoutDeviceRect imageRect = LayoutDeviceRect::FromAppUnits(
+ nsRect(nsPoint(), mImageRenderer.GetSize()), appUnitsPerDevPixel);
+
+ SVGImageContext svgContext;
+ Maybe<ImageIntRegion> region;
+ gfx::IntSize decodeSize =
+ nsLayoutUtils::ComputeImageContainerDrawingParameters(
+ img, aForFrame, imageRect, imageRect, aSc, flags, svgContext,
+ region);
+
+ RefPtr<WebRenderImageProvider> provider;
+ drawResult = img->GetImageProvider(aManager->LayerManager(), decodeSize,
+ svgContext, region, flags,
+ getter_AddRefs(provider));
+
+ Maybe<wr::ImageKey> key =
+ aManager->CommandBuilder().CreateImageProviderKey(
+ aItem, provider, drawResult, aResources);
+ if (key.isNothing()) {
+ break;
+ }
+
+ auto rendering =
+ wr::ToImageRendering(aItem->Frame()->UsedImageRendering());
+ if (mFill) {
+ float epsilon = 0.0001;
+ bool noVerticalBorders = widths[0] <= epsilon && widths[2] < epsilon;
+ bool noHorizontalBorders = widths[1] <= epsilon && widths[3] < epsilon;
+
+ // Border image with no border. It's a little silly but WebRender
+ // currently does not handle this. We could fall back to a blob image
+ // but there are reftests that are sensible to the test going through a
+ // blob while the reference doesn't.
+ if (noVerticalBorders && noHorizontalBorders) {
+ aBuilder.PushImage(dest, clip, !aItem->BackfaceIsHidden(), false,
+ rendering, key.value());
+ break;
+ }
+
+ // Fall-back if we want to fill the middle area and opposite edges are
+ // both empty.
+ // TODO(bug 1609893): moving some of the repetition handling code out
+ // of the image shader will make it easier to handle these cases
+ // properly.
+ if (noHorizontalBorders || noVerticalBorders) {
+ return ImgDrawResult::NOT_SUPPORTED;
+ }
+ }
+
+ wr::WrBorderImage params{
+ wr::ToBorderWidths(widths[0], widths[1], widths[2], widths[3]),
+ key.value(),
+ rendering,
+ mImageSize.width / appUnitsPerDevPixel,
+ mImageSize.height / appUnitsPerDevPixel,
+ mFill,
+ wr::ToDeviceIntSideOffsets(slice[0], slice[1], slice[2], slice[3]),
+ wr::ToRepeatMode(mRepeatModeHorizontal),
+ wr::ToRepeatMode(mRepeatModeVertical)};
+
+ aBuilder.PushBorderImage(dest, clip, !aItem->BackfaceIsHidden(), params);
+ break;
+ }
+ case StyleImage::Tag::Gradient: {
+ const StyleGradient& gradient = *mImageRenderer.GetGradientData();
+ nsCSSGradientRenderer renderer = nsCSSGradientRenderer::Create(
+ aForFrame->PresContext(), aForFrame->Style(), gradient, mImageSize);
+
+ wr::ExtendMode extendMode;
+ nsTArray<wr::GradientStop> stops;
+ LayoutDevicePoint lineStart;
+ LayoutDevicePoint lineEnd;
+ LayoutDeviceSize gradientRadius;
+ LayoutDevicePoint gradientCenter;
+ float gradientAngle;
+ renderer.BuildWebRenderParameters(1.0, extendMode, stops, lineStart,
+ lineEnd, gradientRadius, gradientCenter,
+ gradientAngle);
+
+ if (gradient.IsLinear()) {
+ LayoutDevicePoint startPoint =
+ LayoutDevicePoint(dest.min.x, dest.min.y) + lineStart;
+ LayoutDevicePoint endPoint =
+ LayoutDevicePoint(dest.min.x, dest.min.y) + lineEnd;
+
+ aBuilder.PushBorderGradient(
+ dest, clip, !aItem->BackfaceIsHidden(),
+ wr::ToBorderWidths(widths[0], widths[1], widths[2], widths[3]),
+ (float)(mImageSize.width) / appUnitsPerDevPixel,
+ (float)(mImageSize.height) / appUnitsPerDevPixel, mFill,
+ wr::ToDeviceIntSideOffsets(slice[0], slice[1], slice[2], slice[3]),
+ wr::ToLayoutPoint(startPoint), wr::ToLayoutPoint(endPoint), stops,
+ extendMode);
+ } else if (gradient.IsRadial()) {
+ aBuilder.PushBorderRadialGradient(
+ dest, clip, !aItem->BackfaceIsHidden(),
+ wr::ToBorderWidths(widths[0], widths[1], widths[2], widths[3]),
+ mFill, wr::ToLayoutPoint(lineStart),
+ wr::ToLayoutSize(gradientRadius), stops, extendMode);
+ } else {
+ MOZ_ASSERT(gradient.IsConic());
+ aBuilder.PushBorderConicGradient(
+ dest, clip, !aItem->BackfaceIsHidden(),
+ wr::ToBorderWidths(widths[0], widths[1], widths[2], widths[3]),
+ mFill, wr::ToLayoutPoint(gradientCenter), gradientAngle, stops,
+ extendMode);
+ }
+ break;
+ }
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unsupport border image type");
+ drawResult = ImgDrawResult::NOT_SUPPORTED;
+ }
+
+ return drawResult;
+}
+
+nsCSSBorderImageRenderer::nsCSSBorderImageRenderer(
+ const nsCSSBorderImageRenderer& aRhs)
+ : mImageRenderer(aRhs.mImageRenderer),
+ mImageSize(aRhs.mImageSize),
+ mSlice(aRhs.mSlice),
+ mWidths(aRhs.mWidths),
+ mImageOutset(aRhs.mImageOutset),
+ mArea(aRhs.mArea),
+ mClip(aRhs.mClip),
+ mRepeatModeHorizontal(aRhs.mRepeatModeHorizontal),
+ mRepeatModeVertical(aRhs.mRepeatModeVertical),
+ mFill(aRhs.mFill) {
+ Unused << mImageRenderer.PrepareResult();
+}
+
+nsCSSBorderImageRenderer& nsCSSBorderImageRenderer::operator=(
+ const nsCSSBorderImageRenderer& aRhs) {
+ mImageRenderer = aRhs.mImageRenderer;
+ mImageSize = aRhs.mImageSize;
+ mSlice = aRhs.mSlice;
+ mWidths = aRhs.mWidths;
+ mImageOutset = aRhs.mImageOutset;
+ mArea = aRhs.mArea;
+ mClip = aRhs.mClip;
+ mRepeatModeHorizontal = aRhs.mRepeatModeHorizontal;
+ mRepeatModeVertical = aRhs.mRepeatModeVertical;
+ mFill = aRhs.mFill;
+ Unused << mImageRenderer.PrepareResult();
+
+ return *this;
+}
+
+nsCSSBorderImageRenderer::nsCSSBorderImageRenderer(
+ nsIFrame* aForFrame, const nsRect& aBorderArea,
+ const nsStyleBorder& aStyleBorder, Sides aSkipSides,
+ const nsImageRenderer& aImageRenderer)
+ : mImageRenderer(aImageRenderer) {
+ // Determine the border image area, which by default corresponds to the
+ // border box but can be modified by 'border-image-outset'.
+ // Note that 'border-radius' do not apply to 'border-image' borders per
+ // <http://dev.w3.org/csswg/css-backgrounds/#corner-clipping>.
+ nsMargin borderWidths(aStyleBorder.GetComputedBorder());
+ mImageOutset = aStyleBorder.GetImageOutset();
+ if (nsCSSRendering::IsBoxDecorationSlice(aStyleBorder) &&
+ !aSkipSides.IsEmpty()) {
+ mArea = nsCSSRendering::BoxDecorationRectForBorder(
+ aForFrame, aBorderArea, aSkipSides, &aStyleBorder);
+ if (mArea.IsEqualEdges(aBorderArea)) {
+ // No need for a clip, just skip the sides we don't want.
+ borderWidths.ApplySkipSides(aSkipSides);
+ mImageOutset.ApplySkipSides(aSkipSides);
+ mArea.Inflate(mImageOutset);
+ } else {
+ // We're drawing borders around the joined continuation boxes so we need
+ // to clip that to the slice that we want for this frame.
+ mArea.Inflate(mImageOutset);
+ mImageOutset.ApplySkipSides(aSkipSides);
+ mClip = aBorderArea;
+ mClip.Inflate(mImageOutset);
+ }
+ } else {
+ mArea = aBorderArea;
+ mArea.Inflate(mImageOutset);
+ }
+
+ // Calculate the image size used to compute slice points.
+ CSSSizeOrRatio intrinsicSize = mImageRenderer.ComputeIntrinsicSize();
+ mImageSize = nsImageRenderer::ComputeConcreteSize(
+ CSSSizeOrRatio(), intrinsicSize, mArea.Size());
+ mImageRenderer.SetPreferredSize(intrinsicSize, mImageSize);
+
+ // Compute the used values of 'border-image-slice' and 'border-image-width';
+ // we do them together because the latter can depend on the former.
+ nsMargin slice;
+ nsMargin border;
+ for (const auto s : mozilla::AllPhysicalSides()) {
+ const auto& slice = aStyleBorder.mBorderImageSlice.offsets.Get(s);
+ int32_t imgDimension =
+ SideIsVertical(s) ? mImageSize.width : mImageSize.height;
+ nscoord borderDimension = SideIsVertical(s) ? mArea.width : mArea.height;
+ double value;
+ if (slice.IsNumber()) {
+ value = nsPresContext::CSSPixelsToAppUnits(NS_lround(slice.AsNumber()));
+ } else {
+ MOZ_ASSERT(slice.IsPercentage());
+ value = slice.AsPercentage()._0 * imgDimension;
+ }
+ if (value < 0) {
+ value = 0;
+ }
+ if (value > imgDimension && imgDimension > 0) {
+ value = imgDimension;
+ }
+ mSlice.Side(s) = value;
+
+ const auto& width = aStyleBorder.mBorderImageWidth.Get(s);
+ switch (width.tag) {
+ case StyleBorderImageSideWidth::Tag::LengthPercentage:
+ value =
+ std::max(0, width.AsLengthPercentage().Resolve(borderDimension));
+ break;
+ case StyleBorderImageSideWidth::Tag::Number:
+ value = width.AsNumber() * borderWidths.Side(s);
+ break;
+ case StyleBorderImageSideWidth::Tag::Auto:
+ value = mSlice.Side(s);
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("unexpected CSS unit for border image area");
+ value = 0;
+ break;
+ }
+ // NSToCoordRoundWithClamp rounds towards infinity, but that's OK
+ // because we expect value to be non-negative.
+ MOZ_ASSERT(value >= 0);
+ mWidths.Side(s) = NSToCoordRoundWithClamp(value);
+ MOZ_ASSERT(mWidths.Side(s) >= 0);
+ }
+
+ // "If two opposite border-image-width offsets are large enough that they
+ // overlap, their used values are proportionately reduced until they no
+ // longer overlap."
+ uint32_t combinedBorderWidth =
+ uint32_t(mWidths.left) + uint32_t(mWidths.right);
+ double scaleX = combinedBorderWidth > uint32_t(mArea.width)
+ ? mArea.width / double(combinedBorderWidth)
+ : 1.0;
+ uint32_t combinedBorderHeight =
+ uint32_t(mWidths.top) + uint32_t(mWidths.bottom);
+ double scaleY = combinedBorderHeight > uint32_t(mArea.height)
+ ? mArea.height / double(combinedBorderHeight)
+ : 1.0;
+ double scale = std::min(scaleX, scaleY);
+ if (scale < 1.0) {
+ mWidths.left *= scale;
+ mWidths.right *= scale;
+ mWidths.top *= scale;
+ mWidths.bottom *= scale;
+ NS_ASSERTION(mWidths.left + mWidths.right <= mArea.width &&
+ mWidths.top + mWidths.bottom <= mArea.height,
+ "rounding error in width reduction???");
+ }
+
+ mRepeatModeHorizontal = aStyleBorder.mBorderImageRepeatH;
+ mRepeatModeVertical = aStyleBorder.mBorderImageRepeatV;
+ mFill = aStyleBorder.mBorderImageSlice.fill;
+}
diff --git a/layout/painting/nsCSSRenderingBorders.h b/layout/painting/nsCSSRenderingBorders.h
new file mode 100644
index 0000000000..8817083208
--- /dev/null
+++ b/layout/painting/nsCSSRenderingBorders.h
@@ -0,0 +1,357 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef NS_CSS_RENDERING_BORDERS_H
+#define NS_CSS_RENDERING_BORDERS_H
+
+#include "gfxRect.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/BezierUtils.h"
+#include "mozilla/gfx/PathHelpers.h"
+#include "mozilla/RefPtr.h"
+#include "nsColor.h"
+#include "nsCOMPtr.h"
+#include "nsIFrame.h"
+#include "nsImageRenderer.h"
+#include "gfxUtils.h"
+
+struct nsBorderColors;
+
+namespace mozilla {
+class nsDisplayItem;
+class nsDisplayList;
+class nsDisplayListBuilder;
+
+class nsDisplayBorder;
+class nsDisplayButtonBorder;
+class nsDisplayButtonForeground;
+class nsDisplayOutline;
+
+enum class StyleBorderStyle : uint8_t;
+enum class StyleBorderImageRepeat : uint8_t;
+
+namespace gfx {
+class GradientStops;
+} // namespace gfx
+namespace layers {
+class StackingContextHelper;
+} // namespace layers
+} // namespace mozilla
+
+// define this to enable a bunch of debug dump info
+#undef DEBUG_NEW_BORDERS
+
+/*
+ * Helper class that handles border rendering.
+ *
+ * aDrawTarget -- the DrawTarget to which the border should be rendered
+ * outsideRect -- the rectangle on the outer edge of the border
+ *
+ * For any parameter where an array of side values is passed in,
+ * they are in top, right, bottom, left order.
+ *
+ * borderStyles -- one border style enum per side
+ * borderWidths -- one border width per side
+ * borderRadii -- a RectCornerRadii struct describing the w/h for each rounded
+ * corner. If the corner doesn't have a border radius, 0,0 should be given for
+ * it. borderColors -- one nscolor per side
+ *
+ * skipSides -- a bit mask specifying which sides, if any, to skip
+ * backgroundColor -- the background color of the element.
+ * Used in calculating colors for 2-tone borders, such as inset and outset
+ * gapRect - a rectangle that should be clipped out to leave a gap in a border,
+ * or nullptr if none.
+ */
+
+typedef enum {
+ BorderColorStyleNone,
+ BorderColorStyleSolid,
+ BorderColorStyleLight,
+ BorderColorStyleDark
+} BorderColorStyle;
+
+class nsPresContext;
+
+class nsCSSBorderRenderer final {
+ typedef mozilla::gfx::Bezier Bezier;
+ typedef mozilla::gfx::ColorPattern ColorPattern;
+ typedef mozilla::gfx::DrawTarget DrawTarget;
+ typedef mozilla::gfx::Float Float;
+ typedef mozilla::gfx::Path Path;
+ typedef mozilla::gfx::Point Point;
+ typedef mozilla::gfx::Rect Rect;
+ typedef mozilla::gfx::RectCornerRadii RectCornerRadii;
+ typedef mozilla::gfx::StrokeOptions StrokeOptions;
+
+ friend class mozilla::nsDisplayOutline;
+ friend class mozilla::nsDisplayButtonBorder;
+ friend class mozilla::nsDisplayButtonForeground;
+
+ public:
+ nsCSSBorderRenderer(nsPresContext* aPresContext, DrawTarget* aDrawTarget,
+ const Rect& aDirtyRect, Rect& aOuterRect,
+ const mozilla::StyleBorderStyle* aBorderStyles,
+ const Float* aBorderWidths, RectCornerRadii& aBorderRadii,
+ const nscolor* aBorderColors, bool aBackfaceIsVisible,
+ const mozilla::Maybe<Rect>& aClipRect);
+
+ // draw the entire border
+ void DrawBorders();
+
+ void CreateWebRenderCommands(
+ mozilla::nsDisplayItem* aItem, mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const mozilla::layers::StackingContextHelper& aSc);
+
+ // utility function used for background painting as well as borders
+ static void ComputeInnerRadii(const RectCornerRadii& aRadii,
+ const Float* aBorderSizes,
+ RectCornerRadii* aInnerRadiiRet);
+
+ // Given aRadii as the border radii for a rectangle, compute the
+ // appropriate radii for another rectangle *outside* that rectangle
+ // by increasing the radii, except keeping sharp corners sharp.
+ // Used for spread box-shadows
+ static void ComputeOuterRadii(const RectCornerRadii& aRadii,
+ const Float* aBorderSizes,
+ RectCornerRadii* aOuterRadiiRet);
+
+ static bool AllCornersZeroSize(const RectCornerRadii& corners);
+
+ private:
+ RectCornerRadii mBorderCornerDimensions;
+
+ nsPresContext* mPresContext;
+
+ // destination DrawTarget and dirty rect
+ DrawTarget* mDrawTarget;
+ Rect mDirtyRect;
+
+ // the rectangle of the outside and the inside of the border
+ Rect mOuterRect;
+ Rect mInnerRect;
+
+ // the style and size of the border
+ mozilla::StyleBorderStyle mBorderStyles[4];
+ Float mBorderWidths[4];
+ RectCornerRadii mBorderRadii;
+
+ // the colors for 'border-top-color' et. al.
+ nscolor mBorderColors[4];
+
+ // calculated values
+ bool mAllBordersSameStyle;
+ bool mAllBordersSameWidth;
+ bool mOneUnitBorder;
+ bool mNoBorderRadius;
+ bool mAvoidStroke;
+ bool mBackfaceIsVisible;
+ mozilla::Maybe<Rect> mLocalClip;
+
+ // For all the sides in the bitmask, would they be rendered
+ // in an identical color and style?
+ bool AreBorderSideFinalStylesSame(mozilla::SideBits aSides);
+
+ // For the given style, is the given corner a solid color?
+ bool IsSolidCornerStyle(mozilla::StyleBorderStyle aStyle,
+ mozilla::Corner aCorner);
+
+ // For the given corner, is the given corner mergeable into one dot?
+ bool IsCornerMergeable(mozilla::Corner aCorner);
+
+ // For the given solid corner, what color style should be used?
+ BorderColorStyle BorderColorStyleForSolidCorner(
+ mozilla::StyleBorderStyle aStyle, mozilla::Corner aCorner);
+
+ //
+ // Path generation functions
+ //
+
+ // Get the Rect for drawing the given corner
+ Rect GetCornerRect(mozilla::Corner aCorner);
+ // add the path for drawing the given side without any adjacent corners to the
+ // context
+ Rect GetSideClipWithoutCornersRect(mozilla::Side aSide);
+
+ // Create a clip path for the wedge that this side of
+ // the border should take up. This is only called
+ // when we're drawing separate border sides, so we know
+ // that ADD compositing is taking place.
+ //
+ // This code needs to make sure that the individual pieces
+ // don't ever (mathematically) overlap; the pixel overlap
+ // is taken care of by the ADD compositing.
+ already_AddRefed<Path> GetSideClipSubPath(mozilla::Side aSide);
+
+ // Return start or end point for dashed/dotted side
+ Point GetStraightBorderPoint(mozilla::Side aSide, mozilla::Corner aCorner,
+ bool* aIsUnfilled, Float aDotOffset = 0.0f);
+
+ // Return bezier control points for the outer and the inner curve for given
+ // corner
+ void GetOuterAndInnerBezier(Bezier* aOuterBezier, Bezier* aInnerBezier,
+ mozilla::Corner aCorner);
+
+ // Given a set of sides to fill and a color, do so in the fastest way.
+ //
+ // Stroke tends to be faster for smaller borders because it doesn't go
+ // through the tessellator, which has initialization overhead. If
+ // we're rendering all sides, we can use stroke at any thickness; we
+ // also do TL/BR pairs at 1px thickness using stroke.
+ //
+ // If we can't stroke, then if it's a TL/BR pair, we use the specific
+ // TL/BR paths. Otherwise, we do the full path and fill.
+ //
+ // Calling code is expected to only set up a clip as necessary; no
+ // clip is needed if we can render the entire border in 1 or 2 passes.
+ void FillSolidBorder(const Rect& aOuterRect, const Rect& aInnerRect,
+ const RectCornerRadii& aBorderRadii,
+ const Float* aBorderSizes, mozilla::SideBits aSides,
+ const ColorPattern& aColor);
+
+ //
+ // core rendering
+ //
+
+ // draw the border for the given sides, using the style of the first side
+ // present in the bitmask
+ void DrawBorderSides(mozilla::SideBits aSides);
+
+ // Setup the stroke options for the given dashed/dotted side
+ void SetupDashedOptions(StrokeOptions* aStrokeOptions, Float aDash[2],
+ mozilla::Side aSide, Float aBorderLength,
+ bool isCorner);
+
+ // Draw the given dashed/dotte side
+ void DrawDashedOrDottedSide(mozilla::Side aSide);
+
+ // Draw the given dotted side, each dot separately
+ void DrawDottedSideSlow(mozilla::Side aSide);
+
+ // Draw the given dashed/dotted corner
+ void DrawDashedOrDottedCorner(mozilla::Side aSide, mozilla::Corner aCorner);
+
+ // Draw the given dotted corner, each segment separately
+ void DrawDottedCornerSlow(mozilla::Side aSide, mozilla::Corner aCorner);
+
+ // Draw the given dashed corner, each dot separately
+ void DrawDashedCornerSlow(mozilla::Side aSide, mozilla::Corner aCorner);
+
+ // Draw the given dashed/dotted corner with solid style
+ void DrawFallbackSolidCorner(mozilla::Side aSide, mozilla::Corner aCorner);
+
+ // Analyze if all border sides have the same width.
+ bool AllBordersSameWidth();
+
+ // Analyze if all borders are 'solid' this also considers hidden or 'none'
+ // borders because they can be considered 'solid' borders of 0 width and
+ // with no color effect.
+ bool AllBordersSolid();
+
+ // Draw a solid color border that is uniformly the same width.
+ void DrawSingleWidthSolidBorder();
+
+ // Draw any border which is solid on all sides.
+ void DrawSolidBorder();
+};
+
+class nsCSSBorderImageRenderer final {
+ typedef mozilla::nsImageRenderer nsImageRenderer;
+
+ public:
+ static mozilla::Maybe<nsCSSBorderImageRenderer> CreateBorderImageRenderer(
+ nsPresContext* aPresContext, nsIFrame* aForFrame,
+ const nsRect& aBorderArea, const nsStyleBorder& aStyleBorder,
+ const nsRect& aDirtyRect, nsIFrame::Sides aSkipSides, uint32_t aFlags,
+ mozilla::image::ImgDrawResult* aDrawResult);
+
+ mozilla::image::ImgDrawResult DrawBorderImage(nsPresContext* aPresContext,
+ gfxContext& aRenderingContext,
+ nsIFrame* aForFrame,
+ const nsRect& aDirtyRect);
+ mozilla::image::ImgDrawResult CreateWebRenderCommands(
+ mozilla::nsDisplayItem* aItem, nsIFrame* aForFrame,
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const mozilla::layers::StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager,
+ mozilla::nsDisplayListBuilder* aDisplayListBuilder);
+
+ nsCSSBorderImageRenderer(const nsCSSBorderImageRenderer& aRhs);
+ nsCSSBorderImageRenderer& operator=(const nsCSSBorderImageRenderer& aRhs);
+
+ private:
+ nsCSSBorderImageRenderer(nsIFrame* aForFrame, const nsRect& aBorderArea,
+ const nsStyleBorder& aStyleBorder,
+ nsIFrame::Sides aSkipSides,
+ const nsImageRenderer& aImageRenderer);
+
+ nsImageRenderer mImageRenderer;
+ nsSize mImageSize;
+ nsMargin mSlice;
+ nsMargin mWidths;
+ nsMargin mImageOutset;
+ nsRect mArea;
+ nsRect mClip;
+ mozilla::StyleBorderImageRepeat mRepeatModeHorizontal;
+ mozilla::StyleBorderImageRepeat mRepeatModeVertical;
+ bool mFill;
+
+ friend class mozilla::nsDisplayBorder;
+ friend struct nsCSSRendering;
+};
+
+namespace mozilla {
+#ifdef DEBUG_NEW_BORDERS
+# include <stdarg.h>
+
+static inline void PrintAsString(const mozilla::gfx::Point& p) {
+ fprintf(stderr, "[%f,%f]", p.x, p.y);
+}
+
+static inline void PrintAsString(const mozilla::gfx::Size& s) {
+ fprintf(stderr, "[%f %f]", s.width, s.height);
+}
+
+static inline void PrintAsString(const mozilla::gfx::Rect& r) {
+ fprintf(stderr, "[%f %f %f %f]", r.X(), r.Y(), r.Width(), r.Height());
+}
+
+static inline void PrintAsString(const mozilla::gfx::Float f) {
+ fprintf(stderr, "%f", f);
+}
+
+static inline void PrintAsString(const char* s) { fprintf(stderr, "%s", s); }
+
+static inline void PrintAsStringNewline(const char* s = nullptr) {
+ if (s) fprintf(stderr, "%s", s);
+ fprintf(stderr, "\n");
+ fflush(stderr);
+}
+
+static inline MOZ_FORMAT_PRINTF(1, 2) void PrintAsFormatString(const char* fmt,
+ ...) {
+ va_list vl;
+ va_start(vl, fmt);
+ vfprintf(stderr, fmt, vl);
+ va_end(vl);
+}
+
+#else
+static inline void PrintAsString(const mozilla::gfx::Point& p) {}
+static inline void PrintAsString(const mozilla::gfx::Size& s) {}
+static inline void PrintAsString(const mozilla::gfx::Rect& r) {}
+static inline void PrintAsString(const mozilla::gfx::Float f) {}
+static inline void PrintAsString(const char* s) {}
+static inline void PrintAsStringNewline(const char* s = nullptr) {}
+static inline MOZ_FORMAT_PRINTF(1, 2) void PrintAsFormatString(const char* fmt,
+ ...) {}
+#endif
+
+} // namespace mozilla
+
+#endif /* NS_CSS_RENDERING_BORDERS_H */
diff --git a/layout/painting/nsCSSRenderingGradients.cpp b/layout/painting/nsCSSRenderingGradients.cpp
new file mode 100644
index 0000000000..f4a859cf34
--- /dev/null
+++ b/layout/painting/nsCSSRenderingGradients.cpp
@@ -0,0 +1,1299 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* utility functions for drawing borders and backgrounds */
+
+#include "nsCSSRenderingGradients.h"
+
+#include <tuple>
+
+#include "gfx2DGlue.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/ComputedStyle.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/Helpers.h"
+#include "mozilla/MathAlgorithms.h"
+#include "mozilla/ProfilerLabels.h"
+
+#include "nsLayoutUtils.h"
+#include "nsStyleConsts.h"
+#include "nsPresContext.h"
+#include "nsPoint.h"
+#include "nsRect.h"
+#include "nsCSSColorUtils.h"
+#include "gfxContext.h"
+#include "nsStyleStructInlines.h"
+#include "nsCSSProps.h"
+#include "gfxUtils.h"
+#include "gfxGradientCache.h"
+
+#include "mozilla/layers/StackingContextHelper.h"
+#include "mozilla/layers/WebRenderLayerManager.h"
+#include "mozilla/webrender/WebRenderTypes.h"
+#include "mozilla/webrender/WebRenderAPI.h"
+#include "Units.h"
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+
+static CSSPoint ResolvePosition(const Position& aPos, const CSSSize& aSize) {
+ CSSCoord h = aPos.horizontal.ResolveToCSSPixels(aSize.width);
+ CSSCoord v = aPos.vertical.ResolveToCSSPixels(aSize.height);
+ return CSSPoint(h, v);
+}
+
+// Given a box with size aBoxSize and origin (0,0), and an angle aAngle,
+// and a starting point for the gradient line aStart, find the endpoint of
+// the gradient line --- the intersection of the gradient line with a line
+// perpendicular to aAngle that passes through the farthest corner in the
+// direction aAngle.
+static CSSPoint ComputeGradientLineEndFromAngle(const CSSPoint& aStart,
+ double aAngle,
+ const CSSSize& aBoxSize) {
+ double dx = cos(-aAngle);
+ double dy = sin(-aAngle);
+ CSSPoint farthestCorner(dx > 0 ? aBoxSize.width : 0,
+ dy > 0 ? aBoxSize.height : 0);
+ CSSPoint delta = farthestCorner - aStart;
+ double u = delta.x * dy - delta.y * dx;
+ return farthestCorner + CSSPoint(-u * dy, u * dx);
+}
+
+// Compute the start and end points of the gradient line for a linear gradient.
+static std::tuple<CSSPoint, CSSPoint> ComputeLinearGradientLine(
+ nsPresContext* aPresContext, const StyleGradient& aGradient,
+ const CSSSize& aBoxSize) {
+ using X = StyleHorizontalPositionKeyword;
+ using Y = StyleVerticalPositionKeyword;
+
+ const StyleLineDirection& direction = aGradient.AsLinear().direction;
+ const bool isModern =
+ aGradient.AsLinear().compat_mode == StyleGradientCompatMode::Modern;
+
+ CSSPoint center(aBoxSize.width / 2, aBoxSize.height / 2);
+ switch (direction.tag) {
+ case StyleLineDirection::Tag::Angle: {
+ double angle = direction.AsAngle().ToRadians();
+ if (isModern) {
+ angle = M_PI_2 - angle;
+ }
+ CSSPoint end = ComputeGradientLineEndFromAngle(center, angle, aBoxSize);
+ CSSPoint start = CSSPoint(aBoxSize.width, aBoxSize.height) - end;
+ return {start, end};
+ }
+ case StyleLineDirection::Tag::Vertical: {
+ CSSPoint start(center.x, 0);
+ CSSPoint end(center.x, aBoxSize.height);
+ if (isModern == (direction.AsVertical() == Y::Top)) {
+ std::swap(start.y, end.y);
+ }
+ return {start, end};
+ }
+ case StyleLineDirection::Tag::Horizontal: {
+ CSSPoint start(0, center.y);
+ CSSPoint end(aBoxSize.width, center.y);
+ if (isModern == (direction.AsHorizontal() == X::Left)) {
+ std::swap(start.x, end.x);
+ }
+ return {start, end};
+ }
+ case StyleLineDirection::Tag::Corner: {
+ const auto& corner = direction.AsCorner();
+ const X& h = corner._0;
+ const Y& v = corner._1;
+
+ if (isModern) {
+ float xSign = h == X::Right ? 1.0 : -1.0;
+ float ySign = v == Y::Top ? 1.0 : -1.0;
+ double angle = atan2(ySign * aBoxSize.width, xSign * aBoxSize.height);
+ CSSPoint end = ComputeGradientLineEndFromAngle(center, angle, aBoxSize);
+ CSSPoint start = CSSPoint(aBoxSize.width, aBoxSize.height) - end;
+ return {start, end};
+ }
+
+ CSSCoord startX = h == X::Left ? 0.0 : aBoxSize.width;
+ CSSCoord startY = v == Y::Top ? 0.0 : aBoxSize.height;
+
+ CSSPoint start(startX, startY);
+ CSSPoint end = CSSPoint(aBoxSize.width, aBoxSize.height) - start;
+ return {start, end};
+ }
+ default:
+ break;
+ }
+ MOZ_ASSERT_UNREACHABLE("Unknown line direction");
+ return {CSSPoint(), CSSPoint()};
+}
+
+using EndingShape = StyleGenericEndingShape<Length, LengthPercentage>;
+using RadialGradientRadii =
+ Variant<StyleShapeExtent, std::pair<CSSCoord, CSSCoord>>;
+
+static RadialGradientRadii ComputeRadialGradientRadii(const EndingShape& aShape,
+ const CSSSize& aSize) {
+ if (aShape.IsCircle()) {
+ auto& circle = aShape.AsCircle();
+ if (circle.IsExtent()) {
+ return RadialGradientRadii(circle.AsExtent());
+ }
+ CSSCoord radius = circle.AsRadius().ToCSSPixels();
+ return RadialGradientRadii(std::make_pair(radius, radius));
+ }
+ auto& ellipse = aShape.AsEllipse();
+ if (ellipse.IsExtent()) {
+ return RadialGradientRadii(ellipse.AsExtent());
+ }
+
+ auto& radii = ellipse.AsRadii();
+ return RadialGradientRadii(
+ std::make_pair(radii._0.ResolveToCSSPixels(aSize.width),
+ radii._1.ResolveToCSSPixels(aSize.height)));
+}
+
+// Compute the start and end points of the gradient line for a radial gradient.
+// Also returns the horizontal and vertical radii defining the circle or
+// ellipse to use.
+static std::tuple<CSSPoint, CSSPoint, CSSCoord, CSSCoord>
+ComputeRadialGradientLine(const StyleGradient& aGradient,
+ const CSSSize& aBoxSize) {
+ const auto& radial = aGradient.AsRadial();
+ const EndingShape& endingShape = radial.shape;
+ const Position& position = radial.position;
+ CSSPoint start = ResolvePosition(position, aBoxSize);
+
+ // Compute gradient shape: the x and y radii of an ellipse.
+ CSSCoord radiusX, radiusY;
+ CSSCoord leftDistance = Abs(start.x);
+ CSSCoord rightDistance = Abs(aBoxSize.width - start.x);
+ CSSCoord topDistance = Abs(start.y);
+ CSSCoord bottomDistance = Abs(aBoxSize.height - start.y);
+
+ auto radii = ComputeRadialGradientRadii(endingShape, aBoxSize);
+ if (radii.is<StyleShapeExtent>()) {
+ switch (radii.as<StyleShapeExtent>()) {
+ case StyleShapeExtent::ClosestSide:
+ radiusX = std::min(leftDistance, rightDistance);
+ radiusY = std::min(topDistance, bottomDistance);
+ if (endingShape.IsCircle()) {
+ radiusX = radiusY = std::min(radiusX, radiusY);
+ }
+ break;
+ case StyleShapeExtent::ClosestCorner: {
+ // Compute x and y distances to nearest corner
+ CSSCoord offsetX = std::min(leftDistance, rightDistance);
+ CSSCoord offsetY = std::min(topDistance, bottomDistance);
+ if (endingShape.IsCircle()) {
+ radiusX = radiusY = NS_hypot(offsetX, offsetY);
+ } else {
+ // maintain aspect ratio
+ radiusX = offsetX * M_SQRT2;
+ radiusY = offsetY * M_SQRT2;
+ }
+ break;
+ }
+ case StyleShapeExtent::FarthestSide:
+ radiusX = std::max(leftDistance, rightDistance);
+ radiusY = std::max(topDistance, bottomDistance);
+ if (endingShape.IsCircle()) {
+ radiusX = radiusY = std::max(radiusX, radiusY);
+ }
+ break;
+ case StyleShapeExtent::FarthestCorner: {
+ // Compute x and y distances to nearest corner
+ CSSCoord offsetX = std::max(leftDistance, rightDistance);
+ CSSCoord offsetY = std::max(topDistance, bottomDistance);
+ if (endingShape.IsCircle()) {
+ radiusX = radiusY = NS_hypot(offsetX, offsetY);
+ } else {
+ // maintain aspect ratio
+ radiusX = offsetX * M_SQRT2;
+ radiusY = offsetY * M_SQRT2;
+ }
+ break;
+ }
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unknown shape extent keyword?");
+ radiusX = radiusY = 0;
+ }
+ } else {
+ auto pair = radii.as<std::pair<CSSCoord, CSSCoord>>();
+ radiusX = pair.first;
+ radiusY = pair.second;
+ }
+
+ // The gradient line end point is where the gradient line intersects
+ // the ellipse.
+ CSSPoint end = start + CSSPoint(radiusX, 0);
+ return {start, end, radiusX, radiusY};
+}
+
+// Compute the center and the start angle of the conic gradient.
+static std::tuple<CSSPoint, float> ComputeConicGradientProperties(
+ const StyleGradient& aGradient, const CSSSize& aBoxSize) {
+ const auto& conic = aGradient.AsConic();
+ const Position& position = conic.position;
+ float angle = static_cast<float>(conic.angle.ToRadians());
+ CSSPoint center = ResolvePosition(position, aBoxSize);
+
+ return {center, angle};
+}
+
+static float Interpolate(float aF1, float aF2, float aFrac) {
+ return aF1 + aFrac * (aF2 - aF1);
+}
+
+static StyleAbsoluteColor Interpolate(const StyleAbsoluteColor& aLeft,
+ const StyleAbsoluteColor& aRight,
+ float aFrac) {
+ // NOTE: This has to match the interpolation method that WebRender uses which
+ // right now is sRGB. In the future we should implement interpolation in more
+ // gradient color-spaces.
+ static constexpr auto kMethod = StyleColorInterpolationMethod{
+ StyleColorSpace::Srgb,
+ StyleHueInterpolationMethod::Shorter,
+ };
+ return Servo_InterpolateColor(kMethod, &aRight, &aLeft, aFrac);
+}
+
+static nscoord FindTileStart(nscoord aDirtyCoord, nscoord aTilePos,
+ nscoord aTileDim) {
+ NS_ASSERTION(aTileDim > 0, "Non-positive tile dimension");
+ double multiples = floor(double(aDirtyCoord - aTilePos) / aTileDim);
+ return NSToCoordRound(multiples * aTileDim + aTilePos);
+}
+
+static gfxFloat LinearGradientStopPositionForPoint(
+ const gfxPoint& aGradientStart, const gfxPoint& aGradientEnd,
+ const gfxPoint& aPoint) {
+ gfxPoint d = aGradientEnd - aGradientStart;
+ gfxPoint p = aPoint - aGradientStart;
+ /**
+ * Compute a parameter t such that a line perpendicular to the
+ * d vector, passing through aGradientStart + d*t, also
+ * passes through aPoint.
+ *
+ * t is given by
+ * (p.x - d.x*t)*d.x + (p.y - d.y*t)*d.y = 0
+ *
+ * Solving for t we get
+ * numerator = d.x*p.x + d.y*p.y
+ * denominator = d.x^2 + d.y^2
+ * t = numerator/denominator
+ *
+ * In nsCSSRendering::PaintGradient we know the length of d
+ * is not zero.
+ */
+ double numerator = d.x.value * p.x.value + d.y.value * p.y.value;
+ double denominator = d.x.value * d.x.value + d.y.value * d.y.value;
+ return numerator / denominator;
+}
+
+static bool RectIsBeyondLinearGradientEdge(const gfxRect& aRect,
+ const gfxMatrix& aPatternMatrix,
+ const nsTArray<ColorStop>& aStops,
+ const gfxPoint& aGradientStart,
+ const gfxPoint& aGradientEnd,
+ StyleAbsoluteColor* aOutEdgeColor) {
+ gfxFloat topLeft = LinearGradientStopPositionForPoint(
+ aGradientStart, aGradientEnd,
+ aPatternMatrix.TransformPoint(aRect.TopLeft()));
+ gfxFloat topRight = LinearGradientStopPositionForPoint(
+ aGradientStart, aGradientEnd,
+ aPatternMatrix.TransformPoint(aRect.TopRight()));
+ gfxFloat bottomLeft = LinearGradientStopPositionForPoint(
+ aGradientStart, aGradientEnd,
+ aPatternMatrix.TransformPoint(aRect.BottomLeft()));
+ gfxFloat bottomRight = LinearGradientStopPositionForPoint(
+ aGradientStart, aGradientEnd,
+ aPatternMatrix.TransformPoint(aRect.BottomRight()));
+
+ const ColorStop& firstStop = aStops[0];
+ if (topLeft < firstStop.mPosition && topRight < firstStop.mPosition &&
+ bottomLeft < firstStop.mPosition && bottomRight < firstStop.mPosition) {
+ *aOutEdgeColor = firstStop.mColor;
+ return true;
+ }
+
+ const ColorStop& lastStop = aStops.LastElement();
+ if (topLeft >= lastStop.mPosition && topRight >= lastStop.mPosition &&
+ bottomLeft >= lastStop.mPosition && bottomRight >= lastStop.mPosition) {
+ *aOutEdgeColor = lastStop.mColor;
+ return true;
+ }
+
+ return false;
+}
+
+static void ResolveMidpoints(nsTArray<ColorStop>& stops) {
+ for (size_t x = 1; x < stops.Length() - 1;) {
+ if (!stops[x].mIsMidpoint) {
+ x++;
+ continue;
+ }
+
+ const auto& color1 = stops[x - 1].mColor;
+ const auto& color2 = stops[x + 1].mColor;
+ float offset1 = stops[x - 1].mPosition;
+ float offset2 = stops[x + 1].mPosition;
+ float offset = stops[x].mPosition;
+ // check if everything coincides. If so, ignore the midpoint.
+ if (offset - offset1 == offset2 - offset) {
+ stops.RemoveElementAt(x);
+ continue;
+ }
+
+ // Check if we coincide with the left colorstop.
+ if (offset1 == offset) {
+ // Morph the midpoint to a regular stop with the color of the next
+ // color stop.
+ stops[x].mColor = color2;
+ stops[x].mIsMidpoint = false;
+ continue;
+ }
+
+ // Check if we coincide with the right colorstop.
+ if (offset2 == offset) {
+ // Morph the midpoint to a regular stop with the color of the previous
+ // color stop.
+ stops[x].mColor = color1;
+ stops[x].mIsMidpoint = false;
+ continue;
+ }
+
+ float midpoint = (offset - offset1) / (offset2 - offset1);
+ ColorStop newStops[9];
+ if (midpoint > .5f) {
+ for (size_t y = 0; y < 7; y++) {
+ newStops[y].mPosition = offset1 + (offset - offset1) * (7 + y) / 13;
+ }
+
+ newStops[7].mPosition = offset + (offset2 - offset) / 3;
+ newStops[8].mPosition = offset + (offset2 - offset) * 2 / 3;
+ } else {
+ newStops[0].mPosition = offset1 + (offset - offset1) / 3;
+ newStops[1].mPosition = offset1 + (offset - offset1) * 2 / 3;
+
+ for (size_t y = 0; y < 7; y++) {
+ newStops[y + 2].mPosition = offset + (offset2 - offset) * y / 13;
+ }
+ }
+ // calculate colors
+
+ for (auto& newStop : newStops) {
+ // Calculate the intermediate color stops per the formula of the CSS
+ // images spec. http://dev.w3.org/csswg/css-images/#color-stop-syntax 9
+ // points were chosen since it is the minimum number of stops that always
+ // give the smoothest appearace regardless of midpoint position and
+ // difference in luminance of the end points.
+ const float relativeOffset =
+ (newStop.mPosition - offset1) / (offset2 - offset1);
+ const float multiplier = powf(relativeOffset, logf(.5f) / logf(midpoint));
+
+ auto srgb1 = color1.ToColorSpace(StyleColorSpace::Srgb);
+ auto srgb2 = color2.ToColorSpace(StyleColorSpace::Srgb);
+
+ const float red =
+ srgb1.components._0 +
+ multiplier * (srgb2.components._0 - srgb1.components._0);
+ const float green =
+ srgb1.components._1 +
+ multiplier * (srgb2.components._1 - srgb1.components._1);
+ const float blue =
+ srgb1.components._2 +
+ multiplier * (srgb2.components._2 - srgb1.components._2);
+ const float alpha =
+ srgb1.alpha + multiplier * (srgb2.alpha - srgb1.alpha);
+
+ newStop.mColor = StyleAbsoluteColor::Srgb(red, green, blue, alpha);
+ }
+
+ stops.ReplaceElementsAt(x, 1, newStops, 9);
+ x += 9;
+ }
+}
+
+static StyleAbsoluteColor TransparentColor(const StyleAbsoluteColor& aColor) {
+ auto color = aColor;
+ color.alpha = 0.0f;
+ return color;
+}
+
+// Adjusts and adds color stops in such a way that drawing the gradient with
+// unpremultiplied interpolation looks nearly the same as if it were drawn with
+// premultiplied interpolation.
+static const float kAlphaIncrementPerGradientStep = 0.1f;
+static void ResolvePremultipliedAlpha(nsTArray<ColorStop>& aStops) {
+ for (size_t x = 1; x < aStops.Length(); x++) {
+ const ColorStop leftStop = aStops[x - 1];
+ const ColorStop rightStop = aStops[x];
+
+ // if the left and right stop have the same alpha value, we don't need
+ // to do anything. Hardstops should be instant, and also should never
+ // require dealing with interpolation.
+ if (leftStop.mColor.alpha == rightStop.mColor.alpha ||
+ leftStop.mPosition == rightStop.mPosition) {
+ continue;
+ }
+
+ // Is the stop on the left 100% transparent? If so, have it adopt the color
+ // of the right stop
+ if (leftStop.mColor.alpha == 0) {
+ aStops[x - 1].mColor = TransparentColor(rightStop.mColor);
+ continue;
+ }
+
+ // Is the stop on the right completely transparent?
+ // If so, duplicate it and assign it the color on the left.
+ if (rightStop.mColor.alpha == 0) {
+ ColorStop newStop = rightStop;
+ newStop.mColor = TransparentColor(leftStop.mColor);
+ aStops.InsertElementAt(x, newStop);
+ x++;
+ continue;
+ }
+
+ // Now handle cases where one or both of the stops are partially
+ // transparent.
+ if (leftStop.mColor.alpha != 1.0f || rightStop.mColor.alpha != 1.0f) {
+ // Calculate how many extra steps. We do a step per 10% transparency.
+ size_t stepCount =
+ NSToIntFloor(fabsf(leftStop.mColor.alpha - rightStop.mColor.alpha) /
+ kAlphaIncrementPerGradientStep);
+ for (size_t y = 1; y < stepCount; y++) {
+ float frac = static_cast<float>(y) / stepCount;
+ ColorStop newStop(
+ Interpolate(leftStop.mPosition, rightStop.mPosition, frac), false,
+ Interpolate(leftStop.mColor, rightStop.mColor, frac));
+ aStops.InsertElementAt(x, newStop);
+ x++;
+ }
+ }
+ }
+}
+
+static ColorStop InterpolateColorStop(const ColorStop& aFirst,
+ const ColorStop& aSecond,
+ double aPosition,
+ const StyleAbsoluteColor& aDefault) {
+ MOZ_ASSERT(aFirst.mPosition <= aPosition);
+ MOZ_ASSERT(aPosition <= aSecond.mPosition);
+
+ double delta = aSecond.mPosition - aFirst.mPosition;
+ if (delta < 1e-6) {
+ return ColorStop(aPosition, false, aDefault);
+ }
+
+ return ColorStop(aPosition, false,
+ Interpolate(aFirst.mColor, aSecond.mColor,
+ (aPosition - aFirst.mPosition) / delta));
+}
+
+// Clamp and extend the given ColorStop array in-place to fit exactly into the
+// range [0, 1].
+static void ClampColorStops(nsTArray<ColorStop>& aStops) {
+ MOZ_ASSERT(aStops.Length() > 0);
+
+ // If all stops are outside the range, then get rid of everything and replace
+ // with a single colour.
+ if (aStops.Length() < 2 || aStops[0].mPosition > 1 ||
+ aStops.LastElement().mPosition < 0) {
+ const auto c = aStops[0].mPosition > 1 ? aStops[0].mColor
+ : aStops.LastElement().mColor;
+ aStops.Clear();
+ aStops.AppendElement(ColorStop(0, false, c));
+ return;
+ }
+
+ // Create the 0 and 1 points if they fall in the range of |aStops|, and
+ // discard all stops outside the range [0, 1].
+ // XXX: If we have stops positioned at 0 or 1, we only keep the innermost of
+ // those stops. This should be fine for the current user(s) of this function.
+ for (size_t i = aStops.Length() - 1; i > 0; i--) {
+ if (aStops[i - 1].mPosition < 1 && aStops[i].mPosition >= 1) {
+ // Add a point to position 1.
+ aStops[i] =
+ InterpolateColorStop(aStops[i - 1], aStops[i],
+ /* aPosition = */ 1, aStops[i - 1].mColor);
+ // Remove all the elements whose position is greater than 1.
+ aStops.RemoveLastElements(aStops.Length() - (i + 1));
+ }
+ if (aStops[i - 1].mPosition <= 0 && aStops[i].mPosition > 0) {
+ // Add a point to position 0.
+ aStops[i - 1] =
+ InterpolateColorStop(aStops[i - 1], aStops[i],
+ /* aPosition = */ 0, aStops[i].mColor);
+ // Remove all of the preceding stops -- they are all negative.
+ aStops.RemoveElementsAt(0, i - 1);
+ break;
+ }
+ }
+
+ MOZ_ASSERT(aStops[0].mPosition >= -1e6);
+ MOZ_ASSERT(aStops.LastElement().mPosition - 1 <= 1e6);
+
+ // The end points won't exist yet if they don't fall in the original range of
+ // |aStops|. Create them if needed.
+ if (aStops[0].mPosition > 0) {
+ aStops.InsertElementAt(0, ColorStop(0, false, aStops[0].mColor));
+ }
+ if (aStops.LastElement().mPosition < 1) {
+ aStops.AppendElement(ColorStop(1, false, aStops.LastElement().mColor));
+ }
+}
+
+namespace mozilla {
+
+template <typename T>
+static StyleAbsoluteColor GetSpecifiedColor(
+ const StyleGenericGradientItem<StyleColor, T>& aItem,
+ const ComputedStyle& aStyle) {
+ if (aItem.IsInterpolationHint()) {
+ return StyleAbsoluteColor::Transparent();
+ }
+ const StyleColor& c = aItem.IsSimpleColorStop()
+ ? aItem.AsSimpleColorStop()
+ : aItem.AsComplexColorStop().color;
+
+ return c.ResolveColor(aStyle.StyleText()->mColor);
+}
+
+static Maybe<double> GetSpecifiedGradientPosition(
+ const StyleGenericGradientItem<StyleColor, StyleLengthPercentage>& aItem,
+ CSSCoord aLineLength) {
+ if (aItem.IsSimpleColorStop()) {
+ return Nothing();
+ }
+
+ const LengthPercentage& pos = aItem.IsComplexColorStop()
+ ? aItem.AsComplexColorStop().position
+ : aItem.AsInterpolationHint();
+
+ if (pos.ConvertsToPercentage()) {
+ return Some(pos.ToPercentage());
+ }
+
+ if (aLineLength < 1e-6) {
+ return Some(0.0);
+ }
+ return Some(pos.ResolveToCSSPixels(aLineLength) / aLineLength);
+}
+
+// aLineLength argument is unused for conic-gradients.
+static Maybe<double> GetSpecifiedGradientPosition(
+ const StyleGenericGradientItem<StyleColor, StyleAngleOrPercentage>& aItem,
+ CSSCoord aLineLength) {
+ if (aItem.IsSimpleColorStop()) {
+ return Nothing();
+ }
+
+ const StyleAngleOrPercentage& pos = aItem.IsComplexColorStop()
+ ? aItem.AsComplexColorStop().position
+ : aItem.AsInterpolationHint();
+
+ if (pos.IsPercentage()) {
+ return Some(pos.AsPercentage()._0);
+ }
+
+ return Some(pos.AsAngle().ToRadians() / (2 * M_PI));
+}
+
+template <typename T>
+static nsTArray<ColorStop> ComputeColorStopsForItems(
+ ComputedStyle* aComputedStyle,
+ Span<const StyleGenericGradientItem<StyleColor, T>> aItems,
+ CSSCoord aLineLength) {
+ MOZ_ASSERT(aItems.Length() >= 2,
+ "The parser should reject gradients with less than two stops");
+
+ nsTArray<ColorStop> stops(aItems.Length());
+
+ // If there is a run of stops before stop i that did not have specified
+ // positions, then this is the index of the first stop in that run.
+ Maybe<size_t> firstUnsetPosition;
+ for (size_t i = 0; i < aItems.Length(); ++i) {
+ const auto& stop = aItems[i];
+ double position;
+
+ Maybe<double> specifiedPosition =
+ GetSpecifiedGradientPosition(stop, aLineLength);
+
+ if (specifiedPosition) {
+ position = *specifiedPosition;
+ } else if (i == 0) {
+ // First stop defaults to position 0.0
+ position = 0.0;
+ } else if (i == aItems.Length() - 1) {
+ // Last stop defaults to position 1.0
+ position = 1.0;
+ } else {
+ // Other stops with no specified position get their position assigned
+ // later by interpolation, see below.
+ // Remember where the run of stops with no specified position starts,
+ // if it starts here.
+ if (firstUnsetPosition.isNothing()) {
+ firstUnsetPosition.emplace(i);
+ }
+ MOZ_ASSERT(!stop.IsInterpolationHint(),
+ "Interpolation hints always specify position");
+ auto color = GetSpecifiedColor(stop, *aComputedStyle);
+ stops.AppendElement(ColorStop(0, false, color));
+ continue;
+ }
+
+ if (i > 0) {
+ // Prevent decreasing stop positions by advancing this position
+ // to the previous stop position, if necessary
+ double previousPosition = firstUnsetPosition
+ ? stops[*firstUnsetPosition - 1].mPosition
+ : stops[i - 1].mPosition;
+ position = std::max(position, previousPosition);
+ }
+ auto stopColor = GetSpecifiedColor(stop, *aComputedStyle);
+ stops.AppendElement(
+ ColorStop(position, stop.IsInterpolationHint(), stopColor));
+ if (firstUnsetPosition) {
+ // Interpolate positions for all stops that didn't have a specified
+ // position
+ double p = stops[*firstUnsetPosition - 1].mPosition;
+ double d = (stops[i].mPosition - p) / (i - *firstUnsetPosition + 1);
+ for (size_t j = *firstUnsetPosition; j < i; ++j) {
+ p += d;
+ stops[j].mPosition = p;
+ }
+ firstUnsetPosition.reset();
+ }
+ }
+
+ return stops;
+}
+
+static nsTArray<ColorStop> ComputeColorStops(ComputedStyle* aComputedStyle,
+ const StyleGradient& aGradient,
+ CSSCoord aLineLength) {
+ if (aGradient.IsLinear()) {
+ return ComputeColorStopsForItems(
+ aComputedStyle, aGradient.AsLinear().items.AsSpan(), aLineLength);
+ }
+ if (aGradient.IsRadial()) {
+ return ComputeColorStopsForItems(
+ aComputedStyle, aGradient.AsRadial().items.AsSpan(), aLineLength);
+ }
+ return ComputeColorStopsForItems(
+ aComputedStyle, aGradient.AsConic().items.AsSpan(), aLineLength);
+}
+
+nsCSSGradientRenderer nsCSSGradientRenderer::Create(
+ nsPresContext* aPresContext, ComputedStyle* aComputedStyle,
+ const StyleGradient& aGradient, const nsSize& aIntrinsicSize) {
+ auto srcSize = CSSSize::FromAppUnits(aIntrinsicSize);
+
+ // Compute "gradient line" start and end relative to the intrinsic size of
+ // the gradient.
+ CSSPoint lineStart, lineEnd, center; // center is for conic gradients only
+ CSSCoord radiusX = 0, radiusY = 0; // for radial gradients only
+ float angle = 0.0; // for conic gradients only
+ if (aGradient.IsLinear()) {
+ std::tie(lineStart, lineEnd) =
+ ComputeLinearGradientLine(aPresContext, aGradient, srcSize);
+ } else if (aGradient.IsRadial()) {
+ std::tie(lineStart, lineEnd, radiusX, radiusY) =
+ ComputeRadialGradientLine(aGradient, srcSize);
+ } else {
+ MOZ_ASSERT(aGradient.IsConic());
+ std::tie(center, angle) =
+ ComputeConicGradientProperties(aGradient, srcSize);
+ }
+ // Avoid sending Infs or Nans to downwind draw targets.
+ if (!lineStart.IsFinite() || !lineEnd.IsFinite()) {
+ lineStart = lineEnd = CSSPoint(0, 0);
+ }
+ if (!center.IsFinite()) {
+ center = CSSPoint(0, 0);
+ }
+ CSSCoord lineLength =
+ NS_hypot(lineEnd.x - lineStart.x, lineEnd.y - lineStart.y);
+
+ // Build color stop array and compute stop positions
+ nsTArray<ColorStop> stops =
+ ComputeColorStops(aComputedStyle, aGradient, lineLength);
+
+ ResolveMidpoints(stops);
+
+ nsCSSGradientRenderer renderer;
+ renderer.mPresContext = aPresContext;
+ renderer.mGradient = &aGradient;
+ renderer.mStops = std::move(stops);
+ renderer.mLineStart = {
+ aPresContext->CSSPixelsToDevPixels(lineStart.x),
+ aPresContext->CSSPixelsToDevPixels(lineStart.y),
+ };
+ renderer.mLineEnd = {
+ aPresContext->CSSPixelsToDevPixels(lineEnd.x),
+ aPresContext->CSSPixelsToDevPixels(lineEnd.y),
+ };
+ renderer.mRadiusX = aPresContext->CSSPixelsToDevPixels(radiusX);
+ renderer.mRadiusY = aPresContext->CSSPixelsToDevPixels(radiusY);
+ renderer.mCenter = {
+ aPresContext->CSSPixelsToDevPixels(center.x),
+ aPresContext->CSSPixelsToDevPixels(center.y),
+ };
+ renderer.mAngle = angle;
+ return renderer;
+}
+
+void nsCSSGradientRenderer::Paint(gfxContext& aContext, const nsRect& aDest,
+ const nsRect& aFillArea,
+ const nsSize& aRepeatSize,
+ const CSSIntRect& aSrc,
+ const nsRect& aDirtyRect, float aOpacity) {
+ AUTO_PROFILER_LABEL("nsCSSGradientRenderer::Paint", GRAPHICS);
+
+ if (aDest.IsEmpty() || aFillArea.IsEmpty()) {
+ return;
+ }
+
+ nscoord appUnitsPerDevPixel = mPresContext->AppUnitsPerDevPixel();
+
+ gfxFloat lineLength =
+ NS_hypot(mLineEnd.x - mLineStart.x, mLineEnd.y - mLineStart.y);
+ bool cellContainsFill = aDest.Contains(aFillArea);
+
+ // If a non-repeating linear gradient is axis-aligned and there are no gaps
+ // between tiles, we can optimise away most of the work by converting to a
+ // repeating linear gradient and filling the whole destination rect at once.
+ bool forceRepeatToCoverTiles =
+ mGradient->IsLinear() &&
+ (mLineStart.x == mLineEnd.x) != (mLineStart.y == mLineEnd.y) &&
+ aRepeatSize.width == aDest.width && aRepeatSize.height == aDest.height &&
+ !mGradient->AsLinear().repeating && !aSrc.IsEmpty() && !cellContainsFill;
+
+ gfxMatrix matrix;
+ if (forceRepeatToCoverTiles) {
+ // Length of the source rectangle along the gradient axis.
+ double rectLen;
+ // The position of the start of the rectangle along the gradient.
+ double offset;
+
+ // The gradient line is "backwards". Flip the line upside down to make
+ // things easier, and then rotate the matrix to turn everything back the
+ // right way up.
+ if (mLineStart.x > mLineEnd.x || mLineStart.y > mLineEnd.y) {
+ std::swap(mLineStart, mLineEnd);
+ matrix.PreScale(-1, -1);
+ }
+
+ // Fit the gradient line exactly into the source rect.
+ // aSrc is relative to aIntrinsincSize.
+ // srcRectDev will be relative to srcSize, so in the same coordinate space
+ // as lineStart / lineEnd.
+ gfxRect srcRectDev = nsLayoutUtils::RectToGfxRect(
+ CSSPixel::ToAppUnits(aSrc), appUnitsPerDevPixel);
+ if (mLineStart.x != mLineEnd.x) {
+ rectLen = srcRectDev.width;
+ offset = (srcRectDev.x - mLineStart.x) / lineLength;
+ mLineStart.x = srcRectDev.x;
+ mLineEnd.x = srcRectDev.XMost();
+ } else {
+ rectLen = srcRectDev.height;
+ offset = (srcRectDev.y - mLineStart.y) / lineLength;
+ mLineStart.y = srcRectDev.y;
+ mLineEnd.y = srcRectDev.YMost();
+ }
+
+ // Adjust gradient stop positions for the new gradient line.
+ double scale = lineLength / rectLen;
+ for (size_t i = 0; i < mStops.Length(); i++) {
+ mStops[i].mPosition = (mStops[i].mPosition - offset) * fabs(scale);
+ }
+
+ // Clamp or extrapolate gradient stops to exactly [0, 1].
+ ClampColorStops(mStops);
+
+ lineLength = rectLen;
+ }
+
+ // Eliminate negative-position stops if the gradient is radial.
+ double firstStop = mStops[0].mPosition;
+ if (mGradient->IsRadial() && firstStop < 0.0) {
+ if (mGradient->AsRadial().repeating) {
+ // Choose an instance of the repeated pattern that gives us all positive
+ // stop-offsets.
+ double lastStop = mStops[mStops.Length() - 1].mPosition;
+ double stopDelta = lastStop - firstStop;
+ // If all the stops are in approximately the same place then logic below
+ // will kick in that makes us draw just the last stop color, so don't
+ // try to do anything in that case. We certainly need to avoid
+ // dividing by zero.
+ if (stopDelta >= 1e-6) {
+ double instanceCount = ceil(-firstStop / stopDelta);
+ // Advance stops by instanceCount multiples of the period of the
+ // repeating gradient.
+ double offset = instanceCount * stopDelta;
+ for (uint32_t i = 0; i < mStops.Length(); i++) {
+ mStops[i].mPosition += offset;
+ }
+ }
+ } else {
+ // Move negative-position stops to position 0.0. We may also need
+ // to set the color of the stop to the color the gradient should have
+ // at the center of the ellipse.
+ for (uint32_t i = 0; i < mStops.Length(); i++) {
+ double pos = mStops[i].mPosition;
+ if (pos < 0.0) {
+ mStops[i].mPosition = 0.0;
+ // If this is the last stop, we don't need to adjust the color,
+ // it will fill the entire area.
+ if (i < mStops.Length() - 1) {
+ double nextPos = mStops[i + 1].mPosition;
+ // If nextPos is approximately equal to pos, then we don't
+ // need to adjust the color of this stop because it's
+ // not going to be displayed.
+ // If nextPos is negative, we don't need to adjust the color of
+ // this stop since it's not going to be displayed because
+ // nextPos will also be moved to 0.0.
+ if (nextPos >= 0.0 && nextPos - pos >= 1e-6) {
+ // Compute how far the new position 0.0 is along the interval
+ // between pos and nextPos.
+ // XXX Color interpolation (in cairo, too) should use the
+ // CSS 'color-interpolation' property!
+ float frac = float((0.0 - pos) / (nextPos - pos));
+ mStops[i].mColor =
+ Interpolate(mStops[i].mColor, mStops[i + 1].mColor, frac);
+ }
+ }
+ }
+ }
+ }
+ firstStop = mStops[0].mPosition;
+ MOZ_ASSERT(firstStop >= 0.0, "Failed to fix stop offsets");
+ }
+
+ if (mGradient->IsRadial() && !mGradient->AsRadial().repeating) {
+ // Direct2D can only handle a particular class of radial gradients because
+ // of the way the it specifies gradients. Setting firstStop to 0, when we
+ // can, will help us stay on the fast path. Currently we don't do this
+ // for repeating gradients but we could by adjusting the stop collection
+ // to start at 0
+ firstStop = 0;
+ }
+
+ double lastStop = mStops[mStops.Length() - 1].mPosition;
+ // Cairo gradients must have stop positions in the range [0, 1]. So,
+ // stop positions will be normalized below by subtracting firstStop and then
+ // multiplying by stopScale.
+ double stopScale;
+ double stopOrigin = firstStop;
+ double stopEnd = lastStop;
+ double stopDelta = lastStop - firstStop;
+ bool zeroRadius =
+ mGradient->IsRadial() && (mRadiusX < 1e-6 || mRadiusY < 1e-6);
+ if (stopDelta < 1e-6 || (!mGradient->IsConic() && lineLength < 1e-6) ||
+ zeroRadius) {
+ // Stops are all at the same place. Map all stops to 0.0.
+ // For repeating radial gradients, or for any radial gradients with
+ // a zero radius, we need to fill with the last stop color, so just set
+ // both radii to 0.
+ if (mGradient->Repeating() || zeroRadius) {
+ mRadiusX = mRadiusY = 0.0;
+ }
+ stopDelta = 0.0;
+ }
+
+ // Don't normalize non-repeating or degenerate gradients below 0..1
+ // This keeps the gradient line as large as the box and doesn't
+ // lets us avoiding having to get padding correct for stops
+ // at 0 and 1
+ if (!mGradient->Repeating() || stopDelta == 0.0) {
+ stopOrigin = std::min(stopOrigin, 0.0);
+ stopEnd = std::max(stopEnd, 1.0);
+ }
+ stopScale = 1.0 / (stopEnd - stopOrigin);
+
+ // Create the gradient pattern.
+ RefPtr<gfxPattern> gradientPattern;
+ gfxPoint gradientStart;
+ gfxPoint gradientEnd;
+ if (mGradient->IsLinear()) {
+ // Compute the actual gradient line ends we need to pass to cairo after
+ // stops have been normalized.
+ gradientStart = mLineStart + (mLineEnd - mLineStart) * stopOrigin;
+ gradientEnd = mLineStart + (mLineEnd - mLineStart) * stopEnd;
+
+ if (stopDelta == 0.0) {
+ // Stops are all at the same place. For repeating gradients, this will
+ // just paint the last stop color. We don't need to do anything.
+ // For non-repeating gradients, this should render as two colors, one
+ // on each "side" of the gradient line segment, which is a point. All
+ // our stops will be at 0.0; we just need to set the direction vector
+ // correctly.
+ gradientEnd = gradientStart + (mLineEnd - mLineStart);
+ }
+
+ gradientPattern = new gfxPattern(gradientStart.x, gradientStart.y,
+ gradientEnd.x, gradientEnd.y);
+ } else if (mGradient->IsRadial()) {
+ NS_ASSERTION(firstStop >= 0.0,
+ "Negative stops not allowed for radial gradients");
+
+ // To form an ellipse, we'll stretch a circle vertically, if necessary.
+ // So our radii are based on radiusX.
+ double innerRadius = mRadiusX * stopOrigin;
+ double outerRadius = mRadiusX * stopEnd;
+ if (stopDelta == 0.0) {
+ // Stops are all at the same place. See above (except we now have
+ // the inside vs. outside of an ellipse).
+ outerRadius = innerRadius + 1;
+ }
+ gradientPattern = new gfxPattern(mLineStart.x, mLineStart.y, innerRadius,
+ mLineStart.x, mLineStart.y, outerRadius);
+ if (mRadiusX != mRadiusY) {
+ // Stretch the circles into ellipses vertically by setting a transform
+ // in the pattern.
+ // Recall that this is the transform from user space to pattern space.
+ // So to stretch the ellipse by factor of P vertically, we scale
+ // user coordinates by 1/P.
+ matrix.PreTranslate(mLineStart);
+ matrix.PreScale(1.0, mRadiusX / mRadiusY);
+ matrix.PreTranslate(-mLineStart);
+ }
+ } else {
+ gradientPattern =
+ new gfxPattern(mCenter.x, mCenter.y, mAngle, stopOrigin, stopEnd);
+ }
+ // Use a pattern transform to take account of source and dest rects
+ matrix.PreTranslate(gfxPoint(mPresContext->CSSPixelsToDevPixels(aSrc.x),
+ mPresContext->CSSPixelsToDevPixels(aSrc.y)));
+ matrix.PreScale(
+ gfxFloat(nsPresContext::CSSPixelsToAppUnits(aSrc.width)) / aDest.width,
+ gfxFloat(nsPresContext::CSSPixelsToAppUnits(aSrc.height)) / aDest.height);
+ gradientPattern->SetMatrix(matrix);
+
+ if (stopDelta == 0.0) {
+ // Non-repeating gradient with all stops in same place -> just add
+ // first stop and last stop, both at position 0.
+ // Repeating gradient with all stops in the same place, or radial
+ // gradient with radius of 0 -> just paint the last stop color.
+ // We use firstStop offset to keep |stops| with same units (will later
+ // normalize to 0).
+ auto firstColor(mStops[0].mColor);
+ auto lastColor(mStops.LastElement().mColor);
+ mStops.Clear();
+
+ if (!mGradient->Repeating() && !zeroRadius) {
+ mStops.AppendElement(ColorStop(firstStop, false, firstColor));
+ }
+ mStops.AppendElement(ColorStop(firstStop, false, lastColor));
+ }
+
+ ResolvePremultipliedAlpha(mStops);
+
+ bool isRepeat = mGradient->Repeating() || forceRepeatToCoverTiles;
+
+ // Now set normalized color stops in pattern.
+ // Offscreen gradient surface cache (not a tile):
+ // On some backends (e.g. D2D), the GradientStops object holds an offscreen
+ // surface which is a lookup table used to evaluate the gradient. This surface
+ // can use much memory (ram and/or GPU ram) and can be expensive to create. So
+ // we cache it. The cache key correlates 1:1 with the arguments for
+ // CreateGradientStops (also the implied backend type) Note that GradientStop
+ // is a simple struct with a stop value (while GradientStops has the surface).
+ nsTArray<gfx::GradientStop> rawStops(mStops.Length());
+ rawStops.SetLength(mStops.Length());
+ for (uint32_t i = 0; i < mStops.Length(); i++) {
+ rawStops[i].color = ToDeviceColor(mStops[i].mColor);
+ rawStops[i].color.a *= aOpacity;
+ rawStops[i].offset = stopScale * (mStops[i].mPosition - stopOrigin);
+ }
+ RefPtr<mozilla::gfx::GradientStops> gs =
+ gfxGradientCache::GetOrCreateGradientStops(
+ aContext.GetDrawTarget(), rawStops,
+ isRepeat ? gfx::ExtendMode::REPEAT : gfx::ExtendMode::CLAMP);
+ gradientPattern->SetColorStops(gs);
+
+ // Paint gradient tiles. This isn't terribly efficient, but doing it this
+ // way is simple and sure to get pixel-snapping right. We could speed things
+ // up by drawing tiles into temporary surfaces and copying those to the
+ // destination, but after pixel-snapping tiles may not all be the same size.
+ nsRect dirty;
+ if (!dirty.IntersectRect(aDirtyRect, aFillArea)) return;
+
+ gfxRect areaToFill =
+ nsLayoutUtils::RectToGfxRect(aFillArea, appUnitsPerDevPixel);
+ gfxRect dirtyAreaToFill =
+ nsLayoutUtils::RectToGfxRect(dirty, appUnitsPerDevPixel);
+ dirtyAreaToFill.RoundOut();
+
+ Matrix ctm = aContext.CurrentMatrix();
+ bool isCTMPreservingAxisAlignedRectangles =
+ ctm.PreservesAxisAlignedRectangles();
+
+ // xStart/yStart are the top-left corner of the top-left tile.
+ nscoord xStart = FindTileStart(dirty.x, aDest.x, aRepeatSize.width);
+ nscoord yStart = FindTileStart(dirty.y, aDest.y, aRepeatSize.height);
+ nscoord xEnd = forceRepeatToCoverTiles ? xStart + aDest.width : dirty.XMost();
+ nscoord yEnd =
+ forceRepeatToCoverTiles ? yStart + aDest.height : dirty.YMost();
+
+ if (TryPaintTilesWithExtendMode(aContext, gradientPattern, xStart, yStart,
+ dirtyAreaToFill, aDest, aRepeatSize,
+ forceRepeatToCoverTiles)) {
+ return;
+ }
+
+ // x and y are the top-left corner of the tile to draw
+ for (nscoord y = yStart; y < yEnd; y += aRepeatSize.height) {
+ for (nscoord x = xStart; x < xEnd; x += aRepeatSize.width) {
+ // The coordinates of the tile
+ gfxRect tileRect = nsLayoutUtils::RectToGfxRect(
+ nsRect(x, y, aDest.width, aDest.height), appUnitsPerDevPixel);
+ // The actual area to fill with this tile is the intersection of this
+ // tile with the overall area we're supposed to be filling
+ gfxRect fillRect =
+ forceRepeatToCoverTiles ? areaToFill : tileRect.Intersect(areaToFill);
+ // Try snapping the fill rect. Snap its top-left and bottom-right
+ // independently to preserve the orientation.
+ gfxPoint snappedFillRectTopLeft = fillRect.TopLeft();
+ gfxPoint snappedFillRectTopRight = fillRect.TopRight();
+ gfxPoint snappedFillRectBottomRight = fillRect.BottomRight();
+ // Snap three points instead of just two to ensure we choose the
+ // correct orientation if there's a reflection.
+ if (isCTMPreservingAxisAlignedRectangles &&
+ aContext.UserToDevicePixelSnapped(snappedFillRectTopLeft, true) &&
+ aContext.UserToDevicePixelSnapped(snappedFillRectBottomRight, true) &&
+ aContext.UserToDevicePixelSnapped(snappedFillRectTopRight, true)) {
+ if (snappedFillRectTopLeft.x == snappedFillRectBottomRight.x ||
+ snappedFillRectTopLeft.y == snappedFillRectBottomRight.y) {
+ // Nothing to draw; avoid scaling by zero and other weirdness that
+ // could put the context in an error state.
+ continue;
+ }
+ // Set the context's transform to the transform that maps fillRect to
+ // snappedFillRect. The part of the gradient that was going to
+ // exactly fill fillRect will fill snappedFillRect instead.
+ gfxMatrix transform = gfxUtils::TransformRectToRect(
+ fillRect, snappedFillRectTopLeft, snappedFillRectTopRight,
+ snappedFillRectBottomRight);
+ aContext.SetMatrixDouble(transform);
+ }
+ aContext.NewPath();
+ aContext.Rectangle(fillRect);
+
+ gfxRect dirtyFillRect = fillRect.Intersect(dirtyAreaToFill);
+ gfxRect fillRectRelativeToTile = dirtyFillRect - tileRect.TopLeft();
+ auto edgeColor = StyleAbsoluteColor::Transparent();
+ if (mGradient->IsLinear() && !isRepeat &&
+ RectIsBeyondLinearGradientEdge(fillRectRelativeToTile, matrix, mStops,
+ gradientStart, gradientEnd,
+ &edgeColor)) {
+ edgeColor.alpha *= aOpacity;
+ aContext.SetColor(ToSRGBColor(edgeColor));
+ } else {
+ aContext.SetMatrixDouble(
+ aContext.CurrentMatrixDouble().Copy().PreTranslate(
+ tileRect.TopLeft()));
+ aContext.SetPattern(gradientPattern);
+ }
+ aContext.Fill();
+ aContext.SetMatrix(ctm);
+ }
+ }
+}
+
+bool nsCSSGradientRenderer::TryPaintTilesWithExtendMode(
+ gfxContext& aContext, gfxPattern* aGradientPattern, nscoord aXStart,
+ nscoord aYStart, const gfxRect& aDirtyAreaToFill, const nsRect& aDest,
+ const nsSize& aRepeatSize, bool aForceRepeatToCoverTiles) {
+ // If we have forced a non-repeating gradient to repeat to cover tiles,
+ // then it will be faster to just paint it once using that optimization
+ if (aForceRepeatToCoverTiles) {
+ return false;
+ }
+
+ nscoord appUnitsPerDevPixel = mPresContext->AppUnitsPerDevPixel();
+
+ // We can only use this fast path if we don't have to worry about pixel
+ // snapping, and there is no spacing between tiles. We could handle spacing
+ // by increasing the size of tileSurface and leaving it transparent, but I'm
+ // not sure it's worth it
+ bool canUseExtendModeForTiling = (aXStart % appUnitsPerDevPixel == 0) &&
+ (aYStart % appUnitsPerDevPixel == 0) &&
+ (aDest.width % appUnitsPerDevPixel == 0) &&
+ (aDest.height % appUnitsPerDevPixel == 0) &&
+ (aRepeatSize.width == aDest.width) &&
+ (aRepeatSize.height == aDest.height);
+
+ if (!canUseExtendModeForTiling) {
+ return false;
+ }
+
+ IntSize tileSize{
+ NSAppUnitsToIntPixels(aDest.width, appUnitsPerDevPixel),
+ NSAppUnitsToIntPixels(aDest.height, appUnitsPerDevPixel),
+ };
+
+ // Check whether this is a reasonable surface size and doesn't overflow
+ // before doing calculations with the tile size
+ if (!Factory::ReasonableSurfaceSize(tileSize)) {
+ return false;
+ }
+
+ // We only want to do this when there are enough tiles to justify the
+ // overhead of painting to an offscreen surface. The heuristic here
+ // is when we will be painting at least 16 tiles or more, this is kind
+ // of arbitrary
+ bool shouldUseExtendModeForTiling =
+ aDirtyAreaToFill.Area() > (tileSize.width * tileSize.height) * 16.0;
+
+ if (!shouldUseExtendModeForTiling) {
+ return false;
+ }
+
+ // Draw the gradient pattern into a surface for our single tile
+ RefPtr<gfx::SourceSurface> tileSurface;
+ {
+ RefPtr<gfx::DrawTarget> tileTarget =
+ aContext.GetDrawTarget()->CreateSimilarDrawTarget(
+ tileSize, gfx::SurfaceFormat::B8G8R8A8);
+ if (!tileTarget || !tileTarget->IsValid()) {
+ return false;
+ }
+
+ {
+ gfxContext tileContext(tileTarget);
+
+ tileContext.SetPattern(aGradientPattern);
+ tileContext.Paint();
+ }
+
+ tileSurface = tileTarget->Snapshot();
+ tileTarget = nullptr;
+ }
+
+ // Draw the gradient using tileSurface as a repeating pattern masked by
+ // the dirtyRect
+ Matrix tileTransform = Matrix::Translation(
+ NSAppUnitsToFloatPixels(aXStart, appUnitsPerDevPixel),
+ NSAppUnitsToFloatPixels(aYStart, appUnitsPerDevPixel));
+
+ aContext.NewPath();
+ aContext.Rectangle(aDirtyAreaToFill);
+ aContext.Fill(SurfacePattern(tileSurface, ExtendMode::REPEAT, tileTransform));
+
+ return true;
+}
+
+void nsCSSGradientRenderer::BuildWebRenderParameters(
+ float aOpacity, wr::ExtendMode& aMode, nsTArray<wr::GradientStop>& aStops,
+ LayoutDevicePoint& aLineStart, LayoutDevicePoint& aLineEnd,
+ LayoutDeviceSize& aGradientRadius, LayoutDevicePoint& aGradientCenter,
+ float& aGradientAngle) {
+ aMode =
+ mGradient->Repeating() ? wr::ExtendMode::Repeat : wr::ExtendMode::Clamp;
+
+ aStops.SetLength(mStops.Length());
+ for (uint32_t i = 0; i < mStops.Length(); i++) {
+ aStops[i].color = wr::ToColorF(ToDeviceColor(mStops[i].mColor));
+ aStops[i].color.a *= aOpacity;
+ aStops[i].offset = mStops[i].mPosition;
+ }
+
+ aLineStart = LayoutDevicePoint(mLineStart.x, mLineStart.y);
+ aLineEnd = LayoutDevicePoint(mLineEnd.x, mLineEnd.y);
+ aGradientRadius = LayoutDeviceSize(mRadiusX, mRadiusY);
+ aGradientCenter = LayoutDevicePoint(mCenter.x, mCenter.y);
+ aGradientAngle = mAngle;
+}
+
+void nsCSSGradientRenderer::BuildWebRenderDisplayItems(
+ wr::DisplayListBuilder& aBuilder, const layers::StackingContextHelper& aSc,
+ const nsRect& aDest, const nsRect& aFillArea, const nsSize& aRepeatSize,
+ const CSSIntRect& aSrc, bool aIsBackfaceVisible, float aOpacity) {
+ if (aDest.IsEmpty() || aFillArea.IsEmpty()) {
+ return;
+ }
+
+ wr::ExtendMode extendMode;
+ nsTArray<wr::GradientStop> stops;
+ LayoutDevicePoint lineStart;
+ LayoutDevicePoint lineEnd;
+ LayoutDeviceSize gradientRadius;
+ LayoutDevicePoint gradientCenter;
+ float gradientAngle;
+ BuildWebRenderParameters(aOpacity, extendMode, stops, lineStart, lineEnd,
+ gradientRadius, gradientCenter, gradientAngle);
+
+ nscoord appUnitsPerDevPixel = mPresContext->AppUnitsPerDevPixel();
+
+ nsPoint firstTile =
+ nsPoint(FindTileStart(aFillArea.x, aDest.x, aRepeatSize.width),
+ FindTileStart(aFillArea.y, aDest.y, aRepeatSize.height));
+
+ // Translate the parameters into device coordinates
+ LayoutDeviceRect clipBounds =
+ LayoutDevicePixel::FromAppUnits(aFillArea, appUnitsPerDevPixel);
+ LayoutDeviceRect firstTileBounds = LayoutDevicePixel::FromAppUnits(
+ nsRect(firstTile, aDest.Size()), appUnitsPerDevPixel);
+ LayoutDeviceSize tileRepeat =
+ LayoutDevicePixel::FromAppUnits(aRepeatSize, appUnitsPerDevPixel);
+
+ // Calculate the bounds of the gradient display item, which starts at the
+ // first tile and extends to the end of clip bounds
+ LayoutDevicePoint tileToClip =
+ clipBounds.BottomRight() - firstTileBounds.TopLeft();
+ LayoutDeviceRect gradientBounds = LayoutDeviceRect(
+ firstTileBounds.TopLeft(), LayoutDeviceSize(tileToClip.x, tileToClip.y));
+
+ // Calculate the tile spacing, which is the repeat size minus the tile size
+ LayoutDeviceSize tileSpacing = tileRepeat - firstTileBounds.Size();
+
+ // srcTransform is used for scaling the gradient to match aSrc
+ LayoutDeviceRect srcTransform = LayoutDeviceRect(
+ nsPresContext::CSSPixelsToAppUnits(aSrc.x),
+ nsPresContext::CSSPixelsToAppUnits(aSrc.y),
+ aDest.width / ((float)nsPresContext::CSSPixelsToAppUnits(aSrc.width)),
+ aDest.height / ((float)nsPresContext::CSSPixelsToAppUnits(aSrc.height)));
+
+ lineStart.x = (lineStart.x - srcTransform.x) * srcTransform.width;
+ lineStart.y = (lineStart.y - srcTransform.y) * srcTransform.height;
+
+ gradientCenter.x = (gradientCenter.x - srcTransform.x) * srcTransform.width;
+ gradientCenter.y = (gradientCenter.y - srcTransform.y) * srcTransform.height;
+
+ if (mGradient->IsLinear()) {
+ lineEnd.x = (lineEnd.x - srcTransform.x) * srcTransform.width;
+ lineEnd.y = (lineEnd.y - srcTransform.y) * srcTransform.height;
+
+ aBuilder.PushLinearGradient(
+ mozilla::wr::ToLayoutRect(gradientBounds),
+ mozilla::wr::ToLayoutRect(clipBounds), aIsBackfaceVisible,
+ mozilla::wr::ToLayoutPoint(lineStart),
+ mozilla::wr::ToLayoutPoint(lineEnd), stops, extendMode,
+ mozilla::wr::ToLayoutSize(firstTileBounds.Size()),
+ mozilla::wr::ToLayoutSize(tileSpacing));
+ } else if (mGradient->IsRadial()) {
+ gradientRadius.width *= srcTransform.width;
+ gradientRadius.height *= srcTransform.height;
+
+ aBuilder.PushRadialGradient(
+ mozilla::wr::ToLayoutRect(gradientBounds),
+ mozilla::wr::ToLayoutRect(clipBounds), aIsBackfaceVisible,
+ mozilla::wr::ToLayoutPoint(lineStart),
+ mozilla::wr::ToLayoutSize(gradientRadius), stops, extendMode,
+ mozilla::wr::ToLayoutSize(firstTileBounds.Size()),
+ mozilla::wr::ToLayoutSize(tileSpacing));
+ } else {
+ MOZ_ASSERT(mGradient->IsConic());
+ aBuilder.PushConicGradient(
+ mozilla::wr::ToLayoutRect(gradientBounds),
+ mozilla::wr::ToLayoutRect(clipBounds), aIsBackfaceVisible,
+ mozilla::wr::ToLayoutPoint(gradientCenter), gradientAngle, stops,
+ extendMode, mozilla::wr::ToLayoutSize(firstTileBounds.Size()),
+ mozilla::wr::ToLayoutSize(tileSpacing));
+ }
+}
+
+} // namespace mozilla
diff --git a/layout/painting/nsCSSRenderingGradients.h b/layout/painting/nsCSSRenderingGradients.h
new file mode 100644
index 0000000000..5cf00fb4f8
--- /dev/null
+++ b/layout/painting/nsCSSRenderingGradients.h
@@ -0,0 +1,125 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsCSSRenderingGradients_h__
+#define nsCSSRenderingGradients_h__
+
+#include "gfxRect.h"
+#include "nsStyleStruct.h"
+#include "Units.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/webrender/webrender_ffi.h"
+
+class gfxPattern;
+
+namespace mozilla {
+
+namespace layers {
+class StackingContextHelper;
+} // namespace layers
+
+namespace wr {
+class DisplayListBuilder;
+} // namespace wr
+
+// A resolved color stop, with a specific position along the gradient line and
+// a color.
+struct ColorStop {
+ ColorStop() : mPosition(0), mIsMidpoint(false) {}
+ ColorStop(double aPosition, bool aIsMidPoint,
+ const StyleAbsoluteColor& aColor)
+ : mPosition(aPosition), mIsMidpoint(aIsMidPoint), mColor(aColor) {}
+ double mPosition; // along the gradient line; 0=start, 1=end
+ bool mIsMidpoint;
+ StyleAbsoluteColor mColor;
+};
+
+class nsCSSGradientRenderer final {
+ public:
+ /**
+ * Prepare a nsCSSGradientRenderer for a gradient for an element.
+ * aIntrinsicSize - the size of the source gradient.
+ */
+ static nsCSSGradientRenderer Create(nsPresContext* aPresContext,
+ ComputedStyle* aComputedStyle,
+ const StyleGradient& aGradient,
+ const nsSize& aIntrinsiceSize);
+
+ /**
+ * Draw the gradient to aContext
+ * aDest - where the first tile of gradient is
+ * aFill - the area to be filled with tiles of aDest
+ * aSrc - the area of the gradient that will fill aDest
+ * aRepeatSize - the distance from the origin of a tile
+ * to the next origin of a tile
+ * aDirtyRect - pixels outside of this area may be skipped
+ */
+ void Paint(gfxContext& aContext, const nsRect& aDest, const nsRect& aFill,
+ const nsSize& aRepeatSize, const mozilla::CSSIntRect& aSrc,
+ const nsRect& aDirtyRect, float aOpacity = 1.0);
+
+ /**
+ * Collect the gradient parameters
+ */
+ void BuildWebRenderParameters(float aOpacity, wr::ExtendMode& aMode,
+ nsTArray<wr::GradientStop>& aStops,
+ LayoutDevicePoint& aLineStart,
+ LayoutDevicePoint& aLineEnd,
+ LayoutDeviceSize& aGradientRadius,
+ LayoutDevicePoint& aGradientCenter,
+ float& aGradientAngle);
+
+ /**
+ * Build display items for the gradient
+ * aLayer - the layer to make this display item relative to
+ * aDest - where the first tile of gradient is
+ * aFill - the area to be filled with tiles of aDest
+ * aRepeatSize - the distance from the origin of a tile
+ * to the next origin of a tile
+ * aSrc - the area of the gradient that will fill aDest
+ */
+ void BuildWebRenderDisplayItems(wr::DisplayListBuilder& aBuilder,
+ const layers::StackingContextHelper& aSc,
+ const nsRect& aDest, const nsRect& aFill,
+ const nsSize& aRepeatSize,
+ const mozilla::CSSIntRect& aSrc,
+ bool aIsBackfaceVisible,
+ float aOpacity = 1.0);
+
+ private:
+ nsCSSGradientRenderer()
+ : mPresContext(nullptr),
+ mGradient(nullptr),
+ mRadiusX(0.0),
+ mRadiusY(0.0),
+ mAngle(0.0) {}
+
+ /**
+ * Attempts to paint the tiles for a gradient by painting it once to an
+ * offscreen surface and then painting that offscreen surface with
+ * ExtendMode::Repeat to cover all tiles.
+ *
+ * Returns false if the optimization wasn't able to be used, in which case
+ * a fallback should be used.
+ */
+ bool TryPaintTilesWithExtendMode(
+ gfxContext& aContext, gfxPattern* aGradientPattern, nscoord aXStart,
+ nscoord aYStart, const gfxRect& aDirtyAreaToFill, const nsRect& aDest,
+ const nsSize& aRepeatSize, bool aForceRepeatToCoverTiles);
+
+ nsPresContext* mPresContext;
+ const StyleGradient* mGradient;
+ nsTArray<ColorStop> mStops;
+ gfxPoint mLineStart, mLineEnd; // only for linear/radial gradients
+ double mRadiusX, mRadiusY; // only for radial gradients
+ gfxPoint mCenter; // only for conic gradients
+ float mAngle; // only for conic gradients
+};
+
+} // namespace mozilla
+
+#endif /* nsCSSRenderingGradients_h__ */
diff --git a/layout/painting/nsDisplayItemTypes.h b/layout/painting/nsDisplayItemTypes.h
new file mode 100644
index 0000000000..f0c7c333e0
--- /dev/null
+++ b/layout/painting/nsDisplayItemTypes.h
@@ -0,0 +1,73 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+// IWYU pragma: private, include "nsDisplayList.h"
+
+/**
+ * It's useful to be able to dynamically check the type of certain items.
+ * Every subclass of nsDisplayItem must have a new type added here for the
+ * purposes of easy comparison and matching of items in different display lists.
+ */
+
+#ifndef NSDISPLAYITEMTYPES_H_
+#define NSDISPLAYITEMTYPES_H_
+
+enum class DisplayItemType : uint8_t {
+ TYPE_ZERO = 0, /** Spacer so that the first item starts at 1 */
+
+#define DECLARE_DISPLAY_ITEM_TYPE(name, flags) TYPE_##name,
+#include "nsDisplayItemTypesList.h"
+#undef DECLARE_DISPLAY_ITEM_TYPE
+
+ TYPE_MAX
+};
+
+enum {
+ // Number of bits needed to represent all types
+ TYPE_BITS = 8
+};
+
+enum DisplayItemFlags {
+ TYPE_RENDERS_NO_IMAGES = 1 << 0,
+ TYPE_IS_CONTENTFUL = 1 << 1,
+ TYPE_IS_CONTAINER = 1 << 2
+};
+
+inline const char* DisplayItemTypeName(DisplayItemType aType) {
+ switch (aType) {
+#define DECLARE_DISPLAY_ITEM_TYPE(name, flags) \
+ case DisplayItemType::TYPE_##name: \
+ return #name;
+#include "nsDisplayItemTypesList.h"
+#undef DECLARE_DISPLAY_ITEM_TYPE
+
+ default:
+ return "TYPE_UNKNOWN";
+ }
+}
+
+inline uint8_t GetDisplayItemFlagsForType(DisplayItemType aType) {
+ static const uint8_t flags[static_cast<uint32_t>(DisplayItemType::TYPE_MAX)] =
+ {0
+#define DECLARE_DISPLAY_ITEM_TYPE(name, flags) , flags
+#include "nsDisplayItemTypesList.h"
+#undef DECLARE_DISPLAY_ITEM_TYPE
+ };
+
+ return flags[static_cast<uint32_t>(aType)];
+}
+
+inline DisplayItemType GetDisplayItemTypeFromKey(uint32_t aDisplayItemKey) {
+ static const uint32_t typeMask = (1 << TYPE_BITS) - 1;
+ DisplayItemType type =
+ static_cast<DisplayItemType>(aDisplayItemKey & typeMask);
+ NS_ASSERTION(
+ type >= DisplayItemType::TYPE_ZERO && type < DisplayItemType::TYPE_MAX,
+ "Invalid display item type!");
+ return type;
+}
+
+#endif /*NSDISPLAYITEMTYPES_H_*/
diff --git a/layout/painting/nsDisplayItemTypesList.h b/layout/painting/nsDisplayItemTypesList.h
new file mode 100644
index 0000000000..b3d4f14c4d
--- /dev/null
+++ b/layout/painting/nsDisplayItemTypesList.h
@@ -0,0 +1,120 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+// IWYU pragma: private, include "nsDisplayList.h"
+DECLARE_DISPLAY_ITEM_TYPE(ALT_FEEDBACK, 0)
+DECLARE_DISPLAY_ITEM_TYPE(ASYNC_ZOOM,
+ TYPE_RENDERS_NO_IMAGES | TYPE_IS_CONTAINER)
+DECLARE_DISPLAY_ITEM_TYPE(BACKDROP_FILTER, TYPE_IS_CONTAINER)
+DECLARE_DISPLAY_ITEM_TYPE(BACKDROP_ROOT_CONTAINER, TYPE_IS_CONTAINER)
+DECLARE_DISPLAY_ITEM_TYPE(BACKGROUND, TYPE_IS_CONTENTFUL)
+DECLARE_DISPLAY_ITEM_TYPE(BACKGROUND_COLOR, TYPE_RENDERS_NO_IMAGES)
+DECLARE_DISPLAY_ITEM_TYPE(BLEND_CONTAINER,
+ TYPE_RENDERS_NO_IMAGES | TYPE_IS_CONTAINER)
+DECLARE_DISPLAY_ITEM_TYPE(BLEND_MODE,
+ TYPE_RENDERS_NO_IMAGES | TYPE_IS_CONTAINER)
+DECLARE_DISPLAY_ITEM_TYPE(BORDER, 0)
+DECLARE_DISPLAY_ITEM_TYPE(BOX_SHADOW_INNER, TYPE_RENDERS_NO_IMAGES)
+DECLARE_DISPLAY_ITEM_TYPE(BOX_SHADOW_OUTER, TYPE_RENDERS_NO_IMAGES)
+DECLARE_DISPLAY_ITEM_TYPE(BUTTON_BORDER_BACKGROUND, TYPE_IS_CONTENTFUL)
+DECLARE_DISPLAY_ITEM_TYPE(BUTTON_BOX_SHADOW_OUTER,
+ TYPE_RENDERS_NO_IMAGES | TYPE_IS_CONTENTFUL)
+DECLARE_DISPLAY_ITEM_TYPE(BUTTON_FOREGROUND, TYPE_IS_CONTENTFUL)
+DECLARE_DISPLAY_ITEM_TYPE(CANVAS, TYPE_RENDERS_NO_IMAGES)
+DECLARE_DISPLAY_ITEM_TYPE(CANVAS_BACKGROUND_COLOR, TYPE_RENDERS_NO_IMAGES)
+DECLARE_DISPLAY_ITEM_TYPE(CANVAS_BACKGROUND_IMAGE, TYPE_IS_CONTENTFUL)
+DECLARE_DISPLAY_ITEM_TYPE(CANVAS_FOCUS, TYPE_RENDERS_NO_IMAGES)
+DECLARE_DISPLAY_ITEM_TYPE(CANVAS_THEMED_BACKGROUND,
+ TYPE_RENDERS_NO_IMAGES | TYPE_IS_CONTENTFUL)
+DECLARE_DISPLAY_ITEM_TYPE(CARET, TYPE_RENDERS_NO_IMAGES | TYPE_IS_CONTENTFUL)
+DECLARE_DISPLAY_ITEM_TYPE(CHECKED_CHECKBOX,
+ TYPE_RENDERS_NO_IMAGES | TYPE_IS_CONTENTFUL)
+DECLARE_DISPLAY_ITEM_TYPE(CHECKED_RADIOBUTTON,
+ TYPE_RENDERS_NO_IMAGES | TYPE_IS_CONTENTFUL)
+DECLARE_DISPLAY_ITEM_TYPE(COLUMN_RULE, TYPE_RENDERS_NO_IMAGES)
+DECLARE_DISPLAY_ITEM_TYPE(COMBOBOX_FOCUS, TYPE_RENDERS_NO_IMAGES)
+DECLARE_DISPLAY_ITEM_TYPE(COMPOSITOR_HITTEST_INFO, TYPE_RENDERS_NO_IMAGES)
+DECLARE_DISPLAY_ITEM_TYPE(CONTAINER, TYPE_RENDERS_NO_IMAGES | TYPE_IS_CONTAINER)
+DECLARE_DISPLAY_ITEM_TYPE(DESTINATION, TYPE_RENDERS_NO_IMAGES)
+DECLARE_DISPLAY_ITEM_TYPE(EVENT_RECEIVER, TYPE_RENDERS_NO_IMAGES)
+DECLARE_DISPLAY_ITEM_TYPE(FIELDSET_BORDER_BACKGROUND, 0)
+DECLARE_DISPLAY_ITEM_TYPE(FILTER, TYPE_RENDERS_NO_IMAGES | TYPE_IS_CONTAINER)
+DECLARE_DISPLAY_ITEM_TYPE(FIXED_POSITION,
+ TYPE_RENDERS_NO_IMAGES | TYPE_IS_CONTAINER)
+DECLARE_DISPLAY_ITEM_TYPE(FOREIGN_OBJECT,
+ TYPE_IS_CONTENTFUL | TYPE_IS_CONTAINER)
+DECLARE_DISPLAY_ITEM_TYPE(FRAMESET_BLANK, TYPE_RENDERS_NO_IMAGES)
+DECLARE_DISPLAY_ITEM_TYPE(FRAMESET_BORDER, TYPE_RENDERS_NO_IMAGES)
+DECLARE_DISPLAY_ITEM_TYPE(GENERIC, TYPE_RENDERS_NO_IMAGES)
+DECLARE_DISPLAY_ITEM_TYPE(GRADIENT, TYPE_IS_CONTENTFUL)
+DECLARE_DISPLAY_ITEM_TYPE(HEADER_FOOTER, TYPE_RENDERS_NO_IMAGES)
+DECLARE_DISPLAY_ITEM_TYPE(IMAGE, TYPE_IS_CONTENTFUL)
+DECLARE_DISPLAY_ITEM_TYPE(LINK, TYPE_RENDERS_NO_IMAGES)
+DECLARE_DISPLAY_ITEM_TYPE(LIST_FOCUS, TYPE_RENDERS_NO_IMAGES)
+DECLARE_DISPLAY_ITEM_TYPE(MARGIN_GUIDES, TYPE_RENDERS_NO_IMAGES)
+DECLARE_DISPLAY_ITEM_TYPE(MASK, TYPE_IS_CONTAINER)
+DECLARE_DISPLAY_ITEM_TYPE(OPACITY, TYPE_RENDERS_NO_IMAGES | TYPE_IS_CONTAINER)
+DECLARE_DISPLAY_ITEM_TYPE(OPTION_EVENT_GRABBER,
+ TYPE_RENDERS_NO_IMAGES | TYPE_IS_CONTAINER)
+DECLARE_DISPLAY_ITEM_TYPE(OUTLINE, TYPE_RENDERS_NO_IMAGES)
+DECLARE_DISPLAY_ITEM_TYPE(OWN_LAYER, TYPE_RENDERS_NO_IMAGES | TYPE_IS_CONTAINER)
+DECLARE_DISPLAY_ITEM_TYPE(PERSPECTIVE,
+ TYPE_RENDERS_NO_IMAGES | TYPE_IS_CONTAINER)
+DECLARE_DISPLAY_ITEM_TYPE(RANGE_FOCUS_RING, 0)
+DECLARE_DISPLAY_ITEM_TYPE(REMOTE, TYPE_RENDERS_NO_IMAGES)
+DECLARE_DISPLAY_ITEM_TYPE(SCROLL_INFO_LAYER,
+ TYPE_RENDERS_NO_IMAGES | TYPE_IS_CONTAINER)
+DECLARE_DISPLAY_ITEM_TYPE(SLIDER_MARKS, TYPE_RENDERS_NO_IMAGES)
+DECLARE_DISPLAY_ITEM_TYPE(SELECTION_OVERLAY, TYPE_RENDERS_NO_IMAGES)
+DECLARE_DISPLAY_ITEM_TYPE(SOLID_COLOR, TYPE_RENDERS_NO_IMAGES)
+DECLARE_DISPLAY_ITEM_TYPE(SOLID_COLOR_REGION, TYPE_RENDERS_NO_IMAGES)
+DECLARE_DISPLAY_ITEM_TYPE(SUBDOCUMENT,
+ TYPE_RENDERS_NO_IMAGES | TYPE_IS_CONTAINER)
+DECLARE_DISPLAY_ITEM_TYPE(STICKY_POSITION,
+ TYPE_RENDERS_NO_IMAGES | TYPE_IS_CONTAINER)
+DECLARE_DISPLAY_ITEM_TYPE(SVG_GEOMETRY, TYPE_IS_CONTENTFUL)
+DECLARE_DISPLAY_ITEM_TYPE(SVG_IMAGE, TYPE_IS_CONTENTFUL)
+DECLARE_DISPLAY_ITEM_TYPE(SVG_TEXT, TYPE_IS_CONTENTFUL)
+DECLARE_DISPLAY_ITEM_TYPE(SVG_WRAPPER, TYPE_IS_CONTAINER)
+DECLARE_DISPLAY_ITEM_TYPE(TABLE_BACKGROUND_COLOR, TYPE_RENDERS_NO_IMAGES)
+DECLARE_DISPLAY_ITEM_TYPE(TABLE_BACKGROUND_IMAGE, TYPE_IS_CONTENTFUL)
+DECLARE_DISPLAY_ITEM_TYPE(TABLE_BLEND_CONTAINER,
+ TYPE_RENDERS_NO_IMAGES | TYPE_IS_CONTAINER)
+DECLARE_DISPLAY_ITEM_TYPE(TABLE_BLEND_MODE,
+ TYPE_RENDERS_NO_IMAGES | TYPE_IS_CONTAINER)
+DECLARE_DISPLAY_ITEM_TYPE(TABLE_BORDER_COLLAPSE, 0)
+DECLARE_DISPLAY_ITEM_TYPE(TABLE_CELL_BACKGROUND, 0)
+DECLARE_DISPLAY_ITEM_TYPE(TABLE_CELL_SELECTION, TYPE_RENDERS_NO_IMAGES)
+DECLARE_DISPLAY_ITEM_TYPE(TABLE_THEMED_BACKGROUND_IMAGE, TYPE_IS_CONTENTFUL)
+DECLARE_DISPLAY_ITEM_TYPE(TABLE_FIXED_POSITION,
+ TYPE_RENDERS_NO_IMAGES | TYPE_IS_CONTAINER)
+DECLARE_DISPLAY_ITEM_TYPE(TEXT, TYPE_RENDERS_NO_IMAGES | TYPE_IS_CONTENTFUL)
+DECLARE_DISPLAY_ITEM_TYPE(TEXT_OVERFLOW,
+ TYPE_RENDERS_NO_IMAGES | TYPE_IS_CONTENTFUL)
+DECLARE_DISPLAY_ITEM_TYPE(THEMED_BACKGROUND, TYPE_RENDERS_NO_IMAGES)
+DECLARE_DISPLAY_ITEM_TYPE(TRANSFORM, TYPE_RENDERS_NO_IMAGES | TYPE_IS_CONTAINER)
+DECLARE_DISPLAY_ITEM_TYPE(VIDEO, TYPE_RENDERS_NO_IMAGES | TYPE_IS_CONTENTFUL)
+DECLARE_DISPLAY_ITEM_TYPE(WRAP_LIST, TYPE_RENDERS_NO_IMAGES | TYPE_IS_CONTAINER)
+DECLARE_DISPLAY_ITEM_TYPE(ZOOM, TYPE_RENDERS_NO_IMAGES | TYPE_IS_CONTAINER)
+
+#if defined(MOZ_REFLOW_PERF_DSP) && defined(MOZ_REFLOW_PERF)
+DECLARE_DISPLAY_ITEM_TYPE(REFLOW_COUNT, TYPE_RENDERS_NO_IMAGES)
+#endif
+
+DECLARE_DISPLAY_ITEM_TYPE(XUL_GROUP_BACKGROUND, 0)
+DECLARE_DISPLAY_ITEM_TYPE(XUL_TEXT_BOX, TYPE_RENDERS_NO_IMAGES)
+DECLARE_DISPLAY_ITEM_TYPE(XUL_TREE_BODY, 0)
+DECLARE_DISPLAY_ITEM_TYPE(XUL_TREE_COL_SPLITTER_TARGET, TYPE_RENDERS_NO_IMAGES)
+
+DECLARE_DISPLAY_ITEM_TYPE(MATHML_BAR, TYPE_RENDERS_NO_IMAGES)
+DECLARE_DISPLAY_ITEM_TYPE(MATHML_CHAR_FOREGROUND, TYPE_RENDERS_NO_IMAGES)
+DECLARE_DISPLAY_ITEM_TYPE(MATHML_ERROR, TYPE_RENDERS_NO_IMAGES)
+DECLARE_DISPLAY_ITEM_TYPE(MATHML_MENCLOSE_NOTATION, TYPE_RENDERS_NO_IMAGES)
+DECLARE_DISPLAY_ITEM_TYPE(MATHML_SELECTION_RECT, TYPE_RENDERS_NO_IMAGES)
+DECLARE_DISPLAY_ITEM_TYPE(MATHML_SLASH, TYPE_RENDERS_NO_IMAGES)
+#ifdef DEBUG
+DECLARE_DISPLAY_ITEM_TYPE(MATHML_BOUNDING_METRICS, TYPE_RENDERS_NO_IMAGES)
+DECLARE_DISPLAY_ITEM_TYPE(MATHML_CHAR_DEBUG, TYPE_RENDERS_NO_IMAGES)
+#endif
diff --git a/layout/painting/nsDisplayList.cpp b/layout/painting/nsDisplayList.cpp
new file mode 100644
index 0000000000..da806037d1
--- /dev/null
+++ b/layout/painting/nsDisplayList.cpp
@@ -0,0 +1,8708 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+/*
+ * structures that represent things to be painted (ordered in z-order),
+ * used during painting and hit testing
+ */
+
+#include "nsDisplayList.h"
+
+#include <stdint.h>
+#include <algorithm>
+#include <limits>
+
+#include "gfxContext.h"
+#include "gfxUtils.h"
+#include "mozilla/DisplayPortUtils.h"
+#include "mozilla/Likely.h"
+#include "mozilla/dom/BrowserChild.h"
+#include "mozilla/dom/HTMLCanvasElement.h"
+#include "mozilla/dom/RemoteBrowser.h"
+#include "mozilla/dom/Selection.h"
+#include "mozilla/dom/ServiceWorkerRegistrar.h"
+#include "mozilla/dom/ServiceWorkerRegistration.h"
+#include "mozilla/dom/SVGElement.h"
+#include "mozilla/dom/TouchEvent.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/ShapeUtils.h"
+#include "mozilla/StaticPrefs_apz.h"
+#include "mozilla/StaticPrefs_gfx.h"
+#include "mozilla/StaticPrefs_layers.h"
+#include "mozilla/StaticPrefs_layout.h"
+#include "mozilla/StaticPrefs_print.h"
+#include "mozilla/SVGIntegrationUtils.h"
+#include "mozilla/SVGUtils.h"
+#include "mozilla/ViewportUtils.h"
+#include "nsCSSRendering.h"
+#include "nsCSSRenderingGradients.h"
+#include "nsRefreshDriver.h"
+#include "nsRegion.h"
+#include "nsStyleStructInlines.h"
+#include "nsStyleTransformMatrix.h"
+#include "nsTransitionManager.h"
+#include "gfxMatrix.h"
+#include "nsLayoutUtils.h"
+#include "nsIScrollableFrame.h"
+#include "nsIFrameInlines.h"
+#include "nsStyleConsts.h"
+#include "BorderConsts.h"
+#include "mozilla/MathAlgorithms.h"
+
+#include "imgIContainer.h"
+#include "nsImageFrame.h"
+#include "nsSubDocumentFrame.h"
+#include "nsViewManager.h"
+#include "ImageContainer.h"
+#include "nsCanvasFrame.h"
+#include "nsSubDocumentFrame.h"
+#include "StickyScrollContainer.h"
+#include "mozilla/AnimationPerformanceWarning.h"
+#include "mozilla/AnimationUtils.h"
+#include "mozilla/AutoRestore.h"
+#include "mozilla/EffectCompositor.h"
+#include "mozilla/EffectSet.h"
+#include "mozilla/HashTable.h"
+#include "mozilla/LookAndFeel.h"
+#include "mozilla/OperatorNewExtensions.h"
+#include "mozilla/PendingAnimationTracker.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/ProfilerLabels.h"
+#include "mozilla/ProfilerMarkers.h"
+#include "mozilla/StyleAnimationValue.h"
+#include "mozilla/ServoBindings.h"
+#include "mozilla/SVGClipPathFrame.h"
+#include "mozilla/SVGMaskFrame.h"
+#include "mozilla/SVGObserverUtils.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/Unused.h"
+#include "mozilla/ViewportFrame.h"
+#include "mozilla/gfx/gfxVars.h"
+#include "ActiveLayerTracker.h"
+#include "nsEscape.h"
+#include "nsPrintfCString.h"
+#include "UnitTransforms.h"
+#include "LayerAnimationInfo.h"
+#include "mozilla/EventStateManager.h"
+#include "nsCaret.h"
+#include "nsDOMTokenList.h"
+#include "nsCSSProps.h"
+#include "nsTableCellFrame.h"
+#include "nsTableColFrame.h"
+#include "nsTextFrame.h"
+#include "nsTextPaintStyle.h"
+#include "nsSliderFrame.h"
+#include "nsFocusManager.h"
+#include "TextDrawTarget.h"
+#include "mozilla/layers/AnimationHelper.h"
+#include "mozilla/layers/CompositorThread.h"
+#include "mozilla/layers/InputAPZContext.h"
+#include "mozilla/layers/RenderRootStateManager.h"
+#include "mozilla/layers/StackingContextHelper.h"
+#include "mozilla/layers/TreeTraversal.h"
+#include "mozilla/layers/WebRenderBridgeChild.h"
+#include "mozilla/layers/WebRenderLayerManager.h"
+#include "mozilla/layers/WebRenderMessages.h"
+#include "mozilla/layers/WebRenderScrollData.h"
+
+namespace mozilla {
+
+using namespace dom;
+using namespace gfx;
+using namespace layout;
+using namespace layers;
+using namespace image;
+
+LazyLogModule sContentDisplayListLog("dl.content");
+LazyLogModule sParentDisplayListLog("dl.parent");
+
+LazyLogModule& GetLoggerByProcess() {
+ return XRE_IsContentProcess() ? sContentDisplayListLog
+ : sParentDisplayListLog;
+}
+
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+void AssertUniqueItem(nsDisplayItem* aItem) {
+ for (nsDisplayItem* i : aItem->Frame()->DisplayItems()) {
+ if (i != aItem && !i->HasDeletedFrame() && i->Frame() == aItem->Frame() &&
+ i->GetPerFrameKey() == aItem->GetPerFrameKey()) {
+ if (i->IsPreProcessedItem() || i->IsPreProcessed()) {
+ continue;
+ }
+ MOZ_DIAGNOSTIC_ASSERT(false, "Duplicate display item!");
+ }
+ }
+}
+#endif
+
+bool ShouldBuildItemForEvents(const DisplayItemType aType) {
+ return aType == DisplayItemType::TYPE_COMPOSITOR_HITTEST_INFO ||
+ (GetDisplayItemFlagsForType(aType) & TYPE_IS_CONTAINER);
+}
+
+static bool ItemTypeSupportsHitTesting(const DisplayItemType aType) {
+ switch (aType) {
+ case DisplayItemType::TYPE_BACKGROUND:
+ case DisplayItemType::TYPE_BACKGROUND_COLOR:
+ case DisplayItemType::TYPE_THEMED_BACKGROUND:
+ return true;
+ default:
+ return false;
+ }
+}
+
+void InitializeHitTestInfo(nsDisplayListBuilder* aBuilder,
+ nsPaintedDisplayItem* aItem,
+ const DisplayItemType aType) {
+ if (ItemTypeSupportsHitTesting(aType)) {
+ aItem->InitializeHitTestInfo(aBuilder);
+ }
+}
+
+/* static */
+already_AddRefed<ActiveScrolledRoot> ActiveScrolledRoot::CreateASRForFrame(
+ const ActiveScrolledRoot* aParent, nsIScrollableFrame* aScrollableFrame,
+ bool aIsRetained) {
+ nsIFrame* f = do_QueryFrame(aScrollableFrame);
+
+ RefPtr<ActiveScrolledRoot> asr;
+ if (aIsRetained) {
+ asr = f->GetProperty(ActiveScrolledRootCache());
+ }
+
+ if (!asr) {
+ asr = new ActiveScrolledRoot();
+
+ if (aIsRetained) {
+ RefPtr<ActiveScrolledRoot> ref = asr;
+ f->SetProperty(ActiveScrolledRootCache(), ref.forget().take());
+ }
+ }
+ asr->mParent = aParent;
+ asr->mScrollableFrame = aScrollableFrame;
+ asr->mViewId = Nothing();
+ asr->mDepth = aParent ? aParent->mDepth + 1 : 1;
+ asr->mRetained = aIsRetained;
+
+ return asr.forget();
+}
+
+/* static */
+bool ActiveScrolledRoot::IsAncestor(const ActiveScrolledRoot* aAncestor,
+ const ActiveScrolledRoot* aDescendant) {
+ if (!aAncestor) {
+ // nullptr is the root
+ return true;
+ }
+ if (Depth(aAncestor) > Depth(aDescendant)) {
+ return false;
+ }
+ const ActiveScrolledRoot* asr = aDescendant;
+ while (asr) {
+ if (asr == aAncestor) {
+ return true;
+ }
+ asr = asr->mParent;
+ }
+ return false;
+}
+
+/* static */
+bool ActiveScrolledRoot::IsProperAncestor(
+ const ActiveScrolledRoot* aAncestor,
+ const ActiveScrolledRoot* aDescendant) {
+ return aAncestor != aDescendant && IsAncestor(aAncestor, aDescendant);
+}
+
+/* static */
+nsCString ActiveScrolledRoot::ToString(
+ const ActiveScrolledRoot* aActiveScrolledRoot) {
+ nsAutoCString str;
+ for (const auto* asr = aActiveScrolledRoot; asr; asr = asr->mParent) {
+ str.AppendPrintf("<0x%p>", asr->mScrollableFrame);
+ if (asr->mParent) {
+ str.AppendLiteral(", ");
+ }
+ }
+ return std::move(str);
+}
+
+ScrollableLayerGuid::ViewID ActiveScrolledRoot::ComputeViewId() const {
+ nsIContent* content = mScrollableFrame->GetScrolledFrame()->GetContent();
+ return nsLayoutUtils::FindOrCreateIDFor(content);
+}
+
+ActiveScrolledRoot::~ActiveScrolledRoot() {
+ if (mScrollableFrame && mRetained) {
+ nsIFrame* f = do_QueryFrame(mScrollableFrame);
+ f->RemoveProperty(ActiveScrolledRootCache());
+ }
+}
+
+static uint64_t AddAnimationsForWebRender(
+ nsDisplayItem* aItem, RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder,
+ const Maybe<LayoutDevicePoint>& aPosition = Nothing()) {
+ auto* effects = EffectSet::GetForFrame(aItem->Frame(), aItem->GetType());
+ if (!effects || effects->IsEmpty()) {
+ // If there is no animation on the nsIFrame, that means
+ // 1) we've never created any animations on this frame or
+ // 2) the frame was reconstruced or
+ // 3) all animations on the frame have finished
+ // in such cases we don't need do anything here.
+ //
+ // Even if there is a WebRenderAnimationData for the display item type on
+ // this frame, it's going to be discarded since it's not marked as being
+ // used.
+ return 0;
+ }
+
+ RefPtr<WebRenderAnimationData> animationData =
+ aManager->CommandBuilder()
+ .CreateOrRecycleWebRenderUserData<WebRenderAnimationData>(aItem);
+ AnimationInfo& animationInfo = animationData->GetAnimationInfo();
+ animationInfo.AddAnimationsForDisplayItem(
+ aItem->Frame(), aDisplayListBuilder, aItem, aItem->GetType(),
+ aManager->LayerManager(), aPosition);
+ animationInfo.StartPendingAnimations(
+ aManager->LayerManager()->GetAnimationReadyTime());
+
+ // Note that animationsId can be 0 (uninitialized in AnimationInfo) if there
+ // are no active animations.
+ uint64_t animationsId = animationInfo.GetCompositorAnimationsId();
+ if (!animationInfo.GetAnimations().IsEmpty()) {
+ OpAddCompositorAnimations anim(
+ CompositorAnimations(animationInfo.GetAnimations(), animationsId));
+ aManager->WrBridge()->AddWebRenderParentCommand(anim);
+ aManager->AddActiveCompositorAnimationId(animationsId);
+ } else if (animationsId) {
+ aManager->AddCompositorAnimationsIdForDiscard(animationsId);
+ animationsId = 0;
+ }
+
+ return animationsId;
+}
+
+static bool GenerateAndPushTextMask(nsIFrame* aFrame, gfxContext* aContext,
+ const nsRect& aFillRect,
+ nsDisplayListBuilder* aBuilder) {
+ if (aBuilder->IsForGenerateGlyphMask()) {
+ return false;
+ }
+
+ SVGObserverUtils::GetAndObserveBackgroundClip(aFrame);
+
+ // The main function of enabling background-clip:text property value.
+ // When a nsDisplayBackgroundImage detects "text" bg-clip style, it will call
+ // this function to
+ // 1. Generate a mask by all descendant text frames
+ // 2. Push the generated mask into aContext.
+
+ gfxContext* sourceCtx = aContext;
+ LayoutDeviceRect bounds = LayoutDeviceRect::FromAppUnits(
+ aFillRect, aFrame->PresContext()->AppUnitsPerDevPixel());
+
+ // Create a mask surface.
+ RefPtr<DrawTarget> sourceTarget = sourceCtx->GetDrawTarget();
+ RefPtr<DrawTarget> maskDT = sourceTarget->CreateClippedDrawTarget(
+ bounds.ToUnknownRect(), SurfaceFormat::A8);
+ if (!maskDT || !maskDT->IsValid()) {
+ return false;
+ }
+ gfxContext maskCtx(maskDT, /* aPreserveTransform */ true);
+ maskCtx.Multiply(Matrix::Translation(bounds.TopLeft().ToUnknownPoint()));
+
+ // Shade text shape into mask A8 surface.
+ nsLayoutUtils::PaintFrame(
+ &maskCtx, aFrame, nsRect(nsPoint(0, 0), aFrame->GetSize()),
+ NS_RGB(255, 255, 255), nsDisplayListBuilderMode::GenerateGlyph);
+
+ // Push the generated mask into aContext, so that the caller can pop and
+ // blend with it.
+
+ Matrix currentMatrix = sourceCtx->CurrentMatrix();
+ Matrix invCurrentMatrix = currentMatrix;
+ invCurrentMatrix.Invert();
+
+ RefPtr<SourceSurface> maskSurface = maskDT->Snapshot();
+ sourceCtx->PushGroupForBlendBack(gfxContentType::COLOR_ALPHA, 1.0,
+ maskSurface, invCurrentMatrix);
+
+ return true;
+}
+
+nsDisplayWrapper* nsDisplayWrapList::CreateShallowCopy(
+ nsDisplayListBuilder* aBuilder) {
+ const nsDisplayWrapList* wrappedItem = AsDisplayWrapList();
+ MOZ_ASSERT(wrappedItem);
+
+ // Create a new nsDisplayWrapList using a copy-constructor. This is done
+ // to preserve the information about bounds.
+ nsDisplayWrapper* wrapper =
+ new (aBuilder) nsDisplayWrapper(aBuilder, *wrappedItem);
+ wrapper->SetType(nsDisplayWrapper::ItemType());
+ MOZ_ASSERT(wrapper);
+
+ // Set the display list pointer of the new wrapper item to the display list
+ // of the wrapped item.
+ wrapper->mListPtr = wrappedItem->mListPtr;
+ return wrapper;
+}
+
+nsDisplayWrapList* nsDisplayListBuilder::MergeItems(
+ nsTArray<nsDisplayItem*>& aItems) {
+ // For merging, we create a temporary item by cloning the last item of the
+ // mergeable items list. This ensures that the temporary item will have the
+ // correct frame and bounds.
+ nsDisplayWrapList* last = aItems.PopLastElement()->AsDisplayWrapList();
+ MOZ_ASSERT(last);
+ nsDisplayWrapList* merged = last->Clone(this);
+ MOZ_ASSERT(merged);
+ AddTemporaryItem(merged);
+
+ // Create nsDisplayWrappers that point to the internal display lists of the
+ // items we are merging. These nsDisplayWrappers are added to the display list
+ // of the temporary item.
+ for (nsDisplayItem* item : aItems) {
+ MOZ_ASSERT(item);
+ MOZ_ASSERT(merged->CanMerge(item));
+ merged->Merge(item);
+ MOZ_ASSERT(item->AsDisplayWrapList());
+ merged->GetChildren()->AppendToTop(
+ static_cast<nsDisplayWrapList*>(item)->CreateShallowCopy(this));
+ }
+
+ merged->GetChildren()->AppendToTop(last->CreateShallowCopy(this));
+
+ return merged;
+}
+
+void nsDisplayListBuilder::AutoCurrentActiveScrolledRootSetter::
+ SetCurrentActiveScrolledRoot(
+ const ActiveScrolledRoot* aActiveScrolledRoot) {
+ MOZ_ASSERT(!mUsed);
+
+ // Set the builder's mCurrentActiveScrolledRoot.
+ mBuilder->mCurrentActiveScrolledRoot = aActiveScrolledRoot;
+
+ // We also need to adjust the builder's mCurrentContainerASR.
+ // mCurrentContainerASR needs to be an ASR that all the container's
+ // contents have finite bounds with respect to. If aActiveScrolledRoot
+ // is an ancestor ASR of mCurrentContainerASR, that means we need to
+ // set mCurrentContainerASR to aActiveScrolledRoot, because otherwise
+ // the items that will be created with aActiveScrolledRoot wouldn't
+ // have finite bounds with respect to mCurrentContainerASR. There's one
+ // exception, in the case where there's a content clip on the builder
+ // that is scrolled by a descendant ASR of aActiveScrolledRoot. This
+ // content clip will clip all items that are created while this
+ // AutoCurrentActiveScrolledRootSetter exists. This means that the items
+ // created during our lifetime will have finite bounds with respect to
+ // the content clip's ASR, even if the items' actual ASR is an ancestor
+ // of that. And it also means that mCurrentContainerASR only needs to be
+ // set to the content clip's ASR and not all the way to aActiveScrolledRoot.
+ // This case is tested by fixed-pos-scrolled-clip-opacity-layerize.html
+ // and fixed-pos-scrolled-clip-opacity-inside-layerize.html.
+
+ // finiteBoundsASR is the leafmost ASR that all items created during
+ // object's lifetime have finite bounds with respect to.
+ const ActiveScrolledRoot* finiteBoundsASR =
+ ActiveScrolledRoot::PickDescendant(mContentClipASR, aActiveScrolledRoot);
+
+ // mCurrentContainerASR is adjusted so that it's still an ancestor of
+ // finiteBoundsASR.
+ mBuilder->mCurrentContainerASR = ActiveScrolledRoot::PickAncestor(
+ mBuilder->mCurrentContainerASR, finiteBoundsASR);
+
+ // If we are entering out-of-flow content inside a CSS filter, mark
+ // scroll frames wrt. which the content is fixed as containing such content.
+ if (mBuilder->mFilterASR && ActiveScrolledRoot::IsAncestor(
+ aActiveScrolledRoot, mBuilder->mFilterASR)) {
+ for (const ActiveScrolledRoot* asr = mBuilder->mFilterASR;
+ asr && asr != aActiveScrolledRoot; asr = asr->mParent) {
+ asr->mScrollableFrame->SetHasOutOfFlowContentInsideFilter();
+ }
+ }
+
+ mUsed = true;
+}
+
+void nsDisplayListBuilder::AutoCurrentActiveScrolledRootSetter::
+ InsertScrollFrame(nsIScrollableFrame* aScrollableFrame) {
+ MOZ_ASSERT(!mUsed);
+ size_t descendantsEndIndex = mBuilder->mActiveScrolledRoots.Length();
+ const ActiveScrolledRoot* parentASR = mBuilder->mCurrentActiveScrolledRoot;
+ const ActiveScrolledRoot* asr =
+ mBuilder->AllocateActiveScrolledRoot(parentASR, aScrollableFrame);
+ mBuilder->mCurrentActiveScrolledRoot = asr;
+
+ // All child ASRs of parentASR that were created while this
+ // AutoCurrentActiveScrolledRootSetter object was on the stack belong to us
+ // now. Reparent them to asr.
+ for (size_t i = mDescendantsStartIndex; i < descendantsEndIndex; i++) {
+ ActiveScrolledRoot* descendantASR = mBuilder->mActiveScrolledRoots[i];
+ if (ActiveScrolledRoot::IsAncestor(parentASR, descendantASR)) {
+ descendantASR->IncrementDepth();
+ if (descendantASR->mParent == parentASR) {
+ descendantASR->mParent = asr;
+ }
+ }
+ }
+
+ mUsed = true;
+}
+
+nsDisplayListBuilder::AutoContainerASRTracker::AutoContainerASRTracker(
+ nsDisplayListBuilder* aBuilder)
+ : mBuilder(aBuilder), mSavedContainerASR(aBuilder->mCurrentContainerASR) {
+ mBuilder->mCurrentContainerASR = mBuilder->mCurrentActiveScrolledRoot;
+}
+
+nsPresContext* nsDisplayListBuilder::CurrentPresContext() {
+ return CurrentPresShellState()->mPresShell->GetPresContext();
+}
+
+/* static */
+nsRect nsDisplayListBuilder::OutOfFlowDisplayData::ComputeVisibleRectForFrame(
+ nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ const nsRect& aVisibleRect, const nsRect& aDirtyRect,
+ nsRect* aOutDirtyRect) {
+ nsRect visible = aVisibleRect;
+ nsRect dirtyRectRelativeToDirtyFrame = aDirtyRect;
+
+ bool inPartialUpdate =
+ aBuilder->IsRetainingDisplayList() && aBuilder->IsPartialUpdate();
+ if (StaticPrefs::apz_allow_zooming() &&
+ DisplayPortUtils::IsFixedPosFrameInDisplayPort(aFrame) &&
+ aBuilder->IsPaintingToWindow() && !inPartialUpdate) {
+ dirtyRectRelativeToDirtyFrame =
+ nsRect(nsPoint(0, 0), aFrame->GetParent()->GetSize());
+
+ // If there's a visual viewport size set, restrict the amount of the
+ // fixed-position element we paint to the visual viewport. (In general
+ // the fixed-position element can be as large as the layout viewport,
+ // which at a high zoom level can cause us to paint too large of an
+ // area.)
+ PresShell* presShell = aFrame->PresShell();
+ if (presShell->IsVisualViewportSizeSet()) {
+ dirtyRectRelativeToDirtyFrame =
+ nsRect(presShell->GetVisualViewportOffsetRelativeToLayoutViewport(),
+ presShell->GetVisualViewportSize());
+ // But if we have a displayport, expand it to the displayport, so
+ // that async-scrolling the visual viewport within the layout viewport
+ // will not checkerboard.
+ if (nsIFrame* rootScrollFrame = presShell->GetRootScrollFrame()) {
+ nsRect displayport;
+ // Note that the displayport here is already in the right coordinate
+ // space: it's relative to the scroll port (= layout viewport), but
+ // covers the visual viewport with some margins around it, which is
+ // exactly what we want.
+ if (DisplayPortUtils::GetDisplayPort(
+ rootScrollFrame->GetContent(), &displayport,
+ DisplayPortOptions().With(ContentGeometryType::Fixed))) {
+ dirtyRectRelativeToDirtyFrame = displayport;
+ }
+ }
+ }
+ visible = dirtyRectRelativeToDirtyFrame;
+ if (StaticPrefs::apz_test_logging_enabled() &&
+ presShell->GetDocument()->IsContentDocument()) {
+ nsLayoutUtils::LogAdditionalTestData(
+ aBuilder, "fixedPosDisplayport",
+ ToString(CSSSize::FromAppUnits(visible)));
+ }
+ }
+
+ *aOutDirtyRect = dirtyRectRelativeToDirtyFrame - aFrame->GetPosition();
+ visible -= aFrame->GetPosition();
+
+ nsRect overflowRect = aFrame->InkOverflowRect();
+
+ if (aFrame->IsTransformed() && EffectCompositor::HasAnimationsForCompositor(
+ aFrame, DisplayItemType::TYPE_TRANSFORM)) {
+ /**
+ * Add a fuzz factor to the overflow rectangle so that elements only
+ * just out of view are pulled into the display list, so they can be
+ * prerendered if necessary.
+ */
+ overflowRect.Inflate(nsPresContext::CSSPixelsToAppUnits(32));
+ }
+
+ visible.IntersectRect(visible, overflowRect);
+ aOutDirtyRect->IntersectRect(*aOutDirtyRect, overflowRect);
+
+ return visible;
+}
+
+nsDisplayListBuilder::Linkifier::Linkifier(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame,
+ nsDisplayList* aList)
+ : mList(aList) {
+ // Find the element that we need to check for link-ness, bailing out if
+ // we can't find one.
+ Element* elem = Element::FromNodeOrNull(aFrame->GetContent());
+ if (!elem) {
+ return;
+ }
+
+ // If the element has an id and/or name attribute, generate a destination
+ // for possible internal linking.
+ auto maybeGenerateDest = [&](const nsAtom* aAttr) {
+ nsAutoString attrValue;
+ elem->GetAttr(aAttr, attrValue);
+ if (!attrValue.IsEmpty()) {
+ NS_ConvertUTF16toUTF8 dest(attrValue);
+ // Ensure that we only emit a given destination once, although there may
+ // be multiple frames associated with a given element; we'll simply use
+ // the first of them as the target of any links to it.
+ // XXX(jfkthame) This prevents emitting duplicate destinations *on the
+ // same page*, but does not prevent duplicates on subsequent pages, as
+ // each new page is handled by a new temporary DisplayListBuilder. This
+ // seems to be harmless in practice, though a bit wasteful of space. To
+ // fix, we need to maintain the set of already-seen destinations globally
+ // for the print job, rather than attached to the (per-page) builder.
+ if (aBuilder->mDestinations.EnsureInserted(dest)) {
+ auto* destination = MakeDisplayItem<nsDisplayDestination>(
+ aBuilder, aFrame, dest.get(), aFrame->GetRect().TopLeft());
+ mList->AppendToTop(destination);
+ }
+ }
+ };
+
+ if (StaticPrefs::print_save_as_pdf_internal_destinations_enabled()) {
+ if (elem->HasID()) {
+ maybeGenerateDest(nsGkAtoms::id);
+ }
+ if (elem->HasName()) {
+ maybeGenerateDest(nsGkAtoms::name);
+ }
+ }
+
+ // Links don't nest, so if the builder already has a destination, no need to
+ // check for a link element here.
+ if (!aBuilder->mLinkSpec.IsEmpty()) {
+ return;
+ }
+
+ // Check if we have actually found a link.
+ if (!elem->IsLink()) {
+ return;
+ }
+
+ nsCOMPtr<nsIURI> uri = elem->GetHrefURI();
+ if (!uri) {
+ return;
+ }
+
+ // Is it a local (in-page) destination?
+ bool hasRef, eqExRef;
+ nsIURI* docURI;
+ if (StaticPrefs::print_save_as_pdf_internal_destinations_enabled() &&
+ NS_SUCCEEDED(uri->GetHasRef(&hasRef)) && hasRef &&
+ (docURI = aFrame->PresContext()->Document()->GetDocumentURI()) &&
+ NS_SUCCEEDED(uri->EqualsExceptRef(docURI, &eqExRef)) && eqExRef) {
+ if (NS_FAILED(uri->GetRef(aBuilder->mLinkSpec)) ||
+ aBuilder->mLinkSpec.IsEmpty()) {
+ return;
+ }
+ // The destination name is simply a string; we don't want URL-escaping
+ // applied to it.
+ NS_UnescapeURL(aBuilder->mLinkSpec);
+ // Mark the link spec as being an internal destination
+ aBuilder->mLinkSpec.Insert('#', 0);
+ } else {
+ if (NS_FAILED(uri->GetSpec(aBuilder->mLinkSpec)) ||
+ aBuilder->mLinkSpec.IsEmpty()) {
+ return;
+ }
+ }
+
+ // Record that we need to reset the builder's state on destruction.
+ mBuilderToReset = aBuilder;
+}
+
+void nsDisplayListBuilder::Linkifier::MaybeAppendLink(
+ nsDisplayListBuilder* aBuilder, nsIFrame* aFrame) {
+ // Note that we may generate a link here even if the constructor bailed out
+ // without updating aBuilder->LinkSpec(), because it may have been set by
+ // an ancestor that was associated with a link element.
+ if (!aBuilder->mLinkSpec.IsEmpty()) {
+ auto* link = MakeDisplayItem<nsDisplayLink>(
+ aBuilder, aFrame, aBuilder->mLinkSpec.get(), aFrame->GetRect());
+ mList->AppendToTop(link);
+ }
+}
+
+uint32_t nsDisplayListBuilder::sPaintSequenceNumber(1);
+
+nsDisplayListBuilder::nsDisplayListBuilder(nsIFrame* aReferenceFrame,
+ nsDisplayListBuilderMode aMode,
+ bool aBuildCaret,
+ bool aRetainingDisplayList)
+ : mReferenceFrame(aReferenceFrame),
+ mIgnoreScrollFrame(nullptr),
+ mCurrentActiveScrolledRoot(nullptr),
+ mCurrentContainerASR(nullptr),
+ mCurrentFrame(aReferenceFrame),
+ mCurrentReferenceFrame(aReferenceFrame),
+ mGlassDisplayItem(nullptr),
+ mCaretFrame(nullptr),
+ mScrollInfoItemsForHoisting(nullptr),
+ mFirstClipChainToDestroy(nullptr),
+ mTableBackgroundSet(nullptr),
+ mCurrentScrollParentId(ScrollableLayerGuid::NULL_SCROLL_ID),
+ mCurrentScrollbarTarget(ScrollableLayerGuid::NULL_SCROLL_ID),
+ mFilterASR(nullptr),
+ mDirtyRect(-1, -1, -1, -1),
+ mBuildingExtraPagesForPageNum(0),
+ mHasGlassItemDuringPartial(false),
+ mMode(aMode),
+ mContainsBlendMode(false),
+ mIsBuildingScrollbar(false),
+ mCurrentScrollbarWillHaveLayer(false),
+ mBuildCaret(aBuildCaret),
+ mRetainingDisplayList(aRetainingDisplayList),
+ mPartialUpdate(false),
+ mIgnoreSuppression(false),
+ mIncludeAllOutOfFlows(false),
+ mDescendIntoSubdocuments(true),
+ mSelectedFramesOnly(false),
+ mAllowMergingAndFlattening(true),
+ mInTransform(false),
+ mInEventsOnly(false),
+ mInFilter(false),
+ mInPageSequence(false),
+ mIsInChromePresContext(false),
+ mSyncDecodeImages(false),
+ mIsPaintingToWindow(false),
+ mUseHighQualityScaling(false),
+ mIsPaintingForWebRender(false),
+ mIsCompositingCheap(false),
+ mAncestorHasApzAwareEventHandler(false),
+ mHaveScrollableDisplayPort(false),
+ mWindowDraggingAllowed(false),
+ mIsBuildingForPopup(nsLayoutUtils::IsPopup(aReferenceFrame)),
+ mForceLayerForScrollParent(false),
+ mContainsNonMinimalDisplayPort(false),
+ mAsyncPanZoomEnabled(nsLayoutUtils::AsyncPanZoomEnabled(aReferenceFrame)),
+ mBuildingInvisibleItems(false),
+ mIsBuilding(false),
+ mInInvalidSubtree(false),
+ mDisablePartialUpdates(false),
+ mPartialBuildFailed(false),
+ mIsInActiveDocShell(false),
+ mBuildAsyncZoomContainer(false),
+ mIsRelativeToLayoutViewport(false),
+ mUseOverlayScrollbars(false),
+ mAlwaysLayerizeScrollbars(false) {
+ MOZ_COUNT_CTOR(nsDisplayListBuilder);
+
+ mBuildCompositorHitTestInfo = mAsyncPanZoomEnabled && IsForPainting();
+
+ ShouldRebuildDisplayListDueToPrefChange();
+
+ mUseOverlayScrollbars =
+ !!LookAndFeel::GetInt(LookAndFeel::IntID::UseOverlayScrollbars);
+
+ mAlwaysLayerizeScrollbars =
+ StaticPrefs::layout_scrollbars_always_layerize_track();
+
+ static_assert(
+ static_cast<uint32_t>(DisplayItemType::TYPE_MAX) < (1 << TYPE_BITS),
+ "Check TYPE_MAX should not overflow");
+
+ mIsReusingStackingContextItems =
+ mRetainingDisplayList && StaticPrefs::layout_display_list_retain_sc();
+}
+
+static PresShell* GetFocusedPresShell() {
+ nsPIDOMWindowOuter* focusedWnd =
+ nsFocusManager::GetFocusManager()->GetFocusedWindow();
+ if (!focusedWnd) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIDocShell> focusedDocShell = focusedWnd->GetDocShell();
+ if (!focusedDocShell) {
+ return nullptr;
+ }
+
+ return focusedDocShell->GetPresShell();
+}
+
+void nsDisplayListBuilder::BeginFrame() {
+ nsCSSRendering::BeginFrameTreesLocked();
+
+ mIsPaintingToWindow = false;
+ mUseHighQualityScaling = false;
+ mIgnoreSuppression = false;
+ mInTransform = false;
+ mInFilter = false;
+ mSyncDecodeImages = false;
+
+ if (!mBuildCaret) {
+ return;
+ }
+
+ RefPtr<PresShell> presShell = GetFocusedPresShell();
+ if (presShell) {
+ RefPtr<nsCaret> caret = presShell->GetCaret();
+ mCaretFrame = caret->GetPaintGeometry(&mCaretRect);
+
+ // The focused pres shell may not be in the document that we're
+ // painting, or be in a popup. Check if the display root for
+ // the caret matches the display root that we're painting, and
+ // only use it if it matches.
+ if (mCaretFrame &&
+ nsLayoutUtils::GetDisplayRootFrame(mCaretFrame) !=
+ nsLayoutUtils::GetDisplayRootFrame(mReferenceFrame)) {
+ mCaretFrame = nullptr;
+ }
+ }
+}
+
+void nsDisplayListBuilder::AddEffectUpdate(dom::RemoteBrowser* aBrowser,
+ const dom::EffectsInfo& aUpdate) {
+ dom::EffectsInfo update = aUpdate;
+ // For printing we create one display item for each page that an iframe
+ // appears on, the proper visible rect is the union of all the visible rects
+ // we get from each display item.
+ nsPresContext* pc =
+ mReferenceFrame ? mReferenceFrame->PresContext() : nullptr;
+ if (pc && (pc->Type() != nsPresContext::eContext_Galley)) {
+ Maybe<dom::EffectsInfo> existing = mEffectsUpdates.MaybeGet(aBrowser);
+ if (existing.isSome()) {
+ // Only the visible rect should differ, the scales should match.
+ MOZ_ASSERT(existing->mRasterScale == aUpdate.mRasterScale &&
+ existing->mTransformToAncestorScale ==
+ aUpdate.mTransformToAncestorScale);
+ update.mVisibleRect = update.mVisibleRect.Union(existing->mVisibleRect);
+ }
+ }
+ mEffectsUpdates.InsertOrUpdate(aBrowser, update);
+}
+
+void nsDisplayListBuilder::EndFrame() {
+ NS_ASSERTION(!mInInvalidSubtree,
+ "Someone forgot to cleanup mInInvalidSubtree!");
+ mCurrentContainerASR = nullptr;
+ mActiveScrolledRoots.Clear();
+ mEffectsUpdates.Clear();
+ FreeClipChains();
+ FreeTemporaryItems();
+ nsCSSRendering::EndFrameTreesLocked();
+ mCaretFrame = nullptr;
+}
+
+void nsDisplayListBuilder::MarkFrameForDisplay(nsIFrame* aFrame,
+ const nsIFrame* aStopAtFrame) {
+ mFramesMarkedForDisplay.AppendElement(aFrame);
+ for (nsIFrame* f = aFrame; f;
+ f = nsLayoutUtils::GetParentOrPlaceholderForCrossDoc(f)) {
+ if (f->HasAnyStateBits(NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO)) {
+ return;
+ }
+ f->AddStateBits(NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO);
+ if (f == aStopAtFrame) {
+ // we've reached a frame that we know will be painted, so we can stop.
+ break;
+ }
+ }
+}
+
+void nsDisplayListBuilder::AddFrameMarkedForDisplayIfVisible(nsIFrame* aFrame) {
+ mFramesMarkedForDisplayIfVisible.AppendElement(aFrame);
+}
+
+static void MarkFrameForDisplayIfVisibleInternal(nsIFrame* aFrame,
+ const nsIFrame* aStopAtFrame) {
+ for (nsIFrame* f = aFrame; f; f = nsLayoutUtils::GetDisplayListParent(f)) {
+ if (f->ForceDescendIntoIfVisible()) {
+ return;
+ }
+ f->SetForceDescendIntoIfVisible(true);
+
+ // This condition must match the condition in
+ // nsLayoutUtils::GetParentOrPlaceholderFor which is used by
+ // nsLayoutUtils::GetDisplayListParent
+ if (f->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW) && !f->GetPrevInFlow()) {
+ nsIFrame* parent = f->GetParent();
+ if (parent && !parent->ForceDescendIntoIfVisible()) {
+ // If the GetDisplayListParent call is going to walk to a placeholder,
+ // in rare cases the placeholder might be contained in a different
+ // continuation from the oof. So we have to make sure to mark the oofs
+ // parent. In the common case this doesn't make us do any extra work,
+ // just changes the order in which we visit the frames since walking
+ // through placeholders will walk through the parent, and we stop when
+ // we find a ForceDescendIntoIfVisible bit set.
+ MarkFrameForDisplayIfVisibleInternal(parent, aStopAtFrame);
+ }
+ }
+
+ if (f == aStopAtFrame) {
+ // we've reached a frame that we know will be painted, so we can stop.
+ break;
+ }
+ }
+}
+
+void nsDisplayListBuilder::MarkFrameForDisplayIfVisible(
+ nsIFrame* aFrame, const nsIFrame* aStopAtFrame) {
+ AddFrameMarkedForDisplayIfVisible(aFrame);
+
+ MarkFrameForDisplayIfVisibleInternal(aFrame, aStopAtFrame);
+}
+
+void nsDisplayListBuilder::SetGlassDisplayItem(nsDisplayItem* aItem) {
+ // Web pages or extensions could trigger the "Multiple glass backgrounds
+ // found?" warning by using -moz-appearance:win-borderless-glass etc on their
+ // own elements (as long as they are root frames, which is rare as each doc
+ // only gets one near the root). We only care about the first one, since that
+ // will be the background of the root window.
+
+ if (IsPartialUpdate()) {
+ if (aItem->Frame()->Style()->IsRootElementStyle()) {
+#ifdef DEBUG
+ if (mHasGlassItemDuringPartial) {
+ NS_WARNING("Multiple glass backgrounds found?");
+ } else
+#endif
+ if (!mHasGlassItemDuringPartial) {
+ mHasGlassItemDuringPartial = true;
+ aItem->SetIsGlassItem();
+ }
+ }
+ return;
+ }
+
+ if (aItem->Frame()->Style()->IsRootElementStyle()) {
+#ifdef DEBUG
+ if (mGlassDisplayItem) {
+ NS_WARNING("Multiple glass backgrounds found?");
+ } else
+#endif
+ if (!mGlassDisplayItem) {
+ mGlassDisplayItem = aItem;
+ mGlassDisplayItem->SetIsGlassItem();
+ }
+ }
+}
+
+bool nsDisplayListBuilder::NeedToForceTransparentSurfaceForItem(
+ nsDisplayItem* aItem) {
+ return aItem == mGlassDisplayItem;
+}
+
+void nsDisplayListBuilder::SetIsRelativeToLayoutViewport() {
+ mIsRelativeToLayoutViewport = true;
+ UpdateShouldBuildAsyncZoomContainer();
+}
+
+void nsDisplayListBuilder::UpdateShouldBuildAsyncZoomContainer() {
+ const Document* document = mReferenceFrame->PresContext()->Document();
+ mBuildAsyncZoomContainer = !mIsRelativeToLayoutViewport &&
+ !document->Fullscreen() &&
+ nsLayoutUtils::AllowZoomingForDocument(document);
+}
+
+// Certain prefs may cause display list items to be added or removed when they
+// are toggled. In those cases, we need to fully rebuild the display list.
+bool nsDisplayListBuilder::ShouldRebuildDisplayListDueToPrefChange() {
+ // If we transition between wrapping the RCD-RSF contents into an async
+ // zoom container vs. not, we need to rebuild the display list. This only
+ // happens when the zooming or container scrolling prefs are toggled
+ // (manually by the user, or during test setup).
+ bool didBuildAsyncZoomContainer = mBuildAsyncZoomContainer;
+ UpdateShouldBuildAsyncZoomContainer();
+
+ bool hadOverlayScrollbarsLastTime = mUseOverlayScrollbars;
+ mUseOverlayScrollbars =
+ !!LookAndFeel::GetInt(LookAndFeel::IntID::UseOverlayScrollbars);
+
+ bool alwaysLayerizedScrollbarsLastTime = mAlwaysLayerizeScrollbars;
+ mAlwaysLayerizeScrollbars =
+ StaticPrefs::layout_scrollbars_always_layerize_track();
+
+ if (didBuildAsyncZoomContainer != mBuildAsyncZoomContainer) {
+ return true;
+ }
+
+ if (hadOverlayScrollbarsLastTime != mUseOverlayScrollbars) {
+ return true;
+ }
+
+ if (alwaysLayerizedScrollbarsLastTime != mAlwaysLayerizeScrollbars) {
+ return true;
+ }
+
+ return false;
+}
+
+void nsDisplayListBuilder::AddScrollFrameToNotify(
+ nsIScrollableFrame* aScrollFrame) {
+ mScrollFramesToNotify.insert(aScrollFrame);
+}
+
+void nsDisplayListBuilder::NotifyAndClearScrollFrames() {
+ for (const auto& it : mScrollFramesToNotify) {
+ it->NotifyApzTransaction();
+ }
+ mScrollFramesToNotify.clear();
+}
+
+bool nsDisplayListBuilder::MarkOutOfFlowFrameForDisplay(
+ nsIFrame* aDirtyFrame, nsIFrame* aFrame, const nsRect& aVisibleRect,
+ const nsRect& aDirtyRect) {
+ MOZ_ASSERT(aFrame->GetParent() == aDirtyFrame);
+ nsRect dirty;
+ nsRect visible = OutOfFlowDisplayData::ComputeVisibleRectForFrame(
+ this, aFrame, aVisibleRect, aDirtyRect, &dirty);
+ if (!aFrame->HasAnyStateBits(NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO) &&
+ visible.IsEmpty()) {
+ return false;
+ }
+
+ // Only MarkFrameForDisplay if we're dirty. If this is a nested out-of-flow
+ // frame, then it will also mark any outer frames to ensure that building
+ // reaches the dirty feame.
+ if (!dirty.IsEmpty() || aFrame->ForceDescendIntoIfVisible()) {
+ MarkFrameForDisplay(aFrame, aDirtyFrame);
+ }
+
+ return true;
+}
+
+static void UnmarkFrameForDisplay(nsIFrame* aFrame,
+ const nsIFrame* aStopAtFrame) {
+ for (nsIFrame* f = aFrame; f;
+ f = nsLayoutUtils::GetParentOrPlaceholderForCrossDoc(f)) {
+ if (!f->HasAnyStateBits(NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO)) {
+ return;
+ }
+ f->RemoveStateBits(NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO);
+ if (f == aStopAtFrame) {
+ // we've reached a frame that we know will be painted, so we can stop.
+ break;
+ }
+ }
+}
+
+static void UnmarkFrameForDisplayIfVisible(nsIFrame* aFrame) {
+ for (nsIFrame* f = aFrame; f; f = nsLayoutUtils::GetDisplayListParent(f)) {
+ if (!f->ForceDescendIntoIfVisible()) {
+ return;
+ }
+ f->SetForceDescendIntoIfVisible(false);
+
+ // This condition must match the condition in
+ // nsLayoutUtils::GetParentOrPlaceholderFor which is used by
+ // nsLayoutUtils::GetDisplayListParent
+ if (f->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW) && !f->GetPrevInFlow()) {
+ nsIFrame* parent = f->GetParent();
+ if (parent && parent->ForceDescendIntoIfVisible()) {
+ // If the GetDisplayListParent call is going to walk to a placeholder,
+ // in rare cases the placeholder might be contained in a different
+ // continuation from the oof. So we have to make sure to mark the oofs
+ // parent. In the common case this doesn't make us do any extra work,
+ // just changes the order in which we visit the frames since walking
+ // through placeholders will walk through the parent, and we stop when
+ // we find a ForceDescendIntoIfVisible bit set.
+ UnmarkFrameForDisplayIfVisible(f);
+ }
+ }
+ }
+}
+
+nsDisplayListBuilder::~nsDisplayListBuilder() {
+ NS_ASSERTION(mFramesMarkedForDisplay.Length() == 0,
+ "All frames should have been unmarked");
+ NS_ASSERTION(mFramesWithOOFData.Length() == 0,
+ "All OOF data should have been removed");
+ NS_ASSERTION(mPresShellStates.Length() == 0,
+ "All presshells should have been exited");
+
+ DisplayItemClipChain* c = mFirstClipChainToDestroy;
+ while (c) {
+ DisplayItemClipChain* next = c->mNextClipChainToDestroy;
+ c->DisplayItemClipChain::~DisplayItemClipChain();
+ c = next;
+ }
+
+ MOZ_COUNT_DTOR(nsDisplayListBuilder);
+}
+
+uint32_t nsDisplayListBuilder::GetBackgroundPaintFlags() {
+ uint32_t flags = 0;
+ if (mSyncDecodeImages) {
+ flags |= nsCSSRendering::PAINTBG_SYNC_DECODE_IMAGES;
+ }
+ if (mIsPaintingToWindow) {
+ flags |= nsCSSRendering::PAINTBG_TO_WINDOW;
+ }
+ if (mUseHighQualityScaling) {
+ flags |= nsCSSRendering::PAINTBG_HIGH_QUALITY_SCALING;
+ }
+ return flags;
+}
+
+// TODO(emilio): Maybe unify BackgroundPaintFlags and IamgeRendererFlags.
+uint32_t nsDisplayListBuilder::GetImageRendererFlags() const {
+ uint32_t flags = 0;
+ if (mSyncDecodeImages) {
+ flags |= nsImageRenderer::FLAG_SYNC_DECODE_IMAGES;
+ }
+ if (mIsPaintingToWindow) {
+ flags |= nsImageRenderer::FLAG_PAINTING_TO_WINDOW;
+ }
+ if (mUseHighQualityScaling) {
+ flags |= nsImageRenderer::FLAG_HIGH_QUALITY_SCALING;
+ }
+ return flags;
+}
+
+uint32_t nsDisplayListBuilder::GetImageDecodeFlags() const {
+ uint32_t flags = imgIContainer::FLAG_ASYNC_NOTIFY;
+ if (mSyncDecodeImages) {
+ flags |= imgIContainer::FLAG_SYNC_DECODE;
+ } else {
+ flags |= imgIContainer::FLAG_SYNC_DECODE_IF_FAST;
+ }
+ if (mIsPaintingToWindow || mUseHighQualityScaling) {
+ flags |= imgIContainer::FLAG_HIGH_QUALITY_SCALING;
+ }
+ return flags;
+}
+
+void nsDisplayListBuilder::SubtractFromVisibleRegion(nsRegion* aVisibleRegion,
+ const nsRegion& aRegion) {
+ if (aRegion.IsEmpty()) {
+ return;
+ }
+
+ nsRegion tmp;
+ tmp.Sub(*aVisibleRegion, aRegion);
+ // Don't let *aVisibleRegion get too complex, but don't let it fluff out
+ // to its bounds either, which can be very bad (see bug 516740).
+ // Do let aVisibleRegion get more complex if by doing so we reduce its
+ // area by at least half.
+ if (tmp.GetNumRects() <= 15 || tmp.Area() <= aVisibleRegion->Area() / 2) {
+ *aVisibleRegion = tmp;
+ }
+}
+
+nsCaret* nsDisplayListBuilder::GetCaret() {
+ RefPtr<nsCaret> caret = CurrentPresShellState()->mPresShell->GetCaret();
+ return caret;
+}
+
+void nsDisplayListBuilder::IncrementPresShellPaintCount(PresShell* aPresShell) {
+ if (mIsPaintingToWindow) {
+ aPresShell->IncrementPaintCount();
+ }
+}
+
+void nsDisplayListBuilder::EnterPresShell(const nsIFrame* aReferenceFrame,
+ bool aPointerEventsNoneDoc) {
+ PresShellState* state = mPresShellStates.AppendElement();
+ state->mPresShell = aReferenceFrame->PresShell();
+ state->mFirstFrameMarkedForDisplay = mFramesMarkedForDisplay.Length();
+ state->mFirstFrameWithOOFData = mFramesWithOOFData.Length();
+
+ nsIScrollableFrame* sf = state->mPresShell->GetRootScrollFrameAsScrollable();
+ if (sf && IsInSubdocument()) {
+ // We are forcing a rebuild of nsDisplayCanvasBackgroundColor to make sure
+ // that the canvas background color will be set correctly, and that only one
+ // unscrollable item will be created.
+ // This is done to avoid, for example, a case where only scrollbar frames
+ // are invalidated - we would skip creating nsDisplayCanvasBackgroundColor
+ // and possibly end up with an extra nsDisplaySolidColor item.
+ // We skip this for the root document, since we don't want to use
+ // MarkFrameForDisplayIfVisible before ComputeRebuildRegion. We'll
+ // do it manually there.
+ nsCanvasFrame* canvasFrame = do_QueryFrame(sf->GetScrolledFrame());
+ if (canvasFrame) {
+ MarkFrameForDisplayIfVisible(canvasFrame, aReferenceFrame);
+ }
+ }
+
+#ifdef DEBUG
+ state->mAutoLayoutPhase.emplace(aReferenceFrame->PresContext(),
+ nsLayoutPhase::DisplayListBuilding);
+#endif
+
+ state->mPresShell->UpdateCanvasBackground();
+
+ bool buildCaret = mBuildCaret;
+ if (mIgnoreSuppression || !state->mPresShell->IsPaintingSuppressed()) {
+ state->mIsBackgroundOnly = false;
+ } else {
+ state->mIsBackgroundOnly = true;
+ buildCaret = false;
+ }
+
+ bool pointerEventsNone = aPointerEventsNoneDoc;
+ if (IsInSubdocument()) {
+ pointerEventsNone |= mPresShellStates[mPresShellStates.Length() - 2]
+ .mInsidePointerEventsNoneDoc;
+ }
+ state->mInsidePointerEventsNoneDoc = pointerEventsNone;
+
+ state->mPresShellIgnoreScrollFrame =
+ state->mPresShell->IgnoringViewportScrolling()
+ ? state->mPresShell->GetRootScrollFrame()
+ : nullptr;
+
+ nsPresContext* pc = aReferenceFrame->PresContext();
+ mIsInChromePresContext = pc->IsChrome();
+ nsIDocShell* docShell = pc->GetDocShell();
+
+ if (docShell) {
+ docShell->GetWindowDraggingAllowed(&mWindowDraggingAllowed);
+ }
+
+ state->mTouchEventPrefEnabledDoc = dom::TouchEvent::PrefEnabled(docShell);
+
+ if (!buildCaret) {
+ return;
+ }
+
+ // Caret frames add visual area to their frame, but we don't update the
+ // overflow area. Use flags to make sure we build display items for that frame
+ // instead.
+ if (mCaretFrame && mCaretFrame->PresShell() == state->mPresShell) {
+ MarkFrameForDisplay(mCaretFrame, aReferenceFrame);
+ }
+}
+
+// A non-blank paint is a paint that does not just contain the canvas
+// background.
+static bool DisplayListIsNonBlank(nsDisplayList* aList) {
+ for (nsDisplayItem* i : *aList) {
+ switch (i->GetType()) {
+ case DisplayItemType::TYPE_COMPOSITOR_HITTEST_INFO:
+ case DisplayItemType::TYPE_CANVAS_BACKGROUND_COLOR:
+ case DisplayItemType::TYPE_CANVAS_BACKGROUND_IMAGE:
+ continue;
+ case DisplayItemType::TYPE_SOLID_COLOR:
+ case DisplayItemType::TYPE_BACKGROUND:
+ case DisplayItemType::TYPE_BACKGROUND_COLOR:
+ if (i->Frame()->IsCanvasFrame()) {
+ continue;
+ }
+ return true;
+ default:
+ return true;
+ }
+ }
+ return false;
+}
+
+// A contentful paint is a paint that does contains DOM content (text,
+// images, non-blank canvases, SVG): "First Contentful Paint entry
+// contains a DOMHighResTimeStamp reporting the time when the browser
+// first rendered any text, image (including background images),
+// non-white canvas or SVG. This excludes any content of iframes, but
+// includes text with pending webfonts. This is the first time users
+// could start consuming page content."
+static bool DisplayListIsContentful(nsDisplayListBuilder* aBuilder,
+ nsDisplayList* aList) {
+ for (nsDisplayItem* i : *aList) {
+ DisplayItemType type = i->GetType();
+ nsDisplayList* children = i->GetChildren();
+
+ switch (type) {
+ case DisplayItemType::TYPE_SUBDOCUMENT: // iframes are ignored
+ break;
+ // CANVASes check if they may have been modified (as a stand-in
+ // actually tracking all modifications)
+ default:
+ if (i->IsContentful()) {
+ bool dummy;
+ nsRect bound = i->GetBounds(aBuilder, &dummy);
+ if (!bound.IsEmpty()) {
+ return true;
+ }
+ }
+ if (children) {
+ if (DisplayListIsContentful(aBuilder, children)) {
+ return true;
+ }
+ }
+ break;
+ }
+ }
+ return false;
+}
+
+void nsDisplayListBuilder::LeavePresShell(const nsIFrame* aReferenceFrame,
+ nsDisplayList* aPaintedContents) {
+ NS_ASSERTION(
+ CurrentPresShellState()->mPresShell == aReferenceFrame->PresShell(),
+ "Presshell mismatch");
+
+ if (mIsPaintingToWindow && aPaintedContents) {
+ nsPresContext* pc = aReferenceFrame->PresContext();
+ if (!pc->HadNonBlankPaint()) {
+ if (!CurrentPresShellState()->mIsBackgroundOnly &&
+ DisplayListIsNonBlank(aPaintedContents)) {
+ pc->NotifyNonBlankPaint();
+ }
+ }
+ nsRootPresContext* rootPresContext = pc->GetRootPresContext();
+ if (!pc->HadFirstContentfulPaint() && rootPresContext) {
+ if (!CurrentPresShellState()->mIsBackgroundOnly) {
+ if (pc->HasEverBuiltInvisibleText() ||
+ DisplayListIsContentful(this, aPaintedContents)) {
+ pc->NotifyContentfulPaint();
+ }
+ }
+ }
+ }
+
+ ResetMarkedFramesForDisplayList(aReferenceFrame);
+ mPresShellStates.RemoveLastElement();
+
+ if (!mPresShellStates.IsEmpty()) {
+ nsPresContext* pc = CurrentPresContext();
+ nsIDocShell* docShell = pc->GetDocShell();
+ if (docShell) {
+ docShell->GetWindowDraggingAllowed(&mWindowDraggingAllowed);
+ }
+ mIsInChromePresContext = pc->IsChrome();
+ } else {
+ for (uint32_t i = 0; i < mFramesMarkedForDisplayIfVisible.Length(); ++i) {
+ UnmarkFrameForDisplayIfVisible(mFramesMarkedForDisplayIfVisible[i]);
+ }
+ mFramesMarkedForDisplayIfVisible.SetLength(0);
+ }
+}
+
+void nsDisplayListBuilder::FreeClipChains() {
+ // Iterate the clip chains from newest to oldest (forward
+ // iteration), so that we destroy descendants first which
+ // will drop the ref count on their ancestors.
+ DisplayItemClipChain** indirect = &mFirstClipChainToDestroy;
+
+ while (*indirect) {
+ if (!(*indirect)->mRefCount) {
+ DisplayItemClipChain* next = (*indirect)->mNextClipChainToDestroy;
+
+ mClipDeduplicator.erase(*indirect);
+ (*indirect)->DisplayItemClipChain::~DisplayItemClipChain();
+ Destroy(DisplayListArenaObjectId::CLIPCHAIN, *indirect);
+
+ *indirect = next;
+ } else {
+ indirect = &(*indirect)->mNextClipChainToDestroy;
+ }
+ }
+}
+
+void nsDisplayListBuilder::FreeTemporaryItems() {
+ for (nsDisplayItem* i : mTemporaryItems) {
+ // Temporary display items are not added to the frames.
+ MOZ_ASSERT(i->Frame());
+ i->RemoveFrame(i->Frame());
+ i->Destroy(this);
+ }
+
+ mTemporaryItems.Clear();
+}
+
+void nsDisplayListBuilder::ResetMarkedFramesForDisplayList(
+ const nsIFrame* aReferenceFrame) {
+ // Unmark and pop off the frames marked for display in this pres shell.
+ uint32_t firstFrameForShell =
+ CurrentPresShellState()->mFirstFrameMarkedForDisplay;
+ for (uint32_t i = firstFrameForShell; i < mFramesMarkedForDisplay.Length();
+ ++i) {
+ UnmarkFrameForDisplay(mFramesMarkedForDisplay[i], aReferenceFrame);
+ }
+ mFramesMarkedForDisplay.SetLength(firstFrameForShell);
+
+ firstFrameForShell = CurrentPresShellState()->mFirstFrameWithOOFData;
+ for (uint32_t i = firstFrameForShell; i < mFramesWithOOFData.Length(); ++i) {
+ mFramesWithOOFData[i]->RemoveProperty(OutOfFlowDisplayDataProperty());
+ }
+ mFramesWithOOFData.SetLength(firstFrameForShell);
+}
+
+void nsDisplayListBuilder::ClearFixedBackgroundDisplayData() {
+ CurrentPresShellState()->mFixedBackgroundDisplayData = Nothing();
+}
+
+void nsDisplayListBuilder::MarkFramesForDisplayList(
+ nsIFrame* aDirtyFrame, const nsFrameList& aFrames) {
+ nsRect visibleRect = GetVisibleRect();
+ nsRect dirtyRect = GetDirtyRect();
+
+ // If we are entering content that is fixed to the RCD-RSF, we are
+ // crossing the async zoom container boundary, and need to convert from
+ // visual to layout coordinates.
+ if (ViewportFrame* viewportFrame = do_QueryFrame(aDirtyFrame)) {
+ if (IsForEventDelivery() && ShouldBuildAsyncZoomContainer() &&
+ viewportFrame->PresContext()->IsRootContentDocumentCrossProcess()) {
+ if (viewportFrame->PresShell()->GetRootScrollFrame()) {
+#ifdef DEBUG
+ for (nsIFrame* f : aFrames) {
+ MOZ_ASSERT(ViewportUtils::IsZoomedContentRoot(f));
+ }
+#endif
+ visibleRect = ViewportUtils::VisualToLayout(visibleRect,
+ viewportFrame->PresShell());
+ dirtyRect = ViewportUtils::VisualToLayout(dirtyRect,
+ viewportFrame->PresShell());
+ }
+#ifdef DEBUG
+ else {
+ // This is an edge case that should only happen if we are in a
+ // document with a XUL root element so that it does not have a root
+ // scroll frame but it has fixed pos content and all of the frames in
+ // aFrames are that fixed pos content.
+ for (nsIFrame* f : aFrames) {
+ MOZ_ASSERT(!ViewportUtils::IsZoomedContentRoot(f) &&
+ f->GetParent() == aDirtyFrame &&
+ f->StyleDisplay()->mPosition ==
+ StylePositionProperty::Fixed);
+ }
+ // There's no root scroll frame so there can't be any zooming or async
+ // panning so we don't need to adjust the visible and dirty rects.
+ }
+#endif
+ }
+ }
+
+ bool markedFrames = false;
+ for (nsIFrame* e : aFrames) {
+ // Skip the AccessibleCaret frame when building no caret.
+ if (!IsBuildingCaret()) {
+ nsIContent* content = e->GetContent();
+ if (content && content->IsInNativeAnonymousSubtree() &&
+ content->IsElement()) {
+ auto* classList = content->AsElement()->ClassList();
+ if (classList->Contains(u"moz-accessiblecaret"_ns)) {
+ continue;
+ }
+ }
+ }
+ if (MarkOutOfFlowFrameForDisplay(aDirtyFrame, e, visibleRect, dirtyRect)) {
+ markedFrames = true;
+ }
+ }
+
+ if (markedFrames) {
+ // mClipState.GetClipChainForContainingBlockDescendants can return pointers
+ // to objects on the stack, so we need to clone the chain.
+ const DisplayItemClipChain* clipChain =
+ CopyWholeChain(mClipState.GetClipChainForContainingBlockDescendants());
+ const DisplayItemClipChain* combinedClipChain =
+ mClipState.GetCurrentCombinedClipChain(this);
+ const ActiveScrolledRoot* asr = mCurrentActiveScrolledRoot;
+
+ OutOfFlowDisplayData* data = new OutOfFlowDisplayData(
+ clipChain, combinedClipChain, asr, this->mCurrentScrollParentId,
+ visibleRect, dirtyRect);
+ aDirtyFrame->SetProperty(
+ nsDisplayListBuilder::OutOfFlowDisplayDataProperty(), data);
+ mFramesWithOOFData.AppendElement(aDirtyFrame);
+ }
+
+ if (!aDirtyFrame->GetParent()) {
+ // This is the viewport frame of aDirtyFrame's presshell.
+ // Store the current display data so that it can be used for fixed
+ // background images.
+ NS_ASSERTION(
+ CurrentPresShellState()->mPresShell == aDirtyFrame->PresShell(),
+ "Presshell mismatch");
+ MOZ_ASSERT(!CurrentPresShellState()->mFixedBackgroundDisplayData,
+ "already traversed this presshell's root frame?");
+
+ const DisplayItemClipChain* clipChain =
+ CopyWholeChain(mClipState.GetClipChainForContainingBlockDescendants());
+ const DisplayItemClipChain* combinedClipChain =
+ mClipState.GetCurrentCombinedClipChain(this);
+ const ActiveScrolledRoot* asr = mCurrentActiveScrolledRoot;
+ CurrentPresShellState()->mFixedBackgroundDisplayData.emplace(
+ clipChain, combinedClipChain, asr, this->mCurrentScrollParentId,
+ GetVisibleRect(), GetDirtyRect());
+ }
+}
+
+/**
+ * Mark all preserve-3d children with
+ * NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO to make sure
+ * nsIFrame::BuildDisplayListForChild() would visit them. Also compute
+ * dirty rect for preserve-3d children.
+ *
+ * @param aDirtyFrame is the frame to mark children extending context.
+ */
+void nsDisplayListBuilder::MarkPreserve3DFramesForDisplayList(
+ nsIFrame* aDirtyFrame) {
+ for (const auto& childList : aDirtyFrame->ChildLists()) {
+ for (nsIFrame* child : childList.mList) {
+ if (child->Combines3DTransformWithAncestors()) {
+ MarkFrameForDisplay(child, aDirtyFrame);
+ }
+
+ if (child->IsBlockWrapper()) {
+ // Mark preserve-3d frames inside the block wrapper.
+ MarkPreserve3DFramesForDisplayList(child);
+ }
+ }
+ }
+}
+
+ActiveScrolledRoot* nsDisplayListBuilder::AllocateActiveScrolledRoot(
+ const ActiveScrolledRoot* aParent, nsIScrollableFrame* aScrollableFrame) {
+ RefPtr<ActiveScrolledRoot> asr = ActiveScrolledRoot::CreateASRForFrame(
+ aParent, aScrollableFrame, IsRetainingDisplayList());
+ mActiveScrolledRoots.AppendElement(asr);
+ return asr;
+}
+
+const DisplayItemClipChain* nsDisplayListBuilder::AllocateDisplayItemClipChain(
+ const DisplayItemClip& aClip, const ActiveScrolledRoot* aASR,
+ const DisplayItemClipChain* aParent) {
+ MOZ_ASSERT(!(aParent && aParent->mOnStack));
+ void* p = Allocate(sizeof(DisplayItemClipChain),
+ DisplayListArenaObjectId::CLIPCHAIN);
+ DisplayItemClipChain* c = new (KnownNotNull, p)
+ DisplayItemClipChain(aClip, aASR, aParent, mFirstClipChainToDestroy);
+#ifdef DEBUG
+ c->mOnStack = false;
+#endif
+ auto result = mClipDeduplicator.insert(c);
+ if (!result.second) {
+ // An equivalent clip chain item was already created, so let's return that
+ // instead. Destroy the one we just created.
+ // Note that this can cause clip chains from different coordinate systems to
+ // collapse into the same clip chain object, because clip chains do not keep
+ // track of the reference frame that they were created in.
+ c->DisplayItemClipChain::~DisplayItemClipChain();
+ Destroy(DisplayListArenaObjectId::CLIPCHAIN, c);
+ return *(result.first);
+ }
+ mFirstClipChainToDestroy = c;
+ return c;
+}
+
+struct ClipChainItem {
+ DisplayItemClip clip;
+ const ActiveScrolledRoot* asr;
+};
+
+static const DisplayItemClipChain* FindCommonAncestorClipForIntersection(
+ const DisplayItemClipChain* aOne, const DisplayItemClipChain* aTwo) {
+ for (const ActiveScrolledRoot* asr =
+ ActiveScrolledRoot::PickDescendant(aOne->mASR, aTwo->mASR);
+ asr; asr = asr->mParent) {
+ if (aOne == aTwo) {
+ return aOne;
+ }
+ if (aOne->mASR == asr) {
+ aOne = aOne->mParent;
+ }
+ if (aTwo->mASR == asr) {
+ aTwo = aTwo->mParent;
+ }
+ if (!aOne) {
+ return aTwo;
+ }
+ if (!aTwo) {
+ return aOne;
+ }
+ }
+ return nullptr;
+}
+
+const DisplayItemClipChain* nsDisplayListBuilder::CreateClipChainIntersection(
+ const DisplayItemClipChain* aAncestor,
+ const DisplayItemClipChain* aLeafClip1,
+ const DisplayItemClipChain* aLeafClip2) {
+ AutoTArray<ClipChainItem, 8> intersectedClips;
+
+ const DisplayItemClipChain* clip1 = aLeafClip1;
+ const DisplayItemClipChain* clip2 = aLeafClip2;
+
+ const ActiveScrolledRoot* asr = ActiveScrolledRoot::PickDescendant(
+ clip1 ? clip1->mASR : nullptr, clip2 ? clip2->mASR : nullptr);
+
+ // Build up the intersection from the leaf to the root and put it into
+ // intersectedClips. The loop below will convert intersectedClips into an
+ // actual DisplayItemClipChain.
+ // (We need to do this in two passes because we need the parent clip in order
+ // to create the DisplayItemClipChain object, but the parent clip has not
+ // been created at that point.)
+ while (!aAncestor || asr != aAncestor->mASR) {
+ if (clip1 && clip1->mASR == asr) {
+ if (clip2 && clip2->mASR == asr) {
+ DisplayItemClip intersection = clip1->mClip;
+ intersection.IntersectWith(clip2->mClip);
+ intersectedClips.AppendElement(ClipChainItem{intersection, asr});
+ clip2 = clip2->mParent;
+ } else {
+ intersectedClips.AppendElement(ClipChainItem{clip1->mClip, asr});
+ }
+ clip1 = clip1->mParent;
+ } else if (clip2 && clip2->mASR == asr) {
+ intersectedClips.AppendElement(ClipChainItem{clip2->mClip, asr});
+ clip2 = clip2->mParent;
+ }
+ if (!asr) {
+ MOZ_ASSERT(!aAncestor, "We should have exited this loop earlier");
+ break;
+ }
+ asr = asr->mParent;
+ }
+
+ // Convert intersectedClips into a DisplayItemClipChain.
+ const DisplayItemClipChain* parentSC = aAncestor;
+ for (auto& sc : Reversed(intersectedClips)) {
+ parentSC = AllocateDisplayItemClipChain(sc.clip, sc.asr, parentSC);
+ }
+ return parentSC;
+}
+
+const DisplayItemClipChain* nsDisplayListBuilder::CreateClipChainIntersection(
+ const DisplayItemClipChain* aLeafClip1,
+ const DisplayItemClipChain* aLeafClip2) {
+ // aLeafClip2 might be a reference to a clip on the stack. We need to make
+ // sure that CreateClipChainIntersection will allocate the actual intersected
+ // clip in the builder's arena, so for the aLeafClip1 == nullptr case,
+ // we supply nullptr as the common ancestor so that
+ // CreateClipChainIntersection clones the whole chain.
+ const DisplayItemClipChain* ancestorClip =
+ aLeafClip1 ? FindCommonAncestorClipForIntersection(aLeafClip1, aLeafClip2)
+ : nullptr;
+
+ return CreateClipChainIntersection(ancestorClip, aLeafClip1, aLeafClip2);
+}
+
+const DisplayItemClipChain* nsDisplayListBuilder::CopyWholeChain(
+ const DisplayItemClipChain* aClipChain) {
+ return CreateClipChainIntersection(nullptr, aClipChain, nullptr);
+}
+
+const nsIFrame* nsDisplayListBuilder::FindReferenceFrameFor(
+ const nsIFrame* aFrame, nsPoint* aOffset) const {
+ auto MaybeApplyAdditionalOffset = [&]() {
+ if (auto offset = AdditionalOffset()) {
+ *aOffset += *offset;
+ }
+ };
+
+ if (aFrame == mCurrentFrame) {
+ if (aOffset) {
+ *aOffset = mCurrentOffsetToReferenceFrame;
+ }
+ return mCurrentReferenceFrame;
+ }
+
+ for (const nsIFrame* f = aFrame; f;
+ f = nsLayoutUtils::GetCrossDocParentFrameInProcess(f)) {
+ if (f == mReferenceFrame || f->IsTransformed()) {
+ if (aOffset) {
+ *aOffset = aFrame->GetOffsetToCrossDoc(f);
+ MaybeApplyAdditionalOffset();
+ }
+ return f;
+ }
+ }
+
+ if (aOffset) {
+ *aOffset = aFrame->GetOffsetToCrossDoc(mReferenceFrame);
+ MaybeApplyAdditionalOffset();
+ }
+
+ return mReferenceFrame;
+}
+
+// Sticky frames are active if their nearest scrollable frame is also active.
+static bool IsStickyFrameActive(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame) {
+ MOZ_ASSERT(aFrame->StyleDisplay()->mPosition ==
+ StylePositionProperty::Sticky);
+
+ StickyScrollContainer* stickyScrollContainer =
+ StickyScrollContainer::GetStickyScrollContainerForFrame(aFrame);
+ return stickyScrollContainer &&
+ stickyScrollContainer->ScrollFrame()->IsMaybeAsynchronouslyScrolled();
+}
+
+bool nsDisplayListBuilder::IsAnimatedGeometryRoot(nsIFrame* aFrame,
+ nsIFrame** aParent) {
+ if (aFrame == mReferenceFrame) {
+ return true;
+ }
+
+ if (!IsPaintingToWindow()) {
+ if (aParent) {
+ *aParent = nsLayoutUtils::GetCrossDocParentFrameInProcess(aFrame);
+ }
+ return false;
+ }
+
+ nsIFrame* parent = nsLayoutUtils::GetCrossDocParentFrameInProcess(aFrame);
+ if (!parent) {
+ return true;
+ }
+ *aParent = parent;
+
+ if (aFrame->StyleDisplay()->mPosition == StylePositionProperty::Sticky &&
+ IsStickyFrameActive(this, aFrame)) {
+ return true;
+ }
+
+ if (aFrame->IsTransformed()) {
+ if (EffectCompositor::HasAnimationsForCompositor(
+ aFrame, DisplayItemType::TYPE_TRANSFORM)) {
+ return true;
+ }
+ }
+
+ LayoutFrameType parentType = parent->Type();
+ if (parentType == LayoutFrameType::Scroll ||
+ parentType == LayoutFrameType::ListControl) {
+ nsIScrollableFrame* sf = do_QueryFrame(parent);
+ if (sf->GetScrolledFrame() == aFrame) {
+ MOZ_ASSERT(!aFrame->IsTransformed());
+ return sf->IsMaybeAsynchronouslyScrolled();
+ }
+ }
+
+ return false;
+}
+
+nsIFrame* nsDisplayListBuilder::FindAnimatedGeometryRootFrameFor(
+ nsIFrame* aFrame) {
+ MOZ_ASSERT(nsLayoutUtils::IsAncestorFrameCrossDocInProcess(
+ RootReferenceFrame(), aFrame));
+ nsIFrame* cursor = aFrame;
+ while (cursor != RootReferenceFrame()) {
+ nsIFrame* next;
+ if (IsAnimatedGeometryRoot(cursor, &next)) {
+ return cursor;
+ }
+ cursor = next;
+ }
+ return cursor;
+}
+
+static nsRect ApplyAllClipNonRoundedIntersection(
+ const DisplayItemClipChain* aClipChain, const nsRect& aRect) {
+ nsRect result = aRect;
+ while (aClipChain) {
+ result = aClipChain->mClip.ApplyNonRoundedIntersection(result);
+ aClipChain = aClipChain->mParent;
+ }
+ return result;
+}
+
+void nsDisplayListBuilder::AdjustWindowDraggingRegion(nsIFrame* aFrame) {
+ if (!mWindowDraggingAllowed || !IsForPainting()) {
+ return;
+ }
+
+ const nsStyleUIReset* styleUI = aFrame->StyleUIReset();
+ if (styleUI->mWindowDragging == StyleWindowDragging::Default) {
+ // This frame has the default value and doesn't influence the window
+ // dragging region.
+ return;
+ }
+
+ LayoutDeviceToLayoutDeviceMatrix4x4 referenceFrameToRootReferenceFrame;
+
+ // The const_cast is for nsLayoutUtils::GetTransformToAncestor.
+ nsIFrame* referenceFrame =
+ const_cast<nsIFrame*>(FindReferenceFrameFor(aFrame));
+
+ if (IsInTransform()) {
+ // Only support 2d rectilinear transforms. Transform support is needed for
+ // the horizontal flip transform that's applied to the urlbar textbox in
+ // RTL mode - it should be able to exclude itself from the draggable region.
+ referenceFrameToRootReferenceFrame =
+ ViewAs<LayoutDeviceToLayoutDeviceMatrix4x4>(
+ nsLayoutUtils::GetTransformToAncestor(RelativeTo{referenceFrame},
+ RelativeTo{mReferenceFrame})
+ .GetMatrix());
+ Matrix referenceFrameToRootReferenceFrame2d;
+ if (!referenceFrameToRootReferenceFrame.Is2D(
+ &referenceFrameToRootReferenceFrame2d) ||
+ !referenceFrameToRootReferenceFrame2d.IsRectilinear()) {
+ return;
+ }
+ } else {
+ MOZ_ASSERT(referenceFrame == mReferenceFrame,
+ "referenceFrameToRootReferenceFrame needs to be adjusted");
+ }
+
+ // We do some basic visibility checking on the frame's border box here.
+ // We intersect it both with the current dirty rect and with the current
+ // clip. Either one is just a conservative approximation on its own, but
+ // their intersection luckily works well enough for our purposes, so that
+ // we don't have to do full-blown visibility computations.
+ // The most important case we need to handle is the scrolled-off tab:
+ // If the tab bar overflows, tab parts that are clipped by the scrollbox
+ // should not be allowed to interfere with the window dragging region. Using
+ // just the current DisplayItemClip is not enough to cover this case
+ // completely because clips are reset while building stacking context
+ // contents, so for example we'd fail to clip frames that have a clip path
+ // applied to them. But the current dirty rect doesn't get reset in that
+ // case, so we use it to make this case work.
+ nsRect borderBox = aFrame->GetRectRelativeToSelf().Intersect(mVisibleRect);
+ borderBox += ToReferenceFrame(aFrame);
+ const DisplayItemClipChain* clip =
+ ClipState().GetCurrentCombinedClipChain(this);
+ borderBox = ApplyAllClipNonRoundedIntersection(clip, borderBox);
+ if (borderBox.IsEmpty()) {
+ return;
+ }
+
+ LayoutDeviceRect devPixelBorderBox = LayoutDevicePixel::FromAppUnits(
+ borderBox, aFrame->PresContext()->AppUnitsPerDevPixel());
+
+ LayoutDeviceRect transformedDevPixelBorderBox =
+ TransformBy(referenceFrameToRootReferenceFrame, devPixelBorderBox);
+ transformedDevPixelBorderBox.Round();
+ LayoutDeviceIntRect transformedDevPixelBorderBoxInt;
+
+ if (!transformedDevPixelBorderBox.ToIntRect(
+ &transformedDevPixelBorderBoxInt)) {
+ return;
+ }
+
+ LayoutDeviceIntRegion& region =
+ styleUI->mWindowDragging == StyleWindowDragging::Drag
+ ? mWindowDraggingRegion
+ : mWindowNoDraggingRegion;
+
+ if (!IsRetainingDisplayList()) {
+ region.OrWith(transformedDevPixelBorderBoxInt);
+ return;
+ }
+
+ gfx::IntRect rect(transformedDevPixelBorderBoxInt.ToUnknownRect());
+ if (styleUI->mWindowDragging == StyleWindowDragging::Drag) {
+ mRetainedWindowDraggingRegion.Add(aFrame, rect);
+ } else {
+ mRetainedWindowNoDraggingRegion.Add(aFrame, rect);
+ }
+}
+
+LayoutDeviceIntRegion nsDisplayListBuilder::GetWindowDraggingRegion() const {
+ LayoutDeviceIntRegion result;
+ if (!IsRetainingDisplayList()) {
+ result.Sub(mWindowDraggingRegion, mWindowNoDraggingRegion);
+ return result;
+ }
+
+ LayoutDeviceIntRegion dragRegion =
+ mRetainedWindowDraggingRegion.ToLayoutDeviceIntRegion();
+
+ LayoutDeviceIntRegion noDragRegion =
+ mRetainedWindowNoDraggingRegion.ToLayoutDeviceIntRegion();
+
+ result.Sub(dragRegion, noDragRegion);
+ return result;
+}
+
+void nsDisplayTransform::AddSizeOfExcludingThis(nsWindowSizes& aSizes) const {
+ nsPaintedDisplayItem::AddSizeOfExcludingThis(aSizes);
+ aSizes.mLayoutRetainedDisplayListSize +=
+ aSizes.mState.mMallocSizeOf(mTransformPreserves3D.get());
+}
+
+void nsDisplayListBuilder::AddSizeOfExcludingThis(nsWindowSizes& aSizes) const {
+ mPool.AddSizeOfExcludingThis(aSizes, Arena::ArenaKind::DisplayList);
+
+ size_t n = 0;
+ MallocSizeOf mallocSizeOf = aSizes.mState.mMallocSizeOf;
+ n += mDocumentWillChangeBudgets.ShallowSizeOfExcludingThis(mallocSizeOf);
+ n += mFrameWillChangeBudgets.ShallowSizeOfExcludingThis(mallocSizeOf);
+ n += mEffectsUpdates.ShallowSizeOfExcludingThis(mallocSizeOf);
+ n += mWindowExcludeGlassRegion.SizeOfExcludingThis(mallocSizeOf);
+ n += mRetainedWindowDraggingRegion.SizeOfExcludingThis(mallocSizeOf);
+ n += mRetainedWindowNoDraggingRegion.SizeOfExcludingThis(mallocSizeOf);
+ n += mRetainedWindowOpaqueRegion.SizeOfExcludingThis(mallocSizeOf);
+ // XXX can't measure mClipDeduplicator since it uses std::unordered_set.
+
+ aSizes.mLayoutRetainedDisplayListSize += n;
+}
+
+void RetainedDisplayList::AddSizeOfExcludingThis(nsWindowSizes& aSizes) const {
+ for (nsDisplayItem* item : *this) {
+ item->AddSizeOfExcludingThis(aSizes);
+ if (RetainedDisplayList* children = item->GetChildren()) {
+ children->AddSizeOfExcludingThis(aSizes);
+ }
+ }
+
+ size_t n = 0;
+
+ n += mDAG.mDirectPredecessorList.ShallowSizeOfExcludingThis(
+ aSizes.mState.mMallocSizeOf);
+ n += mDAG.mNodesInfo.ShallowSizeOfExcludingThis(aSizes.mState.mMallocSizeOf);
+ n += mOldItems.ShallowSizeOfExcludingThis(aSizes.mState.mMallocSizeOf);
+
+ aSizes.mLayoutRetainedDisplayListSize += n;
+}
+
+size_t nsDisplayListBuilder::WeakFrameRegion::SizeOfExcludingThis(
+ MallocSizeOf aMallocSizeOf) const {
+ size_t n = 0;
+ n += mFrames.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ for (const auto& frame : mFrames) {
+ const UniquePtr<WeakFrame>& weakFrame = frame.mWeakFrame;
+ n += aMallocSizeOf(weakFrame.get());
+ }
+ n += mRects.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ return n;
+}
+
+/**
+ * Removes modified frames and rects from this WeakFrameRegion.
+ */
+void nsDisplayListBuilder::WeakFrameRegion::RemoveModifiedFramesAndRects() {
+ MOZ_ASSERT(mFrames.Length() == mRects.Length());
+
+ uint32_t i = 0;
+ uint32_t length = mFrames.Length();
+
+ while (i < length) {
+ auto& wrapper = mFrames[i];
+
+ if (!wrapper.mWeakFrame->IsAlive() ||
+ AnyContentAncestorModified(wrapper.mWeakFrame->GetFrame())) {
+ // To avoid multiple O(n) shifts in the array, move the last element of
+ // the array to the current position and decrease the array length.
+ mFrameSet.Remove(wrapper.mFrame);
+ mFrames[i] = std::move(mFrames[length - 1]);
+ mRects[i] = std::move(mRects[length - 1]);
+ length--;
+ } else {
+ i++;
+ }
+ }
+
+ mFrames.TruncateLength(length);
+ mRects.TruncateLength(length);
+}
+
+void nsDisplayListBuilder::RemoveModifiedWindowRegions() {
+ mRetainedWindowDraggingRegion.RemoveModifiedFramesAndRects();
+ mRetainedWindowNoDraggingRegion.RemoveModifiedFramesAndRects();
+ mWindowExcludeGlassRegion.RemoveModifiedFramesAndRects();
+ mRetainedWindowOpaqueRegion.RemoveModifiedFramesAndRects();
+
+ mHasGlassItemDuringPartial = false;
+}
+
+void nsDisplayListBuilder::ClearRetainedWindowRegions() {
+ mRetainedWindowDraggingRegion.Clear();
+ mRetainedWindowNoDraggingRegion.Clear();
+ mWindowExcludeGlassRegion.Clear();
+ mRetainedWindowOpaqueRegion.Clear();
+
+ mGlassDisplayItem = nullptr;
+}
+
+const uint32_t gWillChangeAreaMultiplier = 3;
+static uint32_t GetLayerizationCost(const nsSize& aSize) {
+ // There's significant overhead for each layer created from Gecko
+ // (IPC+Shared Objects) and from the backend (like an OpenGL texture).
+ // Therefore we set a minimum cost threshold of a 64x64 area.
+ const int minBudgetCost = 64 * 64;
+
+ const uint32_t budgetCost = std::max(
+ minBudgetCost, nsPresContext::AppUnitsToIntCSSPixels(aSize.width) *
+ nsPresContext::AppUnitsToIntCSSPixels(aSize.height));
+
+ return budgetCost;
+}
+
+bool nsDisplayListBuilder::AddToWillChangeBudget(nsIFrame* aFrame,
+ const nsSize& aSize) {
+ MOZ_ASSERT(IsForPainting());
+
+ if (aFrame->MayHaveWillChangeBudget()) {
+ // The frame is already in the will-change budget.
+ return true;
+ }
+
+ const nsPresContext* presContext = aFrame->PresContext();
+ const nsRect area = presContext->GetVisibleArea();
+ const uint32_t budgetLimit =
+ nsPresContext::AppUnitsToIntCSSPixels(area.width) *
+ nsPresContext::AppUnitsToIntCSSPixels(area.height);
+ const uint32_t cost = GetLayerizationCost(aSize);
+
+ DocumentWillChangeBudget& documentBudget =
+ mDocumentWillChangeBudgets.LookupOrInsert(presContext);
+
+ const bool onBudget =
+ (documentBudget + cost) / gWillChangeAreaMultiplier < budgetLimit;
+
+ if (onBudget) {
+ documentBudget += cost;
+ mFrameWillChangeBudgets.InsertOrUpdate(
+ aFrame, FrameWillChangeBudget(presContext, cost));
+ aFrame->SetMayHaveWillChangeBudget(true);
+ }
+
+ return onBudget;
+}
+
+bool nsDisplayListBuilder::IsInWillChangeBudget(nsIFrame* aFrame,
+ const nsSize& aSize) {
+ if (!IsForPainting()) {
+ // If this nsDisplayListBuilder is not for painting, the layerization should
+ // not matter. Do the simple thing and return false.
+ return false;
+ }
+
+ const bool onBudget = AddToWillChangeBudget(aFrame, aSize);
+ if (onBudget) {
+ return true;
+ }
+
+ auto* pc = aFrame->PresContext();
+ auto* doc = pc->Document();
+ if (!doc->HasWarnedAbout(Document::eIgnoringWillChangeOverBudget)) {
+ AutoTArray<nsString, 2> params;
+ params.AppendElement()->AppendInt(gWillChangeAreaMultiplier);
+
+ nsRect area = pc->GetVisibleArea();
+ uint32_t budgetLimit = nsPresContext::AppUnitsToIntCSSPixels(area.width) *
+ nsPresContext::AppUnitsToIntCSSPixels(area.height);
+ params.AppendElement()->AppendInt(budgetLimit);
+
+ doc->WarnOnceAbout(Document::eIgnoringWillChangeOverBudget, false, params);
+ }
+
+ return false;
+}
+
+void nsDisplayListBuilder::ClearWillChangeBudgetStatus(nsIFrame* aFrame) {
+ MOZ_ASSERT(IsForPainting());
+
+ if (!aFrame->MayHaveWillChangeBudget()) {
+ return;
+ }
+
+ aFrame->SetMayHaveWillChangeBudget(false);
+ RemoveFromWillChangeBudgets(aFrame);
+}
+
+void nsDisplayListBuilder::RemoveFromWillChangeBudgets(const nsIFrame* aFrame) {
+ if (auto entry = mFrameWillChangeBudgets.Lookup(aFrame)) {
+ const FrameWillChangeBudget& frameBudget = entry.Data();
+
+ auto documentBudget =
+ mDocumentWillChangeBudgets.Lookup(frameBudget.mPresContext);
+
+ if (documentBudget) {
+ *documentBudget -= frameBudget.mUsage;
+ }
+
+ entry.Remove();
+ }
+}
+
+void nsDisplayListBuilder::ClearWillChangeBudgets() {
+ mFrameWillChangeBudgets.Clear();
+ mDocumentWillChangeBudgets.Clear();
+}
+
+void nsDisplayListBuilder::EnterSVGEffectsContents(
+ nsIFrame* aEffectsFrame, nsDisplayList* aHoistedItemsStorage) {
+ MOZ_ASSERT(aHoistedItemsStorage);
+ if (mSVGEffectsFrames.IsEmpty()) {
+ MOZ_ASSERT(!mScrollInfoItemsForHoisting);
+ mScrollInfoItemsForHoisting = aHoistedItemsStorage;
+ }
+ mSVGEffectsFrames.AppendElement(aEffectsFrame);
+}
+
+void nsDisplayListBuilder::ExitSVGEffectsContents() {
+ MOZ_ASSERT(!mSVGEffectsFrames.IsEmpty());
+ mSVGEffectsFrames.RemoveLastElement();
+ MOZ_ASSERT(mScrollInfoItemsForHoisting);
+ if (mSVGEffectsFrames.IsEmpty()) {
+ mScrollInfoItemsForHoisting = nullptr;
+ }
+}
+
+bool nsDisplayListBuilder::ShouldBuildScrollInfoItemsForHoisting() const {
+ /*
+ * Note: if changing the conditions under which scroll info layers
+ * are created, make a corresponding change to
+ * ScrollFrameWillBuildScrollInfoLayer() in nsSliderFrame.cpp.
+ */
+ for (nsIFrame* frame : mSVGEffectsFrames) {
+ if (SVGIntegrationUtils::UsesSVGEffectsNotSupportedInCompositor(frame)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void nsDisplayListBuilder::AppendNewScrollInfoItemForHoisting(
+ nsDisplayScrollInfoLayer* aScrollInfoItem) {
+ MOZ_ASSERT(ShouldBuildScrollInfoItemsForHoisting());
+ MOZ_ASSERT(mScrollInfoItemsForHoisting);
+ mScrollInfoItemsForHoisting->AppendToTop(aScrollInfoItem);
+}
+
+void nsDisplayListBuilder::BuildCompositorHitTestInfoIfNeeded(
+ nsIFrame* aFrame, nsDisplayList* aList) {
+ MOZ_ASSERT(aFrame);
+ MOZ_ASSERT(aList);
+
+ if (!BuildCompositorHitTestInfo()) {
+ return;
+ }
+
+ const CompositorHitTestInfo info = aFrame->GetCompositorHitTestInfo(this);
+ if (info != CompositorHitTestInvisibleToHit) {
+ aList->AppendNewToTop<nsDisplayCompositorHitTestInfo>(this, aFrame);
+ }
+}
+
+void nsDisplayListBuilder::AddReusableDisplayItem(nsDisplayItem* aItem) {
+ mReuseableItems.Insert(aItem);
+}
+
+void nsDisplayListBuilder::RemoveReusedDisplayItem(nsDisplayItem* aItem) {
+ MOZ_ASSERT(aItem->IsReusedItem());
+ mReuseableItems.Remove(aItem);
+}
+
+void nsDisplayListBuilder::ClearReuseableDisplayItems() {
+ const size_t total = mReuseableItems.Count();
+
+ size_t reused = 0;
+ for (auto* item : mReuseableItems) {
+ if (item->IsReusedItem()) {
+ reused++;
+ item->SetReusable();
+ } else {
+ item->Destroy(this);
+ }
+ }
+
+ DL_LOGI("RDL - Reused %zu of %zu SC display items", reused, total);
+ mReuseableItems.Clear();
+}
+
+void nsDisplayListBuilder::ReuseDisplayItem(nsDisplayItem* aItem) {
+ const auto* previous = mCurrentContainerASR;
+ const auto* asr = aItem->GetActiveScrolledRoot();
+ mCurrentContainerASR =
+ ActiveScrolledRoot::PickAncestor(asr, mCurrentContainerASR);
+
+ if (previous != mCurrentContainerASR) {
+ DL_LOGV("RDL - Changed mCurrentContainerASR from %p to %p", previous,
+ mCurrentContainerASR);
+ }
+
+ aItem->SetReusedItem();
+}
+
+void nsDisplayListSet::CopyTo(const nsDisplayListSet& aDestination) const {
+ for (size_t i = 0; i < mLists.size(); ++i) {
+ auto* from = mLists[i];
+ auto* to = aDestination.mLists[i];
+
+ from->CopyTo(to);
+ }
+}
+
+void nsDisplayListSet::MoveTo(const nsDisplayListSet& aDestination) const {
+ aDestination.BorderBackground()->AppendToTop(BorderBackground());
+ aDestination.BlockBorderBackgrounds()->AppendToTop(BlockBorderBackgrounds());
+ aDestination.Floats()->AppendToTop(Floats());
+ aDestination.Content()->AppendToTop(Content());
+ aDestination.PositionedDescendants()->AppendToTop(PositionedDescendants());
+ aDestination.Outlines()->AppendToTop(Outlines());
+}
+
+nsRect nsDisplayList::GetClippedBounds(nsDisplayListBuilder* aBuilder) const {
+ nsRect bounds;
+ for (nsDisplayItem* i : *this) {
+ bounds.UnionRect(bounds, i->GetClippedBounds(aBuilder));
+ }
+ return bounds;
+}
+
+nsRect nsDisplayList::GetClippedBoundsWithRespectToASR(
+ nsDisplayListBuilder* aBuilder, const ActiveScrolledRoot* aASR,
+ nsRect* aBuildingRect) const {
+ nsRect bounds;
+ for (nsDisplayItem* i : *this) {
+ nsRect r = i->GetClippedBounds(aBuilder);
+ if (aASR != i->GetActiveScrolledRoot() && !r.IsEmpty()) {
+ if (Maybe<nsRect> clip = i->GetClipWithRespectToASR(aBuilder, aASR)) {
+ r = clip.ref();
+ }
+ }
+ if (aBuildingRect) {
+ aBuildingRect->UnionRect(*aBuildingRect, i->GetBuildingRect());
+ }
+ bounds.UnionRect(bounds, r);
+ }
+ return bounds;
+}
+
+nsRect nsDisplayList::GetBuildingRect() const {
+ nsRect result;
+ for (nsDisplayItem* i : *this) {
+ result.UnionRect(result, i->GetBuildingRect());
+ }
+ return result;
+}
+
+static void TriggerPendingAnimations(Document& aDoc,
+ const TimeStamp& aReadyTime) {
+ MOZ_ASSERT(!aReadyTime.IsNull(),
+ "Animation ready time is not set. Perhaps we're using a layer"
+ " manager that doesn't update it");
+ if (PendingAnimationTracker* tracker = aDoc.GetPendingAnimationTracker()) {
+ PresShell* presShell = aDoc.GetPresShell();
+ // If paint-suppression is in effect then we haven't finished painting
+ // this document yet so we shouldn't start animations
+ if (!presShell || !presShell->IsPaintingSuppressed()) {
+ tracker->TriggerPendingAnimationsOnNextTick(aReadyTime);
+ }
+ }
+ auto recurse = [&aReadyTime](Document& aDoc) {
+ TriggerPendingAnimations(aDoc, aReadyTime);
+ return CallState::Continue;
+ };
+ aDoc.EnumerateSubDocuments(recurse);
+}
+
+WindowRenderer* nsDisplayListBuilder::GetWidgetWindowRenderer(nsView** aView) {
+ if (aView) {
+ *aView = RootReferenceFrame()->GetView();
+ }
+ if (RootReferenceFrame() !=
+ nsLayoutUtils::GetDisplayRootFrame(RootReferenceFrame())) {
+ return nullptr;
+ }
+ nsIWidget* window = RootReferenceFrame()->GetNearestWidget();
+ if (window) {
+ return window->GetWindowRenderer();
+ }
+ return nullptr;
+}
+
+WebRenderLayerManager* nsDisplayListBuilder::GetWidgetLayerManager(
+ nsView** aView) {
+ WindowRenderer* renderer = GetWidgetWindowRenderer();
+ if (renderer) {
+ return renderer->AsWebRender();
+ }
+ return nullptr;
+}
+
+void nsDisplayList::Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx,
+ int32_t aAppUnitsPerDevPixel) {
+ FlattenedDisplayListIterator iter(aBuilder, this);
+ while (iter.HasNext()) {
+ nsPaintedDisplayItem* item = iter.GetNextItem()->AsPaintedDisplayItem();
+ if (!item) {
+ continue;
+ }
+
+ nsRect visible = item->GetClippedBounds(aBuilder);
+ visible = visible.Intersect(item->GetPaintRect(aBuilder, aCtx));
+ if (visible.IsEmpty()) {
+ continue;
+ }
+
+ DisplayItemClip currentClip = item->GetClip();
+ if (currentClip.HasClip()) {
+ aCtx->Save();
+ if (currentClip.IsRectClippedByRoundedCorner(visible)) {
+ currentClip.ApplyTo(aCtx, aAppUnitsPerDevPixel);
+ } else {
+ currentClip.ApplyRectTo(aCtx, aAppUnitsPerDevPixel);
+ }
+ }
+ aCtx->NewPath();
+
+ item->Paint(aBuilder, aCtx);
+
+ if (currentClip.HasClip()) {
+ aCtx->Restore();
+ }
+ }
+}
+
+/**
+ * We paint by executing a layer manager transaction, constructing a
+ * single layer representing the display list, and then making it the
+ * root of the layer manager, drawing into the PaintedLayers.
+ */
+void nsDisplayList::PaintRoot(nsDisplayListBuilder* aBuilder, gfxContext* aCtx,
+ uint32_t aFlags,
+ Maybe<double> aDisplayListBuildTime) {
+ AUTO_PROFILER_LABEL("nsDisplayList::PaintRoot", GRAPHICS);
+
+ RefPtr<WebRenderLayerManager> layerManager;
+ WindowRenderer* renderer = nullptr;
+ bool widgetTransaction = false;
+ bool doBeginTransaction = true;
+ nsView* view = nullptr;
+ if (aFlags & PAINT_USE_WIDGET_LAYERS) {
+ renderer = aBuilder->GetWidgetWindowRenderer(&view);
+ if (renderer) {
+ // The fallback renderer doesn't retain any content, so it's
+ // not meaningful to use it when drawing to an external context.
+ if (aCtx && renderer->AsFallback()) {
+ MOZ_ASSERT(!(aFlags & PAINT_EXISTING_TRANSACTION));
+ renderer = nullptr;
+ } else {
+ layerManager = renderer->AsWebRender();
+ doBeginTransaction = !(aFlags & PAINT_EXISTING_TRANSACTION);
+ widgetTransaction = true;
+ }
+ }
+ }
+
+ nsIFrame* frame = aBuilder->RootReferenceFrame();
+ nsPresContext* presContext = frame->PresContext();
+ PresShell* presShell = presContext->PresShell();
+ Document* document = presShell->GetDocument();
+
+ ScopeExit g([&]() {
+#ifdef DEBUG
+ MOZ_ASSERT(!layerManager || !layerManager->GetTarget());
+#endif
+
+ // For layers-free mode, we check the invalidation state bits in the
+ // EndTransaction. So we clear the invalidation state bits after
+ // EndTransaction.
+ if (widgetTransaction ||
+ // SVG-as-an-image docs don't paint as part of the retained layer tree,
+ // but they still need the invalidation state bits cleared in order for
+ // invalidation for CSS/SMIL animation to work properly.
+ (document && document->IsBeingUsedAsImage())) {
+ DL_LOGD("Clearing invalidation state bits");
+ frame->ClearInvalidationStateBits();
+ }
+ });
+
+ if (!renderer) {
+ if (!aCtx) {
+ NS_WARNING("Nowhere to paint into");
+ return;
+ }
+ bool prevIsCompositingCheap = aBuilder->SetIsCompositingCheap(false);
+ Paint(aBuilder, aCtx, presContext->AppUnitsPerDevPixel());
+
+ aBuilder->SetIsCompositingCheap(prevIsCompositingCheap);
+ return;
+ }
+
+ if (renderer->GetBackendType() == LayersBackend::LAYERS_WR) {
+ MOZ_ASSERT(layerManager);
+ if (doBeginTransaction) {
+ if (aCtx) {
+ if (!layerManager->BeginTransactionWithTarget(aCtx, nsCString())) {
+ return;
+ }
+ } else {
+ if (!layerManager->BeginTransaction(nsCString())) {
+ return;
+ }
+ }
+ }
+
+ bool prevIsCompositingCheap = aBuilder->SetIsCompositingCheap(true);
+ layerManager->SetTransactionIdAllocator(presContext->RefreshDriver());
+
+ bool sent = false;
+ if (aFlags & PAINT_IDENTICAL_DISPLAY_LIST) {
+ MOZ_ASSERT(!aCtx);
+ sent = layerManager->EndEmptyTransaction();
+ }
+
+ if (!sent) {
+ auto* wrManager = static_cast<WebRenderLayerManager*>(layerManager.get());
+
+ nsIDocShell* docShell = presContext->GetDocShell();
+ WrFiltersHolder wrFilters;
+ gfx::Matrix5x4* colorMatrix =
+ nsDocShell::Cast(docShell)->GetColorMatrix();
+ if (colorMatrix) {
+ wrFilters.filters.AppendElement(
+ wr::FilterOp::ColorMatrix(colorMatrix->components));
+ }
+
+ wrManager->EndTransactionWithoutLayer(this, aBuilder,
+ std::move(wrFilters), nullptr,
+ aDisplayListBuildTime.valueOr(0.0));
+ }
+
+ aBuilder->SetIsCompositingCheap(prevIsCompositingCheap);
+ if (document && widgetTransaction) {
+ TriggerPendingAnimations(*document,
+ layerManager->GetAnimationReadyTime());
+ }
+
+ if (presContext->RefreshDriver()->HasScheduleFlush()) {
+ presContext->NotifyInvalidation(layerManager->GetLastTransactionId(),
+ frame->GetRect());
+ }
+
+ return;
+ }
+
+ FallbackRenderer* fallback = renderer->AsFallback();
+ MOZ_ASSERT(fallback);
+
+ if (doBeginTransaction) {
+ MOZ_ASSERT(!aCtx);
+ if (!fallback->BeginTransaction()) {
+ return;
+ }
+ }
+
+ bool temp = aBuilder->SetIsCompositingCheap(renderer->IsCompositingCheap());
+ fallback->EndTransactionWithList(aBuilder, this,
+ presContext->AppUnitsPerDevPixel(),
+ WindowRenderer::END_DEFAULT);
+
+ aBuilder->SetIsCompositingCheap(temp);
+
+ if (document && widgetTransaction) {
+ TriggerPendingAnimations(*document, renderer->GetAnimationReadyTime());
+ }
+}
+
+void nsDisplayList::DeleteAll(nsDisplayListBuilder* aBuilder) {
+ for (auto* item : TakeItems()) {
+ item->Destroy(aBuilder);
+ }
+}
+
+static bool IsFrameReceivingPointerEvents(nsIFrame* aFrame) {
+ return aFrame->Style()->PointerEvents() != StylePointerEvents::None;
+}
+
+// A list of frames, and their z depth. Used for sorting
+// the results of hit testing.
+struct FramesWithDepth {
+ explicit FramesWithDepth(float aDepth) : mDepth(aDepth) {}
+
+ bool operator<(const FramesWithDepth& aOther) const {
+ if (!FuzzyEqual(mDepth, aOther.mDepth, 0.1f)) {
+ // We want to sort so that the shallowest item (highest depth value) is
+ // first
+ return mDepth > aOther.mDepth;
+ }
+ return this < &aOther;
+ }
+ bool operator==(const FramesWithDepth& aOther) const {
+ return this == &aOther;
+ }
+
+ float mDepth;
+ nsTArray<nsIFrame*> mFrames;
+};
+
+// Sort the frames by depth and then moves all the contained frames to the
+// destination
+static void FlushFramesArray(nsTArray<FramesWithDepth>& aSource,
+ nsTArray<nsIFrame*>* aDest) {
+ if (aSource.IsEmpty()) {
+ return;
+ }
+ aSource.Sort();
+ uint32_t length = aSource.Length();
+ for (uint32_t i = 0; i < length; i++) {
+ aDest->AppendElements(std::move(aSource[i].mFrames));
+ }
+ aSource.Clear();
+}
+
+void nsDisplayList::HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
+ nsDisplayItem::HitTestState* aState,
+ nsTArray<nsIFrame*>* aOutFrames) const {
+ nsDisplayItem* item;
+
+ if (aState->mInPreserves3D) {
+ // Collect leaves of the current 3D rendering context.
+ for (nsDisplayItem* item : *this) {
+ auto itemType = item->GetType();
+ if (itemType != DisplayItemType::TYPE_TRANSFORM ||
+ !static_cast<nsDisplayTransform*>(item)->IsLeafOf3DContext()) {
+ item->HitTest(aBuilder, aRect, aState, aOutFrames);
+ } else {
+ // One of leaves in the current 3D rendering context.
+ aState->mItemBuffer.AppendElement(item);
+ }
+ }
+ return;
+ }
+
+ int32_t itemBufferStart = aState->mItemBuffer.Length();
+ for (nsDisplayItem* item : *this) {
+ aState->mItemBuffer.AppendElement(item);
+ }
+
+ AutoTArray<FramesWithDepth, 16> temp;
+ for (int32_t i = aState->mItemBuffer.Length() - 1; i >= itemBufferStart;
+ --i) {
+ // Pop element off the end of the buffer. We want to shorten the buffer
+ // so that recursive calls to HitTest have more buffer space.
+ item = aState->mItemBuffer[i];
+ aState->mItemBuffer.SetLength(i);
+
+ bool snap;
+ nsRect r = item->GetBounds(aBuilder, &snap).Intersect(aRect);
+ auto itemType = item->GetType();
+ bool same3DContext =
+ (itemType == DisplayItemType::TYPE_TRANSFORM &&
+ static_cast<nsDisplayTransform*>(item)->IsParticipating3DContext()) ||
+ (itemType == DisplayItemType::TYPE_PERSPECTIVE &&
+ item->Frame()->Extend3DContext());
+ if (same3DContext &&
+ (itemType != DisplayItemType::TYPE_TRANSFORM ||
+ !static_cast<nsDisplayTransform*>(item)->IsLeafOf3DContext())) {
+ if (!item->GetClip().MayIntersect(aRect)) {
+ continue;
+ }
+ AutoTArray<nsIFrame*, 1> neverUsed;
+ // Start gethering leaves of the 3D rendering context, and
+ // append leaves at the end of mItemBuffer. Leaves are
+ // processed at following iterations.
+ aState->mInPreserves3D = true;
+ item->HitTest(aBuilder, aRect, aState, &neverUsed);
+ aState->mInPreserves3D = false;
+ i = aState->mItemBuffer.Length();
+ continue;
+ }
+ if (same3DContext || item->GetClip().MayIntersect(r)) {
+ AutoTArray<nsIFrame*, 16> outFrames;
+ item->HitTest(aBuilder, aRect, aState, &outFrames);
+
+ // For 3d transforms with preserve-3d we add hit frames into the temp list
+ // so we can sort them later, otherwise we add them directly to the output
+ // list.
+ nsTArray<nsIFrame*>* writeFrames = aOutFrames;
+ if (item->GetType() == DisplayItemType::TYPE_TRANSFORM &&
+ static_cast<nsDisplayTransform*>(item)->IsLeafOf3DContext()) {
+ if (outFrames.Length()) {
+ nsDisplayTransform* transform =
+ static_cast<nsDisplayTransform*>(item);
+ nsPoint point = aRect.TopLeft();
+ // A 1x1 rect means a point, otherwise use the center of the rect
+ if (aRect.width != 1 || aRect.height != 1) {
+ point = aRect.Center();
+ }
+ temp.AppendElement(
+ FramesWithDepth(transform->GetHitDepthAtPoint(aBuilder, point)));
+ writeFrames = &temp[temp.Length() - 1].mFrames;
+ }
+ } else {
+ // We may have just finished a run of consecutive preserve-3d
+ // transforms, so flush these into the destination array before
+ // processing our frame list.
+ FlushFramesArray(temp, aOutFrames);
+ }
+
+ for (uint32_t j = 0; j < outFrames.Length(); j++) {
+ nsIFrame* f = outFrames.ElementAt(j);
+ // Filter out some frames depending on the type of hittest
+ // we are doing. For visibility tests, pass through all frames.
+ // For pointer tests, only pass through frames that are styled
+ // to receive pointer events.
+ if (aBuilder->HitTestIsForVisibility() ||
+ IsFrameReceivingPointerEvents(f)) {
+ writeFrames->AppendElement(f);
+ }
+ }
+
+ if (aBuilder->HitTestIsForVisibility()) {
+ aState->mHitOccludingItem = [&] {
+ if (aState->mHitOccludingItem) {
+ // We already hit something before.
+ return true;
+ }
+ if (aState->mCurrentOpacity == 1.0f &&
+ item->GetOpaqueRegion(aBuilder, &snap).Contains(aRect)) {
+ // An opaque item always occludes everything. Note that we need to
+ // check wrapping opacity and such as well.
+ return true;
+ }
+ float threshold = aBuilder->VisibilityThreshold();
+ if (threshold == 1.0f) {
+ return false;
+ }
+ float itemOpacity = [&] {
+ switch (item->GetType()) {
+ case DisplayItemType::TYPE_OPACITY:
+ return static_cast<nsDisplayOpacity*>(item)->GetOpacity();
+ case DisplayItemType::TYPE_BACKGROUND_COLOR:
+ return static_cast<nsDisplayBackgroundColor*>(item)
+ ->GetOpacity();
+ default:
+ // Be conservative and assume it won't occlude other items.
+ return 0.0f;
+ }
+ }();
+ return itemOpacity * aState->mCurrentOpacity >= threshold;
+ }();
+
+ if (aState->mHitOccludingItem) {
+ // We're exiting early, so pop the remaining items off the buffer.
+ aState->mItemBuffer.TruncateLength(itemBufferStart);
+ break;
+ }
+ }
+ }
+ }
+ // Clear any remaining preserve-3d transforms.
+ FlushFramesArray(temp, aOutFrames);
+ NS_ASSERTION(aState->mItemBuffer.Length() == uint32_t(itemBufferStart),
+ "How did we forget to pop some elements?");
+}
+
+static nsIContent* FindContentInDocument(nsDisplayItem* aItem, Document* aDoc) {
+ nsIFrame* f = aItem->Frame();
+ while (f) {
+ nsPresContext* pc = f->PresContext();
+ if (pc->Document() == aDoc) {
+ return f->GetContent();
+ }
+ f = nsLayoutUtils::GetCrossDocParentFrameInProcess(
+ pc->PresShell()->GetRootFrame());
+ }
+ return nullptr;
+}
+
+struct ZSortItem {
+ nsDisplayItem* item;
+ int32_t zIndex;
+
+ explicit ZSortItem(nsDisplayItem* aItem)
+ : item(aItem), zIndex(aItem->ZIndex()) {}
+
+ operator nsDisplayItem*() { return item; }
+};
+
+struct ZOrderComparator {
+ bool operator()(const ZSortItem& aLeft, const ZSortItem& aRight) const {
+ // Note that we can't just take the difference of the two
+ // z-indices here, because that might overflow a 32-bit int.
+ return aLeft.zIndex < aRight.zIndex;
+ }
+};
+
+void nsDisplayList::SortByZOrder() { Sort<ZSortItem>(ZOrderComparator()); }
+
+struct ContentComparator {
+ nsIContent* mCommonAncestor;
+
+ explicit ContentComparator(nsIContent* aCommonAncestor)
+ : mCommonAncestor(aCommonAncestor) {}
+
+ bool operator()(nsDisplayItem* aLeft, nsDisplayItem* aRight) const {
+ // It's possible that the nsIContent for aItem1 or aItem2 is in a
+ // subdocument of commonAncestor, because display items for subdocuments
+ // have been mixed into the same list. Ensure that we're looking at content
+ // in commonAncestor's document.
+ Document* commonAncestorDoc = mCommonAncestor->OwnerDoc();
+ nsIContent* content1 = FindContentInDocument(aLeft, commonAncestorDoc);
+ nsIContent* content2 = FindContentInDocument(aRight, commonAncestorDoc);
+ if (!content1 || !content2) {
+ NS_ERROR("Document trees are mixed up!");
+ // Something weird going on
+ return true;
+ }
+ return nsLayoutUtils::CompareTreePosition(content1, content2,
+ mCommonAncestor) < 0;
+ }
+};
+
+void nsDisplayList::SortByContentOrder(nsIContent* aCommonAncestor) {
+ Sort<nsDisplayItem*>(ContentComparator(aCommonAncestor));
+}
+
+#if !defined(DEBUG) && !defined(MOZ_DIAGNOSTIC_ASSERT_ENABLED)
+static_assert(sizeof(nsDisplayItem) <= 176, "nsDisplayItem has grown");
+#endif
+
+nsDisplayItem::nsDisplayItem(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame)
+ : nsDisplayItem(aBuilder, aFrame, aBuilder->CurrentActiveScrolledRoot()) {}
+
+nsDisplayItem::nsDisplayItem(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ const ActiveScrolledRoot* aActiveScrolledRoot)
+ : mFrame(aFrame), mActiveScrolledRoot(aActiveScrolledRoot) {
+ MOZ_COUNT_CTOR(nsDisplayItem);
+ MOZ_ASSERT(mFrame);
+ if (aBuilder->IsRetainingDisplayList()) {
+ mFrame->AddDisplayItem(this);
+ }
+
+ aBuilder->FindReferenceFrameFor(aFrame, &mToReferenceFrame);
+ NS_ASSERTION(
+ aBuilder->GetVisibleRect().width >= 0 || !aBuilder->IsForPainting(),
+ "visible rect not set");
+
+ mClipChain = aBuilder->ClipState().GetCurrentCombinedClipChain(aBuilder);
+
+ // The visible rect is for mCurrentFrame, so we have to use
+ // mCurrentOffsetToReferenceFrame
+ nsRect visible = aBuilder->GetVisibleRect() +
+ aBuilder->GetCurrentFrameOffsetToReferenceFrame();
+ SetBuildingRect(visible);
+
+ const nsStyleDisplay* disp = mFrame->StyleDisplay();
+ if (mFrame->BackfaceIsHidden(disp)) {
+ mItemFlags += ItemFlag::BackfaceHidden;
+ }
+ if (mFrame->Combines3DTransformWithAncestors()) {
+ mItemFlags += ItemFlag::Combines3DTransformWithAncestors;
+ }
+}
+
+void nsDisplayItem::SetDeletedFrame() { mItemFlags += ItemFlag::DeletedFrame; }
+
+bool nsDisplayItem::HasDeletedFrame() const {
+ bool retval = mItemFlags.contains(ItemFlag::DeletedFrame) ||
+ (GetType() == DisplayItemType::TYPE_REMOTE &&
+ !static_cast<const nsDisplayRemote*>(this)->GetFrameLoader());
+ MOZ_ASSERT(retval || mFrame);
+ return retval;
+}
+
+/* static */
+bool nsDisplayItem::ForceActiveLayers() {
+ return StaticPrefs::layers_force_active();
+}
+
+int32_t nsDisplayItem::ZIndex() const { return mFrame->ZIndex().valueOr(0); }
+
+void nsDisplayItem::SetClipChain(const DisplayItemClipChain* aClipChain,
+ bool aStore) {
+ mClipChain = aClipChain;
+}
+
+Maybe<nsRect> nsDisplayItem::GetClipWithRespectToASR(
+ nsDisplayListBuilder* aBuilder, const ActiveScrolledRoot* aASR) const {
+ if (const DisplayItemClip* clip =
+ DisplayItemClipChain::ClipForASR(GetClipChain(), aASR)) {
+ return Some(clip->GetClipRect());
+ }
+#ifdef DEBUG
+ NS_ASSERTION(false, "item should have finite clip with respect to aASR");
+#endif
+ return Nothing();
+}
+
+const DisplayItemClip& nsDisplayItem::GetClip() const {
+ const DisplayItemClip* clip =
+ DisplayItemClipChain::ClipForASR(mClipChain, mActiveScrolledRoot);
+ return clip ? *clip : DisplayItemClip::NoClip();
+}
+
+void nsDisplayItem::IntersectClip(nsDisplayListBuilder* aBuilder,
+ const DisplayItemClipChain* aOther,
+ bool aStore) {
+ if (!aOther || mClipChain == aOther) {
+ return;
+ }
+
+ // aOther might be a reference to a clip on the stack. We need to make sure
+ // that CreateClipChainIntersection will allocate the actual intersected
+ // clip in the builder's arena, so for the mClipChain == nullptr case,
+ // we supply nullptr as the common ancestor so that
+ // CreateClipChainIntersection clones the whole chain.
+ const DisplayItemClipChain* ancestorClip =
+ mClipChain ? FindCommonAncestorClipForIntersection(mClipChain, aOther)
+ : nullptr;
+
+ SetClipChain(
+ aBuilder->CreateClipChainIntersection(ancestorClip, mClipChain, aOther),
+ aStore);
+}
+
+nsRect nsDisplayItem::GetClippedBounds(nsDisplayListBuilder* aBuilder) const {
+ bool snap;
+ nsRect r = GetBounds(aBuilder, &snap);
+ return GetClip().ApplyNonRoundedIntersection(r);
+}
+
+nsDisplayContainer::nsDisplayContainer(
+ nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ const ActiveScrolledRoot* aActiveScrolledRoot, nsDisplayList* aList)
+ : nsDisplayItem(aBuilder, aFrame, aActiveScrolledRoot),
+ mChildren(aBuilder) {
+ MOZ_COUNT_CTOR(nsDisplayContainer);
+ mChildren.AppendToTop(aList);
+ UpdateBounds(aBuilder);
+
+ // Clear and store the clip chain set by nsDisplayItem constructor.
+ nsDisplayItem::SetClipChain(nullptr, true);
+}
+
+nsRect nsDisplayItem::GetPaintRect(nsDisplayListBuilder* aBuilder,
+ gfxContext* aCtx) {
+ bool dummy;
+ nsRect result = GetBounds(aBuilder, &dummy);
+ if (aCtx) {
+ result.IntersectRect(result,
+ nsLayoutUtils::RoundGfxRectToAppRect(
+ aCtx->GetClipExtents(),
+ mFrame->PresContext()->AppUnitsPerDevPixel()));
+ }
+ return result;
+}
+
+bool nsDisplayContainer::CreateWebRenderCommands(
+ wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc, RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) {
+ aManager->CommandBuilder().CreateWebRenderCommandsFromDisplayList(
+ GetChildren(), this, aDisplayListBuilder, aSc, aBuilder, aResources,
+ false);
+ return true;
+}
+
+nsRect nsDisplayContainer::GetBounds(nsDisplayListBuilder* aBuilder,
+ bool* aSnap) const {
+ *aSnap = false;
+ return mBounds;
+}
+
+nsRect nsDisplayContainer::GetComponentAlphaBounds(
+ nsDisplayListBuilder* aBuilder) const {
+ return mChildren.GetComponentAlphaBounds(aBuilder);
+}
+
+static nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
+ nsDisplayList* aList,
+ const nsRect& aListBounds) {
+ return aList->GetOpaqueRegion(aBuilder);
+}
+
+nsRegion nsDisplayContainer::GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
+ bool* aSnap) const {
+ return ::mozilla::GetOpaqueRegion(aBuilder, GetChildren(),
+ GetBounds(aBuilder, aSnap));
+}
+
+Maybe<nsRect> nsDisplayContainer::GetClipWithRespectToASR(
+ nsDisplayListBuilder* aBuilder, const ActiveScrolledRoot* aASR) const {
+ // Our children should have finite bounds with respect to |aASR|.
+ if (aASR == mActiveScrolledRoot) {
+ return Some(mBounds);
+ }
+
+ return Some(mChildren.GetClippedBoundsWithRespectToASR(aBuilder, aASR));
+}
+
+void nsDisplayContainer::HitTest(nsDisplayListBuilder* aBuilder,
+ const nsRect& aRect, HitTestState* aState,
+ nsTArray<nsIFrame*>* aOutFrames) {
+ mChildren.HitTest(aBuilder, aRect, aState, aOutFrames);
+}
+
+void nsDisplayContainer::UpdateBounds(nsDisplayListBuilder* aBuilder) {
+ // Container item bounds are expected to be clipped.
+ mBounds =
+ mChildren.GetClippedBoundsWithRespectToASR(aBuilder, mActiveScrolledRoot);
+}
+
+nsRect nsDisplaySolidColor::GetBounds(nsDisplayListBuilder* aBuilder,
+ bool* aSnap) const {
+ *aSnap = true;
+ return mBounds;
+}
+
+void nsDisplaySolidColor::Paint(nsDisplayListBuilder* aBuilder,
+ gfxContext* aCtx) {
+ int32_t appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel();
+ DrawTarget* drawTarget = aCtx->GetDrawTarget();
+ Rect rect = NSRectToSnappedRect(GetPaintRect(aBuilder, aCtx),
+ appUnitsPerDevPixel, *drawTarget);
+ drawTarget->FillRect(rect, ColorPattern(ToDeviceColor(mColor)));
+}
+
+void nsDisplaySolidColor::WriteDebugInfo(std::stringstream& aStream) {
+ aStream << " (rgba " << (int)NS_GET_R(mColor) << "," << (int)NS_GET_G(mColor)
+ << "," << (int)NS_GET_B(mColor) << "," << (int)NS_GET_A(mColor)
+ << ")";
+}
+
+bool nsDisplaySolidColor::CreateWebRenderCommands(
+ wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc, RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) {
+ LayoutDeviceRect bounds = LayoutDeviceRect::FromAppUnits(
+ mBounds, mFrame->PresContext()->AppUnitsPerDevPixel());
+ wr::LayoutRect r = wr::ToLayoutRect(bounds);
+ aBuilder.PushRect(r, r, !BackfaceIsHidden(), false, mIsCheckerboardBackground,
+ wr::ToColorF(ToDeviceColor(mColor)));
+
+ return true;
+}
+
+nsRect nsDisplaySolidColorRegion::GetBounds(nsDisplayListBuilder* aBuilder,
+ bool* aSnap) const {
+ *aSnap = true;
+ return mRegion.GetBounds();
+}
+
+void nsDisplaySolidColorRegion::Paint(nsDisplayListBuilder* aBuilder,
+ gfxContext* aCtx) {
+ int32_t appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel();
+ DrawTarget* drawTarget = aCtx->GetDrawTarget();
+ ColorPattern color(ToDeviceColor(mColor));
+ for (auto iter = mRegion.RectIter(); !iter.Done(); iter.Next()) {
+ Rect rect =
+ NSRectToSnappedRect(iter.Get(), appUnitsPerDevPixel, *drawTarget);
+ drawTarget->FillRect(rect, color);
+ }
+}
+
+void nsDisplaySolidColorRegion::WriteDebugInfo(std::stringstream& aStream) {
+ aStream << " (rgba " << int(mColor.r * 255) << "," << int(mColor.g * 255)
+ << "," << int(mColor.b * 255) << "," << mColor.a << ")";
+}
+
+bool nsDisplaySolidColorRegion::CreateWebRenderCommands(
+ wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc, RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) {
+ for (auto iter = mRegion.RectIter(); !iter.Done(); iter.Next()) {
+ nsRect rect = iter.Get();
+ LayoutDeviceRect layerRects = LayoutDeviceRect::FromAppUnits(
+ rect, mFrame->PresContext()->AppUnitsPerDevPixel());
+ wr::LayoutRect r = wr::ToLayoutRect(layerRects);
+ aBuilder.PushRect(r, r, !BackfaceIsHidden(), false, false,
+ wr::ToColorF(ToDeviceColor(mColor)));
+ }
+
+ return true;
+}
+
+static void RegisterThemeGeometry(nsDisplayListBuilder* aBuilder,
+ nsDisplayItem* aItem, nsIFrame* aFrame,
+ nsITheme::ThemeGeometryType aType) {
+ if (aBuilder->IsInChromeDocumentOrPopup()) {
+ nsIFrame* displayRoot = nsLayoutUtils::GetDisplayRootFrame(aFrame);
+ bool preservesAxisAlignedRectangles = false;
+ nsRect borderBox = nsLayoutUtils::TransformFrameRectToAncestor(
+ aFrame, aFrame->GetRectRelativeToSelf(), displayRoot,
+ &preservesAxisAlignedRectangles);
+ if (preservesAxisAlignedRectangles) {
+ aBuilder->RegisterThemeGeometry(
+ aType, aItem,
+ LayoutDeviceIntRect::FromUnknownRect(borderBox.ToNearestPixels(
+ aFrame->PresContext()->AppUnitsPerDevPixel())));
+ }
+ }
+}
+
+// Return the bounds of the viewport relative to |aFrame|'s reference frame.
+// Returns Nothing() if transforming into |aFrame|'s coordinate space fails.
+static Maybe<nsRect> GetViewportRectRelativeToReferenceFrame(
+ nsDisplayListBuilder* aBuilder, nsIFrame* aFrame) {
+ nsIFrame* rootFrame = aFrame->PresShell()->GetRootFrame();
+ nsRect rootRect = rootFrame->GetRectRelativeToSelf();
+ if (nsLayoutUtils::TransformRect(rootFrame, aFrame, rootRect) ==
+ nsLayoutUtils::TRANSFORM_SUCCEEDED) {
+ return Some(rootRect + aBuilder->ToReferenceFrame(aFrame));
+ }
+ return Nothing();
+}
+
+/* static */ nsDisplayBackgroundImage::InitData
+nsDisplayBackgroundImage::GetInitData(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame, uint16_t aLayer,
+ const nsRect& aBackgroundRect,
+ const ComputedStyle* aBackgroundStyle) {
+ nsPresContext* presContext = aFrame->PresContext();
+ uint32_t flags = aBuilder->GetBackgroundPaintFlags();
+ const nsStyleImageLayers::Layer& layer =
+ aBackgroundStyle->StyleBackground()->mImage.mLayers[aLayer];
+
+ bool isTransformedFixed;
+ nsBackgroundLayerState state = nsCSSRendering::PrepareImageLayer(
+ presContext, aFrame, flags, aBackgroundRect, aBackgroundRect, layer,
+ &isTransformedFixed);
+
+ // background-attachment:fixed is treated as background-attachment:scroll
+ // if it's affected by a transform.
+ // See https://www.w3.org/Bugs/Public/show_bug.cgi?id=17521.
+ bool shouldTreatAsFixed =
+ layer.mAttachment == StyleImageLayerAttachment::Fixed &&
+ !isTransformedFixed;
+
+ bool shouldFixToViewport = shouldTreatAsFixed && !layer.mImage.IsNone();
+ bool isRasterImage = state.mImageRenderer.IsRasterImage();
+ nsCOMPtr<imgIContainer> image;
+ if (isRasterImage) {
+ image = state.mImageRenderer.GetImage();
+ }
+ return InitData{aBuilder, aBackgroundStyle, image,
+ aBackgroundRect, state.mFillArea, state.mDestArea,
+ aLayer, isRasterImage, shouldFixToViewport};
+}
+
+nsDisplayBackgroundImage::nsDisplayBackgroundImage(
+ nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, const InitData& aInitData,
+ nsIFrame* aFrameForBounds)
+ : nsPaintedDisplayItem(aBuilder, aFrame),
+ mBackgroundStyle(aInitData.backgroundStyle),
+ mImage(aInitData.image),
+ mDependentFrame(nullptr),
+ mBackgroundRect(aInitData.backgroundRect),
+ mFillRect(aInitData.fillArea),
+ mDestRect(aInitData.destArea),
+ mLayer(aInitData.layer),
+ mIsRasterImage(aInitData.isRasterImage),
+ mShouldFixToViewport(aInitData.shouldFixToViewport) {
+ MOZ_COUNT_CTOR(nsDisplayBackgroundImage);
+#ifdef DEBUG
+ if (mBackgroundStyle && mBackgroundStyle != mFrame->Style()) {
+ // If this changes, then you also need to adjust css::ImageLoader to
+ // invalidate mFrame as needed.
+ MOZ_ASSERT(mFrame->IsCanvasFrame() ||
+ mFrame->IsFrameOfType(nsIFrame::eTablePart));
+ }
+#endif
+
+ mBounds = GetBoundsInternal(aInitData.builder, aFrameForBounds);
+ if (mShouldFixToViewport) {
+ // Expand the item's visible rect to cover the entire bounds, limited to the
+ // viewport rect. This is necessary because the background's clip can move
+ // asynchronously.
+ if (Maybe<nsRect> viewportRect = GetViewportRectRelativeToReferenceFrame(
+ aInitData.builder, mFrame)) {
+ SetBuildingRect(mBounds.Intersect(*viewportRect));
+ }
+ }
+}
+
+nsDisplayBackgroundImage::~nsDisplayBackgroundImage() {
+ MOZ_COUNT_DTOR(nsDisplayBackgroundImage);
+ if (mDependentFrame) {
+ mDependentFrame->RemoveDisplayItem(this);
+ }
+}
+
+static void SetBackgroundClipRegion(
+ DisplayListClipState::AutoSaveRestore& aClipState, nsIFrame* aFrame,
+ const nsStyleImageLayers::Layer& aLayer, const nsRect& aBackgroundRect,
+ bool aWillPaintBorder) {
+ nsCSSRendering::ImageLayerClipState clip;
+ nsCSSRendering::GetImageLayerClip(
+ aLayer, aFrame, *aFrame->StyleBorder(), aBackgroundRect, aBackgroundRect,
+ aWillPaintBorder, aFrame->PresContext()->AppUnitsPerDevPixel(), &clip);
+
+ if (clip.mHasAdditionalBGClipArea) {
+ aClipState.ClipContentDescendants(
+ clip.mAdditionalBGClipArea, clip.mBGClipArea,
+ clip.mHasRoundedCorners ? clip.mRadii : nullptr);
+ } else {
+ aClipState.ClipContentDescendants(
+ clip.mBGClipArea, clip.mHasRoundedCorners ? clip.mRadii : nullptr);
+ }
+}
+
+/**
+ * This is used for the find bar highlighter overlay. It's only accessible
+ * through the AnonymousContent API, so it's not exposed to general web pages.
+ */
+static bool SpecialCutoutRegionCase(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame,
+ const nsRect& aBackgroundRect,
+ nsDisplayList* aList, nscolor aColor) {
+ nsIContent* content = aFrame->GetContent();
+ if (!content) {
+ return false;
+ }
+
+ void* cutoutRegion = content->GetProperty(nsGkAtoms::cutoutregion);
+ if (!cutoutRegion) {
+ return false;
+ }
+
+ if (NS_GET_A(aColor) == 0) {
+ return true;
+ }
+
+ nsRegion region;
+ region.Sub(aBackgroundRect, *static_cast<nsRegion*>(cutoutRegion));
+ region.MoveBy(aBuilder->ToReferenceFrame(aFrame));
+ aList->AppendNewToTop<nsDisplaySolidColorRegion>(aBuilder, aFrame, region,
+ aColor);
+
+ return true;
+}
+
+enum class TableType : uint8_t {
+ Table,
+ TableCol,
+ TableColGroup,
+ TableRow,
+ TableRowGroup,
+ TableCell,
+
+ MAX,
+};
+
+enum class TableTypeBits : uint8_t { Count = 3 };
+
+static_assert(static_cast<uint8_t>(TableType::MAX) <
+ (1 << (static_cast<uint8_t>(TableTypeBits::Count) + 1)),
+ "TableType cannot fit with TableTypeBits::Count");
+TableType GetTableTypeFromFrame(nsIFrame* aFrame);
+
+static uint16_t CalculateTablePerFrameKey(const uint16_t aIndex,
+ const TableType aType) {
+ const uint32_t key = (aIndex << static_cast<uint8_t>(TableTypeBits::Count)) |
+ static_cast<uint8_t>(aType);
+
+ return static_cast<uint16_t>(key);
+}
+
+static nsDisplayBackgroundImage* CreateBackgroundImage(
+ nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsIFrame* aSecondaryFrame,
+ const nsDisplayBackgroundImage::InitData& aBgData) {
+ const auto index = aBgData.layer;
+
+ if (aSecondaryFrame) {
+ const auto tableType = GetTableTypeFromFrame(aFrame);
+ const uint16_t tableItemIndex = CalculateTablePerFrameKey(index, tableType);
+
+ return MakeDisplayItemWithIndex<nsDisplayTableBackgroundImage>(
+ aBuilder, aSecondaryFrame, tableItemIndex, aBgData, aFrame);
+ }
+
+ return MakeDisplayItemWithIndex<nsDisplayBackgroundImage>(aBuilder, aFrame,
+ index, aBgData);
+}
+
+static nsDisplayThemedBackground* CreateThemedBackground(
+ nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsIFrame* aSecondaryFrame,
+ const nsRect& aBgRect) {
+ if (aSecondaryFrame) {
+ const uint16_t index = static_cast<uint16_t>(GetTableTypeFromFrame(aFrame));
+ return MakeDisplayItemWithIndex<nsDisplayTableThemedBackground>(
+ aBuilder, aSecondaryFrame, index, aBgRect, aFrame);
+ }
+
+ return MakeDisplayItem<nsDisplayThemedBackground>(aBuilder, aFrame, aBgRect);
+}
+
+static nsDisplayBackgroundColor* CreateBackgroundColor(
+ nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsIFrame* aSecondaryFrame,
+ nsRect& aBgRect, const ComputedStyle* aBgSC, nscolor aColor) {
+ if (aSecondaryFrame) {
+ const uint16_t index = static_cast<uint16_t>(GetTableTypeFromFrame(aFrame));
+ return MakeDisplayItemWithIndex<nsDisplayTableBackgroundColor>(
+ aBuilder, aSecondaryFrame, index, aBgRect, aBgSC, aColor, aFrame);
+ }
+
+ return MakeDisplayItem<nsDisplayBackgroundColor>(aBuilder, aFrame, aBgRect,
+ aBgSC, aColor);
+}
+
+static void DealWithWindowsAppearanceHacks(nsIFrame* aFrame,
+ nsDisplayListBuilder* aBuilder) {
+ const auto& disp = *aFrame->StyleDisplay();
+
+ // We use default appearance rather than effective appearance because we want
+ // to handle when titlebar buttons that have appearance: none.
+ const auto defaultAppearance = disp.mDefaultAppearance;
+ if (MOZ_LIKELY(defaultAppearance == StyleAppearance::None)) {
+ return;
+ }
+
+ if (defaultAppearance == StyleAppearance::MozWinExcludeGlass) {
+ // Check for frames that are marked as a part of the region used in
+ // calculating glass margins on Windows.
+ aBuilder->AddWindowExcludeGlassRegion(
+ aFrame, nsRect(aBuilder->ToReferenceFrame(aFrame), aFrame->GetSize()));
+ }
+
+ if (auto type = disp.GetWindowButtonType()) {
+ if (auto* widget = aFrame->GetNearestWidget()) {
+ auto rect = LayoutDevicePixel::FromAppUnitsToNearest(
+ nsRect(aBuilder->ToReferenceFrame(aFrame), aFrame->GetSize()),
+ aFrame->PresContext()->AppUnitsPerDevPixel());
+ widget->SetWindowButtonRect(*type, rect);
+ }
+ }
+}
+
+/*static*/
+AppendedBackgroundType nsDisplayBackgroundImage::AppendBackgroundItemsToTop(
+ nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ const nsRect& aBackgroundRect, nsDisplayList* aList,
+ bool aAllowWillPaintBorderOptimization, const nsRect& aBackgroundOriginRect,
+ nsIFrame* aSecondaryReferenceFrame,
+ Maybe<nsDisplayListBuilder::AutoBuildingDisplayList>*
+ aAutoBuildingDisplayList) {
+ MOZ_ASSERT(!aFrame->IsCanvasFrame(),
+ "We don't expect propagated canvas backgrounds here");
+#ifdef DEBUG
+ {
+ nsIFrame* bgFrame = nsCSSRendering::FindBackgroundFrame(aFrame);
+ MOZ_ASSERT(
+ !bgFrame || bgFrame == aFrame,
+ "Should only suppress backgrounds, never propagate to another frame");
+ }
+#endif
+
+ DealWithWindowsAppearanceHacks(aFrame, aBuilder);
+
+ const bool isThemed = aFrame->IsThemed();
+
+ const ComputedStyle* bgSC = aFrame->Style();
+ const nsStyleBackground* bg = bgSC->StyleBackground();
+ const bool needsBackgroundColor =
+ aBuilder->IsForEventDelivery() ||
+ (EffectCompositor::HasAnimationsForCompositor(
+ aFrame, DisplayItemType::TYPE_BACKGROUND_COLOR) &&
+ !isThemed);
+ if (!needsBackgroundColor && !isThemed && bg->IsTransparent(bgSC)) {
+ return AppendedBackgroundType::None;
+ }
+
+ bool drawBackgroundColor = false;
+ bool drawBackgroundImage = false;
+ nscolor color = NS_RGBA(0, 0, 0, 0);
+ // Don't get background color / images if we propagated our background to the
+ // canvas (that is, if FindBackgroundFrame is null). But don't early return
+ // yet, since we might still need a background-color item for hit-testing.
+ if (!isThemed && nsCSSRendering::FindBackgroundFrame(aFrame)) {
+ color = nsCSSRendering::DetermineBackgroundColor(
+ aFrame->PresContext(), bgSC, aFrame, drawBackgroundImage,
+ drawBackgroundColor);
+ }
+
+ if (SpecialCutoutRegionCase(aBuilder, aFrame, aBackgroundRect, aList,
+ color)) {
+ return AppendedBackgroundType::None;
+ }
+
+ const nsStyleBorder& border = *aFrame->StyleBorder();
+ const bool willPaintBorder =
+ aAllowWillPaintBorderOptimization && !isThemed &&
+ !aFrame->StyleEffects()->HasBoxShadowWithInset(true) &&
+ border.HasBorder();
+
+ auto EnsureBuildingDisplayList = [&] {
+ if (!aAutoBuildingDisplayList || *aAutoBuildingDisplayList) {
+ return;
+ }
+ nsPoint offset = aBuilder->GetCurrentFrame()->GetOffsetTo(aFrame);
+ aAutoBuildingDisplayList->emplace(aBuilder, aFrame,
+ aBuilder->GetVisibleRect() + offset,
+ aBuilder->GetDirtyRect() + offset);
+ };
+
+ // An auxiliary list is necessary in case we have background blending; if that
+ // is the case, background items need to be wrapped by a blend container to
+ // isolate blending to the background
+ nsDisplayList bgItemList(aBuilder);
+ // Even if we don't actually have a background color to paint, we may still
+ // need to create an item for hit testing and we still need to create an item
+ // for background-color animations.
+ if ((drawBackgroundColor && color != NS_RGBA(0, 0, 0, 0)) ||
+ needsBackgroundColor) {
+ EnsureBuildingDisplayList();
+ Maybe<DisplayListClipState::AutoSaveRestore> clipState;
+ nsRect bgColorRect = aBackgroundRect;
+ if (!isThemed && !aBuilder->IsForEventDelivery()) {
+ // Disable the will-paint-border optimization for background
+ // colors with no border-radius. Enabling it for background colors
+ // doesn't help much (there are no tiling issues) and clipping the
+ // background breaks detection of the element's border-box being
+ // opaque. For nonzero border-radius we still need it because we
+ // want to inset the background if possible to avoid antialiasing
+ // artifacts along the rounded corners.
+ const bool useWillPaintBorderOptimization =
+ willPaintBorder &&
+ nsLayoutUtils::HasNonZeroCorner(border.mBorderRadius);
+
+ nsCSSRendering::ImageLayerClipState clip;
+ nsCSSRendering::GetImageLayerClip(
+ bg->BottomLayer(), aFrame, border, aBackgroundRect, aBackgroundRect,
+ useWillPaintBorderOptimization,
+ aFrame->PresContext()->AppUnitsPerDevPixel(), &clip);
+
+ bgColorRect = bgColorRect.Intersect(clip.mBGClipArea);
+ if (clip.mHasAdditionalBGClipArea) {
+ bgColorRect = bgColorRect.Intersect(clip.mAdditionalBGClipArea);
+ }
+ if (clip.mHasRoundedCorners) {
+ clipState.emplace(aBuilder);
+ clipState->ClipContentDescendants(clip.mBGClipArea, clip.mRadii);
+ }
+ }
+
+ nsDisplayBackgroundColor* bgItem = CreateBackgroundColor(
+ aBuilder, aFrame, aSecondaryReferenceFrame, bgColorRect, bgSC,
+ drawBackgroundColor ? color : NS_RGBA(0, 0, 0, 0));
+
+ if (bgItem) {
+ bgItemList.AppendToTop(bgItem);
+ }
+ }
+
+ if (isThemed) {
+ nsDisplayThemedBackground* bgItem = CreateThemedBackground(
+ aBuilder, aFrame, aSecondaryReferenceFrame, aBackgroundRect);
+
+ if (bgItem) {
+ bgItem->Init(aBuilder);
+ bgItemList.AppendToTop(bgItem);
+ }
+
+ if (!bgItemList.IsEmpty()) {
+ aList->AppendToTop(&bgItemList);
+ return AppendedBackgroundType::ThemedBackground;
+ }
+
+ return AppendedBackgroundType::None;
+ }
+
+ if (!drawBackgroundImage) {
+ if (!bgItemList.IsEmpty()) {
+ aList->AppendToTop(&bgItemList);
+ return AppendedBackgroundType::Background;
+ }
+
+ return AppendedBackgroundType::None;
+ }
+
+ const ActiveScrolledRoot* asr = aBuilder->CurrentActiveScrolledRoot();
+
+ bool needBlendContainer = false;
+ const nsRect& bgOriginRect =
+ aBackgroundOriginRect.IsEmpty() ? aBackgroundRect : aBackgroundOriginRect;
+
+ // Passing bg == nullptr in this macro will result in one iteration with
+ // i = 0.
+ NS_FOR_VISIBLE_IMAGE_LAYERS_BACK_TO_FRONT(i, bg->mImage) {
+ if (bg->mImage.mLayers[i].mImage.IsNone()) {
+ continue;
+ }
+
+ EnsureBuildingDisplayList();
+
+ if (bg->mImage.mLayers[i].mBlendMode != StyleBlend::Normal) {
+ needBlendContainer = true;
+ }
+
+ DisplayListClipState::AutoSaveRestore clipState(aBuilder);
+ if (!aBuilder->IsForEventDelivery()) {
+ const nsStyleImageLayers::Layer& layer = bg->mImage.mLayers[i];
+ SetBackgroundClipRegion(clipState, aFrame, layer, aBackgroundRect,
+ willPaintBorder);
+ }
+
+ nsDisplayList thisItemList(aBuilder);
+ nsDisplayBackgroundImage::InitData bgData =
+ nsDisplayBackgroundImage::GetInitData(aBuilder, aFrame, i, bgOriginRect,
+ bgSC);
+
+ if (bgData.shouldFixToViewport) {
+ auto* displayData = aBuilder->GetCurrentFixedBackgroundDisplayData();
+ nsDisplayListBuilder::AutoBuildingDisplayList buildingDisplayList(
+ aBuilder, aFrame, aBuilder->GetVisibleRect(),
+ aBuilder->GetDirtyRect());
+
+ nsDisplayListBuilder::AutoCurrentActiveScrolledRootSetter asrSetter(
+ aBuilder);
+ if (displayData) {
+ asrSetter.SetCurrentActiveScrolledRoot(
+ displayData->mContainingBlockActiveScrolledRoot);
+ asrSetter.SetCurrentScrollParentId(displayData->mScrollParentId);
+ if (nsLayoutUtils::UsesAsyncScrolling(aFrame)) {
+ // Override the dirty rect on the builder to be the dirty rect of
+ // the viewport.
+ // displayData->mDirtyRect is relative to the presshell's viewport
+ // frame (the root frame), and we need it to be relative to aFrame.
+ nsIFrame* rootFrame =
+ aBuilder->CurrentPresShellState()->mPresShell->GetRootFrame();
+ // There cannot be any transforms between aFrame and rootFrame
+ // because then bgData.shouldFixToViewport would have been false.
+ nsRect visibleRect =
+ displayData->mVisibleRect + aFrame->GetOffsetTo(rootFrame);
+ aBuilder->SetVisibleRect(visibleRect);
+ nsRect dirtyRect =
+ displayData->mDirtyRect + aFrame->GetOffsetTo(rootFrame);
+ aBuilder->SetDirtyRect(dirtyRect);
+ }
+ }
+
+ nsDisplayBackgroundImage* bgItem = nullptr;
+ {
+ // The clip is captured by the nsDisplayFixedPosition, so clear the
+ // clip for the nsDisplayBackgroundImage inside.
+ DisplayListClipState::AutoSaveRestore bgImageClip(aBuilder);
+ bgImageClip.Clear();
+ bgItem = CreateBackgroundImage(aBuilder, aFrame,
+ aSecondaryReferenceFrame, bgData);
+ }
+ if (bgItem) {
+ thisItemList.AppendToTop(
+ nsDisplayFixedPosition::CreateForFixedBackground(
+ aBuilder, aFrame, aSecondaryReferenceFrame, bgItem, i, asr));
+ }
+ } else { // bgData.shouldFixToViewport == false
+ nsDisplayBackgroundImage* bgItem = CreateBackgroundImage(
+ aBuilder, aFrame, aSecondaryReferenceFrame, bgData);
+ if (bgItem) {
+ thisItemList.AppendToTop(bgItem);
+ }
+ }
+
+ if (bg->mImage.mLayers[i].mBlendMode != StyleBlend::Normal) {
+ // asr is scrolled. Even if we wrap a fixed background layer, that's
+ // fine, because the item will have a scrolled clip that limits the
+ // item with respect to asr.
+ if (aSecondaryReferenceFrame) {
+ const auto tableType = GetTableTypeFromFrame(aFrame);
+ const uint16_t index = CalculateTablePerFrameKey(i + 1, tableType);
+
+ thisItemList.AppendNewToTopWithIndex<nsDisplayTableBlendMode>(
+ aBuilder, aSecondaryReferenceFrame, index, &thisItemList,
+ bg->mImage.mLayers[i].mBlendMode, asr, aFrame, true);
+ } else {
+ thisItemList.AppendNewToTopWithIndex<nsDisplayBlendMode>(
+ aBuilder, aFrame, i + 1, &thisItemList,
+ bg->mImage.mLayers[i].mBlendMode, asr, true);
+ }
+ }
+ bgItemList.AppendToTop(&thisItemList);
+ }
+
+ if (needBlendContainer) {
+ bgItemList.AppendToTop(
+ nsDisplayBlendContainer::CreateForBackgroundBlendMode(
+ aBuilder, aFrame, aSecondaryReferenceFrame, &bgItemList, asr));
+ }
+
+ if (!bgItemList.IsEmpty()) {
+ aList->AppendToTop(&bgItemList);
+ return AppendedBackgroundType::Background;
+ }
+
+ return AppendedBackgroundType::None;
+}
+
+// Check that the rounded border of aFrame, added to aToReferenceFrame,
+// intersects aRect. Assumes that the unrounded border has already
+// been checked for intersection.
+static bool RoundedBorderIntersectsRect(nsIFrame* aFrame,
+ const nsPoint& aFrameToReferenceFrame,
+ const nsRect& aTestRect) {
+ if (!nsRect(aFrameToReferenceFrame, aFrame->GetSize())
+ .Intersects(aTestRect)) {
+ return false;
+ }
+
+ nscoord radii[8];
+ return !aFrame->GetBorderRadii(radii) ||
+ nsLayoutUtils::RoundedRectIntersectsRect(
+ nsRect(aFrameToReferenceFrame, aFrame->GetSize()), radii,
+ aTestRect);
+}
+
+// Returns TRUE if aContainedRect is guaranteed to be contained in
+// the rounded rect defined by aRoundedRect and aRadii. Complex cases are
+// handled conservatively by returning FALSE in some situations where
+// a more thorough analysis could return TRUE.
+//
+// See also RoundedRectIntersectsRect.
+static bool RoundedRectContainsRect(const nsRect& aRoundedRect,
+ const nscoord aRadii[8],
+ const nsRect& aContainedRect) {
+ nsRegion rgn = nsLayoutUtils::RoundedRectIntersectRect(aRoundedRect, aRadii,
+ aContainedRect);
+ return rgn.Contains(aContainedRect);
+}
+
+bool nsDisplayBackgroundImage::CanApplyOpacity(
+ WebRenderLayerManager* aManager, nsDisplayListBuilder* aBuilder) const {
+ return CanBuildWebRenderDisplayItems(aManager, aBuilder);
+}
+
+bool nsDisplayBackgroundImage::CanBuildWebRenderDisplayItems(
+ WebRenderLayerManager* aManager, nsDisplayListBuilder* aBuilder) const {
+ return mBackgroundStyle->StyleBackground()->mImage.mLayers[mLayer].mClip !=
+ StyleGeometryBox::Text &&
+ nsCSSRendering::CanBuildWebRenderDisplayItemsForStyleImageLayer(
+ aManager, *StyleFrame()->PresContext(), StyleFrame(),
+ mBackgroundStyle->StyleBackground(), mLayer,
+ aBuilder->GetBackgroundPaintFlags());
+}
+
+bool nsDisplayBackgroundImage::CreateWebRenderCommands(
+ wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc, RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) {
+ if (!CanBuildWebRenderDisplayItems(aManager->LayerManager(),
+ aDisplayListBuilder)) {
+ return false;
+ }
+
+ uint32_t paintFlags = aDisplayListBuilder->GetBackgroundPaintFlags();
+ bool dummy;
+ nsCSSRendering::PaintBGParams params =
+ nsCSSRendering::PaintBGParams::ForSingleLayer(
+ *StyleFrame()->PresContext(), GetBounds(aDisplayListBuilder, &dummy),
+ mBackgroundRect, StyleFrame(), paintFlags, mLayer,
+ CompositionOp::OP_OVER, aBuilder.GetInheritedOpacity());
+ params.bgClipRect = &mBounds;
+ ImgDrawResult result =
+ nsCSSRendering::BuildWebRenderDisplayItemsForStyleImageLayer(
+ params, aBuilder, aResources, aSc, aManager, this);
+ if (result == ImgDrawResult::NOT_SUPPORTED) {
+ return false;
+ }
+ return true;
+}
+
+void nsDisplayBackgroundImage::HitTest(nsDisplayListBuilder* aBuilder,
+ const nsRect& aRect,
+ HitTestState* aState,
+ nsTArray<nsIFrame*>* aOutFrames) {
+ if (RoundedBorderIntersectsRect(mFrame, ToReferenceFrame(), aRect)) {
+ aOutFrames->AppendElement(mFrame);
+ }
+}
+
+static nsRect GetInsideClipRect(const nsDisplayItem* aItem,
+ StyleGeometryBox aClip, const nsRect& aRect,
+ const nsRect& aBackgroundRect) {
+ if (aRect.IsEmpty()) {
+ return {};
+ }
+
+ nsIFrame* frame = aItem->Frame();
+
+ nsRect clipRect = aBackgroundRect;
+ if (frame->IsCanvasFrame()) {
+ nsCanvasFrame* canvasFrame = static_cast<nsCanvasFrame*>(frame);
+ clipRect = canvasFrame->CanvasArea() + aItem->ToReferenceFrame();
+ } else if (aClip == StyleGeometryBox::PaddingBox ||
+ aClip == StyleGeometryBox::ContentBox) {
+ nsMargin border = frame->GetUsedBorder();
+ if (aClip == StyleGeometryBox::ContentBox) {
+ border += frame->GetUsedPadding();
+ }
+ border.ApplySkipSides(frame->GetSkipSides());
+ clipRect.Deflate(border);
+ }
+
+ return clipRect.Intersect(aRect);
+}
+
+nsRegion nsDisplayBackgroundImage::GetOpaqueRegion(
+ nsDisplayListBuilder* aBuilder, bool* aSnap) const {
+ nsRegion result;
+ *aSnap = false;
+
+ if (!mBackgroundStyle) {
+ return result;
+ }
+
+ *aSnap = true;
+
+ // For StyleBoxDecorationBreak::Slice, don't try to optimize here, since
+ // this could easily lead to O(N^2) behavior inside InlineBackgroundData,
+ // which expects frames to be sent to it in content order, not reverse
+ // content order which we'll produce here.
+ // Of course, if there's only one frame in the flow, it doesn't matter.
+ if (mFrame->StyleBorder()->mBoxDecorationBreak ==
+ StyleBoxDecorationBreak::Clone ||
+ (!mFrame->GetPrevContinuation() && !mFrame->GetNextContinuation())) {
+ const nsStyleImageLayers::Layer& layer =
+ mBackgroundStyle->StyleBackground()->mImage.mLayers[mLayer];
+ if (layer.mImage.IsOpaque() && layer.mBlendMode == StyleBlend::Normal &&
+ layer.mRepeat.mXRepeat != StyleImageLayerRepeat::Space &&
+ layer.mRepeat.mYRepeat != StyleImageLayerRepeat::Space &&
+ layer.mClip != StyleGeometryBox::Text) {
+ result = GetInsideClipRect(this, layer.mClip, mBounds, mBackgroundRect);
+ }
+ }
+
+ return result;
+}
+
+Maybe<nscolor> nsDisplayBackgroundImage::IsUniform(
+ nsDisplayListBuilder* aBuilder) const {
+ if (!mBackgroundStyle) {
+ return Some(NS_RGBA(0, 0, 0, 0));
+ }
+ return Nothing();
+}
+
+nsRect nsDisplayBackgroundImage::GetPositioningArea() const {
+ if (!mBackgroundStyle) {
+ return nsRect();
+ }
+ nsIFrame* attachedToFrame;
+ bool transformedFixed;
+ return nsCSSRendering::ComputeImageLayerPositioningArea(
+ mFrame->PresContext(), mFrame, mBackgroundRect,
+ mBackgroundStyle->StyleBackground()->mImage.mLayers[mLayer],
+ &attachedToFrame, &transformedFixed) +
+ ToReferenceFrame();
+}
+
+bool nsDisplayBackgroundImage::RenderingMightDependOnPositioningAreaSizeChange()
+ const {
+ if (!mBackgroundStyle) {
+ return false;
+ }
+
+ nscoord radii[8];
+ if (mFrame->GetBorderRadii(radii)) {
+ // A change in the size of the positioning area might change the position
+ // of the rounded corners.
+ return true;
+ }
+
+ const nsStyleImageLayers::Layer& layer =
+ mBackgroundStyle->StyleBackground()->mImage.mLayers[mLayer];
+ return layer.RenderingMightDependOnPositioningAreaSizeChange();
+}
+
+void nsDisplayBackgroundImage::Paint(nsDisplayListBuilder* aBuilder,
+ gfxContext* aCtx) {
+ PaintInternal(aBuilder, aCtx, GetPaintRect(aBuilder, aCtx), &mBounds);
+}
+
+void nsDisplayBackgroundImage::PaintInternal(nsDisplayListBuilder* aBuilder,
+ gfxContext* aCtx,
+ const nsRect& aBounds,
+ nsRect* aClipRect) {
+ gfxContext* ctx = aCtx;
+ StyleGeometryBox clip =
+ mBackgroundStyle->StyleBackground()->mImage.mLayers[mLayer].mClip;
+
+ if (clip == StyleGeometryBox::Text) {
+ if (!GenerateAndPushTextMask(StyleFrame(), aCtx, mBackgroundRect,
+ aBuilder)) {
+ return;
+ }
+ }
+
+ nsCSSRendering::PaintBGParams params =
+ nsCSSRendering::PaintBGParams::ForSingleLayer(
+ *StyleFrame()->PresContext(), aBounds, mBackgroundRect, StyleFrame(),
+ aBuilder->GetBackgroundPaintFlags(), mLayer, CompositionOp::OP_OVER,
+ 1.0f);
+ params.bgClipRect = aClipRect;
+ Unused << nsCSSRendering::PaintStyleImageLayer(params, *aCtx);
+
+ if (clip == StyleGeometryBox::Text) {
+ ctx->PopGroupAndBlend();
+ }
+}
+
+void nsDisplayBackgroundImage::ComputeInvalidationRegion(
+ nsDisplayListBuilder* aBuilder, const nsDisplayItemGeometry* aGeometry,
+ nsRegion* aInvalidRegion) const {
+ if (!mBackgroundStyle) {
+ return;
+ }
+
+ const auto* geometry =
+ static_cast<const nsDisplayBackgroundGeometry*>(aGeometry);
+
+ bool snap;
+ nsRect bounds = GetBounds(aBuilder, &snap);
+ nsRect positioningArea = GetPositioningArea();
+ if (positioningArea.TopLeft() != geometry->mPositioningArea.TopLeft() ||
+ (positioningArea.Size() != geometry->mPositioningArea.Size() &&
+ RenderingMightDependOnPositioningAreaSizeChange())) {
+ // Positioning area changed in a way that could cause everything to change,
+ // so invalidate everything (both old and new painting areas).
+ aInvalidRegion->Or(bounds, geometry->mBounds);
+ return;
+ }
+ if (!mDestRect.IsEqualInterior(geometry->mDestRect)) {
+ // Dest area changed in a way that could cause everything to change,
+ // so invalidate everything (both old and new painting areas).
+ aInvalidRegion->Or(bounds, geometry->mBounds);
+ return;
+ }
+ if (!bounds.IsEqualInterior(geometry->mBounds)) {
+ // Positioning area is unchanged, so invalidate just the change in the
+ // painting area.
+ aInvalidRegion->Xor(bounds, geometry->mBounds);
+ }
+}
+
+nsRect nsDisplayBackgroundImage::GetBounds(nsDisplayListBuilder* aBuilder,
+ bool* aSnap) const {
+ *aSnap = true;
+ return mBounds;
+}
+
+nsRect nsDisplayBackgroundImage::GetBoundsInternal(
+ nsDisplayListBuilder* aBuilder, nsIFrame* aFrameForBounds) {
+ // This allows nsDisplayTableBackgroundImage to change the frame used for
+ // bounds calculation.
+ nsIFrame* frame = aFrameForBounds ? aFrameForBounds : mFrame;
+
+ nsPresContext* presContext = frame->PresContext();
+
+ if (!mBackgroundStyle) {
+ return nsRect();
+ }
+
+ nsRect clipRect = mBackgroundRect;
+ if (frame->IsCanvasFrame()) {
+ nsCanvasFrame* canvasFrame = static_cast<nsCanvasFrame*>(frame);
+ clipRect = canvasFrame->CanvasArea() + ToReferenceFrame();
+ }
+ const nsStyleImageLayers::Layer& layer =
+ mBackgroundStyle->StyleBackground()->mImage.mLayers[mLayer];
+ return nsCSSRendering::GetBackgroundLayerRect(
+ presContext, frame, mBackgroundRect, clipRect, layer,
+ aBuilder->GetBackgroundPaintFlags());
+}
+
+nsDisplayTableBackgroundImage::nsDisplayTableBackgroundImage(
+ nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, const InitData& aData,
+ nsIFrame* aCellFrame)
+ : nsDisplayBackgroundImage(aBuilder, aFrame, aData, aCellFrame),
+ mStyleFrame(aCellFrame) {
+ if (aBuilder->IsRetainingDisplayList()) {
+ mStyleFrame->AddDisplayItem(this);
+ }
+}
+
+nsDisplayTableBackgroundImage::~nsDisplayTableBackgroundImage() {
+ if (mStyleFrame) {
+ mStyleFrame->RemoveDisplayItem(this);
+ }
+}
+
+bool nsDisplayTableBackgroundImage::IsInvalid(nsRect& aRect) const {
+ bool result = mStyleFrame ? mStyleFrame->IsInvalid(aRect) : false;
+ aRect += ToReferenceFrame();
+ return result;
+}
+
+nsDisplayThemedBackground::nsDisplayThemedBackground(
+ nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ const nsRect& aBackgroundRect)
+ : nsPaintedDisplayItem(aBuilder, aFrame), mBackgroundRect(aBackgroundRect) {
+ MOZ_COUNT_CTOR(nsDisplayThemedBackground);
+}
+
+void nsDisplayThemedBackground::Init(nsDisplayListBuilder* aBuilder) {
+ const nsStyleDisplay* disp = StyleFrame()->StyleDisplay();
+ mAppearance = disp->EffectiveAppearance();
+ StyleFrame()->IsThemed(disp, &mThemeTransparency);
+
+ // Perform necessary RegisterThemeGeometry
+ nsITheme* theme = StyleFrame()->PresContext()->Theme();
+ nsITheme::ThemeGeometryType type =
+ theme->ThemeGeometryTypeForWidget(StyleFrame(), mAppearance);
+ if (type != nsITheme::eThemeGeometryTypeUnknown) {
+ RegisterThemeGeometry(aBuilder, this, StyleFrame(), type);
+ }
+
+ if (mAppearance == StyleAppearance::MozWinBorderlessGlass) {
+ aBuilder->SetGlassDisplayItem(this);
+ }
+
+ mBounds = GetBoundsInternal();
+}
+
+void nsDisplayThemedBackground::WriteDebugInfo(std::stringstream& aStream) {
+ aStream << " (themed, appearance:" << (int)mAppearance << ")";
+}
+
+void nsDisplayThemedBackground::HitTest(nsDisplayListBuilder* aBuilder,
+ const nsRect& aRect,
+ HitTestState* aState,
+ nsTArray<nsIFrame*>* aOutFrames) {
+ // Assume that any point in our background rect is a hit.
+ if (mBackgroundRect.Intersects(aRect)) {
+ aOutFrames->AppendElement(mFrame);
+ }
+}
+
+nsRegion nsDisplayThemedBackground::GetOpaqueRegion(
+ nsDisplayListBuilder* aBuilder, bool* aSnap) const {
+ nsRegion result;
+ *aSnap = false;
+
+ if (mThemeTransparency == nsITheme::eOpaque) {
+ *aSnap = true;
+ result = mBackgroundRect;
+ }
+ return result;
+}
+
+Maybe<nscolor> nsDisplayThemedBackground::IsUniform(
+ nsDisplayListBuilder* aBuilder) const {
+ if (mAppearance == StyleAppearance::MozWinBorderlessGlass) {
+ return Some(NS_RGBA(0, 0, 0, 0));
+ }
+ return Nothing();
+}
+
+nsRect nsDisplayThemedBackground::GetPositioningArea() const {
+ return mBackgroundRect;
+}
+
+void nsDisplayThemedBackground::Paint(nsDisplayListBuilder* aBuilder,
+ gfxContext* aCtx) {
+ PaintInternal(aBuilder, aCtx, GetPaintRect(aBuilder, aCtx), nullptr);
+}
+
+void nsDisplayThemedBackground::PaintInternal(nsDisplayListBuilder* aBuilder,
+ gfxContext* aCtx,
+ const nsRect& aBounds,
+ nsRect* aClipRect) {
+ // XXXzw this ignores aClipRect.
+ nsPresContext* presContext = StyleFrame()->PresContext();
+ nsITheme* theme = presContext->Theme();
+ nsRect drawing(mBackgroundRect);
+ theme->GetWidgetOverflow(presContext->DeviceContext(), StyleFrame(),
+ mAppearance, &drawing);
+ drawing.IntersectRect(drawing, aBounds);
+ theme->DrawWidgetBackground(aCtx, StyleFrame(), mAppearance, mBackgroundRect,
+ drawing);
+}
+
+bool nsDisplayThemedBackground::CreateWebRenderCommands(
+ wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc, RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) {
+ nsITheme* theme = StyleFrame()->PresContext()->Theme();
+ return theme->CreateWebRenderCommandsForWidget(aBuilder, aResources, aSc,
+ aManager, StyleFrame(),
+ mAppearance, mBackgroundRect);
+}
+
+bool nsDisplayThemedBackground::IsWindowActive() const {
+ DocumentState docState = mFrame->GetContent()->OwnerDoc()->GetDocumentState();
+ return !docState.HasState(DocumentState::WINDOW_INACTIVE);
+}
+
+void nsDisplayThemedBackground::ComputeInvalidationRegion(
+ nsDisplayListBuilder* aBuilder, const nsDisplayItemGeometry* aGeometry,
+ nsRegion* aInvalidRegion) const {
+ const auto* geometry =
+ static_cast<const nsDisplayThemedBackgroundGeometry*>(aGeometry);
+
+ bool snap;
+ nsRect bounds = GetBounds(aBuilder, &snap);
+ nsRect positioningArea = GetPositioningArea();
+ if (!positioningArea.IsEqualInterior(geometry->mPositioningArea)) {
+ // Invalidate everything (both old and new painting areas).
+ aInvalidRegion->Or(bounds, geometry->mBounds);
+ return;
+ }
+ if (!bounds.IsEqualInterior(geometry->mBounds)) {
+ // Positioning area is unchanged, so invalidate just the change in the
+ // painting area.
+ aInvalidRegion->Xor(bounds, geometry->mBounds);
+ }
+ nsITheme* theme = StyleFrame()->PresContext()->Theme();
+ if (theme->WidgetAppearanceDependsOnWindowFocus(mAppearance) &&
+ IsWindowActive() != geometry->mWindowIsActive) {
+ aInvalidRegion->Or(*aInvalidRegion, bounds);
+ }
+}
+
+nsRect nsDisplayThemedBackground::GetBounds(nsDisplayListBuilder* aBuilder,
+ bool* aSnap) const {
+ *aSnap = true;
+ return mBounds;
+}
+
+nsRect nsDisplayThemedBackground::GetBoundsInternal() {
+ nsPresContext* presContext = mFrame->PresContext();
+
+ nsRect r = mBackgroundRect - ToReferenceFrame();
+ presContext->Theme()->GetWidgetOverflow(
+ presContext->DeviceContext(), mFrame,
+ mFrame->StyleDisplay()->EffectiveAppearance(), &r);
+ return r + ToReferenceFrame();
+}
+
+#if defined(MOZ_REFLOW_PERF_DSP) && defined(MOZ_REFLOW_PERF)
+void nsDisplayReflowCount::Paint(nsDisplayListBuilder* aBuilder,
+ gfxContext* aCtx) {
+ mFrame->PresShell()->PaintCount(mFrameName, aCtx, mFrame->PresContext(),
+ mFrame, ToReferenceFrame(), mColor);
+}
+#endif
+
+bool nsDisplayBackgroundColor::CanApplyOpacity(
+ WebRenderLayerManager* aManager, nsDisplayListBuilder* aBuilder) const {
+ // Don't apply opacity if the background color is animated since the color is
+ // going to be changed on the compositor.
+ return !EffectCompositor::HasAnimationsForCompositor(
+ mFrame, DisplayItemType::TYPE_BACKGROUND_COLOR);
+}
+
+bool nsDisplayBackgroundColor::CreateWebRenderCommands(
+ wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc, RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) {
+ gfx::sRGBColor color = mColor;
+ color.a *= aBuilder.GetInheritedOpacity();
+
+ if (color == sRGBColor() &&
+ !EffectCompositor::HasAnimationsForCompositor(
+ mFrame, DisplayItemType::TYPE_BACKGROUND_COLOR)) {
+ return true;
+ }
+
+ if (HasBackgroundClipText()) {
+ return false;
+ }
+
+ uint64_t animationsId = 0;
+ // We don't support background-color animations on table elements yet.
+ if (GetType() == DisplayItemType::TYPE_BACKGROUND_COLOR) {
+ animationsId =
+ AddAnimationsForWebRender(this, aManager, aDisplayListBuilder);
+ }
+
+ LayoutDeviceRect bounds = LayoutDeviceRect::FromAppUnits(
+ mBackgroundRect, mFrame->PresContext()->AppUnitsPerDevPixel());
+ wr::LayoutRect r = wr::ToLayoutRect(bounds);
+
+ if (animationsId) {
+ wr::WrAnimationProperty prop{
+ wr::WrAnimationType::BackgroundColor,
+ animationsId,
+ };
+ aBuilder.PushRectWithAnimation(r, r, !BackfaceIsHidden(),
+ wr::ToColorF(ToDeviceColor(color)), &prop);
+ } else {
+ aBuilder.StartGroup(this);
+ aBuilder.PushRect(r, r, !BackfaceIsHidden(), false, false,
+ wr::ToColorF(ToDeviceColor(color)));
+ aBuilder.FinishGroup();
+ }
+
+ return true;
+}
+
+void nsDisplayBackgroundColor::PaintWithClip(nsDisplayListBuilder* aBuilder,
+ gfxContext* aCtx,
+ const DisplayItemClip& aClip) {
+ MOZ_ASSERT(!HasBackgroundClipText());
+
+ if (mColor == sRGBColor()) {
+ return;
+ }
+
+ nsRect fillRect = mBackgroundRect;
+ if (aClip.HasClip()) {
+ fillRect.IntersectRect(fillRect, aClip.GetClipRect());
+ }
+
+ DrawTarget* dt = aCtx->GetDrawTarget();
+ int32_t A2D = mFrame->PresContext()->AppUnitsPerDevPixel();
+ Rect bounds = ToRect(nsLayoutUtils::RectToGfxRect(fillRect, A2D));
+ MaybeSnapToDevicePixels(bounds, *dt);
+ ColorPattern fill(ToDeviceColor(mColor));
+
+ if (aClip.GetRoundedRectCount()) {
+ MOZ_ASSERT(aClip.GetRoundedRectCount() == 1);
+
+ AutoTArray<DisplayItemClip::RoundedRect, 1> roundedRect;
+ aClip.AppendRoundedRects(&roundedRect);
+
+ bool pushedClip = false;
+ if (!fillRect.Contains(roundedRect[0].mRect)) {
+ dt->PushClipRect(bounds);
+ pushedClip = true;
+ }
+
+ RectCornerRadii pixelRadii;
+ nsCSSRendering::ComputePixelRadii(roundedRect[0].mRadii, A2D, &pixelRadii);
+ dt->FillRoundedRect(
+ RoundedRect(NSRectToSnappedRect(roundedRect[0].mRect, A2D, *dt),
+ pixelRadii),
+ fill);
+ if (pushedClip) {
+ dt->PopClip();
+ }
+ } else {
+ dt->FillRect(bounds, fill);
+ }
+}
+
+void nsDisplayBackgroundColor::Paint(nsDisplayListBuilder* aBuilder,
+ gfxContext* aCtx) {
+ if (mColor == sRGBColor()) {
+ return;
+ }
+
+#if 0
+ // See https://bugzilla.mozilla.org/show_bug.cgi?id=1148418#c21 for why this
+ // results in a precision induced rounding issue that makes the rect one
+ // pixel shorter in rare cases. Disabled in favor of the old code for now.
+ // Note that the pref layout.css.devPixelsPerPx needs to be set to 1 to
+ // reproduce the bug.
+ //
+ // TODO:
+ // This new path does not include support for background-clip:text; need to
+ // be fixed if/when we switch to this new code path.
+
+ DrawTarget& aDrawTarget = *aCtx->GetDrawTarget();
+
+ Rect rect = NSRectToSnappedRect(mBackgroundRect,
+ mFrame->PresContext()->AppUnitsPerDevPixel(),
+ aDrawTarget);
+ ColorPattern color(ToDeviceColor(mColor));
+ aDrawTarget.FillRect(rect, color);
+#else
+ gfxContext* ctx = aCtx;
+ gfxRect bounds = nsLayoutUtils::RectToGfxRect(
+ mBackgroundRect, mFrame->PresContext()->AppUnitsPerDevPixel());
+
+ if (HasBackgroundClipText()) {
+ if (!GenerateAndPushTextMask(mFrame, aCtx, mBackgroundRect, aBuilder)) {
+ return;
+ }
+
+ ctx->SetColor(mColor);
+ ctx->NewPath();
+ ctx->SnappedRectangle(bounds);
+ ctx->Fill();
+ ctx->PopGroupAndBlend();
+ return;
+ }
+
+ ctx->SetColor(mColor);
+ ctx->NewPath();
+ ctx->SnappedRectangle(bounds);
+ ctx->Fill();
+#endif
+}
+
+nsRegion nsDisplayBackgroundColor::GetOpaqueRegion(
+ nsDisplayListBuilder* aBuilder, bool* aSnap) const {
+ *aSnap = false;
+
+ if (mColor.a != 1 ||
+ // Even if the current alpha channel is 1, we treat this item as if it's
+ // non-opaque if there is a background-color animation since the animation
+ // might change the alpha channel.
+ EffectCompositor::HasAnimationsForCompositor(
+ mFrame, DisplayItemType::TYPE_BACKGROUND_COLOR)) {
+ return nsRegion();
+ }
+
+ if (!mHasStyle || HasBackgroundClipText()) {
+ return nsRegion();
+ }
+
+ *aSnap = true;
+ return GetInsideClipRect(this, mBottomLayerClip, mBackgroundRect,
+ mBackgroundRect);
+}
+
+Maybe<nscolor> nsDisplayBackgroundColor::IsUniform(
+ nsDisplayListBuilder* aBuilder) const {
+ return Some(mColor.ToABGR());
+}
+
+void nsDisplayBackgroundColor::HitTest(nsDisplayListBuilder* aBuilder,
+ const nsRect& aRect,
+ HitTestState* aState,
+ nsTArray<nsIFrame*>* aOutFrames) {
+ if (!RoundedBorderIntersectsRect(mFrame, ToReferenceFrame(), aRect)) {
+ // aRect doesn't intersect our border-radius curve.
+ return;
+ }
+
+ aOutFrames->AppendElement(mFrame);
+}
+
+void nsDisplayBackgroundColor::WriteDebugInfo(std::stringstream& aStream) {
+ aStream << " (rgba " << mColor.r << "," << mColor.g << "," << mColor.b << ","
+ << mColor.a << ")";
+ aStream << " backgroundRect" << mBackgroundRect;
+}
+
+nsRect nsDisplayOutline::GetBounds(nsDisplayListBuilder* aBuilder,
+ bool* aSnap) const {
+ *aSnap = false;
+ return mFrame->InkOverflowRectRelativeToSelf() + ToReferenceFrame();
+}
+
+nsRect nsDisplayOutline::GetInnerRect() const {
+ if (nsRect* savedOutlineInnerRect =
+ mFrame->GetProperty(nsIFrame::OutlineInnerRectProperty())) {
+ return *savedOutlineInnerRect;
+ }
+ return mFrame->GetRectRelativeToSelf();
+}
+
+void nsDisplayOutline::Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) {
+ // TODO join outlines together
+ MOZ_ASSERT(mFrame->StyleOutline()->ShouldPaintOutline(),
+ "Should have not created a nsDisplayOutline!");
+
+ nsRect rect = GetInnerRect() + ToReferenceFrame();
+ nsPresContext* pc = mFrame->PresContext();
+ if (IsThemedOutline()) {
+ rect.Inflate(mFrame->StyleOutline()->mOutlineOffset.ToAppUnits());
+ pc->Theme()->DrawWidgetBackground(aCtx, mFrame,
+ StyleAppearance::FocusOutline, rect,
+ GetPaintRect(aBuilder, aCtx));
+ return;
+ }
+
+ nsCSSRendering::PaintNonThemedOutline(
+ pc, *aCtx, mFrame, GetPaintRect(aBuilder, aCtx), rect, mFrame->Style());
+}
+
+bool nsDisplayOutline::IsThemedOutline() const {
+#ifdef DEBUG
+ nsPresContext* pc = mFrame->PresContext();
+ MOZ_ASSERT(
+ pc->Theme()->ThemeSupportsWidget(pc, mFrame,
+ StyleAppearance::FocusOutline),
+ "All of our supported platforms have support for themed focus-outlines");
+#endif
+ return mFrame->StyleOutline()->mOutlineStyle.IsAuto();
+}
+
+bool nsDisplayOutline::CreateWebRenderCommands(
+ wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc, RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) {
+ nsPresContext* pc = mFrame->PresContext();
+ nsRect rect = GetInnerRect() + ToReferenceFrame();
+ if (IsThemedOutline()) {
+ rect.Inflate(mFrame->StyleOutline()->mOutlineOffset.ToAppUnits());
+ return pc->Theme()->CreateWebRenderCommandsForWidget(
+ aBuilder, aResources, aSc, aManager, mFrame,
+ StyleAppearance::FocusOutline, rect);
+ }
+
+ bool dummy;
+ Maybe<nsCSSBorderRenderer> borderRenderer =
+ nsCSSRendering::CreateBorderRendererForNonThemedOutline(
+ pc, /* aDrawTarget = */ nullptr, mFrame,
+ GetBounds(aDisplayListBuilder, &dummy), rect, mFrame->Style());
+
+ if (!borderRenderer) {
+ // No border renderer means "there is no outline".
+ // Paint nothing and return success.
+ return true;
+ }
+
+ borderRenderer->CreateWebRenderCommands(this, aBuilder, aResources, aSc);
+ return true;
+}
+
+bool nsDisplayOutline::HasRadius() const {
+ const auto& radius = mFrame->StyleBorder()->mBorderRadius;
+ return !nsLayoutUtils::HasNonZeroCorner(radius);
+}
+
+bool nsDisplayOutline::IsInvisibleInRect(const nsRect& aRect) const {
+ const nsStyleOutline* outline = mFrame->StyleOutline();
+ nsRect borderBox(ToReferenceFrame(), mFrame->GetSize());
+ if (borderBox.Contains(aRect) && !HasRadius() &&
+ outline->mOutlineOffset.ToCSSPixels() >= 0.0f) {
+ // aRect is entirely inside the border-rect, and the outline isn't rendered
+ // inside the border-rect, so the outline is not visible.
+ return true;
+ }
+ return false;
+}
+
+void nsDisplayEventReceiver::HitTest(nsDisplayListBuilder* aBuilder,
+ const nsRect& aRect, HitTestState* aState,
+ nsTArray<nsIFrame*>* aOutFrames) {
+ if (!RoundedBorderIntersectsRect(mFrame, ToReferenceFrame(), aRect)) {
+ // aRect doesn't intersect our border-radius curve.
+ return;
+ }
+
+ aOutFrames->AppendElement(mFrame);
+}
+
+bool nsDisplayCompositorHitTestInfo::CreateWebRenderCommands(
+ wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc, RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) {
+ return true;
+}
+
+int32_t nsDisplayCompositorHitTestInfo::ZIndex() const {
+ return mOverrideZIndex ? *mOverrideZIndex : nsDisplayItem::ZIndex();
+}
+
+void nsDisplayCompositorHitTestInfo::SetOverrideZIndex(int32_t aZIndex) {
+ mOverrideZIndex = Some(aZIndex);
+}
+
+nsDisplayCaret::nsDisplayCaret(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aCaretFrame)
+ : nsPaintedDisplayItem(aBuilder, aCaretFrame),
+ mCaret(aBuilder->GetCaret()),
+ mBounds(aBuilder->GetCaretRect() + ToReferenceFrame()) {
+ MOZ_COUNT_CTOR(nsDisplayCaret);
+ // The presence of a caret doesn't change the overflow rect
+ // of the owning frame, so the normal building rect might not
+ // include the caret at all. We use MarkFrameForDisplay to ensure
+ // we build this item, and here we override the building rect
+ // to cover the pixels we're going to draw.
+ SetBuildingRect(mBounds);
+}
+
+#ifdef NS_BUILD_REFCNT_LOGGING
+nsDisplayCaret::~nsDisplayCaret() { MOZ_COUNT_DTOR(nsDisplayCaret); }
+#endif
+
+nsRect nsDisplayCaret::GetBounds(nsDisplayListBuilder* aBuilder,
+ bool* aSnap) const {
+ *aSnap = true;
+ // The caret returns a rect in the coordinates of mFrame.
+ return mBounds;
+}
+
+void nsDisplayCaret::Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) {
+ // Note: Because we exist, we know that the caret is visible, so we don't
+ // need to check for the caret's visibility.
+ mCaret->PaintCaret(*aCtx->GetDrawTarget(), mFrame, ToReferenceFrame());
+}
+
+bool nsDisplayCaret::CreateWebRenderCommands(
+ wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc, RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) {
+ using namespace layers;
+ nsRect caretRect;
+ nsRect hookRect;
+ nscolor caretColor;
+ nsIFrame* frame =
+ mCaret->GetPaintGeometry(&caretRect, &hookRect, &caretColor);
+ MOZ_ASSERT(frame == mFrame, "We're referring different frame");
+ if (!frame) {
+ return true;
+ }
+
+ int32_t appUnitsPerDevPixel = frame->PresContext()->AppUnitsPerDevPixel();
+ gfx::DeviceColor color = ToDeviceColor(caretColor);
+ LayoutDeviceRect devCaretRect = LayoutDeviceRect::FromAppUnits(
+ caretRect + ToReferenceFrame(), appUnitsPerDevPixel);
+ LayoutDeviceRect devHookRect = LayoutDeviceRect::FromAppUnits(
+ hookRect + ToReferenceFrame(), appUnitsPerDevPixel);
+
+ wr::LayoutRect caret = wr::ToLayoutRect(devCaretRect);
+ wr::LayoutRect hook = wr::ToLayoutRect(devHookRect);
+
+ // Note, WR will pixel snap anything that is layout aligned.
+ aBuilder.PushRect(caret, caret, !BackfaceIsHidden(), false, false,
+ wr::ToColorF(color));
+
+ if (!devHookRect.IsEmpty()) {
+ aBuilder.PushRect(hook, hook, !BackfaceIsHidden(), false, false,
+ wr::ToColorF(color));
+ }
+ return true;
+}
+
+nsDisplayBorder::nsDisplayBorder(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame)
+ : nsPaintedDisplayItem(aBuilder, aFrame) {
+ MOZ_COUNT_CTOR(nsDisplayBorder);
+
+ mBounds = CalculateBounds<nsRect>(*mFrame->StyleBorder());
+}
+
+bool nsDisplayBorder::IsInvisibleInRect(const nsRect& aRect) const {
+ nsRect paddingRect = GetPaddingRect();
+ const nsStyleBorder* styleBorder;
+ if (paddingRect.Contains(aRect) &&
+ !(styleBorder = mFrame->StyleBorder())->IsBorderImageSizeAvailable() &&
+ !nsLayoutUtils::HasNonZeroCorner(styleBorder->mBorderRadius)) {
+ // aRect is entirely inside the content rect, and no part
+ // of the border is rendered inside the content rect, so we are not
+ // visible
+ // Skip this if there's a border-image (which draws a background
+ // too) or if there is a border-radius (which makes the border draw
+ // further in).
+ return true;
+ }
+
+ return false;
+}
+
+nsDisplayItemGeometry* nsDisplayBorder::AllocateGeometry(
+ nsDisplayListBuilder* aBuilder) {
+ return new nsDisplayBorderGeometry(this, aBuilder);
+}
+
+void nsDisplayBorder::ComputeInvalidationRegion(
+ nsDisplayListBuilder* aBuilder, const nsDisplayItemGeometry* aGeometry,
+ nsRegion* aInvalidRegion) const {
+ const auto* geometry = static_cast<const nsDisplayBorderGeometry*>(aGeometry);
+ bool snap;
+
+ if (!geometry->mBounds.IsEqualInterior(GetBounds(aBuilder, &snap))) {
+ // We can probably get away with only invalidating the difference
+ // between the border and padding rects, but the XUL ui at least
+ // is apparently painting a background with this?
+ aInvalidRegion->Or(GetBounds(aBuilder, &snap), geometry->mBounds);
+ }
+}
+
+bool nsDisplayBorder::CreateWebRenderCommands(
+ wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc, RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) {
+ nsRect rect = nsRect(ToReferenceFrame(), mFrame->GetSize());
+
+ ImgDrawResult drawResult = nsCSSRendering::CreateWebRenderCommandsForBorder(
+ this, mFrame, rect, aBuilder, aResources, aSc, aManager,
+ aDisplayListBuilder);
+
+ if (drawResult == ImgDrawResult::NOT_SUPPORTED) {
+ return false;
+ }
+ return true;
+};
+
+void nsDisplayBorder::Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) {
+ nsPoint offset = ToReferenceFrame();
+
+ PaintBorderFlags flags = aBuilder->ShouldSyncDecodeImages()
+ ? PaintBorderFlags::SyncDecodeImages
+ : PaintBorderFlags();
+
+ Unused << nsCSSRendering::PaintBorder(
+ mFrame->PresContext(), *aCtx, mFrame, GetPaintRect(aBuilder, aCtx),
+ nsRect(offset, mFrame->GetSize()), mFrame->Style(), flags,
+ mFrame->GetSkipSides());
+}
+
+nsRect nsDisplayBorder::GetBounds(nsDisplayListBuilder* aBuilder,
+ bool* aSnap) const {
+ *aSnap = true;
+ return mBounds;
+}
+
+void nsDisplayBoxShadowOuter::Paint(nsDisplayListBuilder* aBuilder,
+ gfxContext* aCtx) {
+ nsPoint offset = ToReferenceFrame();
+ nsRect borderRect = mFrame->VisualBorderRectRelativeToSelf() + offset;
+ nsPresContext* presContext = mFrame->PresContext();
+
+ AUTO_PROFILER_LABEL("nsDisplayBoxShadowOuter::Paint", GRAPHICS);
+
+ nsCSSRendering::PaintBoxShadowOuter(presContext, *aCtx, mFrame, borderRect,
+ GetPaintRect(aBuilder, aCtx), 1.0f);
+}
+
+nsRect nsDisplayBoxShadowOuter::GetBounds(nsDisplayListBuilder* aBuilder,
+ bool* aSnap) const {
+ *aSnap = false;
+ return mBounds;
+}
+
+nsRect nsDisplayBoxShadowOuter::GetBoundsInternal() {
+ return nsLayoutUtils::GetBoxShadowRectForFrame(mFrame, mFrame->GetSize()) +
+ ToReferenceFrame();
+}
+
+bool nsDisplayBoxShadowOuter::IsInvisibleInRect(const nsRect& aRect) const {
+ nsPoint origin = ToReferenceFrame();
+ nsRect frameRect(origin, mFrame->GetSize());
+ if (!frameRect.Contains(aRect)) {
+ return false;
+ }
+
+ // the visible region is entirely inside the border-rect, and box shadows
+ // never render within the border-rect (unless there's a border radius).
+ nscoord twipsRadii[8];
+ bool hasBorderRadii = mFrame->GetBorderRadii(twipsRadii);
+ if (!hasBorderRadii) {
+ return true;
+ }
+
+ return RoundedRectContainsRect(frameRect, twipsRadii, aRect);
+}
+
+bool nsDisplayBoxShadowOuter::CanBuildWebRenderDisplayItems() const {
+ auto shadows = mFrame->StyleEffects()->mBoxShadow.AsSpan();
+ if (shadows.IsEmpty()) {
+ return false;
+ }
+
+ bool hasBorderRadius;
+ // We don't support native themed things yet like box shadows around
+ // input buttons.
+ //
+ // TODO(emilio): The non-native theme could provide the right rect+radius
+ // instead relatively painlessly, if we find this causes performance issues or
+ // what not.
+ return !nsCSSRendering::HasBoxShadowNativeTheme(mFrame, hasBorderRadius);
+}
+
+bool nsDisplayBoxShadowOuter::CreateWebRenderCommands(
+ wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc, RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) {
+ if (!CanBuildWebRenderDisplayItems()) {
+ return false;
+ }
+
+ int32_t appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel();
+ nsPoint offset = ToReferenceFrame();
+ nsRect borderRect = mFrame->VisualBorderRectRelativeToSelf() + offset;
+ bool snap;
+ nsRect bounds = GetBounds(aDisplayListBuilder, &snap);
+
+ bool hasBorderRadius;
+ bool nativeTheme =
+ nsCSSRendering::HasBoxShadowNativeTheme(mFrame, hasBorderRadius);
+
+ // Don't need the full size of the shadow rect like we do in
+ // nsCSSRendering since WR takes care of calculations for blur
+ // and spread radius.
+ nsRect frameRect =
+ nsCSSRendering::GetShadowRect(borderRect, nativeTheme, mFrame);
+
+ RectCornerRadii borderRadii;
+ if (hasBorderRadius) {
+ hasBorderRadius = nsCSSRendering::GetBorderRadii(frameRect, borderRect,
+ mFrame, borderRadii);
+ }
+
+ // Everything here is in app units, change to device units.
+ LayoutDeviceRect clipRect =
+ LayoutDeviceRect::FromAppUnits(bounds, appUnitsPerDevPixel);
+ auto shadows = mFrame->StyleEffects()->mBoxShadow.AsSpan();
+ MOZ_ASSERT(!shadows.IsEmpty());
+
+ for (const auto& shadow : Reversed(shadows)) {
+ if (shadow.inset) {
+ continue;
+ }
+
+ float blurRadius =
+ float(shadow.base.blur.ToAppUnits()) / float(appUnitsPerDevPixel);
+ gfx::sRGBColor shadowColor = nsCSSRendering::GetShadowColor(
+ shadow.base, mFrame, aBuilder.GetInheritedOpacity());
+
+ // We don't move the shadow rect here since WR does it for us
+ // Now translate everything to device pixels.
+ const nsRect& shadowRect = frameRect;
+ LayoutDevicePoint shadowOffset = LayoutDevicePoint::FromAppUnits(
+ nsPoint(shadow.base.horizontal.ToAppUnits(),
+ shadow.base.vertical.ToAppUnits()),
+ appUnitsPerDevPixel);
+
+ LayoutDeviceRect deviceBox =
+ LayoutDeviceRect::FromAppUnits(shadowRect, appUnitsPerDevPixel);
+ wr::LayoutRect deviceBoxRect = wr::ToLayoutRect(deviceBox);
+ wr::LayoutRect deviceClipRect = wr::ToLayoutRect(clipRect);
+
+ LayoutDeviceSize zeroSize;
+ wr::BorderRadius borderRadius =
+ wr::ToBorderRadius(zeroSize, zeroSize, zeroSize, zeroSize);
+ if (hasBorderRadius) {
+ borderRadius = wr::ToBorderRadius(
+ LayoutDeviceSize::FromUnknownSize(borderRadii.TopLeft()),
+ LayoutDeviceSize::FromUnknownSize(borderRadii.TopRight()),
+ LayoutDeviceSize::FromUnknownSize(borderRadii.BottomLeft()),
+ LayoutDeviceSize::FromUnknownSize(borderRadii.BottomRight()));
+ }
+
+ float spreadRadius =
+ float(shadow.spread.ToAppUnits()) / float(appUnitsPerDevPixel);
+
+ aBuilder.PushBoxShadow(deviceBoxRect, deviceClipRect, !BackfaceIsHidden(),
+ deviceBoxRect, wr::ToLayoutVector2D(shadowOffset),
+ wr::ToColorF(ToDeviceColor(shadowColor)), blurRadius,
+ spreadRadius, borderRadius,
+ wr::BoxShadowClipMode::Outset);
+ }
+
+ return true;
+}
+
+void nsDisplayBoxShadowOuter::ComputeInvalidationRegion(
+ nsDisplayListBuilder* aBuilder, const nsDisplayItemGeometry* aGeometry,
+ nsRegion* aInvalidRegion) const {
+ const auto* geometry =
+ static_cast<const nsDisplayItemGenericGeometry*>(aGeometry);
+ bool snap;
+ if (!geometry->mBounds.IsEqualInterior(GetBounds(aBuilder, &snap)) ||
+ !geometry->mBorderRect.IsEqualInterior(GetBorderRect())) {
+ nsRegion oldShadow, newShadow;
+ nscoord dontCare[8];
+ bool hasBorderRadius = mFrame->GetBorderRadii(dontCare);
+ if (hasBorderRadius) {
+ // If we have rounded corners then we need to invalidate the frame area
+ // too since we paint into it.
+ oldShadow = geometry->mBounds;
+ newShadow = GetBounds(aBuilder, &snap);
+ } else {
+ oldShadow.Sub(geometry->mBounds, geometry->mBorderRect);
+ newShadow.Sub(GetBounds(aBuilder, &snap), GetBorderRect());
+ }
+ aInvalidRegion->Or(oldShadow, newShadow);
+ }
+}
+
+void nsDisplayBoxShadowInner::Paint(nsDisplayListBuilder* aBuilder,
+ gfxContext* aCtx) {
+ nsPoint offset = ToReferenceFrame();
+ nsRect borderRect = nsRect(offset, mFrame->GetSize());
+ nsPresContext* presContext = mFrame->PresContext();
+
+ AUTO_PROFILER_LABEL("nsDisplayBoxShadowInner::Paint", GRAPHICS);
+
+ nsCSSRendering::PaintBoxShadowInner(presContext, *aCtx, mFrame, borderRect);
+}
+
+bool nsDisplayBoxShadowInner::CanCreateWebRenderCommands(
+ nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ const nsPoint& aReferenceOffset) {
+ auto shadows = aFrame->StyleEffects()->mBoxShadow.AsSpan();
+ if (shadows.IsEmpty()) {
+ // Means we don't have to paint anything
+ return true;
+ }
+
+ bool hasBorderRadius;
+ bool nativeTheme =
+ nsCSSRendering::HasBoxShadowNativeTheme(aFrame, hasBorderRadius);
+
+ // We don't support native themed things yet like box shadows around
+ // input buttons.
+ return !nativeTheme;
+}
+
+/* static */
+void nsDisplayBoxShadowInner::CreateInsetBoxShadowWebRenderCommands(
+ wr::DisplayListBuilder& aBuilder, const StackingContextHelper& aSc,
+ nsRect& aVisibleRect, nsIFrame* aFrame, const nsRect& aBorderRect) {
+ if (!nsCSSRendering::ShouldPaintBoxShadowInner(aFrame)) {
+ return;
+ }
+
+ int32_t appUnitsPerDevPixel = aFrame->PresContext()->AppUnitsPerDevPixel();
+
+ auto shadows = aFrame->StyleEffects()->mBoxShadow.AsSpan();
+
+ LayoutDeviceRect clipRect =
+ LayoutDeviceRect::FromAppUnits(aVisibleRect, appUnitsPerDevPixel);
+
+ for (const auto& shadow : Reversed(shadows)) {
+ if (!shadow.inset) {
+ continue;
+ }
+
+ nsRect shadowRect =
+ nsCSSRendering::GetBoxShadowInnerPaddingRect(aFrame, aBorderRect);
+ RectCornerRadii innerRadii;
+ nsCSSRendering::GetShadowInnerRadii(aFrame, aBorderRect, innerRadii);
+
+ // Now translate everything to device pixels.
+ LayoutDeviceRect deviceBoxRect =
+ LayoutDeviceRect::FromAppUnits(shadowRect, appUnitsPerDevPixel);
+ wr::LayoutRect deviceClipRect = wr::ToLayoutRect(clipRect);
+ sRGBColor shadowColor =
+ nsCSSRendering::GetShadowColor(shadow.base, aFrame, 1.0);
+
+ LayoutDevicePoint shadowOffset = LayoutDevicePoint::FromAppUnits(
+ nsPoint(shadow.base.horizontal.ToAppUnits(),
+ shadow.base.vertical.ToAppUnits()),
+ appUnitsPerDevPixel);
+
+ float blurRadius =
+ float(shadow.base.blur.ToAppUnits()) / float(appUnitsPerDevPixel);
+
+ wr::BorderRadius borderRadius = wr::ToBorderRadius(
+ LayoutDeviceSize::FromUnknownSize(innerRadii.TopLeft()),
+ LayoutDeviceSize::FromUnknownSize(innerRadii.TopRight()),
+ LayoutDeviceSize::FromUnknownSize(innerRadii.BottomLeft()),
+ LayoutDeviceSize::FromUnknownSize(innerRadii.BottomRight()));
+ // NOTE: Any spread radius > 0 will render nothing. WR Bug.
+ float spreadRadius =
+ float(shadow.spread.ToAppUnits()) / float(appUnitsPerDevPixel);
+
+ aBuilder.PushBoxShadow(
+ wr::ToLayoutRect(deviceBoxRect), deviceClipRect,
+ !aFrame->BackfaceIsHidden(), wr::ToLayoutRect(deviceBoxRect),
+ wr::ToLayoutVector2D(shadowOffset),
+ wr::ToColorF(ToDeviceColor(shadowColor)), blurRadius, spreadRadius,
+ borderRadius, wr::BoxShadowClipMode::Inset);
+ }
+}
+
+bool nsDisplayBoxShadowInner::CreateWebRenderCommands(
+ wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc, RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) {
+ if (!CanCreateWebRenderCommands(aDisplayListBuilder, mFrame,
+ ToReferenceFrame())) {
+ return false;
+ }
+
+ bool snap;
+ nsRect visible = GetBounds(aDisplayListBuilder, &snap);
+ nsPoint offset = ToReferenceFrame();
+ nsRect borderRect = nsRect(offset, mFrame->GetSize());
+ nsDisplayBoxShadowInner::CreateInsetBoxShadowWebRenderCommands(
+ aBuilder, aSc, visible, mFrame, borderRect);
+
+ return true;
+}
+
+nsDisplayWrapList::nsDisplayWrapList(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame, nsDisplayList* aList)
+ : nsDisplayWrapList(aBuilder, aFrame, aList,
+ aBuilder->CurrentActiveScrolledRoot(), false) {}
+
+nsDisplayWrapList::nsDisplayWrapList(
+ nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayList* aList,
+ const ActiveScrolledRoot* aActiveScrolledRoot, bool aClearClipChain)
+ : nsPaintedDisplayItem(aBuilder, aFrame, aActiveScrolledRoot),
+ mList(aBuilder),
+ mFrameActiveScrolledRoot(aBuilder->CurrentActiveScrolledRoot()),
+ mOverrideZIndex(0),
+ mHasZIndexOverride(false),
+ mClearingClipChain(aClearClipChain) {
+ MOZ_COUNT_CTOR(nsDisplayWrapList);
+
+ mBaseBuildingRect = GetBuildingRect();
+
+ mListPtr = &mList;
+ mListPtr->AppendToTop(aList);
+ mOriginalClipChain = mClipChain;
+ nsDisplayWrapList::UpdateBounds(aBuilder);
+}
+
+nsDisplayWrapList::nsDisplayWrapList(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame, nsDisplayItem* aItem)
+ : nsPaintedDisplayItem(aBuilder, aFrame,
+ aBuilder->CurrentActiveScrolledRoot()),
+ mList(aBuilder),
+ mOverrideZIndex(0),
+ mHasZIndexOverride(false) {
+ MOZ_COUNT_CTOR(nsDisplayWrapList);
+
+ mBaseBuildingRect = GetBuildingRect();
+
+ mListPtr = &mList;
+ mListPtr->AppendToTop(aItem);
+ mOriginalClipChain = mClipChain;
+ nsDisplayWrapList::UpdateBounds(aBuilder);
+
+ if (!aFrame || !aFrame->IsTransformed()) {
+ return;
+ }
+
+ // See the previous nsDisplayWrapList constructor
+ if (aItem->Frame() == aFrame) {
+ mToReferenceFrame = aItem->ToReferenceFrame();
+ }
+
+ nsRect visible = aBuilder->GetVisibleRect() +
+ aBuilder->GetCurrentFrameOffsetToReferenceFrame();
+
+ SetBuildingRect(visible);
+}
+
+nsDisplayWrapList::~nsDisplayWrapList() { MOZ_COUNT_DTOR(nsDisplayWrapList); }
+
+void nsDisplayWrapList::HitTest(nsDisplayListBuilder* aBuilder,
+ const nsRect& aRect, HitTestState* aState,
+ nsTArray<nsIFrame*>* aOutFrames) {
+ mListPtr->HitTest(aBuilder, aRect, aState, aOutFrames);
+}
+
+nsRect nsDisplayWrapList::GetBounds(nsDisplayListBuilder* aBuilder,
+ bool* aSnap) const {
+ *aSnap = false;
+ return mBounds;
+}
+
+nsRegion nsDisplayWrapList::GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
+ bool* aSnap) const {
+ *aSnap = false;
+ bool snap;
+ return ::mozilla::GetOpaqueRegion(aBuilder, GetChildren(),
+ GetBounds(aBuilder, &snap));
+}
+
+Maybe<nscolor> nsDisplayWrapList::IsUniform(
+ nsDisplayListBuilder* aBuilder) const {
+ // We could try to do something but let's conservatively just return Nothing.
+ return Nothing();
+}
+
+void nsDisplayWrapper::Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) {
+ NS_ERROR("nsDisplayWrapper should have been flattened away for painting");
+}
+
+nsRect nsDisplayWrapList::GetComponentAlphaBounds(
+ nsDisplayListBuilder* aBuilder) const {
+ return mListPtr->GetComponentAlphaBounds(aBuilder);
+}
+
+bool nsDisplayWrapList::CreateWebRenderCommandsNewClipListOption(
+ wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc, RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder, bool aNewClipList) {
+ aManager->CommandBuilder().CreateWebRenderCommandsFromDisplayList(
+ GetChildren(), this, aDisplayListBuilder, aSc, aBuilder, aResources,
+ aNewClipList);
+ return true;
+}
+
+static nsresult WrapDisplayList(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame, nsDisplayList* aList,
+ nsDisplayItemWrapper* aWrapper) {
+ if (!aList->GetTop()) {
+ return NS_OK;
+ }
+ nsDisplayItem* item = aWrapper->WrapList(aBuilder, aFrame, aList);
+ if (!item) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ // aList was emptied
+ aList->AppendToTop(item);
+ return NS_OK;
+}
+
+static nsresult WrapEachDisplayItem(nsDisplayListBuilder* aBuilder,
+ nsDisplayList* aList,
+ nsDisplayItemWrapper* aWrapper) {
+ for (nsDisplayItem* item : aList->TakeItems()) {
+ item = aWrapper->WrapItem(aBuilder, item);
+ if (!item) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ aList->AppendToTop(item);
+ }
+ // aList was emptied
+ return NS_OK;
+}
+
+nsresult nsDisplayItemWrapper::WrapLists(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame,
+ const nsDisplayListSet& aIn,
+ const nsDisplayListSet& aOut) {
+ nsresult rv = WrapListsInPlace(aBuilder, aFrame, aIn);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (&aOut == &aIn) {
+ return NS_OK;
+ }
+ aOut.BorderBackground()->AppendToTop(aIn.BorderBackground());
+ aOut.BlockBorderBackgrounds()->AppendToTop(aIn.BlockBorderBackgrounds());
+ aOut.Floats()->AppendToTop(aIn.Floats());
+ aOut.Content()->AppendToTop(aIn.Content());
+ aOut.PositionedDescendants()->AppendToTop(aIn.PositionedDescendants());
+ aOut.Outlines()->AppendToTop(aIn.Outlines());
+ return NS_OK;
+}
+
+nsresult nsDisplayItemWrapper::WrapListsInPlace(
+ nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ const nsDisplayListSet& aLists) {
+ nsresult rv;
+ if (WrapBorderBackground()) {
+ // Our border-backgrounds are in-flow
+ rv = WrapDisplayList(aBuilder, aFrame, aLists.BorderBackground(), this);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ // Our block border-backgrounds are in-flow
+ rv = WrapDisplayList(aBuilder, aFrame, aLists.BlockBorderBackgrounds(), this);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // The floats are not in flow
+ rv = WrapEachDisplayItem(aBuilder, aLists.Floats(), this);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // Our child content is in flow
+ rv = WrapDisplayList(aBuilder, aFrame, aLists.Content(), this);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // The positioned descendants may not be in-flow
+ rv = WrapEachDisplayItem(aBuilder, aLists.PositionedDescendants(), this);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // The outlines may not be in-flow
+ return WrapEachDisplayItem(aBuilder, aLists.Outlines(), this);
+}
+
+nsDisplayOpacity::nsDisplayOpacity(
+ nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayList* aList,
+ const ActiveScrolledRoot* aActiveScrolledRoot, bool aForEventsOnly,
+ bool aNeedsActiveLayer, bool aWrapsBackdropFilter)
+ : nsDisplayWrapList(aBuilder, aFrame, aList, aActiveScrolledRoot, true),
+ mOpacity(aFrame->StyleEffects()->mOpacity),
+ mForEventsOnly(aForEventsOnly),
+ mNeedsActiveLayer(aNeedsActiveLayer),
+ mChildOpacityState(ChildOpacityState::Unknown),
+ mWrapsBackdropFilter(aWrapsBackdropFilter) {
+ MOZ_COUNT_CTOR(nsDisplayOpacity);
+}
+
+void nsDisplayOpacity::HitTest(nsDisplayListBuilder* aBuilder,
+ const nsRect& aRect,
+ nsDisplayItem::HitTestState* aState,
+ nsTArray<nsIFrame*>* aOutFrames) {
+ AutoRestore<float> opacity(aState->mCurrentOpacity);
+ aState->mCurrentOpacity *= mOpacity;
+
+ // TODO(emilio): special-casing zero is a bit arbitrary... Maybe we should
+ // only consider fully opaque items? Or make this configurable somehow?
+ if (aBuilder->HitTestIsForVisibility() && mOpacity == 0.0f) {
+ return;
+ }
+ nsDisplayWrapList::HitTest(aBuilder, aRect, aState, aOutFrames);
+}
+
+nsRegion nsDisplayOpacity::GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
+ bool* aSnap) const {
+ *aSnap = false;
+ // The only time where mOpacity == 1.0 should be when we have will-change.
+ // We could report this as opaque then but when the will-change value starts
+ // animating the element would become non opaque and could cause repaints.
+ return nsRegion();
+}
+
+void nsDisplayOpacity::Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) {
+ if (GetOpacity() == 0.0f) {
+ return;
+ }
+
+ if (GetOpacity() == 1.0f) {
+ GetChildren()->Paint(aBuilder, aCtx,
+ mFrame->PresContext()->AppUnitsPerDevPixel());
+ return;
+ }
+
+ // TODO: Compute a bounds rect to pass to PushLayer for a smaller
+ // allocation.
+ aCtx->GetDrawTarget()->PushLayer(false, GetOpacity(), nullptr, gfx::Matrix());
+ GetChildren()->Paint(aBuilder, aCtx,
+ mFrame->PresContext()->AppUnitsPerDevPixel());
+ aCtx->GetDrawTarget()->PopLayer();
+}
+
+/* static */
+bool nsDisplayOpacity::NeedsActiveLayer(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame) {
+ return EffectCompositor::HasAnimationsForCompositor(
+ aFrame, DisplayItemType::TYPE_OPACITY) ||
+ (ActiveLayerTracker::IsStyleAnimated(
+ aBuilder, aFrame, nsCSSPropertyIDSet::OpacityProperties()));
+}
+
+bool nsDisplayOpacity::CanApplyOpacity(WebRenderLayerManager* aManager,
+ nsDisplayListBuilder* aBuilder) const {
+ return !EffectCompositor::HasAnimationsForCompositor(
+ mFrame, DisplayItemType::TYPE_OPACITY);
+}
+
+// Only try folding our opacity down if we have at most |kOpacityMaxChildCount|
+// children that don't overlap and can all apply the opacity to themselves.
+static const size_t kOpacityMaxChildCount = 3;
+
+// |kOpacityMaxListSize| defines an early exit condition for opacity items that
+// are likely have more child items than |kOpacityMaxChildCount|.
+static const size_t kOpacityMaxListSize = kOpacityMaxChildCount * 2;
+
+/**
+ * Recursively iterates through |aList| and collects at most
+ * |kOpacityMaxChildCount| display item pointers to items that return true for
+ * CanApplyOpacity(). The item pointers are added to |aArray|.
+ *
+ * LayerEventRegions and WrapList items are ignored.
+ *
+ * We need to do this recursively, because the child display items might contain
+ * nested nsDisplayWrapLists.
+ *
+ * Returns false if there are more than |kOpacityMaxChildCount| items, or if an
+ * item that returns false for CanApplyOpacity() is encountered.
+ * Otherwise returns true.
+ */
+static bool CollectItemsWithOpacity(WebRenderLayerManager* aManager,
+ nsDisplayListBuilder* aBuilder,
+ nsDisplayList* aList,
+ nsTArray<nsPaintedDisplayItem*>& aArray) {
+ if (aList->Length() > kOpacityMaxListSize) {
+ // Exit early, since |aList| will likely contain more than
+ // |kOpacityMaxChildCount| items.
+ return false;
+ }
+
+ for (nsDisplayItem* i : *aList) {
+ const DisplayItemType type = i->GetType();
+
+ if (type == DisplayItemType::TYPE_COMPOSITOR_HITTEST_INFO) {
+ continue;
+ }
+
+ // Descend only into wraplists.
+ if (type == DisplayItemType::TYPE_WRAP_LIST ||
+ type == DisplayItemType::TYPE_CONTAINER) {
+ // The current display item has children, process them first.
+ if (!CollectItemsWithOpacity(aManager, aBuilder, i->GetChildren(),
+ aArray)) {
+ return false;
+ }
+
+ continue;
+ }
+
+ if (aArray.Length() == kOpacityMaxChildCount) {
+ return false;
+ }
+
+ auto* item = i->AsPaintedDisplayItem();
+ if (!item || !item->CanApplyOpacity(aManager, aBuilder)) {
+ return false;
+ }
+
+ aArray.AppendElement(item);
+ }
+
+ return true;
+}
+
+bool nsDisplayOpacity::CanApplyToChildren(WebRenderLayerManager* aManager,
+ nsDisplayListBuilder* aBuilder) {
+ if (mChildOpacityState == ChildOpacityState::Deferred) {
+ return false;
+ }
+
+ // Iterate through the child display list and copy at most
+ // |kOpacityMaxChildCount| child display item pointers to a temporary list.
+ AutoTArray<nsPaintedDisplayItem*, kOpacityMaxChildCount> items;
+ if (!CollectItemsWithOpacity(aManager, aBuilder, &mList, items)) {
+ mChildOpacityState = ChildOpacityState::Deferred;
+ return false;
+ }
+
+ struct {
+ nsPaintedDisplayItem* item{};
+ nsRect bounds;
+ } children[kOpacityMaxChildCount];
+
+ bool snap;
+ size_t childCount = 0;
+ for (nsPaintedDisplayItem* item : items) {
+ children[childCount].item = item;
+ children[childCount].bounds = item->GetBounds(aBuilder, &snap);
+ childCount++;
+ }
+
+ for (size_t i = 0; i < childCount; i++) {
+ for (size_t j = i + 1; j < childCount; j++) {
+ if (children[i].bounds.Intersects(children[j].bounds)) {
+ mChildOpacityState = ChildOpacityState::Deferred;
+ return false;
+ }
+ }
+ }
+
+ mChildOpacityState = ChildOpacityState::Applied;
+ return true;
+}
+
+/**
+ * Returns true if this nsDisplayOpacity contains only a filter or a mask item
+ * that has the same frame as the opacity item, and that supports painting with
+ * opacity. In this case the opacity item can be optimized away.
+ */
+bool nsDisplayOpacity::ApplyToMask() {
+ if (mList.Length() != 1) {
+ return false;
+ }
+
+ nsDisplayItem* item = mList.GetBottom();
+ if (item->Frame() != mFrame) {
+ // The effect item needs to have the same frame as the opacity item.
+ return false;
+ }
+
+ const DisplayItemType type = item->GetType();
+ if (type == DisplayItemType::TYPE_MASK) {
+ return true;
+ }
+
+ return false;
+}
+
+bool nsDisplayOpacity::CanApplyOpacityToChildren(
+ WebRenderLayerManager* aManager, nsDisplayListBuilder* aBuilder,
+ float aInheritedOpacity) {
+ if (mFrame->GetPrevContinuation() || mFrame->GetNextContinuation() ||
+ mFrame->HasAnyStateBits(NS_FRAME_PART_OF_IBSPLIT)) {
+ // If we've been split, then we might need to merge, so
+ // don't flatten us away.
+ return false;
+ }
+
+ if (mNeedsActiveLayer || mOpacity == 0.0) {
+ // If our opacity is zero then we'll discard all descendant display items
+ // except for layer event regions, so there's no point in doing this
+ // optimization (and if we do do it, then invalidations of those descendants
+ // might trigger repainting).
+ return false;
+ }
+
+ if (mList.IsEmpty()) {
+ return false;
+ }
+
+ // We can only flatten opacity items into a mask if we haven't
+ // already flattened an earlier ancestor, since the SVG code pulls the opacity
+ // from style directly, and won't know about the outer opacity value.
+ if (aInheritedOpacity == 1.0f && ApplyToMask()) {
+ MOZ_ASSERT(SVGIntegrationUtils::UsingEffectsForFrame(mFrame));
+ mChildOpacityState = ChildOpacityState::Applied;
+ return true;
+ }
+
+ // Return true if we successfully applied opacity to child items.
+ return CanApplyToChildren(aManager, aBuilder);
+}
+
+void nsDisplayOpacity::ComputeInvalidationRegion(
+ nsDisplayListBuilder* aBuilder, const nsDisplayItemGeometry* aGeometry,
+ nsRegion* aInvalidRegion) const {
+ const auto* geometry =
+ static_cast<const nsDisplayOpacityGeometry*>(aGeometry);
+
+ bool snap;
+ if (mOpacity != geometry->mOpacity) {
+ aInvalidRegion->Or(GetBounds(aBuilder, &snap), geometry->mBounds);
+ }
+}
+
+void nsDisplayOpacity::WriteDebugInfo(std::stringstream& aStream) {
+ aStream << " (opacity " << mOpacity << ", mChildOpacityState: ";
+ switch (mChildOpacityState) {
+ case ChildOpacityState::Unknown:
+ aStream << "Unknown";
+ break;
+ case ChildOpacityState::Applied:
+ aStream << "Applied";
+ break;
+ case ChildOpacityState::Deferred:
+ aStream << "Deferred";
+ break;
+ default:
+ break;
+ }
+
+ aStream << ")";
+}
+
+bool nsDisplayOpacity::CreateWebRenderCommands(
+ wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc, RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) {
+ MOZ_ASSERT(mChildOpacityState != ChildOpacityState::Applied);
+ float oldOpacity = aBuilder.GetInheritedOpacity();
+ const DisplayItemClipChain* oldClipChain = aBuilder.GetInheritedClipChain();
+ aBuilder.SetInheritedOpacity(1.0f);
+ aBuilder.SetInheritedClipChain(nullptr);
+ float opacity = mOpacity * oldOpacity;
+ float* opacityForSC = &opacity;
+
+ uint64_t animationsId =
+ AddAnimationsForWebRender(this, aManager, aDisplayListBuilder);
+ wr::WrAnimationProperty prop{
+ wr::WrAnimationType::Opacity,
+ animationsId,
+ };
+
+ wr::StackingContextParams params;
+ params.animation = animationsId ? &prop : nullptr;
+ params.opacity = opacityForSC;
+ params.clip =
+ wr::WrStackingContextClip::ClipChain(aBuilder.CurrentClipChainId());
+ if (mWrapsBackdropFilter) {
+ params.flags |= wr::StackingContextFlags::WRAPS_BACKDROP_FILTER;
+ }
+ StackingContextHelper sc(aSc, GetActiveScrolledRoot(), mFrame, this, aBuilder,
+ params);
+
+ aManager->CommandBuilder().CreateWebRenderCommandsFromDisplayList(
+ &mList, this, aDisplayListBuilder, sc, aBuilder, aResources);
+ aBuilder.SetInheritedOpacity(oldOpacity);
+ aBuilder.SetInheritedClipChain(oldClipChain);
+ return true;
+}
+
+nsDisplayBlendMode::nsDisplayBlendMode(
+ nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayList* aList,
+ StyleBlend aBlendMode, const ActiveScrolledRoot* aActiveScrolledRoot,
+ const bool aIsForBackground)
+ : nsDisplayWrapList(aBuilder, aFrame, aList, aActiveScrolledRoot, true),
+ mBlendMode(aBlendMode),
+ mIsForBackground(aIsForBackground) {
+ MOZ_COUNT_CTOR(nsDisplayBlendMode);
+}
+
+nsRegion nsDisplayBlendMode::GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
+ bool* aSnap) const {
+ *aSnap = false;
+ // We are never considered opaque
+ return nsRegion();
+}
+
+bool nsDisplayBlendMode::CreateWebRenderCommands(
+ wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc, RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) {
+ wr::StackingContextParams params;
+ params.mix_blend_mode =
+ wr::ToMixBlendMode(nsCSSRendering::GetGFXBlendMode(mBlendMode));
+ params.clip =
+ wr::WrStackingContextClip::ClipChain(aBuilder.CurrentClipChainId());
+ StackingContextHelper sc(aSc, GetActiveScrolledRoot(), mFrame, this, aBuilder,
+ params);
+
+ return nsDisplayWrapList::CreateWebRenderCommands(
+ aBuilder, aResources, sc, aManager, aDisplayListBuilder);
+}
+
+void nsDisplayBlendMode::Paint(nsDisplayListBuilder* aBuilder,
+ gfxContext* aCtx) {
+ // This should be switched to use PushLayerWithBlend, once it's
+ // been implemented for all DrawTarget backends.
+ DrawTarget* dt = aCtx->GetDrawTarget();
+ int32_t appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel();
+ Rect rect = NSRectToRect(GetPaintRect(aBuilder, aCtx), appUnitsPerDevPixel);
+ rect.RoundOut();
+
+ // Create a temporary DrawTarget that is clipped to the area that
+ // we're going to draw to. This will include the same transform as
+ // is currently on |dt|.
+ RefPtr<DrawTarget> temp =
+ dt->CreateClippedDrawTarget(rect, SurfaceFormat::B8G8R8A8);
+ if (!temp) {
+ return;
+ }
+
+ gfxContext ctx(temp, /* aPreserveTransform */ true);
+
+ GetChildren()->Paint(aBuilder, &ctx,
+ mFrame->PresContext()->AppUnitsPerDevPixel());
+
+ // Draw the temporary DT to the real destination, applying the blend mode, but
+ // no transform.
+ temp->Flush();
+ RefPtr<SourceSurface> surface = temp->Snapshot();
+ gfxContextMatrixAutoSaveRestore saveMatrix(aCtx);
+ dt->SetTransform(Matrix());
+ dt->DrawSurface(
+ surface, Rect(surface->GetRect()), Rect(surface->GetRect()),
+ DrawSurfaceOptions(),
+ DrawOptions(1.0f, nsCSSRendering::GetGFXBlendMode(mBlendMode)));
+}
+
+gfx::CompositionOp nsDisplayBlendMode::BlendMode() {
+ return nsCSSRendering::GetGFXBlendMode(mBlendMode);
+}
+
+bool nsDisplayBlendMode::CanMerge(const nsDisplayItem* aItem) const {
+ // Items for the same content element should be merged into a single
+ // compositing group.
+ if (!HasDifferentFrame(aItem) || !HasSameTypeAndClip(aItem) ||
+ !HasSameContent(aItem)) {
+ return false;
+ }
+
+ const auto* item = static_cast<const nsDisplayBlendMode*>(aItem);
+ if (mIsForBackground || item->mIsForBackground) {
+ // Don't merge background-blend-mode items
+ return false;
+ }
+
+ return true;
+}
+
+/* static */
+nsDisplayBlendContainer* nsDisplayBlendContainer::CreateForMixBlendMode(
+ nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayList* aList,
+ const ActiveScrolledRoot* aActiveScrolledRoot) {
+ return MakeDisplayItem<nsDisplayBlendContainer>(aBuilder, aFrame, aList,
+ aActiveScrolledRoot, false);
+}
+
+/* static */
+nsDisplayBlendContainer* nsDisplayBlendContainer::CreateForBackgroundBlendMode(
+ nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsIFrame* aSecondaryFrame,
+ nsDisplayList* aList, const ActiveScrolledRoot* aActiveScrolledRoot) {
+ if (aSecondaryFrame) {
+ auto type = GetTableTypeFromFrame(aFrame);
+ auto index = static_cast<uint16_t>(type);
+
+ return MakeDisplayItemWithIndex<nsDisplayTableBlendContainer>(
+ aBuilder, aSecondaryFrame, index, aList, aActiveScrolledRoot, true,
+ aFrame);
+ }
+
+ return MakeDisplayItemWithIndex<nsDisplayBlendContainer>(
+ aBuilder, aFrame, 1, aList, aActiveScrolledRoot, true);
+}
+
+nsDisplayBlendContainer::nsDisplayBlendContainer(
+ nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayList* aList,
+ const ActiveScrolledRoot* aActiveScrolledRoot, bool aIsForBackground)
+ : nsDisplayWrapList(aBuilder, aFrame, aList, aActiveScrolledRoot, true),
+ mIsForBackground(aIsForBackground) {
+ MOZ_COUNT_CTOR(nsDisplayBlendContainer);
+}
+
+void nsDisplayBlendContainer::Paint(nsDisplayListBuilder* aBuilder,
+ gfxContext* aCtx) {
+ aCtx->GetDrawTarget()->PushLayer(false, 1.0, nullptr, gfx::Matrix());
+ GetChildren()->Paint(aBuilder, aCtx,
+ mFrame->PresContext()->AppUnitsPerDevPixel());
+ aCtx->GetDrawTarget()->PopLayer();
+}
+
+bool nsDisplayBlendContainer::CreateWebRenderCommands(
+ wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc, RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) {
+ wr::StackingContextParams params;
+ params.flags |= wr::StackingContextFlags::IS_BLEND_CONTAINER;
+ params.clip =
+ wr::WrStackingContextClip::ClipChain(aBuilder.CurrentClipChainId());
+ StackingContextHelper sc(aSc, GetActiveScrolledRoot(), mFrame, this, aBuilder,
+ params);
+
+ return nsDisplayWrapList::CreateWebRenderCommands(
+ aBuilder, aResources, sc, aManager, aDisplayListBuilder);
+}
+
+nsDisplayOwnLayer::nsDisplayOwnLayer(
+ nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayList* aList,
+ const ActiveScrolledRoot* aActiveScrolledRoot,
+ nsDisplayOwnLayerFlags aFlags, const ScrollbarData& aScrollbarData,
+ bool aForceActive, bool aClearClipChain)
+ : nsDisplayWrapList(aBuilder, aFrame, aList, aActiveScrolledRoot,
+ aClearClipChain),
+ mFlags(aFlags),
+ mScrollbarData(aScrollbarData),
+ mForceActive(aForceActive),
+ mWrAnimationId(0) {
+ MOZ_COUNT_CTOR(nsDisplayOwnLayer);
+}
+
+bool nsDisplayOwnLayer::IsScrollThumbLayer() const {
+ return mScrollbarData.mScrollbarLayerType == ScrollbarLayerType::Thumb;
+}
+
+bool nsDisplayOwnLayer::IsScrollbarContainer() const {
+ return mScrollbarData.mScrollbarLayerType == ScrollbarLayerType::Container;
+}
+
+bool nsDisplayOwnLayer::IsRootScrollbarContainer() const {
+ if (!IsScrollbarContainer()) {
+ return false;
+ }
+
+ return mFrame->PresContext()->IsRootContentDocumentCrossProcess() &&
+ mScrollbarData.mTargetViewId ==
+ nsLayoutUtils::ScrollIdForRootScrollFrame(mFrame->PresContext());
+}
+
+bool nsDisplayOwnLayer::IsZoomingLayer() const {
+ return GetType() == DisplayItemType::TYPE_ASYNC_ZOOM;
+}
+
+bool nsDisplayOwnLayer::IsFixedPositionLayer() const {
+ return GetType() == DisplayItemType::TYPE_FIXED_POSITION;
+}
+
+bool nsDisplayOwnLayer::IsStickyPositionLayer() const {
+ return GetType() == DisplayItemType::TYPE_STICKY_POSITION;
+}
+
+bool nsDisplayOwnLayer::HasDynamicToolbar() const {
+ if (!mFrame->PresContext()->IsRootContentDocumentCrossProcess()) {
+ return false;
+ }
+ return mFrame->PresContext()->HasDynamicToolbar() ||
+ // For tests on Android, this pref is set to simulate the dynamic
+ // toolbar
+ StaticPrefs::apz_fixed_margin_override_enabled();
+}
+
+bool nsDisplayOwnLayer::ShouldFixedAndStickyContentGetAnimationIds() const {
+#if defined(MOZ_WIDGET_ANDROID)
+ return mFrame->PresContext()->IsRootContentDocumentCrossProcess();
+#else
+ return false;
+#endif
+}
+
+bool nsDisplayOwnLayer::CreateWebRenderCommands(
+ wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc, RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) {
+ Maybe<wr::WrAnimationProperty> prop;
+ bool needsProp = aManager->LayerManager()->AsyncPanZoomEnabled() &&
+ (IsScrollThumbLayer() || IsZoomingLayer() ||
+ (IsFixedPositionLayer() &&
+ ShouldFixedAndStickyContentGetAnimationIds()) ||
+ (IsStickyPositionLayer() &&
+ ShouldFixedAndStickyContentGetAnimationIds()) ||
+ (IsRootScrollbarContainer() && HasDynamicToolbar()));
+
+ if (needsProp) {
+ // APZ is enabled and this is a scroll thumb or zooming layer, so we need
+ // to create and set an animation id. That way APZ can adjust the position/
+ // zoom of this content asynchronously as needed.
+ RefPtr<WebRenderAPZAnimationData> animationData =
+ aManager->CommandBuilder()
+ .CreateOrRecycleWebRenderUserData<WebRenderAPZAnimationData>(this);
+ mWrAnimationId = animationData->GetAnimationId();
+
+ prop.emplace();
+ prop->id = mWrAnimationId;
+ prop->key = wr::SpatialKey(uint64_t(mFrame), GetPerFrameKey(),
+ wr::SpatialKeyKind::APZ);
+ prop->effect_type = wr::WrAnimationType::Transform;
+ }
+
+ wr::StackingContextParams params;
+ params.animation = prop.ptrOr(nullptr);
+ params.clip =
+ wr::WrStackingContextClip::ClipChain(aBuilder.CurrentClipChainId());
+ if (IsScrollbarContainer() && IsRootScrollbarContainer()) {
+ params.prim_flags |= wr::PrimitiveFlags::IS_SCROLLBAR_CONTAINER;
+ }
+ if (IsZoomingLayer() ||
+ ((IsFixedPositionLayer() &&
+ ShouldFixedAndStickyContentGetAnimationIds()) ||
+ (IsStickyPositionLayer() &&
+ ShouldFixedAndStickyContentGetAnimationIds()) ||
+ (IsRootScrollbarContainer() && HasDynamicToolbar()))) {
+ params.is_2d_scale_translation = true;
+ params.should_snap = true;
+ }
+
+ StackingContextHelper sc(aSc, GetActiveScrolledRoot(), mFrame, this, aBuilder,
+ params);
+
+ nsDisplayWrapList::CreateWebRenderCommands(aBuilder, aResources, sc, aManager,
+ aDisplayListBuilder);
+ return true;
+}
+
+bool nsDisplayOwnLayer::UpdateScrollData(WebRenderScrollData* aData,
+ WebRenderLayerScrollData* aLayerData) {
+ bool isRelevantToApz =
+ (IsScrollThumbLayer() || IsScrollbarContainer() || IsZoomingLayer() ||
+ (IsFixedPositionLayer() &&
+ ShouldFixedAndStickyContentGetAnimationIds()) ||
+ (IsStickyPositionLayer() &&
+ ShouldFixedAndStickyContentGetAnimationIds()));
+
+ if (!isRelevantToApz) {
+ return false;
+ }
+
+ if (!aLayerData) {
+ return true;
+ }
+
+ if (IsZoomingLayer()) {
+ aLayerData->SetZoomAnimationId(mWrAnimationId);
+ return true;
+ }
+
+ if (IsFixedPositionLayer() && ShouldFixedAndStickyContentGetAnimationIds()) {
+ aLayerData->SetFixedPositionAnimationId(mWrAnimationId);
+ return true;
+ }
+
+ if (IsStickyPositionLayer() && ShouldFixedAndStickyContentGetAnimationIds()) {
+ aLayerData->SetStickyPositionAnimationId(mWrAnimationId);
+ return true;
+ }
+
+ MOZ_ASSERT(IsScrollbarContainer() || IsScrollThumbLayer());
+
+ aLayerData->SetScrollbarData(mScrollbarData);
+
+ if (IsRootScrollbarContainer() && HasDynamicToolbar()) {
+ aLayerData->SetScrollbarAnimationId(mWrAnimationId);
+ return true;
+ }
+
+ if (IsScrollThumbLayer()) {
+ aLayerData->SetScrollbarAnimationId(mWrAnimationId);
+ LayoutDeviceRect bounds = LayoutDeviceIntRect::FromAppUnits(
+ mBounds, mFrame->PresContext()->AppUnitsPerDevPixel());
+ // We use a resolution of 1.0 because this is a WebRender codepath which
+ // always uses containerless scrolling, and so resolution doesn't apply to
+ // scrollbars.
+ LayerIntRect layerBounds =
+ RoundedOut(bounds * LayoutDeviceToLayerScale(1.0f));
+ aLayerData->SetVisibleRegion(LayerIntRegion(layerBounds));
+ }
+ return true;
+}
+
+void nsDisplayOwnLayer::WriteDebugInfo(std::stringstream& aStream) {
+ aStream << nsPrintfCString(" (flags 0x%x) (scrolltarget %" PRIu64 ")",
+ (int)mFlags, mScrollbarData.mTargetViewId)
+ .get();
+}
+
+nsDisplaySubDocument::nsDisplaySubDocument(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame,
+ nsSubDocumentFrame* aSubDocFrame,
+ nsDisplayList* aList,
+ nsDisplayOwnLayerFlags aFlags)
+ : nsDisplayOwnLayer(aBuilder, aFrame, aList,
+ aBuilder->CurrentActiveScrolledRoot(), aFlags),
+ mScrollParentId(aBuilder->GetCurrentScrollParentId()),
+ mShouldFlatten(false),
+ mSubDocFrame(aSubDocFrame) {
+ MOZ_COUNT_CTOR(nsDisplaySubDocument);
+
+ if (mSubDocFrame && mSubDocFrame != mFrame) {
+ mSubDocFrame->AddDisplayItem(this);
+ }
+}
+
+nsDisplaySubDocument::~nsDisplaySubDocument() {
+ MOZ_COUNT_DTOR(nsDisplaySubDocument);
+ if (mSubDocFrame) {
+ mSubDocFrame->RemoveDisplayItem(this);
+ }
+}
+
+nsIFrame* nsDisplaySubDocument::FrameForInvalidation() const {
+ return mSubDocFrame ? mSubDocFrame : mFrame;
+}
+
+void nsDisplaySubDocument::RemoveFrame(nsIFrame* aFrame) {
+ if (aFrame == mSubDocFrame) {
+ mSubDocFrame = nullptr;
+ SetDeletedFrame();
+ }
+ nsDisplayOwnLayer::RemoveFrame(aFrame);
+}
+
+static bool UseDisplayPortForViewport(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame) {
+ return aBuilder->IsPaintingToWindow() &&
+ DisplayPortUtils::ViewportHasDisplayPort(aFrame->PresContext());
+}
+
+nsRect nsDisplaySubDocument::GetBounds(nsDisplayListBuilder* aBuilder,
+ bool* aSnap) const {
+ bool usingDisplayPort = UseDisplayPortForViewport(aBuilder, mFrame);
+
+ if ((mFlags & nsDisplayOwnLayerFlags::GenerateScrollableLayer) &&
+ usingDisplayPort) {
+ *aSnap = false;
+ return mFrame->GetRect() + aBuilder->ToReferenceFrame(mFrame);
+ }
+
+ return nsDisplayOwnLayer::GetBounds(aBuilder, aSnap);
+}
+
+nsRegion nsDisplaySubDocument::GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
+ bool* aSnap) const {
+ bool usingDisplayPort = UseDisplayPortForViewport(aBuilder, mFrame);
+
+ if ((mFlags & nsDisplayOwnLayerFlags::GenerateScrollableLayer) &&
+ usingDisplayPort) {
+ *aSnap = false;
+ return nsRegion();
+ }
+
+ return nsDisplayOwnLayer::GetOpaqueRegion(aBuilder, aSnap);
+}
+
+/* static */
+nsDisplayFixedPosition* nsDisplayFixedPosition::CreateForFixedBackground(
+ nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsIFrame* aSecondaryFrame,
+ nsDisplayBackgroundImage* aImage, const uint16_t aIndex,
+ const ActiveScrolledRoot* aScrollTargetASR) {
+ nsDisplayList temp(aBuilder);
+ temp.AppendToTop(aImage);
+
+ if (aSecondaryFrame) {
+ auto tableType = GetTableTypeFromFrame(aFrame);
+ const uint16_t index = CalculateTablePerFrameKey(aIndex + 1, tableType);
+ return MakeDisplayItemWithIndex<nsDisplayTableFixedPosition>(
+ aBuilder, aSecondaryFrame, index, &temp, aFrame, aScrollTargetASR);
+ }
+
+ return MakeDisplayItemWithIndex<nsDisplayFixedPosition>(
+ aBuilder, aFrame, aIndex + 1, &temp, aScrollTargetASR);
+}
+
+nsDisplayFixedPosition::nsDisplayFixedPosition(
+ nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayList* aList,
+ const ActiveScrolledRoot* aActiveScrolledRoot,
+ const ActiveScrolledRoot* aScrollTargetASR)
+ : nsDisplayOwnLayer(aBuilder, aFrame, aList, aActiveScrolledRoot),
+ mScrollTargetASR(aScrollTargetASR),
+ mIsFixedBackground(false) {
+ MOZ_COUNT_CTOR(nsDisplayFixedPosition);
+}
+
+nsDisplayFixedPosition::nsDisplayFixedPosition(
+ nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayList* aList,
+ const ActiveScrolledRoot* aScrollTargetASR)
+ : nsDisplayOwnLayer(aBuilder, aFrame, aList,
+ aBuilder->CurrentActiveScrolledRoot()),
+ // For fixed backgrounds, this is the ASR for the nearest scroll frame.
+ mScrollTargetASR(aScrollTargetASR),
+ mIsFixedBackground(true) {
+ MOZ_COUNT_CTOR(nsDisplayFixedPosition);
+}
+
+ScrollableLayerGuid::ViewID nsDisplayFixedPosition::GetScrollTargetId() {
+ if (mScrollTargetASR &&
+ (mIsFixedBackground || !nsLayoutUtils::IsReallyFixedPos(mFrame))) {
+ return mScrollTargetASR->GetViewId();
+ }
+ return nsLayoutUtils::ScrollIdForRootScrollFrame(mFrame->PresContext());
+}
+
+bool nsDisplayFixedPosition::CreateWebRenderCommands(
+ wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc, RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) {
+ SideBits sides = SideBits::eNone;
+ if (!mIsFixedBackground) {
+ sides = nsLayoutUtils::GetSideBitsForFixedPositionContent(mFrame);
+ }
+
+ // We install this RAII scrolltarget tracker so that any
+ // nsDisplayCompositorHitTestInfo items inside this fixed-pos item (and that
+ // share the same ASR as this item) use the correct scroll target. That way
+ // attempts to scroll on those items will scroll the root scroll frame.
+ wr::DisplayListBuilder::FixedPosScrollTargetTracker tracker(
+ aBuilder, GetActiveScrolledRoot(), GetScrollTargetId(), sides);
+ return nsDisplayOwnLayer::CreateWebRenderCommands(
+ aBuilder, aResources, aSc, aManager, aDisplayListBuilder);
+}
+
+bool nsDisplayFixedPosition::UpdateScrollData(
+ WebRenderScrollData* aData, WebRenderLayerScrollData* aLayerData) {
+ if (aLayerData) {
+ if (!mIsFixedBackground) {
+ aLayerData->SetFixedPositionSides(
+ nsLayoutUtils::GetSideBitsForFixedPositionContent(mFrame));
+ }
+ aLayerData->SetFixedPositionScrollContainerId(GetScrollTargetId());
+ }
+ nsDisplayOwnLayer::UpdateScrollData(aData, aLayerData);
+ return true;
+}
+
+void nsDisplayFixedPosition::WriteDebugInfo(std::stringstream& aStream) {
+ aStream << nsPrintfCString(
+ " (containerASR %s) (scrolltarget %" PRIu64 ")",
+ ActiveScrolledRoot::ToString(mScrollTargetASR).get(),
+ GetScrollTargetId())
+ .get();
+}
+
+TableType GetTableTypeFromFrame(nsIFrame* aFrame) {
+ if (aFrame->IsTableFrame()) {
+ return TableType::Table;
+ }
+
+ if (aFrame->IsTableColFrame()) {
+ return TableType::TableCol;
+ }
+
+ if (aFrame->IsTableColGroupFrame()) {
+ return TableType::TableColGroup;
+ }
+
+ if (aFrame->IsTableRowFrame()) {
+ return TableType::TableRow;
+ }
+
+ if (aFrame->IsTableRowGroupFrame()) {
+ return TableType::TableRowGroup;
+ }
+
+ if (aFrame->IsTableCellFrame()) {
+ return TableType::TableCell;
+ }
+
+ MOZ_ASSERT_UNREACHABLE("Invalid frame.");
+ return TableType::Table;
+}
+
+nsDisplayTableFixedPosition::nsDisplayTableFixedPosition(
+ nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayList* aList,
+ nsIFrame* aAncestorFrame, const ActiveScrolledRoot* aScrollTargetASR)
+ : nsDisplayFixedPosition(aBuilder, aFrame, aList, aScrollTargetASR),
+ mAncestorFrame(aAncestorFrame) {
+ if (aBuilder->IsRetainingDisplayList()) {
+ mAncestorFrame->AddDisplayItem(this);
+ }
+}
+
+nsDisplayStickyPosition::nsDisplayStickyPosition(
+ nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayList* aList,
+ const ActiveScrolledRoot* aActiveScrolledRoot,
+ const ActiveScrolledRoot* aContainerASR, bool aClippedToDisplayPort)
+ : nsDisplayOwnLayer(aBuilder, aFrame, aList, aActiveScrolledRoot),
+ mContainerASR(aContainerASR),
+ mClippedToDisplayPort(aClippedToDisplayPort),
+ mShouldFlatten(false) {
+ MOZ_COUNT_CTOR(nsDisplayStickyPosition);
+}
+
+// Returns the smallest distance from "0" to the range [min, max] where
+// min <= max. Despite the name, the return value is actually a 1-D vector,
+// and so may be negative if max < 0.
+static nscoord DistanceToRange(nscoord min, nscoord max) {
+ MOZ_ASSERT(min <= max);
+ if (max < 0) {
+ return max;
+ }
+ if (min > 0) {
+ return min;
+ }
+ MOZ_ASSERT(min <= 0 && max >= 0);
+ return 0;
+}
+
+// Returns the magnitude of the part of the range [min, max] that is greater
+// than zero. The return value is always non-negative.
+static nscoord PositivePart(nscoord min, nscoord max) {
+ MOZ_ASSERT(min <= max);
+ if (min >= 0) {
+ return max - min;
+ }
+ if (max > 0) {
+ return max;
+ }
+ return 0;
+}
+
+// Returns the magnitude of the part of the range [min, max] that is less
+// than zero. The return value is always non-negative.
+static nscoord NegativePart(nscoord min, nscoord max) {
+ MOZ_ASSERT(min <= max);
+ if (max <= 0) {
+ return max - min;
+ }
+ if (min < 0) {
+ return 0 - min;
+ }
+ return 0;
+}
+
+StickyScrollContainer* nsDisplayStickyPosition::GetStickyScrollContainer() {
+ StickyScrollContainer* stickyScrollContainer =
+ StickyScrollContainer::GetStickyScrollContainerForFrame(mFrame);
+ if (stickyScrollContainer) {
+ // If there's no ASR for the scrollframe that this sticky item is attached
+ // to, then don't create a WR sticky item for it either. Trying to do so
+ // will end in sadness because WR will interpret some coordinates as
+ // relative to the nearest enclosing scrollframe, which will correspond
+ // to the nearest ancestor ASR on the gecko side. That ASR will not be the
+ // same as the scrollframe this sticky item is actually supposed to be
+ // attached to, thus the sadness.
+ // Not sending WR the sticky item is ok, because the enclosing scrollframe
+ // will never be asynchronously scrolled. Instead we will always position
+ // the sticky items correctly on the gecko side and WR will never need to
+ // adjust their position itself.
+ MOZ_ASSERT(
+ stickyScrollContainer->ScrollFrame()->IsMaybeAsynchronouslyScrolled());
+ if (!stickyScrollContainer->ScrollFrame()
+ ->IsMaybeAsynchronouslyScrolled()) {
+ stickyScrollContainer = nullptr;
+ }
+ }
+ return stickyScrollContainer;
+}
+
+bool nsDisplayStickyPosition::CreateWebRenderCommands(
+ wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc, RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) {
+ StickyScrollContainer* stickyScrollContainer = GetStickyScrollContainer();
+
+ Maybe<wr::SpaceAndClipChainHelper> saccHelper;
+
+ if (stickyScrollContainer) {
+ float auPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel();
+
+ bool snap;
+ nsRect itemBounds = GetBounds(aDisplayListBuilder, &snap);
+
+ Maybe<float> topMargin;
+ Maybe<float> rightMargin;
+ Maybe<float> bottomMargin;
+ Maybe<float> leftMargin;
+ wr::StickyOffsetBounds vBounds = {0.0, 0.0};
+ wr::StickyOffsetBounds hBounds = {0.0, 0.0};
+ nsPoint appliedOffset;
+
+ nsRectAbsolute outer;
+ nsRectAbsolute inner;
+ stickyScrollContainer->GetScrollRanges(mFrame, &outer, &inner);
+
+ nsIFrame* scrollFrame = do_QueryFrame(stickyScrollContainer->ScrollFrame());
+ nsPoint offset =
+ scrollFrame->GetOffsetToCrossDoc(Frame()) + ToReferenceFrame();
+
+ // Adjust the scrollPort coordinates to be relative to the reference frame,
+ // so that it is in the same space as everything else.
+ nsRect scrollPort =
+ stickyScrollContainer->ScrollFrame()->GetScrollPortRect();
+ scrollPort += offset;
+
+ // The following computations make more sense upon understanding the
+ // semantics of "inner" and "outer", which is explained in the comment on
+ // SetStickyPositionData in Layers.h.
+
+ if (outer.YMost() != inner.YMost()) {
+ // Question: How far will itemBounds.y be from the top of the scrollport
+ // when we have scrolled from the current scroll position of "0" to
+ // reach the range [inner.YMost(), outer.YMost()] where the item gets
+ // stuck?
+ // Answer: the current distance is "itemBounds.y - scrollPort.y". That
+ // needs to be adjusted by the distance to the range, less any other
+ // sticky ranges that fall between 0 and the range. If the distance is
+ // negative (i.e. inner.YMost() <= outer.YMost() < 0) then we would be
+ // scrolling upwards (decreasing scroll offset) to reach that range,
+ // which would increase itemBounds.y and make it farther away from the
+ // top of the scrollport. So in that case the adjustment is -distance.
+ // If the distance is positive (0 < inner.YMost() <= outer.YMost()) then
+ // we would be scrolling downwards, itemBounds.y would decrease, and we
+ // again need to adjust by -distance. If we are already in the range
+ // then no adjustment is needed and distance is 0 so again using
+ // -distance works. If the distance is positive, and the item has both
+ // top and bottom sticky ranges, then the bottom sticky range may fall
+ // (entirely[1] or partly[2]) between the current scroll position.
+ // [1]: 0 <= outer.Y() <= inner.Y() < inner.YMost() <= outer.YMost()
+ // [2]: outer.Y() < 0 <= inner.Y() < inner.YMost() <= outer.YMost()
+ // In these cases, the item doesn't actually move for that part of the
+ // distance, so we need to subtract out that bit, which can be computed
+ // as the positive portion of the range [outer.Y(), inner.Y()].
+ nscoord distance = DistanceToRange(inner.YMost(), outer.YMost());
+ if (distance > 0) {
+ distance -= PositivePart(outer.Y(), inner.Y());
+ }
+ topMargin = Some(NSAppUnitsToFloatPixels(
+ itemBounds.y - scrollPort.y - distance, auPerDevPixel));
+ // Question: What is the maximum positive ("downward") offset that WR
+ // will have to apply to this item in order to prevent the item from
+ // visually moving?
+ // Answer: Since the item is "sticky" in the range [inner.YMost(),
+ // outer.YMost()], the maximum offset will be the size of the range, which
+ // is outer.YMost() - inner.YMost().
+ vBounds.max =
+ NSAppUnitsToFloatPixels(outer.YMost() - inner.YMost(), auPerDevPixel);
+ // Question: how much of an offset has layout already applied to the item?
+ // Answer: if we are
+ // (a) inside the sticky range (inner.YMost() < 0 <= outer.YMost()), or
+ // (b) past the sticky range (inner.YMost() < outer.YMost() < 0)
+ // then layout has already applied some offset to the position of the
+ // item. The amount of the adjustment is |0 - inner.YMost()| in case (a)
+ // and |outer.YMost() - inner.YMost()| in case (b).
+ if (inner.YMost() < 0) {
+ appliedOffset.y = std::min(0, outer.YMost()) - inner.YMost();
+ MOZ_ASSERT(appliedOffset.y > 0);
+ }
+ }
+ if (outer.Y() != inner.Y()) {
+ // Similar logic as in the previous section, but this time we care about
+ // the distance from itemBounds.YMost() to scrollPort.YMost().
+ nscoord distance = DistanceToRange(outer.Y(), inner.Y());
+ if (distance < 0) {
+ distance += NegativePart(inner.YMost(), outer.YMost());
+ }
+ bottomMargin = Some(NSAppUnitsToFloatPixels(
+ scrollPort.YMost() - itemBounds.YMost() + distance, auPerDevPixel));
+ // And here WR will be moving the item upwards rather than downwards so
+ // again things are inverted from the previous block.
+ vBounds.min =
+ NSAppUnitsToFloatPixels(outer.Y() - inner.Y(), auPerDevPixel);
+ // We can't have appliedOffset be both positive and negative, and the top
+ // adjustment takes priority. So here we only update appliedOffset.y if
+ // it wasn't set by the top-sticky case above.
+ if (appliedOffset.y == 0 && inner.Y() > 0) {
+ appliedOffset.y = std::max(0, outer.Y()) - inner.Y();
+ MOZ_ASSERT(appliedOffset.y < 0);
+ }
+ }
+ // Same as above, but for the x-axis
+ if (outer.XMost() != inner.XMost()) {
+ nscoord distance = DistanceToRange(inner.XMost(), outer.XMost());
+ if (distance > 0) {
+ distance -= PositivePart(outer.X(), inner.X());
+ }
+ leftMargin = Some(NSAppUnitsToFloatPixels(
+ itemBounds.x - scrollPort.x - distance, auPerDevPixel));
+ hBounds.max =
+ NSAppUnitsToFloatPixels(outer.XMost() - inner.XMost(), auPerDevPixel);
+ if (inner.XMost() < 0) {
+ appliedOffset.x = std::min(0, outer.XMost()) - inner.XMost();
+ MOZ_ASSERT(appliedOffset.x > 0);
+ }
+ }
+ if (outer.X() != inner.X()) {
+ nscoord distance = DistanceToRange(outer.X(), inner.X());
+ if (distance < 0) {
+ distance += NegativePart(inner.XMost(), outer.XMost());
+ }
+ rightMargin = Some(NSAppUnitsToFloatPixels(
+ scrollPort.XMost() - itemBounds.XMost() + distance, auPerDevPixel));
+ hBounds.min =
+ NSAppUnitsToFloatPixels(outer.X() - inner.X(), auPerDevPixel);
+ if (appliedOffset.x == 0 && inner.X() > 0) {
+ appliedOffset.x = std::max(0, outer.X()) - inner.X();
+ MOZ_ASSERT(appliedOffset.x < 0);
+ }
+ }
+
+ LayoutDeviceRect bounds =
+ LayoutDeviceRect::FromAppUnits(itemBounds, auPerDevPixel);
+ wr::LayoutVector2D applied = {
+ NSAppUnitsToFloatPixels(appliedOffset.x, auPerDevPixel),
+ NSAppUnitsToFloatPixels(appliedOffset.y, auPerDevPixel)};
+ wr::WrSpatialId spatialId = aBuilder.DefineStickyFrame(
+ wr::ToLayoutRect(bounds), topMargin.ptrOr(nullptr),
+ rightMargin.ptrOr(nullptr), bottomMargin.ptrOr(nullptr),
+ leftMargin.ptrOr(nullptr), vBounds, hBounds, applied,
+ wr::SpatialKey(uint64_t(mFrame), GetPerFrameKey(),
+ wr::SpatialKeyKind::Sticky));
+
+ saccHelper.emplace(aBuilder, spatialId);
+ aManager->CommandBuilder().PushOverrideForASR(mContainerASR, spatialId);
+ }
+
+ {
+ wr::StackingContextParams params;
+ params.clip =
+ wr::WrStackingContextClip::ClipChain(aBuilder.CurrentClipChainId());
+ StackingContextHelper sc(aSc, GetActiveScrolledRoot(), mFrame, this,
+ aBuilder, params);
+ nsDisplayOwnLayer::CreateWebRenderCommands(aBuilder, aResources, sc,
+ aManager, aDisplayListBuilder);
+ }
+
+ if (stickyScrollContainer) {
+ aManager->CommandBuilder().PopOverrideForASR(mContainerASR);
+ }
+
+ return true;
+}
+
+void nsDisplayStickyPosition::CalculateLayerScrollRanges(
+ StickyScrollContainer* aStickyScrollContainer, float aAppUnitsPerDevPixel,
+ float aScaleX, float aScaleY, LayerRectAbsolute& aStickyOuter,
+ LayerRectAbsolute& aStickyInner) {
+ nsRectAbsolute outer;
+ nsRectAbsolute inner;
+ aStickyScrollContainer->GetScrollRanges(mFrame, &outer, &inner);
+ aStickyOuter.SetBox(
+ NSAppUnitsToFloatPixels(outer.X(), aAppUnitsPerDevPixel) * aScaleX,
+ NSAppUnitsToFloatPixels(outer.Y(), aAppUnitsPerDevPixel) * aScaleY,
+ NSAppUnitsToFloatPixels(outer.XMost(), aAppUnitsPerDevPixel) * aScaleX,
+ NSAppUnitsToFloatPixels(outer.YMost(), aAppUnitsPerDevPixel) * aScaleY);
+ aStickyInner.SetBox(
+ NSAppUnitsToFloatPixels(inner.X(), aAppUnitsPerDevPixel) * aScaleX,
+ NSAppUnitsToFloatPixels(inner.Y(), aAppUnitsPerDevPixel) * aScaleY,
+ NSAppUnitsToFloatPixels(inner.XMost(), aAppUnitsPerDevPixel) * aScaleX,
+ NSAppUnitsToFloatPixels(inner.YMost(), aAppUnitsPerDevPixel) * aScaleY);
+}
+
+bool nsDisplayStickyPosition::UpdateScrollData(
+ WebRenderScrollData* aData, WebRenderLayerScrollData* aLayerData) {
+ bool hasDynamicToolbar = HasDynamicToolbar();
+ if (aLayerData && hasDynamicToolbar) {
+ StickyScrollContainer* stickyScrollContainer = GetStickyScrollContainer();
+ if (stickyScrollContainer) {
+ float auPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel();
+ float cumulativeResolution =
+ mFrame->PresShell()->GetCumulativeResolution();
+ LayerRectAbsolute stickyOuter;
+ LayerRectAbsolute stickyInner;
+ CalculateLayerScrollRanges(stickyScrollContainer, auPerDevPixel,
+ cumulativeResolution, cumulativeResolution,
+ stickyOuter, stickyInner);
+ aLayerData->SetStickyScrollRangeOuter(stickyOuter);
+ aLayerData->SetStickyScrollRangeInner(stickyInner);
+
+ SideBits sides =
+ nsLayoutUtils::GetSideBitsForFixedPositionContent(mFrame);
+ aLayerData->SetFixedPositionSides(sides);
+
+ ScrollableLayerGuid::ViewID scrollId =
+ nsLayoutUtils::FindOrCreateIDFor(stickyScrollContainer->ScrollFrame()
+ ->GetScrolledFrame()
+ ->GetContent());
+ aLayerData->SetStickyPositionScrollContainerId(scrollId);
+ }
+ }
+ // Return true if either there is a dynamic toolbar affecting this sticky
+ // item or the OwnLayer base implementation returns true for some other
+ // reason.
+ bool ret = hasDynamicToolbar;
+ ret |= nsDisplayOwnLayer::UpdateScrollData(aData, aLayerData);
+ return ret;
+}
+
+nsDisplayScrollInfoLayer::nsDisplayScrollInfoLayer(
+ nsDisplayListBuilder* aBuilder, nsIFrame* aScrolledFrame,
+ nsIFrame* aScrollFrame, const CompositorHitTestInfo& aHitInfo,
+ const nsRect& aHitArea)
+ : nsDisplayWrapList(aBuilder, aScrollFrame),
+ mScrollFrame(aScrollFrame),
+ mScrolledFrame(aScrolledFrame),
+ mScrollParentId(aBuilder->GetCurrentScrollParentId()),
+ mHitInfo(aHitInfo),
+ mHitArea(aHitArea) {
+#ifdef NS_BUILD_REFCNT_LOGGING
+ MOZ_COUNT_CTOR(nsDisplayScrollInfoLayer);
+#endif
+}
+
+UniquePtr<ScrollMetadata> nsDisplayScrollInfoLayer::ComputeScrollMetadata(
+ nsDisplayListBuilder* aBuilder, WebRenderLayerManager* aLayerManager) {
+ ScrollMetadata metadata = nsLayoutUtils::ComputeScrollMetadata(
+ mScrolledFrame, mScrollFrame, mScrollFrame->GetContent(), Frame(),
+ ToReferenceFrame(), aLayerManager, mScrollParentId,
+ mScrollFrame->GetSize(), false);
+ metadata.GetMetrics().SetIsScrollInfoLayer(true);
+ nsIScrollableFrame* scrollableFrame = mScrollFrame->GetScrollTargetFrame();
+ if (scrollableFrame) {
+ aBuilder->AddScrollFrameToNotify(scrollableFrame);
+ }
+
+ return UniquePtr<ScrollMetadata>(new ScrollMetadata(metadata));
+}
+
+bool nsDisplayScrollInfoLayer::UpdateScrollData(
+ WebRenderScrollData* aData, WebRenderLayerScrollData* aLayerData) {
+ if (aLayerData) {
+ UniquePtr<ScrollMetadata> metadata =
+ ComputeScrollMetadata(aData->GetBuilder(), aData->GetManager());
+ MOZ_ASSERT(aData);
+ MOZ_ASSERT(metadata);
+ aLayerData->AppendScrollMetadata(*aData, *metadata);
+ }
+ return true;
+}
+
+bool nsDisplayScrollInfoLayer::CreateWebRenderCommands(
+ wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc, RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) {
+ ScrollableLayerGuid::ViewID scrollId =
+ nsLayoutUtils::FindOrCreateIDFor(mScrollFrame->GetContent());
+
+ const LayoutDeviceRect devRect = LayoutDeviceRect::FromAppUnits(
+ mHitArea, mScrollFrame->PresContext()->AppUnitsPerDevPixel());
+
+ const wr::LayoutRect rect = wr::ToLayoutRect(devRect);
+
+ aBuilder.PushHitTest(rect, rect, !BackfaceIsHidden(), scrollId, mHitInfo,
+ SideBits::eNone);
+
+ return true;
+}
+
+void nsDisplayScrollInfoLayer::WriteDebugInfo(std::stringstream& aStream) {
+ aStream << " (scrollframe " << mScrollFrame << " scrolledFrame "
+ << mScrolledFrame << ")";
+}
+
+nsDisplayZoom::nsDisplayZoom(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ nsSubDocumentFrame* aSubDocFrame,
+ nsDisplayList* aList, int32_t aAPD,
+ int32_t aParentAPD, nsDisplayOwnLayerFlags aFlags)
+ : nsDisplaySubDocument(aBuilder, aFrame, aSubDocFrame, aList, aFlags),
+ mAPD(aAPD),
+ mParentAPD(aParentAPD) {
+ MOZ_COUNT_CTOR(nsDisplayZoom);
+}
+
+nsRect nsDisplayZoom::GetBounds(nsDisplayListBuilder* aBuilder,
+ bool* aSnap) const {
+ nsRect bounds = nsDisplaySubDocument::GetBounds(aBuilder, aSnap);
+ *aSnap = false;
+ return bounds.ScaleToOtherAppUnitsRoundOut(mAPD, mParentAPD);
+}
+
+void nsDisplayZoom::HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
+ HitTestState* aState,
+ nsTArray<nsIFrame*>* aOutFrames) {
+ nsRect rect;
+ // A 1x1 rect indicates we are just hit testing a point, so pass down a 1x1
+ // rect as well instead of possibly rounding the width or height to zero.
+ if (aRect.width == 1 && aRect.height == 1) {
+ rect.MoveTo(aRect.TopLeft().ScaleToOtherAppUnits(mParentAPD, mAPD));
+ rect.width = rect.height = 1;
+ } else {
+ rect = aRect.ScaleToOtherAppUnitsRoundOut(mParentAPD, mAPD);
+ }
+ mList.HitTest(aBuilder, rect, aState, aOutFrames);
+}
+
+nsDisplayAsyncZoom::nsDisplayAsyncZoom(
+ nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayList* aList,
+ const ActiveScrolledRoot* aActiveScrolledRoot, FrameMetrics::ViewID aViewID)
+ : nsDisplayOwnLayer(aBuilder, aFrame, aList, aActiveScrolledRoot),
+ mViewID(aViewID) {
+ MOZ_COUNT_CTOR(nsDisplayAsyncZoom);
+}
+
+#ifdef NS_BUILD_REFCNT_LOGGING
+nsDisplayAsyncZoom::~nsDisplayAsyncZoom() {
+ MOZ_COUNT_DTOR(nsDisplayAsyncZoom);
+}
+#endif
+
+void nsDisplayAsyncZoom::HitTest(nsDisplayListBuilder* aBuilder,
+ const nsRect& aRect, HitTestState* aState,
+ nsTArray<nsIFrame*>* aOutFrames) {
+#ifdef DEBUG
+ nsIScrollableFrame* scrollFrame = do_QueryFrame(mFrame);
+ MOZ_ASSERT(scrollFrame && ViewportUtils::IsZoomedContentRoot(
+ scrollFrame->GetScrolledFrame()));
+#endif
+ nsRect rect = ViewportUtils::VisualToLayout(aRect, mFrame->PresShell());
+ mList.HitTest(aBuilder, rect, aState, aOutFrames);
+}
+
+bool nsDisplayAsyncZoom::UpdateScrollData(
+ WebRenderScrollData* aData, WebRenderLayerScrollData* aLayerData) {
+ bool ret = nsDisplayOwnLayer::UpdateScrollData(aData, aLayerData);
+ MOZ_ASSERT(ret);
+ if (aLayerData) {
+ aLayerData->SetAsyncZoomContainerId(mViewID);
+ }
+ return ret;
+}
+
+///////////////////////////////////////////////////
+// nsDisplayTransform Implementation
+//
+
+#ifndef DEBUG
+static_assert(sizeof(nsDisplayTransform) <= 512,
+ "nsDisplayTransform has grown");
+#endif
+
+nsDisplayTransform::nsDisplayTransform(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame, nsDisplayList* aList,
+ const nsRect& aChildrenBuildingRect)
+ : nsPaintedDisplayItem(aBuilder, aFrame),
+ mChildren(aBuilder),
+ mTransform(Some(Matrix4x4())),
+ mChildrenBuildingRect(aChildrenBuildingRect),
+ mPrerenderDecision(PrerenderDecision::No),
+ mIsTransformSeparator(true),
+ mHasTransformGetter(false),
+ mHasAssociatedPerspective(false) {
+ MOZ_COUNT_CTOR(nsDisplayTransform);
+ MOZ_ASSERT(aFrame, "Must have a frame!");
+ Init(aBuilder, aList);
+}
+
+nsDisplayTransform::nsDisplayTransform(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame, nsDisplayList* aList,
+ const nsRect& aChildrenBuildingRect,
+ PrerenderDecision aPrerenderDecision)
+ : nsPaintedDisplayItem(aBuilder, aFrame),
+ mChildren(aBuilder),
+ mChildrenBuildingRect(aChildrenBuildingRect),
+ mPrerenderDecision(aPrerenderDecision),
+ mIsTransformSeparator(false),
+ mHasTransformGetter(false),
+ mHasAssociatedPerspective(false) {
+ MOZ_COUNT_CTOR(nsDisplayTransform);
+ MOZ_ASSERT(aFrame, "Must have a frame!");
+ SetReferenceFrameToAncestor(aBuilder);
+ Init(aBuilder, aList);
+}
+
+nsDisplayTransform::nsDisplayTransform(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame, nsDisplayList* aList,
+ const nsRect& aChildrenBuildingRect,
+ decltype(WithTransformGetter))
+ : nsPaintedDisplayItem(aBuilder, aFrame),
+ mChildren(aBuilder),
+ mChildrenBuildingRect(aChildrenBuildingRect),
+ mPrerenderDecision(PrerenderDecision::No),
+ mIsTransformSeparator(false),
+ mHasTransformGetter(true),
+ mHasAssociatedPerspective(false) {
+ MOZ_COUNT_CTOR(nsDisplayTransform);
+ MOZ_ASSERT(aFrame, "Must have a frame!");
+ MOZ_ASSERT(aFrame->GetTransformGetter());
+ Init(aBuilder, aList);
+}
+
+void nsDisplayTransform::SetReferenceFrameToAncestor(
+ nsDisplayListBuilder* aBuilder) {
+ if (mFrame == aBuilder->RootReferenceFrame()) {
+ return;
+ }
+ // We manually recompute mToReferenceFrame without going through the
+ // builder, since this won't apply the 'additional offset'. Our
+ // children will already be painting with that applied, and we don't
+ // want to include it a second time in our transform. We don't recompute
+ // our visible/building rects, since those should still include the additional
+ // offset.
+ // TODO: Are there are things computed using our ToReferenceFrame that should
+ // have the additional offset applied? Should we instead just manually remove
+ // the offset from our transform instead of this more general value?
+ // Can we instead apply the additional offset to us and not our children, like
+ // we do for all other offsets (and how reference frames are supposed to
+ // work)?
+ nsIFrame* outerFrame = nsLayoutUtils::GetCrossDocParentFrameInProcess(mFrame);
+ const nsIFrame* referenceFrame = aBuilder->FindReferenceFrameFor(outerFrame);
+ mToReferenceFrame = mFrame->GetOffsetToCrossDoc(referenceFrame);
+}
+
+void nsDisplayTransform::Init(nsDisplayListBuilder* aBuilder,
+ nsDisplayList* aChildren) {
+ mChildren.AppendToTop(aChildren);
+ UpdateBounds(aBuilder);
+}
+
+bool nsDisplayTransform::ShouldFlattenAway(nsDisplayListBuilder* aBuilder) {
+ return false;
+}
+
+/* Returns the delta specified by the transform-origin property.
+ * This is a positive delta, meaning that it indicates the direction to move
+ * to get from (0, 0) of the frame to the transform origin. This function is
+ * called off the main thread.
+ */
+/* static */
+Point3D nsDisplayTransform::GetDeltaToTransformOrigin(
+ const nsIFrame* aFrame, TransformReferenceBox& aRefBox,
+ float aAppUnitsPerPixel) {
+ MOZ_ASSERT(aFrame, "Can't get delta for a null frame!");
+ MOZ_ASSERT(aFrame->IsTransformed() || aFrame->BackfaceIsHidden() ||
+ aFrame->Combines3DTransformWithAncestors(),
+ "Shouldn't get a delta for an untransformed frame!");
+
+ if (!aFrame->IsTransformed()) {
+ return Point3D();
+ }
+
+ /* For both of the coordinates, if the value of transform is a
+ * percentage, it's relative to the size of the frame. Otherwise, if it's
+ * a distance, it's already computed for us!
+ */
+ const nsStyleDisplay* display = aFrame->StyleDisplay();
+
+ const StyleTransformOrigin& transformOrigin = display->mTransformOrigin;
+ CSSPoint origin = nsStyleTransformMatrix::Convert2DPosition(
+ transformOrigin.horizontal, transformOrigin.vertical, aRefBox);
+
+ if (aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) {
+ // SVG frames (unlike other frames) have a reference box that can be (and
+ // typically is) offset from the TopLeft() of the frame. We need to account
+ // for that here.
+ origin.x += CSSPixel::FromAppUnits(aRefBox.X());
+ origin.y += CSSPixel::FromAppUnits(aRefBox.Y());
+ }
+
+ float scale = AppUnitsPerCSSPixel() / float(aAppUnitsPerPixel);
+ float z = transformOrigin.depth._0;
+ return Point3D(origin.x * scale, origin.y * scale, z * scale);
+}
+
+/* static */
+bool nsDisplayTransform::ComputePerspectiveMatrix(const nsIFrame* aFrame,
+ float aAppUnitsPerPixel,
+ Matrix4x4& aOutMatrix) {
+ MOZ_ASSERT(aFrame, "Can't get delta for a null frame!");
+ MOZ_ASSERT(aFrame->IsTransformed() || aFrame->BackfaceIsHidden() ||
+ aFrame->Combines3DTransformWithAncestors(),
+ "Shouldn't get a delta for an untransformed frame!");
+ MOZ_ASSERT(aOutMatrix.IsIdentity(), "Must have a blank output matrix");
+
+ if (!aFrame->IsTransformed()) {
+ return false;
+ }
+
+ // TODO: Is it possible that the perspectiveFrame's bounds haven't been set
+ // correctly yet (similar to the aBoundsOverride case for
+ // GetResultingTransformMatrix)?
+ nsIFrame* perspectiveFrame =
+ aFrame->GetClosestFlattenedTreeAncestorPrimaryFrame();
+ if (!perspectiveFrame) {
+ return false;
+ }
+
+ /* Grab the values for perspective and perspective-origin (if present) */
+ const nsStyleDisplay* perspectiveDisplay = perspectiveFrame->StyleDisplay();
+ if (perspectiveDisplay->mChildPerspective.IsNone()) {
+ return false;
+ }
+
+ MOZ_ASSERT(perspectiveDisplay->mChildPerspective.IsLength());
+ float perspective =
+ perspectiveDisplay->mChildPerspective.AsLength().ToCSSPixels();
+ perspective = std::max(1.0f, perspective);
+ if (perspective < std::numeric_limits<Float>::epsilon()) {
+ return true;
+ }
+
+ TransformReferenceBox refBox(perspectiveFrame);
+
+ Point perspectiveOrigin = nsStyleTransformMatrix::Convert2DPosition(
+ perspectiveDisplay->mPerspectiveOrigin.horizontal,
+ perspectiveDisplay->mPerspectiveOrigin.vertical, refBox,
+ aAppUnitsPerPixel);
+
+ /* GetOffsetTo computes the offset required to move from 0,0 in
+ * perspectiveFrame to 0,0 in aFrame. Although we actually want the inverse of
+ * this, it's faster to compute this way.
+ */
+ nsPoint frameToPerspectiveOffset = -aFrame->GetOffsetTo(perspectiveFrame);
+ Point frameToPerspectiveGfxOffset(
+ NSAppUnitsToFloatPixels(frameToPerspectiveOffset.x, aAppUnitsPerPixel),
+ NSAppUnitsToFloatPixels(frameToPerspectiveOffset.y, aAppUnitsPerPixel));
+
+ /* Move the perspective origin to be relative to aFrame, instead of relative
+ * to the containing block which is how it was specified in the style system.
+ */
+ perspectiveOrigin += frameToPerspectiveGfxOffset;
+
+ aOutMatrix._34 =
+ -1.0 / NSAppUnitsToFloatPixels(CSSPixel::ToAppUnits(perspective),
+ aAppUnitsPerPixel);
+
+ aOutMatrix.ChangeBasis(Point3D(perspectiveOrigin.x, perspectiveOrigin.y, 0));
+ return true;
+}
+
+nsDisplayTransform::FrameTransformProperties::FrameTransformProperties(
+ const nsIFrame* aFrame, TransformReferenceBox& aRefBox,
+ float aAppUnitsPerPixel)
+ : mFrame(aFrame),
+ mTranslate(aFrame->StyleDisplay()->mTranslate),
+ mRotate(aFrame->StyleDisplay()->mRotate),
+ mScale(aFrame->StyleDisplay()->mScale),
+ mTransform(aFrame->StyleDisplay()->mTransform),
+ mMotion(MotionPathUtils::ResolveMotionPath(aFrame, aRefBox)),
+ mToTransformOrigin(
+ GetDeltaToTransformOrigin(aFrame, aRefBox, aAppUnitsPerPixel)) {}
+
+/* Wraps up the transform matrix in a change-of-basis matrix pair that
+ * translates from local coordinate space to transform coordinate space, then
+ * hands it back.
+ */
+Matrix4x4 nsDisplayTransform::GetResultingTransformMatrix(
+ const FrameTransformProperties& aProperties, TransformReferenceBox& aRefBox,
+ float aAppUnitsPerPixel) {
+ return GetResultingTransformMatrixInternal(aProperties, aRefBox, nsPoint(),
+ aAppUnitsPerPixel, 0);
+}
+
+Matrix4x4 nsDisplayTransform::GetResultingTransformMatrix(
+ const nsIFrame* aFrame, const nsPoint& aOrigin, float aAppUnitsPerPixel,
+ uint32_t aFlags) {
+ TransformReferenceBox refBox(aFrame);
+ FrameTransformProperties props(aFrame, refBox, aAppUnitsPerPixel);
+ return GetResultingTransformMatrixInternal(props, refBox, aOrigin,
+ aAppUnitsPerPixel, aFlags);
+}
+
+Matrix4x4 nsDisplayTransform::GetResultingTransformMatrixInternal(
+ const FrameTransformProperties& aProperties, TransformReferenceBox& aRefBox,
+ const nsPoint& aOrigin, float aAppUnitsPerPixel, uint32_t aFlags) {
+ const nsIFrame* frame = aProperties.mFrame;
+ NS_ASSERTION(frame || !(aFlags & INCLUDE_PERSPECTIVE),
+ "Must have a frame to compute perspective!");
+
+ // Get the underlying transform matrix:
+
+ /* Get the matrix, then change its basis to factor in the origin. */
+ Matrix4x4 result;
+ // Call IsSVGTransformed() regardless of the value of
+ // aProperties.HasTransform(), since we still need any
+ // potential parentsChildrenOnlyTransform.
+ Matrix svgTransform, parentsChildrenOnlyTransform;
+ const bool hasSVGTransforms =
+ frame && frame->HasAnyStateBits(NS_FRAME_MAY_BE_TRANSFORMED) &&
+ frame->IsSVGTransformed(&svgTransform, &parentsChildrenOnlyTransform);
+ bool shouldRound = nsLayoutUtils::ShouldSnapToGrid(frame);
+
+ /* Transformed frames always have a transform, or are preserving 3d (and might
+ * still have perspective!) */
+ if (aProperties.HasTransform()) {
+ result = nsStyleTransformMatrix::ReadTransforms(
+ aProperties.mTranslate, aProperties.mRotate, aProperties.mScale,
+ aProperties.mMotion, aProperties.mTransform, aRefBox,
+ aAppUnitsPerPixel);
+ } else if (hasSVGTransforms) {
+ // Correct the translation components for zoom:
+ float pixelsPerCSSPx = AppUnitsPerCSSPixel() / aAppUnitsPerPixel;
+ svgTransform._31 *= pixelsPerCSSPx;
+ svgTransform._32 *= pixelsPerCSSPx;
+ result = Matrix4x4::From2D(svgTransform);
+ }
+
+ // Apply any translation due to 'transform-origin' and/or 'transform-box':
+ result.ChangeBasis(aProperties.mToTransformOrigin);
+
+ // See the comment for SVGContainerFrame::HasChildrenOnlyTransform for
+ // an explanation of what children-only transforms are.
+ const bool parentHasChildrenOnlyTransform =
+ hasSVGTransforms && !parentsChildrenOnlyTransform.IsIdentity();
+
+ if (parentHasChildrenOnlyTransform) {
+ float pixelsPerCSSPx = AppUnitsPerCSSPixel() / aAppUnitsPerPixel;
+ parentsChildrenOnlyTransform._31 *= pixelsPerCSSPx;
+ parentsChildrenOnlyTransform._32 *= pixelsPerCSSPx;
+
+ Point3D frameOffset(
+ NSAppUnitsToFloatPixels(-frame->GetPosition().x, aAppUnitsPerPixel),
+ NSAppUnitsToFloatPixels(-frame->GetPosition().y, aAppUnitsPerPixel), 0);
+ Matrix4x4 parentsChildrenOnlyTransform3D =
+ Matrix4x4::From2D(parentsChildrenOnlyTransform)
+ .ChangeBasis(frameOffset);
+
+ result *= parentsChildrenOnlyTransform3D;
+ }
+
+ Matrix4x4 perspectiveMatrix;
+ bool hasPerspective = aFlags & INCLUDE_PERSPECTIVE;
+ if (hasPerspective) {
+ if (ComputePerspectiveMatrix(frame, aAppUnitsPerPixel, perspectiveMatrix)) {
+ result *= perspectiveMatrix;
+ }
+ }
+
+ if ((aFlags & INCLUDE_PRESERVE3D_ANCESTORS) && frame &&
+ frame->Combines3DTransformWithAncestors()) {
+ // Include the transform set on our parent
+ nsIFrame* parentFrame =
+ frame->GetClosestFlattenedTreeAncestorPrimaryFrame();
+ NS_ASSERTION(parentFrame && parentFrame->IsTransformed() &&
+ parentFrame->Extend3DContext(),
+ "Preserve3D mismatch!");
+ TransformReferenceBox refBox(parentFrame);
+ FrameTransformProperties props(parentFrame, refBox, aAppUnitsPerPixel);
+
+ uint32_t flags =
+ aFlags & (INCLUDE_PRESERVE3D_ANCESTORS | INCLUDE_PERSPECTIVE);
+
+ // If this frame isn't transformed (but we exist for backface-visibility),
+ // then we're not a reference frame so no offset to origin will be added.
+ // Otherwise we need to manually translate into our parent's coordinate
+ // space.
+ if (frame->IsTransformed()) {
+ nsLayoutUtils::PostTranslate(result, frame->GetPosition(),
+ aAppUnitsPerPixel, shouldRound);
+ }
+ Matrix4x4 parent = GetResultingTransformMatrixInternal(
+ props, refBox, nsPoint(0, 0), aAppUnitsPerPixel, flags);
+ result = result * parent;
+ }
+
+ if (aFlags & OFFSET_BY_ORIGIN) {
+ nsLayoutUtils::PostTranslate(result, aOrigin, aAppUnitsPerPixel,
+ shouldRound);
+ }
+
+ return result;
+}
+
+bool nsDisplayOpacity::CanUseAsyncAnimations(nsDisplayListBuilder* aBuilder) {
+ static constexpr nsCSSPropertyIDSet opacitySet =
+ nsCSSPropertyIDSet::OpacityProperties();
+ if (ActiveLayerTracker::IsStyleAnimated(aBuilder, mFrame, opacitySet)) {
+ return true;
+ }
+
+ EffectCompositor::SetPerformanceWarning(
+ mFrame, opacitySet,
+ AnimationPerformanceWarning(
+ AnimationPerformanceWarning::Type::OpacityFrameInactive));
+
+ return false;
+}
+
+bool nsDisplayTransform::CanUseAsyncAnimations(nsDisplayListBuilder* aBuilder) {
+ return mPrerenderDecision != PrerenderDecision::No;
+}
+
+bool nsDisplayBackgroundColor::CanUseAsyncAnimations(
+ nsDisplayListBuilder* aBuilder) {
+ return StaticPrefs::gfx_omta_background_color();
+}
+
+static bool IsInStickyPositionedSubtree(const nsIFrame* aFrame) {
+ for (const nsIFrame* frame = aFrame; frame;
+ frame = nsLayoutUtils::GetCrossDocParentFrameInProcess(frame)) {
+ if (frame->IsStickyPositioned()) {
+ return true;
+ }
+ }
+ return false;
+}
+
+static bool ShouldUsePartialPrerender(const nsIFrame* aFrame) {
+ return StaticPrefs::layout_animation_prerender_partial() &&
+ // Bug 1642547: Support partial prerender for position:sticky elements.
+ !IsInStickyPositionedSubtree(aFrame);
+}
+
+/* static */
+auto nsDisplayTransform::ShouldPrerenderTransformedContent(
+ nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsRect* aDirtyRect)
+ -> PrerenderInfo {
+ PrerenderInfo result;
+ // If we are in a preserve-3d tree, and we've disallowed async animations, we
+ // return No prerender decision directly.
+ if ((aFrame->Extend3DContext() ||
+ aFrame->Combines3DTransformWithAncestors()) &&
+ !aBuilder->GetPreserves3DAllowAsyncAnimation()) {
+ return result;
+ }
+
+ // Elements whose transform has been modified recently, or which
+ // have a compositor-animated transform, can be prerendered. An element
+ // might have only just had its transform animated in which case
+ // the ActiveLayerManager may not have been notified yet.
+ static constexpr nsCSSPropertyIDSet transformSet =
+ nsCSSPropertyIDSet::TransformLikeProperties();
+ if (!ActiveLayerTracker::IsTransformMaybeAnimated(aFrame) &&
+ !EffectCompositor::HasAnimationsForCompositor(
+ aFrame, DisplayItemType::TYPE_TRANSFORM)) {
+ EffectCompositor::SetPerformanceWarning(
+ aFrame, transformSet,
+ AnimationPerformanceWarning(
+ AnimationPerformanceWarning::Type::TransformFrameInactive));
+
+ // This case happens when we're sure that the frame is not animated and its
+ // preserve-3d ancestors are not, either. So we don't need to pre-render.
+ // However, this decision shouldn't affect the decisions for other frames in
+ // the preserve-3d context. We need this flag to determine whether we should
+ // block async animations on other frames in the current preserve-3d tree.
+ result.mHasAnimations = false;
+ return result;
+ }
+
+ // We should not allow prerender if any ancestor container element has
+ // mask/clip-path effects.
+ //
+ // With prerender and async transform animation, we do not need to restyle an
+ // animated element to respect position changes, since that transform is done
+ // by layer animation. As a result, the container element is not aware of
+ // position change of that containing element and loses the chance to update
+ // the content of mask/clip-path.
+ //
+ // Why do we need to update a mask? This is relative to how we generate a
+ // mask layer in ContainerState::SetupMaskLayerForCSSMask. While creating a
+ // mask layer, to reduce memory usage, we did not choose the size of the
+ // masked element as mask size. Instead, we read the union of bounds of all
+ // children display items by nsDisplayWrapList::GetBounds, which is smaller
+ // than or equal to the masked element's boundary, and use it as the position
+ // size of the mask layer. That union bounds is actually affected by the
+ // geometry of the animated element. To keep the content of mask up to date,
+ // forbidding of prerender is required.
+ for (nsIFrame* container =
+ nsLayoutUtils::GetCrossDocParentFrameInProcess(aFrame);
+ container;
+ container = nsLayoutUtils::GetCrossDocParentFrameInProcess(container)) {
+ const nsStyleSVGReset* svgReset = container->StyleSVGReset();
+ if (svgReset->HasMask() || svgReset->HasClipPath()) {
+ return result;
+ }
+ }
+
+ // If the incoming dirty rect already contains the entire overflow area,
+ // we are already rendering the entire content.
+ nsRect overflow = aFrame->InkOverflowRectRelativeToSelf();
+ // UntransformRect will not touch the output rect (`&untranformedDirtyRect`)
+ // in cases of non-invertible transforms, so we set `untransformedRect` to
+ // `aDirtyRect` as an initial value for such cases.
+ nsRect untransformedDirtyRect = *aDirtyRect;
+ UntransformRect(*aDirtyRect, overflow, aFrame, &untransformedDirtyRect);
+ if (untransformedDirtyRect.Contains(overflow)) {
+ *aDirtyRect = untransformedDirtyRect;
+ result.mDecision = PrerenderDecision::Full;
+ return result;
+ }
+
+ float viewportRatio =
+ StaticPrefs::layout_animation_prerender_viewport_ratio_limit();
+ uint32_t absoluteLimitX =
+ StaticPrefs::layout_animation_prerender_absolute_limit_x();
+ uint32_t absoluteLimitY =
+ StaticPrefs::layout_animation_prerender_absolute_limit_y();
+ nsSize refSize = aBuilder->RootReferenceFrame()->GetSize();
+
+ float resolution = aFrame->PresShell()->GetCumulativeResolution();
+ if (resolution < 1.0f) {
+ refSize.SizeTo(
+ NSCoordSaturatingNonnegativeMultiply(refSize.width, 1.0f / resolution),
+ NSCoordSaturatingNonnegativeMultiply(refSize.height,
+ 1.0f / resolution));
+ }
+
+ // Only prerender if the transformed frame's size is <= a multiple of the
+ // reference frame size (~viewport), and less than an absolute limit.
+ // Both the ratio and the absolute limit are configurable.
+ nscoord maxLength = std::max(nscoord(refSize.width * viewportRatio),
+ nscoord(refSize.height * viewportRatio));
+ nsSize relativeLimit(maxLength, maxLength);
+ nsSize absoluteLimit(
+ aFrame->PresContext()->DevPixelsToAppUnits(absoluteLimitX),
+ aFrame->PresContext()->DevPixelsToAppUnits(absoluteLimitY));
+ nsSize maxSize = Min(relativeLimit, absoluteLimit);
+
+ const auto transform = nsLayoutUtils::GetTransformToAncestor(
+ RelativeTo{aFrame},
+ RelativeTo{nsLayoutUtils::GetDisplayRootFrame(aFrame)});
+ const gfxRect transformedBounds = transform.TransformAndClipBounds(
+ gfxRect(overflow.x, overflow.y, overflow.width, overflow.height),
+ gfxRect::MaxIntRect());
+ const nsSize frameSize =
+ nsSize(transformedBounds.width, transformedBounds.height);
+
+ uint64_t maxLimitArea = uint64_t(maxSize.width) * maxSize.height;
+ uint64_t frameArea = uint64_t(frameSize.width) * frameSize.height;
+ if (frameArea <= maxLimitArea && frameSize <= absoluteLimit) {
+ *aDirtyRect = overflow;
+ result.mDecision = PrerenderDecision::Full;
+ return result;
+ }
+
+ if (ShouldUsePartialPrerender(aFrame)) {
+ *aDirtyRect = nsLayoutUtils::ComputePartialPrerenderArea(
+ aFrame, untransformedDirtyRect, overflow, maxSize);
+ result.mDecision = PrerenderDecision::Partial;
+ return result;
+ }
+
+ if (frameArea > maxLimitArea) {
+ uint64_t appUnitsPerPixel = AppUnitsPerCSSPixel();
+ EffectCompositor::SetPerformanceWarning(
+ aFrame, transformSet,
+ AnimationPerformanceWarning(
+ AnimationPerformanceWarning::Type::ContentTooLargeArea,
+ {
+ int(frameArea / (appUnitsPerPixel * appUnitsPerPixel)),
+ int(maxLimitArea / (appUnitsPerPixel * appUnitsPerPixel)),
+ }));
+ } else {
+ EffectCompositor::SetPerformanceWarning(
+ aFrame, transformSet,
+ AnimationPerformanceWarning(
+ AnimationPerformanceWarning::Type::ContentTooLarge,
+ {
+ nsPresContext::AppUnitsToIntCSSPixels(frameSize.width),
+ nsPresContext::AppUnitsToIntCSSPixels(frameSize.height),
+ nsPresContext::AppUnitsToIntCSSPixels(relativeLimit.width),
+ nsPresContext::AppUnitsToIntCSSPixels(relativeLimit.height),
+ nsPresContext::AppUnitsToIntCSSPixels(absoluteLimit.width),
+ nsPresContext::AppUnitsToIntCSSPixels(absoluteLimit.height),
+ }));
+ }
+
+ return result;
+}
+
+/* If the matrix is singular, or a hidden backface is shown, the frame won't be
+ * visible or hit. */
+static bool IsFrameVisible(nsIFrame* aFrame, const Matrix4x4& aMatrix) {
+ if (aMatrix.IsSingular()) {
+ return false;
+ }
+ if (aFrame->BackfaceIsHidden() && aMatrix.IsBackfaceVisible()) {
+ return false;
+ }
+ return true;
+}
+
+const Matrix4x4Flagged& nsDisplayTransform::GetTransform() const {
+ if (mTransform) {
+ return *mTransform;
+ }
+
+ float scale = mFrame->PresContext()->AppUnitsPerDevPixel();
+
+ if (mHasTransformGetter) {
+ mTransform.emplace((mFrame->GetTransformGetter())(mFrame, scale));
+ Point3D newOrigin =
+ Point3D(NSAppUnitsToFloatPixels(mToReferenceFrame.x, scale),
+ NSAppUnitsToFloatPixels(mToReferenceFrame.y, scale), 0.0f);
+ mTransform->ChangeBasis(newOrigin.x, newOrigin.y, newOrigin.z);
+ } else if (!mIsTransformSeparator) {
+ DebugOnly<bool> isReference = mFrame->IsTransformed() ||
+ mFrame->Combines3DTransformWithAncestors() ||
+ mFrame->Extend3DContext();
+ MOZ_ASSERT(isReference);
+ mTransform.emplace(
+ GetResultingTransformMatrix(mFrame, ToReferenceFrame(), scale,
+ INCLUDE_PERSPECTIVE | OFFSET_BY_ORIGIN));
+ } else {
+ // Use identity matrix
+ mTransform.emplace();
+ }
+
+ return *mTransform;
+}
+
+const Matrix4x4Flagged& nsDisplayTransform::GetInverseTransform() const {
+ if (mInverseTransform) {
+ return *mInverseTransform;
+ }
+
+ MOZ_ASSERT(!GetTransform().IsSingular());
+
+ mInverseTransform.emplace(GetTransform().Inverse());
+
+ return *mInverseTransform;
+}
+
+Matrix4x4 nsDisplayTransform::GetTransformForRendering(
+ LayoutDevicePoint* aOutOrigin) const {
+ if (!mFrame->HasPerspective() || mHasTransformGetter ||
+ mIsTransformSeparator) {
+ if (!mHasTransformGetter && !mIsTransformSeparator && aOutOrigin) {
+ // If aOutOrigin is provided, put the offset to origin into it, because
+ // we need to keep it separate for webrender. The combination of
+ // *aOutOrigin and the returned matrix here should always be equivalent
+ // to what GetTransform() would have returned.
+ float scale = mFrame->PresContext()->AppUnitsPerDevPixel();
+ *aOutOrigin = LayoutDevicePoint::FromAppUnits(ToReferenceFrame(), scale);
+
+ // The rounding behavior should also be the same as GetTransform().
+ if (nsLayoutUtils::ShouldSnapToGrid(mFrame)) {
+ aOutOrigin->Round();
+ }
+ return GetResultingTransformMatrix(mFrame, nsPoint(0, 0), scale,
+ INCLUDE_PERSPECTIVE);
+ }
+ return GetTransform().GetMatrix();
+ }
+ MOZ_ASSERT(!mHasTransformGetter);
+
+ float scale = mFrame->PresContext()->AppUnitsPerDevPixel();
+ // Don't include perspective transform, or the offset to origin, since
+ // nsDisplayPerspective will handle both of those.
+ return GetResultingTransformMatrix(mFrame, ToReferenceFrame(), scale, 0);
+}
+
+const Matrix4x4& nsDisplayTransform::GetAccumulatedPreserved3DTransform(
+ nsDisplayListBuilder* aBuilder) {
+ MOZ_ASSERT(!mFrame->Extend3DContext() || IsLeafOf3DContext());
+
+ if (!IsLeafOf3DContext()) {
+ return GetTransform().GetMatrix();
+ }
+
+ if (!mTransformPreserves3D) {
+ const nsIFrame* establisher; // Establisher of the 3D rendering context.
+ for (establisher = mFrame;
+ establisher && establisher->Combines3DTransformWithAncestors();
+ establisher =
+ establisher->GetClosestFlattenedTreeAncestorPrimaryFrame()) {
+ }
+ const nsIFrame* establisherReference = aBuilder->FindReferenceFrameFor(
+ nsLayoutUtils::GetCrossDocParentFrameInProcess(establisher));
+
+ nsPoint offset = establisher->GetOffsetToCrossDoc(establisherReference);
+ float scale = mFrame->PresContext()->AppUnitsPerDevPixel();
+ uint32_t flags =
+ INCLUDE_PRESERVE3D_ANCESTORS | INCLUDE_PERSPECTIVE | OFFSET_BY_ORIGIN;
+ mTransformPreserves3D = MakeUnique<Matrix4x4>(
+ GetResultingTransformMatrix(mFrame, offset, scale, flags));
+ }
+
+ return *mTransformPreserves3D;
+}
+
+bool nsDisplayTransform::CreateWebRenderCommands(
+ wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc, RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) {
+ // We want to make sure we don't pollute the transform property in the WR
+ // stacking context by including the position of this frame (relative to the
+ // parent reference frame). We need to keep those separate; the position of
+ // this frame goes into the stacking context bounds while the transform goes
+ // into the transform.
+ LayoutDevicePoint position;
+ Matrix4x4 newTransformMatrix = GetTransformForRendering(&position);
+
+ gfx::Matrix4x4* transformForSC = &newTransformMatrix;
+ if (newTransformMatrix.IsIdentity()) {
+ // If the transform is an identity transform, strip it out so that WR
+ // doesn't turn this stacking context into a reference frame, as it
+ // affects positioning. Bug 1345577 tracks a better fix.
+ transformForSC = nullptr;
+
+ // In ChooseScaleAndSetTransform, we round the offset from the reference
+ // frame used to adjust the transform, if there is no transform, or it
+ // is just a translation. We need to do the same here.
+ if (nsLayoutUtils::ShouldSnapToGrid(mFrame)) {
+ position.Round();
+ }
+ }
+
+ auto key = wr::SpatialKey(uint64_t(mFrame), GetPerFrameKey(),
+ wr::SpatialKeyKind::Transform);
+
+ // We don't send animations for transform separator display items.
+ uint64_t animationsId =
+ mIsTransformSeparator
+ ? 0
+ : AddAnimationsForWebRender(
+ this, aManager, aDisplayListBuilder,
+ IsPartialPrerender() ? Some(position) : Nothing());
+ wr::WrAnimationProperty prop{wr::WrAnimationType::Transform, animationsId,
+ key};
+
+ nsDisplayTransform* deferredTransformItem = nullptr;
+ if (!mFrame->ChildrenHavePerspective()) {
+ // If it has perspective, we create a new scroll data via the
+ // UpdateScrollData call because that scenario is more complex. Otherwise
+ // we can just stash the transform on the StackingContextHelper and
+ // apply it to any scroll data that are created inside this
+ // nsDisplayTransform.
+ deferredTransformItem = this;
+ }
+
+ // Determine if we're possibly animated (= would need an active layer in FLB).
+ bool animated = !mIsTransformSeparator &&
+ ActiveLayerTracker::IsTransformMaybeAnimated(Frame());
+
+ wr::StackingContextParams params;
+ params.mBoundTransform = &newTransformMatrix;
+ params.animation = animationsId ? &prop : nullptr;
+
+ wr::WrTransformInfo transform_info;
+ if (transformForSC) {
+ transform_info.transform = wr::ToLayoutTransform(newTransformMatrix);
+ transform_info.key = key;
+ params.mTransformPtr = &transform_info;
+ } else {
+ params.mTransformPtr = nullptr;
+ }
+
+ params.prim_flags = !BackfaceIsHidden()
+ ? wr::PrimitiveFlags::IS_BACKFACE_VISIBLE
+ : wr::PrimitiveFlags{0};
+ params.paired_with_perspective = mHasAssociatedPerspective;
+ params.mDeferredTransformItem = deferredTransformItem;
+ params.mAnimated = animated;
+ // Determine if we would have to rasterize any items in local raster space
+ // (i.e. disable subpixel AA). We don't always need to rasterize locally even
+ // if the stacking context is possibly animated (at the cost of potentially
+ // some false negatives with respect to will-change handling), so we pass in
+ // this determination separately to accurately match with when FLB would
+ // normally disable subpixel AA.
+ params.mRasterizeLocally = animated && Frame()->HasAnimationOfTransform();
+ params.SetPreserve3D(mFrame->Extend3DContext() && !mIsTransformSeparator);
+ params.clip =
+ wr::WrStackingContextClip::ClipChain(aBuilder.CurrentClipChainId());
+
+ LayoutDeviceSize boundsSize = LayoutDeviceSize::FromAppUnits(
+ mChildBounds.Size(), mFrame->PresContext()->AppUnitsPerDevPixel());
+
+ StackingContextHelper sc(aSc, GetActiveScrolledRoot(), mFrame, this, aBuilder,
+ params, LayoutDeviceRect(position, boundsSize));
+
+ aManager->CommandBuilder().CreateWebRenderCommandsFromDisplayList(
+ GetChildren(), this, aDisplayListBuilder, sc, aBuilder, aResources);
+ return true;
+}
+
+bool nsDisplayTransform::UpdateScrollData(
+ WebRenderScrollData* aData, WebRenderLayerScrollData* aLayerData) {
+ if (!mFrame->ChildrenHavePerspective()) {
+ // This case is handled in CreateWebRenderCommands by stashing the transform
+ // on the stacking context.
+ return false;
+ }
+ if (aLayerData) {
+ aLayerData->SetTransform(GetTransform().GetMatrix());
+ aLayerData->SetTransformIsPerspective(true);
+ }
+ return true;
+}
+
+bool nsDisplayTransform::ShouldSkipTransform(
+ nsDisplayListBuilder* aBuilder) const {
+ return (aBuilder->RootReferenceFrame() == mFrame) &&
+ aBuilder->IsForGenerateGlyphMask();
+}
+
+void nsDisplayTransform::Collect3DTransformLeaves(
+ nsDisplayListBuilder* aBuilder, nsTArray<nsDisplayTransform*>& aLeaves) {
+ if (!IsParticipating3DContext() || IsLeafOf3DContext()) {
+ aLeaves.AppendElement(this);
+ return;
+ }
+
+ FlattenedDisplayListIterator iter(aBuilder, &mChildren);
+ while (iter.HasNext()) {
+ nsDisplayItem* item = iter.GetNextItem();
+ if (item->GetType() == DisplayItemType::TYPE_PERSPECTIVE) {
+ auto* perspective = static_cast<nsDisplayPerspective*>(item);
+ if (!perspective->GetChildren()->GetTop()) {
+ continue;
+ }
+ item = perspective->GetChildren()->GetTop();
+ }
+ if (item->GetType() != DisplayItemType::TYPE_TRANSFORM) {
+ gfxCriticalError() << "Invalid child item within 3D transform of type: "
+ << item->Name();
+ continue;
+ }
+ static_cast<nsDisplayTransform*>(item)->Collect3DTransformLeaves(aBuilder,
+ aLeaves);
+ }
+}
+
+static RefPtr<gfx::Path> BuildPathFromPolygon(const RefPtr<DrawTarget>& aDT,
+ const gfx::Polygon& aPolygon) {
+ MOZ_ASSERT(!aPolygon.IsEmpty());
+
+ RefPtr<PathBuilder> pathBuilder = aDT->CreatePathBuilder();
+ const nsTArray<Point4D>& points = aPolygon.GetPoints();
+
+ pathBuilder->MoveTo(points[0].As2DPoint());
+
+ for (size_t i = 1; i < points.Length(); ++i) {
+ pathBuilder->LineTo(points[i].As2DPoint());
+ }
+
+ pathBuilder->Close();
+ return pathBuilder->Finish();
+}
+
+void nsDisplayTransform::CollectSorted3DTransformLeaves(
+ nsDisplayListBuilder* aBuilder, nsTArray<TransformPolygon>& aLeaves) {
+ std::list<TransformPolygon> inputLayers;
+
+ nsTArray<nsDisplayTransform*> leaves;
+ Collect3DTransformLeaves(aBuilder, leaves);
+ for (nsDisplayTransform* item : leaves) {
+ auto bounds = LayoutDeviceRect::FromAppUnits(
+ item->mChildBounds, item->mFrame->PresContext()->AppUnitsPerDevPixel());
+ Matrix4x4 transform = item->GetAccumulatedPreserved3DTransform(aBuilder);
+
+ if (!IsFrameVisible(item->mFrame, transform)) {
+ continue;
+ }
+ gfx::Polygon polygon =
+ gfx::Polygon::FromRect(gfx::Rect(bounds.ToUnknownRect()));
+
+ polygon.TransformToScreenSpace(transform);
+
+ if (polygon.GetPoints().Length() >= 3) {
+ inputLayers.push_back(TransformPolygon(item, std::move(polygon)));
+ }
+ }
+
+ if (inputLayers.empty()) {
+ return;
+ }
+
+ BSPTree<nsDisplayTransform> tree(inputLayers);
+ nsTArray<TransformPolygon> orderedLayers(tree.GetDrawOrder());
+
+ for (TransformPolygon& polygon : orderedLayers) {
+ Matrix4x4 inverse =
+ polygon.data->GetAccumulatedPreserved3DTransform(aBuilder).Inverse();
+
+ MOZ_ASSERT(polygon.geometry);
+ polygon.geometry->TransformToLayerSpace(inverse);
+ }
+
+ aLeaves = std::move(orderedLayers);
+}
+
+void nsDisplayTransform::Paint(nsDisplayListBuilder* aBuilder,
+ gfxContext* aCtx) {
+ Paint(aBuilder, aCtx, Nothing());
+}
+
+void nsDisplayTransform::Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx,
+ const Maybe<gfx::Polygon>& aPolygon) {
+ if (IsParticipating3DContext() && !IsLeafOf3DContext()) {
+ MOZ_ASSERT(!aPolygon);
+ nsTArray<TransformPolygon> leaves;
+ CollectSorted3DTransformLeaves(aBuilder, leaves);
+ for (TransformPolygon& item : leaves) {
+ item.data->Paint(aBuilder, aCtx, item.geometry);
+ }
+ return;
+ }
+
+ gfxContextMatrixAutoSaveRestore saveMatrix(aCtx);
+ Matrix4x4 trans = ShouldSkipTransform(aBuilder)
+ ? Matrix4x4()
+ : GetAccumulatedPreserved3DTransform(aBuilder);
+ if (!IsFrameVisible(mFrame, trans)) {
+ return;
+ }
+
+ Matrix trans2d;
+ if (trans.CanDraw2D(&trans2d)) {
+ aCtx->Multiply(ThebesMatrix(trans2d));
+
+ if (aPolygon) {
+ RefPtr<gfx::Path> path =
+ BuildPathFromPolygon(aCtx->GetDrawTarget(), *aPolygon);
+ aCtx->GetDrawTarget()->PushClip(path);
+ }
+
+ GetChildren()->Paint(aBuilder, aCtx,
+ mFrame->PresContext()->AppUnitsPerDevPixel());
+
+ if (aPolygon) {
+ aCtx->GetDrawTarget()->PopClip();
+ }
+ return;
+ }
+
+ // TODO: Implement 3d transform handling, including plane splitting and
+ // sorting. See BasicCompositor.
+ auto pixelBounds = LayoutDeviceRect::FromAppUnitsToOutside(
+ mChildBounds, mFrame->PresContext()->AppUnitsPerDevPixel());
+ RefPtr<DrawTarget> untransformedDT =
+ gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(
+ IntSize(pixelBounds.Width(), pixelBounds.Height()),
+ SurfaceFormat::B8G8R8A8, true);
+ if (!untransformedDT || !untransformedDT->IsValid()) {
+ return;
+ }
+ untransformedDT->SetTransform(
+ Matrix::Translation(-Point(pixelBounds.X(), pixelBounds.Y())));
+
+ gfxContext groupTarget(untransformedDT, /* aPreserveTransform */ true);
+
+ if (aPolygon) {
+ RefPtr<gfx::Path> path =
+ BuildPathFromPolygon(aCtx->GetDrawTarget(), *aPolygon);
+ aCtx->GetDrawTarget()->PushClip(path);
+ }
+
+ GetChildren()->Paint(aBuilder, &groupTarget,
+ mFrame->PresContext()->AppUnitsPerDevPixel());
+
+ if (aPolygon) {
+ aCtx->GetDrawTarget()->PopClip();
+ }
+
+ RefPtr<SourceSurface> untransformedSurf = untransformedDT->Snapshot();
+
+ trans.PreTranslate(pixelBounds.X(), pixelBounds.Y(), 0);
+ aCtx->GetDrawTarget()->Draw3DTransformedSurface(untransformedSurf, trans);
+}
+
+bool nsDisplayTransform::MayBeAnimated(nsDisplayListBuilder* aBuilder) const {
+ // If EffectCompositor::HasAnimationsForCompositor() is true then we can
+ // completely bypass the main thread for this animation, so it is always
+ // worthwhile.
+ // For ActiveLayerTracker::IsTransformAnimated() cases the main thread is
+ // already involved so there is less to be gained.
+ // Therefore we check that the *post-transform* bounds of this item are
+ // big enough to justify an active layer.
+ return EffectCompositor::HasAnimationsForCompositor(
+ mFrame, DisplayItemType::TYPE_TRANSFORM) ||
+ (ActiveLayerTracker::IsTransformAnimated(aBuilder, mFrame));
+}
+
+nsRect nsDisplayTransform::TransformUntransformedBounds(
+ nsDisplayListBuilder* aBuilder, const Matrix4x4Flagged& aMatrix) const {
+ bool snap;
+ const nsRect untransformedBounds = GetUntransformedBounds(aBuilder, &snap);
+ // GetTransform always operates in dev pixels.
+ const float factor = mFrame->PresContext()->AppUnitsPerDevPixel();
+ return nsLayoutUtils::MatrixTransformRect(untransformedBounds, aMatrix,
+ factor);
+}
+
+/**
+ * Returns the bounds for this transform. The bounds are calculated during
+ * display list building and merging, see |nsDisplayTransform::UpdateBounds()|.
+ */
+nsRect nsDisplayTransform::GetBounds(nsDisplayListBuilder* aBuilder,
+ bool* aSnap) const {
+ *aSnap = false;
+ return mBounds;
+}
+
+void nsDisplayTransform::ComputeBounds(nsDisplayListBuilder* aBuilder) {
+ MOZ_ASSERT(mFrame->Extend3DContext() || IsLeafOf3DContext());
+
+ /* Some transforms can get empty bounds in 2D, but might get transformed again
+ * and get non-empty bounds. A simple example of this would be a 180 degree
+ * rotation getting applied twice.
+ * We should not depend on transforming bounds level by level.
+ *
+ * This function collects the bounds of this transform and stores it in
+ * nsDisplayListBuilder. If this is not a leaf of a 3D context, we recurse
+ * down and include the bounds of the child transforms.
+ * The bounds are transformed with the accumulated transformation matrix up to
+ * the 3D context root coordinate space.
+ */
+ nsDisplayListBuilder::AutoAccumulateTransform accTransform(aBuilder);
+ accTransform.Accumulate(GetTransform().GetMatrix());
+
+ // Do not dive into another 3D context.
+ if (!IsLeafOf3DContext()) {
+ for (nsDisplayItem* i : *GetChildren()) {
+ i->DoUpdateBoundsPreserves3D(aBuilder);
+ }
+ }
+
+ /* The child transforms that extend 3D context further will have empty bounds,
+ * so the untransformed bounds here is the bounds of all the non-preserve-3d
+ * content under this transform.
+ */
+ const nsRect rect = TransformUntransformedBounds(
+ aBuilder, accTransform.GetCurrentTransform());
+ aBuilder->AccumulateRect(rect);
+}
+
+void nsDisplayTransform::DoUpdateBoundsPreserves3D(
+ nsDisplayListBuilder* aBuilder) {
+ MOZ_ASSERT(mFrame->Combines3DTransformWithAncestors() ||
+ IsTransformSeparator());
+ // Updating is not going through to child 3D context.
+ ComputeBounds(aBuilder);
+}
+
+void nsDisplayTransform::UpdateBounds(nsDisplayListBuilder* aBuilder) {
+ UpdateUntransformedBounds(aBuilder);
+
+ if (IsTransformSeparator()) {
+ MOZ_ASSERT(GetTransform().IsIdentity());
+ mBounds = mChildBounds;
+ return;
+ }
+
+ if (mFrame->Extend3DContext()) {
+ if (!Combines3DTransformWithAncestors()) {
+ // The transform establishes a 3D context. |UpdateBoundsFor3D()| will
+ // collect the bounds from the child transforms.
+ UpdateBoundsFor3D(aBuilder);
+ } else {
+ // With nested 3D transforms, the 2D bounds might not be useful.
+ mBounds = nsRect();
+ }
+
+ return;
+ }
+
+ MOZ_ASSERT(!mFrame->Extend3DContext());
+
+ // We would like to avoid calculating 2D bounds here for nested 3D transforms,
+ // but mix-blend-mode relies on having bounds set. See bug 1556956.
+
+ // A stand-alone transform.
+ mBounds = TransformUntransformedBounds(aBuilder, GetTransform());
+}
+
+void nsDisplayTransform::UpdateBoundsFor3D(nsDisplayListBuilder* aBuilder) {
+ MOZ_ASSERT(mFrame->Extend3DContext() &&
+ !mFrame->Combines3DTransformWithAncestors() &&
+ !IsTransformSeparator());
+
+ // Always start updating from an establisher of a 3D rendering context.
+ nsDisplayListBuilder::AutoAccumulateRect accRect(aBuilder);
+ nsDisplayListBuilder::AutoAccumulateTransform accTransform(aBuilder);
+ accTransform.StartRoot();
+ ComputeBounds(aBuilder);
+ mBounds = aBuilder->GetAccumulatedRect();
+}
+
+void nsDisplayTransform::UpdateUntransformedBounds(
+ nsDisplayListBuilder* aBuilder) {
+ mChildBounds = GetChildren()->GetClippedBoundsWithRespectToASR(
+ aBuilder, mActiveScrolledRoot);
+}
+
+#ifdef DEBUG_HIT
+# include <time.h>
+#endif
+
+/* HitTest does some fun stuff with matrix transforms to obtain the answer. */
+void nsDisplayTransform::HitTest(nsDisplayListBuilder* aBuilder,
+ const nsRect& aRect, HitTestState* aState,
+ nsTArray<nsIFrame*>* aOutFrames) {
+ if (aState->mInPreserves3D) {
+ GetChildren()->HitTest(aBuilder, aRect, aState, aOutFrames);
+ return;
+ }
+
+ /* Here's how this works:
+ * 1. Get the matrix. If it's singular, abort (clearly we didn't hit
+ * anything).
+ * 2. Invert the matrix.
+ * 3. Use it to transform the rect into the correct space.
+ * 4. Pass that rect down through to the list's version of HitTest.
+ */
+ // GetTransform always operates in dev pixels.
+ float factor = mFrame->PresContext()->AppUnitsPerDevPixel();
+ Matrix4x4 matrix = GetAccumulatedPreserved3DTransform(aBuilder);
+
+ if (!IsFrameVisible(mFrame, matrix)) {
+ return;
+ }
+
+ const bool oldHitOccludingItem = aState->mHitOccludingItem;
+
+ /* We want to go from transformed-space to regular space.
+ * Thus we have to invert the matrix, which normally does
+ * the reverse operation (e.g. regular->transformed)
+ */
+
+ /* Now, apply the transform and pass it down the channel. */
+ matrix.Invert();
+ nsRect resultingRect;
+ // Magic width/height indicating we're hit testing a point, not a rect
+ const bool testingPoint = aRect.width == 1 && aRect.height == 1;
+ if (testingPoint) {
+ Point4D point =
+ matrix.ProjectPoint(Point(NSAppUnitsToFloatPixels(aRect.x, factor),
+ NSAppUnitsToFloatPixels(aRect.y, factor)));
+ if (!point.HasPositiveWCoord()) {
+ return;
+ }
+
+ Point point2d = point.As2DPoint();
+
+ resultingRect =
+ nsRect(NSFloatPixelsToAppUnits(float(point2d.x), factor),
+ NSFloatPixelsToAppUnits(float(point2d.y), factor), 1, 1);
+
+ } else {
+ Rect originalRect(NSAppUnitsToFloatPixels(aRect.x, factor),
+ NSAppUnitsToFloatPixels(aRect.y, factor),
+ NSAppUnitsToFloatPixels(aRect.width, factor),
+ NSAppUnitsToFloatPixels(aRect.height, factor));
+
+ Rect childGfxBounds(NSAppUnitsToFloatPixels(mChildBounds.x, factor),
+ NSAppUnitsToFloatPixels(mChildBounds.y, factor),
+ NSAppUnitsToFloatPixels(mChildBounds.width, factor),
+ NSAppUnitsToFloatPixels(mChildBounds.height, factor));
+
+ Rect rect = matrix.ProjectRectBounds(originalRect, childGfxBounds);
+
+ resultingRect =
+ nsRect(NSFloatPixelsToAppUnits(float(rect.X()), factor),
+ NSFloatPixelsToAppUnits(float(rect.Y()), factor),
+ NSFloatPixelsToAppUnits(float(rect.Width()), factor),
+ NSFloatPixelsToAppUnits(float(rect.Height()), factor));
+ }
+
+ if (resultingRect.IsEmpty()) {
+ return;
+ }
+
+#ifdef DEBUG_HIT
+ printf("Frame: %p\n", dynamic_cast<void*>(mFrame));
+ printf(" Untransformed point: (%f, %f)\n", resultingRect.X(),
+ resultingRect.Y());
+ uint32_t originalFrameCount = aOutFrames.Length();
+#endif
+
+ GetChildren()->HitTest(aBuilder, resultingRect, aState, aOutFrames);
+
+ if (aState->mHitOccludingItem && !testingPoint &&
+ !mChildBounds.Contains(aRect)) {
+ MOZ_ASSERT(aBuilder->HitTestIsForVisibility());
+ // We're hit-testing a rect that's bigger than our child bounds, but
+ // resultingRect is clipped by our bounds (in ProjectRectBounds above), so
+ // we can't stop hit-testing altogether.
+ //
+ // FIXME(emilio): I think this means that theoretically we might include
+ // some frames fully behind other transformed-but-opaque frames? Then again
+ // that's our pre-existing behavior for other untransformed content that
+ // doesn't fill the whole rect. To be fully correct I think we'd need proper
+ // "known occluded region" tracking, but that might be overkill for our
+ // purposes here.
+ aState->mHitOccludingItem = oldHitOccludingItem;
+ }
+
+#ifdef DEBUG_HIT
+ if (originalFrameCount != aOutFrames.Length())
+ printf(" Hit! Time: %f, first frame: %p\n", static_cast<double>(clock()),
+ dynamic_cast<void*>(aOutFrames.ElementAt(0)));
+ printf("=== end of hit test ===\n");
+#endif
+}
+
+float nsDisplayTransform::GetHitDepthAtPoint(nsDisplayListBuilder* aBuilder,
+ const nsPoint& aPoint) {
+ // GetTransform always operates in dev pixels.
+ float factor = mFrame->PresContext()->AppUnitsPerDevPixel();
+ Matrix4x4 matrix = GetAccumulatedPreserved3DTransform(aBuilder);
+
+ NS_ASSERTION(IsFrameVisible(mFrame, matrix),
+ "We can't have hit a frame that isn't visible!");
+
+ Matrix4x4 inverse = matrix;
+ inverse.Invert();
+ Point4D point =
+ inverse.ProjectPoint(Point(NSAppUnitsToFloatPixels(aPoint.x, factor),
+ NSAppUnitsToFloatPixels(aPoint.y, factor)));
+
+ Point point2d = point.As2DPoint();
+
+ Point3D transformed = matrix.TransformPoint(Point3D(point2d.x, point2d.y, 0));
+ return transformed.z;
+}
+
+/* The transform is opaque iff the transform consists solely of scales and
+ * translations and if the underlying content is opaque. Thus if the transform
+ * is of the form
+ *
+ * |a c e|
+ * |b d f|
+ * |0 0 1|
+ *
+ * We need b and c to be zero.
+ *
+ * We also need to check whether the underlying opaque content completely fills
+ * our visible rect. We use UntransformRect which expands to the axis-aligned
+ * bounding rect, but that's OK since if
+ * mStoredList.GetVisibleRect().Contains(untransformedVisible), then it
+ * certainly contains the actual (non-axis-aligned) untransformed rect.
+ */
+nsRegion nsDisplayTransform::GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
+ bool* aSnap) const {
+ *aSnap = false;
+
+ nsRect untransformedVisible;
+ if (!UntransformBuildingRect(aBuilder, &untransformedVisible)) {
+ return nsRegion();
+ }
+
+ const Matrix4x4Flagged& matrix = GetTransform();
+ Matrix matrix2d;
+ if (!matrix.Is2D(&matrix2d) || !matrix2d.PreservesAxisAlignedRectangles()) {
+ return nsRegion();
+ }
+
+ nsRegion result;
+
+ bool tmpSnap;
+ const nsRect bounds = GetUntransformedBounds(aBuilder, &tmpSnap);
+ const nsRegion opaque =
+ ::mozilla::GetOpaqueRegion(aBuilder, GetChildren(), bounds);
+
+ if (opaque.Contains(untransformedVisible)) {
+ result = GetBuildingRect().Intersect(GetBounds(aBuilder, &tmpSnap));
+ }
+ return result;
+}
+
+nsRect nsDisplayTransform::GetComponentAlphaBounds(
+ nsDisplayListBuilder* aBuilder) const {
+ if (GetChildren()->GetComponentAlphaBounds(aBuilder).IsEmpty()) {
+ return nsRect();
+ }
+
+ bool snap;
+ return GetBounds(aBuilder, &snap);
+}
+
+/* TransformRect takes in as parameters a rectangle (in app space) and returns
+ * the smallest rectangle (in app space) containing the transformed image of
+ * that rectangle. That is, it takes the four corners of the rectangle,
+ * transforms them according to the matrix associated with the specified frame,
+ * then returns the smallest rectangle containing the four transformed points.
+ *
+ * @param aUntransformedBounds The rectangle (in app units) to transform.
+ * @param aFrame The frame whose transformation should be applied.
+ * @param aOrigin The delta from the frame origin to the coordinate space origin
+ * @return The smallest rectangle containing the image of the transformed
+ * rectangle.
+ */
+nsRect nsDisplayTransform::TransformRect(const nsRect& aUntransformedBounds,
+ const nsIFrame* aFrame,
+ TransformReferenceBox& aRefBox) {
+ MOZ_ASSERT(aFrame, "Can't take the transform based on a null frame!");
+
+ float factor = aFrame->PresContext()->AppUnitsPerDevPixel();
+
+ FrameTransformProperties props(aFrame, aRefBox, factor);
+ return nsLayoutUtils::MatrixTransformRect(
+ aUntransformedBounds,
+ GetResultingTransformMatrixInternal(props, aRefBox, nsPoint(), factor,
+ kTransformRectFlags),
+ factor);
+}
+
+bool nsDisplayTransform::UntransformRect(const nsRect& aTransformedBounds,
+ const nsRect& aChildBounds,
+ const nsIFrame* aFrame,
+ nsRect* aOutRect) {
+ MOZ_ASSERT(aFrame, "Can't take the transform based on a null frame!");
+
+ float factor = aFrame->PresContext()->AppUnitsPerDevPixel();
+ Matrix4x4 transform = GetResultingTransformMatrix(aFrame, nsPoint(), factor,
+ kTransformRectFlags);
+ return UntransformRect(aTransformedBounds, aChildBounds, transform, factor,
+ aOutRect);
+}
+
+bool nsDisplayTransform::UntransformRect(const nsRect& aTransformedBounds,
+ const nsRect& aChildBounds,
+ const Matrix4x4& aMatrix,
+ float aAppUnitsPerPixel,
+ nsRect* aOutRect) {
+ if (aMatrix.IsSingular()) {
+ return false;
+ }
+
+ RectDouble result(
+ NSAppUnitsToFloatPixels(aTransformedBounds.x, aAppUnitsPerPixel),
+ NSAppUnitsToFloatPixels(aTransformedBounds.y, aAppUnitsPerPixel),
+ NSAppUnitsToFloatPixels(aTransformedBounds.width, aAppUnitsPerPixel),
+ NSAppUnitsToFloatPixels(aTransformedBounds.height, aAppUnitsPerPixel));
+
+ RectDouble childGfxBounds(
+ NSAppUnitsToFloatPixels(aChildBounds.x, aAppUnitsPerPixel),
+ NSAppUnitsToFloatPixels(aChildBounds.y, aAppUnitsPerPixel),
+ NSAppUnitsToFloatPixels(aChildBounds.width, aAppUnitsPerPixel),
+ NSAppUnitsToFloatPixels(aChildBounds.height, aAppUnitsPerPixel));
+
+ result = aMatrix.Inverse().ProjectRectBounds(result, childGfxBounds);
+ *aOutRect = nsLayoutUtils::RoundGfxRectToAppRect(ThebesRect(result),
+ aAppUnitsPerPixel);
+ return true;
+}
+
+bool nsDisplayTransform::UntransformRect(nsDisplayListBuilder* aBuilder,
+ const nsRect& aRect,
+ nsRect* aOutRect) const {
+ if (GetTransform().IsSingular()) {
+ return false;
+ }
+
+ // GetTransform always operates in dev pixels.
+ float factor = mFrame->PresContext()->AppUnitsPerDevPixel();
+ RectDouble result(NSAppUnitsToFloatPixels(aRect.x, factor),
+ NSAppUnitsToFloatPixels(aRect.y, factor),
+ NSAppUnitsToFloatPixels(aRect.width, factor),
+ NSAppUnitsToFloatPixels(aRect.height, factor));
+
+ bool snap;
+ nsRect childBounds = GetUntransformedBounds(aBuilder, &snap);
+ RectDouble childGfxBounds(
+ NSAppUnitsToFloatPixels(childBounds.x, factor),
+ NSAppUnitsToFloatPixels(childBounds.y, factor),
+ NSAppUnitsToFloatPixels(childBounds.width, factor),
+ NSAppUnitsToFloatPixels(childBounds.height, factor));
+
+ /* We want to untransform the matrix, so invert the transformation first! */
+ result = GetInverseTransform().ProjectRectBounds(result, childGfxBounds);
+
+ *aOutRect = nsLayoutUtils::RoundGfxRectToAppRect(ThebesRect(result), factor);
+
+ return true;
+}
+
+void nsDisplayTransform::WriteDebugInfo(std::stringstream& aStream) {
+ aStream << GetTransform().GetMatrix();
+ if (IsTransformSeparator()) {
+ aStream << " transform-separator";
+ }
+ if (IsLeafOf3DContext()) {
+ aStream << " 3d-context-leaf";
+ }
+ if (mFrame->Extend3DContext()) {
+ aStream << " extends-3d-context";
+ }
+ if (mFrame->Combines3DTransformWithAncestors()) {
+ aStream << " combines-3d-with-ancestors";
+ }
+
+ aStream << " prerender(";
+ switch (mPrerenderDecision) {
+ case PrerenderDecision::No:
+ aStream << "no";
+ break;
+ case PrerenderDecision::Partial:
+ aStream << "partial";
+ break;
+ case PrerenderDecision::Full:
+ aStream << "full";
+ break;
+ }
+ aStream << ")";
+ aStream << " childrenBuildingRect" << mChildrenBuildingRect;
+}
+
+nsDisplayPerspective::nsDisplayPerspective(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame,
+ nsDisplayList* aList)
+ : nsPaintedDisplayItem(aBuilder, aFrame), mList(aBuilder) {
+ mList.AppendToTop(aList);
+ MOZ_ASSERT(mList.Length() == 1);
+ MOZ_ASSERT(mList.GetTop()->GetType() == DisplayItemType::TYPE_TRANSFORM);
+}
+
+void nsDisplayPerspective::Paint(nsDisplayListBuilder* aBuilder,
+ gfxContext* aCtx) {
+ // Just directly recurse into children, since we'll include the persepctive
+ // value in any nsDisplayTransform children.
+ GetChildren()->Paint(aBuilder, aCtx,
+ mFrame->PresContext()->AppUnitsPerDevPixel());
+}
+
+nsRegion nsDisplayPerspective::GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
+ bool* aSnap) const {
+ if (!GetChildren()->GetTop()) {
+ *aSnap = false;
+ return nsRegion();
+ }
+
+ return GetChildren()->GetTop()->GetOpaqueRegion(aBuilder, aSnap);
+}
+
+bool nsDisplayPerspective::CreateWebRenderCommands(
+ wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc, RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) {
+ float appUnitsPerPixel = mFrame->PresContext()->AppUnitsPerDevPixel();
+ Matrix4x4 perspectiveMatrix;
+ DebugOnly<bool> hasPerspective = nsDisplayTransform::ComputePerspectiveMatrix(
+ mFrame, appUnitsPerPixel, perspectiveMatrix);
+ MOZ_ASSERT(hasPerspective, "Why did we create nsDisplayPerspective?");
+
+ /*
+ * ClipListToRange can remove our child after we were created.
+ */
+ if (!GetChildren()->GetTop()) {
+ return false;
+ }
+
+ /*
+ * The resulting matrix is still in the coordinate space of the transformed
+ * frame. Append a translation to the reference frame coordinates.
+ */
+ nsDisplayTransform* transform =
+ static_cast<nsDisplayTransform*>(GetChildren()->GetTop());
+
+ Point3D newOrigin =
+ Point3D(NSAppUnitsToFloatPixels(transform->ToReferenceFrame().x,
+ appUnitsPerPixel),
+ NSAppUnitsToFloatPixels(transform->ToReferenceFrame().y,
+ appUnitsPerPixel),
+ 0.0f);
+ Point3D roundedOrigin(NS_round(newOrigin.x), NS_round(newOrigin.y), 0);
+
+ perspectiveMatrix.PostTranslate(roundedOrigin);
+
+ nsIFrame* perspectiveFrame =
+ mFrame->GetClosestFlattenedTreeAncestorPrimaryFrame();
+
+ // Passing true here is always correct, since perspective always combines
+ // transforms with the descendants. However that'd make WR do a lot of work
+ // that it doesn't really need to do if there aren't other transforms forming
+ // part of the 3D context.
+ //
+ // WR knows how to treat perspective in that case, so the only thing we need
+ // to do is to ensure we pass true when we're involved in a 3d context in any
+ // other way via the transform-style property on either the transformed frame
+ // or the perspective frame in order to not confuse WR's preserve-3d code in
+ // very awful ways.
+ bool preserve3D =
+ mFrame->Extend3DContext() || perspectiveFrame->Extend3DContext();
+
+ wr::StackingContextParams params;
+
+ wr::WrTransformInfo transform_info;
+ transform_info.transform = wr::ToLayoutTransform(perspectiveMatrix);
+ transform_info.key = wr::SpatialKey(uint64_t(mFrame), GetPerFrameKey(),
+ wr::SpatialKeyKind::Perspective);
+ params.mTransformPtr = &transform_info;
+
+ params.reference_frame_kind = wr::WrReferenceFrameKind::Perspective;
+ params.prim_flags = !BackfaceIsHidden()
+ ? wr::PrimitiveFlags::IS_BACKFACE_VISIBLE
+ : wr::PrimitiveFlags{0};
+ params.SetPreserve3D(preserve3D);
+ params.clip =
+ wr::WrStackingContextClip::ClipChain(aBuilder.CurrentClipChainId());
+
+ Maybe<uint64_t> scrollingRelativeTo;
+ for (const auto* asr = GetActiveScrolledRoot(); asr; asr = asr->mParent) {
+ // In OOP documents, the root scrollable frame of the in-process root
+ // document is always active, so using IsAncestorFrameCrossDocInProcess
+ // should be fine here.
+ if (nsLayoutUtils::IsAncestorFrameCrossDocInProcess(
+ asr->mScrollableFrame->GetScrolledFrame(), perspectiveFrame)) {
+ scrollingRelativeTo.emplace(asr->GetViewId());
+ break;
+ }
+ }
+
+ // We put the perspective reference frame wrapping the transformed frame,
+ // even though there may be arbitrarily nested scroll frames in between.
+ //
+ // We need to know how many ancestor scroll-frames are we nested in, in order
+ // for the async scrolling code in WebRender to calculate the right
+ // transformation for the perspective contents.
+ params.scrolling_relative_to = scrollingRelativeTo.ptrOr(nullptr);
+
+ StackingContextHelper sc(aSc, GetActiveScrolledRoot(), mFrame, this, aBuilder,
+ params);
+
+ aManager->CommandBuilder().CreateWebRenderCommandsFromDisplayList(
+ GetChildren(), this, aDisplayListBuilder, sc, aBuilder, aResources);
+
+ return true;
+}
+
+nsDisplayText::nsDisplayText(nsDisplayListBuilder* aBuilder,
+ nsTextFrame* aFrame)
+ : nsPaintedDisplayItem(aBuilder, aFrame),
+ mVisIStartEdge(0),
+ mVisIEndEdge(0) {
+ MOZ_COUNT_CTOR(nsDisplayText);
+ mBounds = mFrame->InkOverflowRectRelativeToSelf() + ToReferenceFrame();
+ // Bug 748228
+ mBounds.Inflate(mFrame->PresContext()->AppUnitsPerDevPixel());
+ mVisibleRect = aBuilder->GetVisibleRect() +
+ aBuilder->GetCurrentFrameOffsetToReferenceFrame();
+}
+
+bool nsDisplayText::CanApplyOpacity(WebRenderLayerManager* aManager,
+ nsDisplayListBuilder* aBuilder) const {
+ auto* f = static_cast<nsTextFrame*>(mFrame);
+
+ if (f->IsSelected()) {
+ return false;
+ }
+
+ const nsStyleText* textStyle = f->StyleText();
+ if (textStyle->HasTextShadow()) {
+ return false;
+ }
+
+ nsTextFrame::TextDecorations decorations;
+ f->GetTextDecorations(f->PresContext(), nsTextFrame::eResolvedColors,
+ decorations);
+ return !decorations.HasDecorationLines();
+}
+
+void nsDisplayText::Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) {
+ AUTO_PROFILER_LABEL("nsDisplayText::Paint", GRAPHICS);
+ // We don't pass mVisibleRect here, since this can be called from within
+ // the WebRender fallback painting path, and we don't want to issue
+ // recorded commands that are dependent on the visible/building rect.
+ RenderToContext(aCtx, aBuilder, GetPaintRect(aBuilder, aCtx));
+}
+
+bool nsDisplayText::CreateWebRenderCommands(
+ wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc, RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) {
+ auto* f = static_cast<nsTextFrame*>(mFrame);
+ auto appUnitsPerDevPixel = f->PresContext()->AppUnitsPerDevPixel();
+
+ nsRect bounds = f->WebRenderBounds() + ToReferenceFrame();
+ // Bug 748228
+ bounds.Inflate(appUnitsPerDevPixel);
+
+ if (bounds.IsEmpty()) {
+ return true;
+ }
+
+ // For large font sizes, punt to a blob image, to avoid the blurry rendering
+ // that results from WR clamping the glyph size used for rasterization.
+ //
+ // (See FONT_SIZE_LIMIT in webrender/src/glyph_rasterizer/mod.rs.)
+ //
+ // This is not strictly accurate, as final used font sizes might not be the
+ // same as claimed by the fontGroup's style.size (eg: due to font-size-adjust
+ // altering the used size of the font actually used).
+ // It also fails to consider how transforms might affect the device-font-size
+ // that webrender uses (and clamps).
+ // But it should be near enough for practical purposes; the limitations just
+ // mean we might sometimes end up with webrender still applying some bitmap
+ // scaling, or bail out when we didn't really need to.
+ constexpr float kWebRenderFontSizeLimit = 320.0;
+ f->EnsureTextRun(nsTextFrame::eInflated);
+ gfxTextRun* textRun = f->GetTextRun(nsTextFrame::eInflated);
+ if (textRun &&
+ textRun->GetFontGroup()->GetStyle()->size > kWebRenderFontSizeLimit) {
+ return false;
+ }
+
+ gfx::Point deviceOffset =
+ LayoutDevicePoint::FromAppUnits(bounds.TopLeft(), appUnitsPerDevPixel)
+ .ToUnknownPoint();
+
+ // Clipping the bounds to the PaintRect (factoring in what's covered by parent
+ // frames) lets us early reject a bunch of things.
+ nsRect visible = mVisibleRect;
+
+ // Add the "source rect" area from which the given shadows could intersect
+ // with mVisibleRect, and which therefore needs to included in the paint
+ // operation, to the `visible` rect that we will use to limit the bounds of
+ // what we send to the renderer.
+ auto addShadowSourceToVisible = [&](Span<const StyleSimpleShadow> aShadows) {
+ for (const auto& shadow : aShadows) {
+ nsRect sourceRect = mVisibleRect;
+ // Negate the offsets, because we're looking for the "source" rect that
+ // could cast a shadow into the visible rect, rather than a "target" area
+ // onto which the visible rect would cast a shadow.
+ sourceRect.MoveBy(-shadow.horizontal.ToAppUnits(),
+ -shadow.vertical.ToAppUnits());
+ // Inflate to account for the shadow blur.
+ sourceRect.Inflate(nsContextBoxBlur::GetBlurRadiusMargin(
+ shadow.blur.ToAppUnits(), appUnitsPerDevPixel));
+ visible.OrWith(sourceRect);
+ }
+ };
+
+ // Shadows can translate things back into view, so we enlarge the notional
+ // "visible" rect to ensure we don't skip painting relevant parts that might
+ // cast a shadow within the visible area.
+ addShadowSourceToVisible(f->StyleText()->mTextShadow.AsSpan());
+
+ // Similarly for shadows that may be cast by ::selection.
+ if (f->IsSelected()) {
+ nsTextPaintStyle textPaint(f);
+ Span<const StyleSimpleShadow> shadows;
+ f->GetSelectionTextShadow(SelectionType::eNormal, textPaint, &shadows);
+ addShadowSourceToVisible(shadows);
+ }
+
+ // Inflate a little extra to allow for potential antialiasing "blur".
+ visible.Inflate(3 * appUnitsPerDevPixel);
+ bounds = bounds.Intersect(visible);
+
+ gfxContext* textDrawer = aBuilder.GetTextContext(aResources, aSc, aManager,
+ this, bounds, deviceOffset);
+
+ aBuilder.StartGroup(this);
+
+ RenderToContext(textDrawer, aDisplayListBuilder, mVisibleRect,
+ aBuilder.GetInheritedOpacity(), true);
+ const bool result = textDrawer->GetTextDrawer()->Finish();
+
+ if (result) {
+ aBuilder.FinishGroup();
+ } else {
+ aBuilder.CancelGroup(true);
+ }
+
+ return result;
+}
+
+void nsDisplayText::RenderToContext(gfxContext* aCtx,
+ nsDisplayListBuilder* aBuilder,
+ const nsRect& aVisibleRect, float aOpacity,
+ bool aIsRecording) {
+ nsTextFrame* f = static_cast<nsTextFrame*>(mFrame);
+
+ // Add 1 pixel of dirty area around mVisibleRect to allow us to paint
+ // antialiased pixels beyond the measured text extents.
+ // This is temporary until we do this in the actual calculation of text
+ // extents.
+ auto A2D = mFrame->PresContext()->AppUnitsPerDevPixel();
+ LayoutDeviceRect extraVisible =
+ LayoutDeviceRect::FromAppUnits(aVisibleRect, A2D);
+ extraVisible.Inflate(1);
+
+ gfxRect pixelVisible(extraVisible.x, extraVisible.y, extraVisible.width,
+ extraVisible.height);
+ pixelVisible.Inflate(2);
+ pixelVisible.RoundOut();
+
+ gfxClipAutoSaveRestore autoSaveClip(aCtx);
+ if (!aBuilder->IsForGenerateGlyphMask() && !aIsRecording) {
+ autoSaveClip.Clip(pixelVisible);
+ }
+
+ NS_ASSERTION(mVisIStartEdge >= 0, "illegal start edge");
+ NS_ASSERTION(mVisIEndEdge >= 0, "illegal end edge");
+
+ gfxContextMatrixAutoSaveRestore matrixSR;
+
+ nsPoint framePt = ToReferenceFrame();
+ if (f->Style()->IsTextCombined()) {
+ float scaleFactor = nsTextFrame::GetTextCombineScaleFactor(f);
+ if (scaleFactor != 1.0f) {
+ if (auto* textDrawer = aCtx->GetTextDrawer()) {
+ // WebRender doesn't support scaling text like this yet
+ textDrawer->FoundUnsupportedFeature();
+ return;
+ }
+ matrixSR.SetContext(aCtx);
+ // Setup matrix to compress text for text-combine-upright if
+ // necessary. This is done here because we want selection be
+ // compressed at the same time as text.
+ gfxPoint pt = nsLayoutUtils::PointToGfxPoint(framePt, A2D);
+ gfxMatrix mat = aCtx->CurrentMatrixDouble()
+ .PreTranslate(pt)
+ .PreScale(scaleFactor, 1.0)
+ .PreTranslate(-pt);
+ aCtx->SetMatrixDouble(mat);
+ }
+ }
+ nsTextFrame::PaintTextParams params(aCtx);
+ params.framePt = gfx::Point(framePt.x, framePt.y);
+ params.dirtyRect = extraVisible;
+
+ if (aBuilder->IsForGenerateGlyphMask()) {
+ params.state = nsTextFrame::PaintTextParams::GenerateTextMask;
+ } else {
+ params.state = nsTextFrame::PaintTextParams::PaintText;
+ }
+
+ f->PaintText(params, mVisIStartEdge, mVisIEndEdge, ToReferenceFrame(),
+ f->IsSelected(), aOpacity);
+}
+
+// This could go to nsDisplayListInvalidation.h, but
+// |nsTextFrame::TextDecorations| requires including of nsTextFrame.h which
+// would produce circular dependencies.
+class nsDisplayTextGeometry : public nsDisplayItemGenericGeometry {
+ public:
+ nsDisplayTextGeometry(nsDisplayText* aItem, nsDisplayListBuilder* aBuilder)
+ : nsDisplayItemGenericGeometry(aItem, aBuilder),
+ mVisIStartEdge(aItem->VisIStartEdge()),
+ mVisIEndEdge(aItem->VisIEndEdge()) {
+ nsTextFrame* f = static_cast<nsTextFrame*>(aItem->Frame());
+ f->GetTextDecorations(f->PresContext(), nsTextFrame::eResolvedColors,
+ mDecorations);
+ }
+
+ /**
+ * We store the computed text decorations here since they are
+ * computed using style data from parent frames. Any changes to these
+ * styles will only invalidate the parent frame and not this frame.
+ */
+ nsTextFrame::TextDecorations mDecorations;
+ nscoord mVisIStartEdge;
+ nscoord mVisIEndEdge;
+};
+
+nsDisplayItemGeometry* nsDisplayText::AllocateGeometry(
+ nsDisplayListBuilder* aBuilder) {
+ return new nsDisplayTextGeometry(this, aBuilder);
+}
+
+void nsDisplayText::ComputeInvalidationRegion(
+ nsDisplayListBuilder* aBuilder, const nsDisplayItemGeometry* aGeometry,
+ nsRegion* aInvalidRegion) const {
+ const nsDisplayTextGeometry* geometry =
+ static_cast<const nsDisplayTextGeometry*>(aGeometry);
+ nsTextFrame* f = static_cast<nsTextFrame*>(mFrame);
+
+ nsTextFrame::TextDecorations decorations;
+ f->GetTextDecorations(f->PresContext(), nsTextFrame::eResolvedColors,
+ decorations);
+
+ bool snap;
+ const nsRect& newRect = geometry->mBounds;
+ nsRect oldRect = GetBounds(aBuilder, &snap);
+ if (decorations != geometry->mDecorations ||
+ mVisIStartEdge != geometry->mVisIStartEdge ||
+ mVisIEndEdge != geometry->mVisIEndEdge ||
+ !oldRect.IsEqualInterior(newRect) ||
+ !geometry->mBorderRect.IsEqualInterior(GetBorderRect())) {
+ aInvalidRegion->Or(oldRect, newRect);
+ }
+}
+
+void nsDisplayText::WriteDebugInfo(std::stringstream& aStream) {
+#ifdef DEBUG
+ aStream << " (\"";
+
+ nsTextFrame* f = static_cast<nsTextFrame*>(mFrame);
+ nsCString buf;
+ f->ToCString(buf);
+
+ aStream << buf.get() << "\")";
+#endif
+}
+
+nsDisplayEffectsBase::nsDisplayEffectsBase(
+ nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayList* aList,
+ const ActiveScrolledRoot* aActiveScrolledRoot, bool aClearClipChain)
+ : nsDisplayWrapList(aBuilder, aFrame, aList, aActiveScrolledRoot,
+ aClearClipChain) {
+ MOZ_COUNT_CTOR(nsDisplayEffectsBase);
+}
+
+nsDisplayEffectsBase::nsDisplayEffectsBase(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame,
+ nsDisplayList* aList)
+ : nsDisplayWrapList(aBuilder, aFrame, aList) {
+ MOZ_COUNT_CTOR(nsDisplayEffectsBase);
+}
+
+nsRegion nsDisplayEffectsBase::GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
+ bool* aSnap) const {
+ *aSnap = false;
+ return nsRegion();
+}
+
+void nsDisplayEffectsBase::HitTest(nsDisplayListBuilder* aBuilder,
+ const nsRect& aRect, HitTestState* aState,
+ nsTArray<nsIFrame*>* aOutFrames) {
+ nsPoint rectCenter(aRect.x + aRect.width / 2, aRect.y + aRect.height / 2);
+ if (SVGIntegrationUtils::HitTestFrameForEffects(
+ mFrame, rectCenter - ToReferenceFrame())) {
+ mList.HitTest(aBuilder, aRect, aState, aOutFrames);
+ }
+}
+
+gfxRect nsDisplayEffectsBase::BBoxInUserSpace() const {
+ return SVGUtils::GetBBox(mFrame);
+}
+
+gfxPoint nsDisplayEffectsBase::UserSpaceOffset() const {
+ return SVGUtils::FrameSpaceInCSSPxToUserSpaceOffset(mFrame);
+}
+
+void nsDisplayEffectsBase::ComputeInvalidationRegion(
+ nsDisplayListBuilder* aBuilder, const nsDisplayItemGeometry* aGeometry,
+ nsRegion* aInvalidRegion) const {
+ const auto* geometry =
+ static_cast<const nsDisplaySVGEffectGeometry*>(aGeometry);
+ bool snap;
+ nsRect bounds = GetBounds(aBuilder, &snap);
+ if (geometry->mFrameOffsetToReferenceFrame != ToReferenceFrame() ||
+ geometry->mUserSpaceOffset != UserSpaceOffset() ||
+ !geometry->mBBox.IsEqualInterior(BBoxInUserSpace())) {
+ // Filter and mask output can depend on the location of the frame's user
+ // space and on the frame's BBox. We need to invalidate if either of these
+ // change relative to the reference frame.
+ // Invalidations from our inactive layer manager are not enough to catch
+ // some of these cases because filters can produce output even if there's
+ // nothing in the filter input.
+ aInvalidRegion->Or(bounds, geometry->mBounds);
+ }
+}
+
+bool nsDisplayEffectsBase::ValidateSVGFrame() {
+ if (mFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) {
+ ISVGDisplayableFrame* svgFrame = do_QueryFrame(mFrame);
+ if (!svgFrame) {
+ return false;
+ }
+ if (auto* svgElement = SVGElement::FromNode(mFrame->GetContent())) {
+ // The SVG spec says only to draw filters if the element
+ // has valid dimensions.
+ return svgElement->HasValidDimensions();
+ }
+ return false;
+ }
+
+ return true;
+}
+
+using PaintFramesParams = SVGIntegrationUtils::PaintFramesParams;
+
+static void ComputeMaskGeometry(PaintFramesParams& aParams) {
+ // Properties are added lazily and may have been removed by a restyle, so
+ // make sure all applicable ones are set again.
+ nsIFrame* firstFrame =
+ nsLayoutUtils::FirstContinuationOrIBSplitSibling(aParams.frame);
+
+ const nsStyleSVGReset* svgReset = firstFrame->StyleSVGReset();
+
+ nsTArray<SVGMaskFrame*> maskFrames;
+ // XXX check return value?
+ SVGObserverUtils::GetAndObserveMasks(firstFrame, &maskFrames);
+
+ if (maskFrames.Length() == 0) {
+ return;
+ }
+
+ gfxContext& ctx = aParams.ctx;
+ nsIFrame* frame = aParams.frame;
+
+ nsPoint offsetToUserSpace =
+ nsLayoutUtils::ComputeOffsetToUserSpace(aParams.builder, aParams.frame);
+
+ auto cssToDevScale = frame->PresContext()->CSSToDevPixelScale();
+ int32_t appUnitsPerDevPixel = frame->PresContext()->AppUnitsPerDevPixel();
+
+ gfxPoint devPixelOffsetToUserSpace =
+ nsLayoutUtils::PointToGfxPoint(offsetToUserSpace, appUnitsPerDevPixel);
+
+ gfxContextMatrixAutoSaveRestore matSR(&ctx);
+ ctx.SetMatrixDouble(
+ ctx.CurrentMatrixDouble().PreTranslate(devPixelOffsetToUserSpace));
+
+ // Convert boaderArea and dirtyRect to user space.
+ nsRect userSpaceBorderArea = aParams.borderArea - offsetToUserSpace;
+ nsRect userSpaceDirtyRect = aParams.dirtyRect - offsetToUserSpace;
+
+ // Union all mask layer rectangles in user space.
+ LayoutDeviceRect maskInUserSpace;
+ for (size_t i = 0; i < maskFrames.Length(); i++) {
+ SVGMaskFrame* maskFrame = maskFrames[i];
+ LayoutDeviceRect currentMaskSurfaceRect;
+
+ if (maskFrame) {
+ auto rect = maskFrame->GetMaskArea(aParams.frame);
+ currentMaskSurfaceRect =
+ CSSRect::FromUnknownRect(ToRect(rect)) * cssToDevScale;
+ } else {
+ nsCSSRendering::ImageLayerClipState clipState;
+ nsCSSRendering::GetImageLayerClip(
+ svgReset->mMask.mLayers[i], frame, *frame->StyleBorder(),
+ userSpaceBorderArea, userSpaceDirtyRect,
+ /* aWillPaintBorder = */ false, appUnitsPerDevPixel, &clipState);
+ currentMaskSurfaceRect = LayoutDeviceRect::FromUnknownRect(
+ ToRect(clipState.mDirtyRectInDevPx));
+ }
+
+ maskInUserSpace = maskInUserSpace.Union(currentMaskSurfaceRect);
+ }
+
+ if (!maskInUserSpace.IsEmpty()) {
+ aParams.maskRect = Some(maskInUserSpace);
+ } else {
+ aParams.maskRect = Nothing();
+ }
+}
+
+nsDisplayMasksAndClipPaths::nsDisplayMasksAndClipPaths(
+ nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayList* aList,
+ const ActiveScrolledRoot* aActiveScrolledRoot, bool aWrapsBackdropFilter)
+ : nsDisplayEffectsBase(aBuilder, aFrame, aList, aActiveScrolledRoot, true),
+ mWrapsBackdropFilter(aWrapsBackdropFilter) {
+ MOZ_COUNT_CTOR(nsDisplayMasksAndClipPaths);
+
+ nsPresContext* presContext = mFrame->PresContext();
+ uint32_t flags =
+ aBuilder->GetBackgroundPaintFlags() | nsCSSRendering::PAINTBG_MASK_IMAGE;
+ const nsStyleSVGReset* svgReset = aFrame->StyleSVGReset();
+ NS_FOR_VISIBLE_IMAGE_LAYERS_BACK_TO_FRONT(i, svgReset->mMask) {
+ const auto& layer = svgReset->mMask.mLayers[i];
+ if (!layer.mImage.IsResolved()) {
+ continue;
+ }
+ const nsRect& borderArea = mFrame->GetRectRelativeToSelf();
+ // NOTE(emilio): We only care about the dest rect so we don't bother
+ // computing a clip.
+ bool isTransformedFixed = false;
+ nsBackgroundLayerState state = nsCSSRendering::PrepareImageLayer(
+ presContext, aFrame, flags, borderArea, borderArea, layer,
+ &isTransformedFixed);
+ mDestRects.AppendElement(state.mDestArea);
+ }
+}
+
+static bool CanMergeDisplayMaskFrame(nsIFrame* aFrame) {
+ // Do not merge items for box-decoration-break:clone elements,
+ // since each box should have its own mask in that case.
+ if (aFrame->StyleBorder()->mBoxDecorationBreak ==
+ StyleBoxDecorationBreak::Clone) {
+ return false;
+ }
+
+ // Do not merge if either frame has a mask. Continuation frames should apply
+ // the mask independently (just like nsDisplayBackgroundImage).
+ if (aFrame->StyleSVGReset()->HasMask()) {
+ return false;
+ }
+
+ return true;
+}
+
+bool nsDisplayMasksAndClipPaths::CanMerge(const nsDisplayItem* aItem) const {
+ // Items for the same content element should be merged into a single
+ // compositing group.
+ if (!HasDifferentFrame(aItem) || !HasSameTypeAndClip(aItem) ||
+ !HasSameContent(aItem)) {
+ return false;
+ }
+
+ return CanMergeDisplayMaskFrame(mFrame) &&
+ CanMergeDisplayMaskFrame(aItem->Frame());
+}
+
+bool nsDisplayMasksAndClipPaths::IsValidMask() {
+ if (!ValidateSVGFrame()) {
+ return false;
+ }
+
+ nsIFrame* firstFrame =
+ nsLayoutUtils::FirstContinuationOrIBSplitSibling(mFrame);
+
+ return !(SVGObserverUtils::GetAndObserveClipPath(firstFrame, nullptr) ==
+ SVGObserverUtils::eHasRefsSomeInvalid ||
+ SVGObserverUtils::GetAndObserveMasks(firstFrame, nullptr) ==
+ SVGObserverUtils::eHasRefsSomeInvalid);
+}
+
+bool nsDisplayMasksAndClipPaths::PaintMask(nsDisplayListBuilder* aBuilder,
+ gfxContext* aMaskContext,
+ bool aHandleOpacity,
+ bool* aMaskPainted) {
+ MOZ_ASSERT(aMaskContext->GetDrawTarget()->GetFormat() == SurfaceFormat::A8);
+
+ imgDrawingParams imgParams(aBuilder->GetImageDecodeFlags());
+ nsRect borderArea = nsRect(ToReferenceFrame(), mFrame->GetSize());
+ PaintFramesParams params(*aMaskContext, mFrame, mBounds, borderArea, aBuilder,
+ aHandleOpacity, imgParams);
+ ComputeMaskGeometry(params);
+ bool maskIsComplete = false;
+ bool painted = SVGIntegrationUtils::PaintMask(params, maskIsComplete);
+ if (aMaskPainted) {
+ *aMaskPainted = painted;
+ }
+
+ return maskIsComplete &&
+ (imgParams.result == ImgDrawResult::SUCCESS ||
+ imgParams.result == ImgDrawResult::SUCCESS_NOT_COMPLETE ||
+ imgParams.result == ImgDrawResult::WRONG_SIZE);
+}
+
+void nsDisplayMasksAndClipPaths::ComputeInvalidationRegion(
+ nsDisplayListBuilder* aBuilder, const nsDisplayItemGeometry* aGeometry,
+ nsRegion* aInvalidRegion) const {
+ nsDisplayEffectsBase::ComputeInvalidationRegion(aBuilder, aGeometry,
+ aInvalidRegion);
+
+ const auto* geometry =
+ static_cast<const nsDisplayMasksAndClipPathsGeometry*>(aGeometry);
+ bool snap;
+ nsRect bounds = GetBounds(aBuilder, &snap);
+
+ if (mDestRects.Length() != geometry->mDestRects.Length()) {
+ aInvalidRegion->Or(bounds, geometry->mBounds);
+ } else {
+ for (size_t i = 0; i < mDestRects.Length(); i++) {
+ if (!mDestRects[i].IsEqualInterior(geometry->mDestRects[i])) {
+ aInvalidRegion->Or(bounds, geometry->mBounds);
+ break;
+ }
+ }
+ }
+}
+
+void nsDisplayMasksAndClipPaths::PaintWithContentsPaintCallback(
+ nsDisplayListBuilder* aBuilder, gfxContext* aCtx,
+ const std::function<void()>& aPaintChildren) {
+ // Clip the drawing target by mVisibleRect, which contains the visible
+ // region of the target frame and its out-of-flow and inflow descendants.
+ Rect bounds = NSRectToRect(GetPaintRect(aBuilder, aCtx),
+ mFrame->PresContext()->AppUnitsPerDevPixel());
+ bounds.RoundOut();
+ gfxClipAutoSaveRestore autoSaveClip(aCtx);
+ autoSaveClip.Clip(bounds);
+
+ imgDrawingParams imgParams(aBuilder->GetImageDecodeFlags());
+ nsRect borderArea = nsRect(ToReferenceFrame(), mFrame->GetSize());
+ PaintFramesParams params(*aCtx, mFrame, GetPaintRect(aBuilder, aCtx),
+ borderArea, aBuilder, false, imgParams);
+
+ ComputeMaskGeometry(params);
+
+ SVGIntegrationUtils::PaintMaskAndClipPath(params, aPaintChildren);
+}
+
+void nsDisplayMasksAndClipPaths::Paint(nsDisplayListBuilder* aBuilder,
+ gfxContext* aCtx) {
+ if (!IsValidMask()) {
+ return;
+ }
+ PaintWithContentsPaintCallback(aBuilder, aCtx, [&] {
+ GetChildren()->Paint(aBuilder, aCtx,
+ mFrame->PresContext()->AppUnitsPerDevPixel());
+ });
+}
+
+static Maybe<wr::WrClipChainId> CreateSimpleClipRegion(
+ const nsDisplayMasksAndClipPaths& aDisplayItem,
+ wr::DisplayListBuilder& aBuilder) {
+ nsIFrame* frame = aDisplayItem.Frame();
+ const auto* style = frame->StyleSVGReset();
+ MOZ_ASSERT(style->HasClipPath() || style->HasMask());
+ if (!SVGIntegrationUtils::UsingSimpleClipPathForFrame(frame)) {
+ return Nothing();
+ }
+
+ const auto& clipPath = style->mClipPath;
+ const auto& shape = *clipPath.AsShape()._0;
+
+ auto appUnitsPerDevPixel = frame->PresContext()->AppUnitsPerDevPixel();
+ const nsRect refBox =
+ nsLayoutUtils::ComputeGeometryBox(frame, clipPath.AsShape()._1);
+
+ wr::WrClipId clipId{};
+
+ switch (shape.tag) {
+ case StyleBasicShape::Tag::Inset: {
+ const nsRect insetRect = ShapeUtils::ComputeInsetRect(shape, refBox) +
+ aDisplayItem.ToReferenceFrame();
+
+ nscoord radii[8] = {0};
+
+ if (ShapeUtils::ComputeInsetRadii(shape, refBox, insetRect, radii)) {
+ clipId = aBuilder.DefineRoundedRectClip(
+ Nothing(),
+ wr::ToComplexClipRegion(insetRect, radii, appUnitsPerDevPixel));
+ } else {
+ clipId = aBuilder.DefineRectClip(
+ Nothing(), wr::ToLayoutRect(LayoutDeviceRect::FromAppUnits(
+ insetRect, appUnitsPerDevPixel)));
+ }
+
+ break;
+ }
+ case StyleBasicShape::Tag::Ellipse:
+ case StyleBasicShape::Tag::Circle: {
+ nsPoint center = ShapeUtils::ComputeCircleOrEllipseCenter(shape, refBox);
+
+ nsSize radii;
+ if (shape.IsEllipse()) {
+ radii = ShapeUtils::ComputeEllipseRadii(shape, center, refBox);
+ } else {
+ nscoord radius = ShapeUtils::ComputeCircleRadius(shape, center, refBox);
+ radii = {radius, radius};
+ }
+
+ nsRect ellipseRect(aDisplayItem.ToReferenceFrame() + center -
+ nsPoint(radii.width, radii.height),
+ radii * 2);
+
+ nscoord ellipseRadii[8];
+ for (const auto corner : AllPhysicalHalfCorners()) {
+ ellipseRadii[corner] =
+ HalfCornerIsX(corner) ? radii.width : radii.height;
+ }
+
+ clipId = aBuilder.DefineRoundedRectClip(
+ Nothing(), wr::ToComplexClipRegion(ellipseRect, ellipseRadii,
+ appUnitsPerDevPixel));
+
+ break;
+ }
+ default:
+ // Please don't add more exceptions, try to find a way to define the clip
+ // without using a mask image.
+ //
+ // And if you _really really_ need to add an exception, add it to
+ // SVGIntegrationUtils::UsingSimpleClipPathForFrame
+ MOZ_ASSERT_UNREACHABLE("Unhandled shape id?");
+ return Nothing();
+ }
+
+ wr::WrClipChainId clipChainId = aBuilder.DefineClipChain({clipId}, true);
+
+ return Some(clipChainId);
+}
+
+static void FillPolygonDataForDisplayItem(
+ const nsDisplayMasksAndClipPaths& aDisplayItem,
+ nsTArray<wr::LayoutPoint>& aPoints, wr::FillRule& aFillRule) {
+ nsIFrame* frame = aDisplayItem.Frame();
+ const auto* style = frame->StyleSVGReset();
+ bool isPolygon = style->HasClipPath() && style->mClipPath.IsShape() &&
+ style->mClipPath.AsShape()._0->IsPolygon();
+ if (!isPolygon) {
+ return;
+ }
+
+ const auto& clipPath = style->mClipPath;
+ const auto& shape = *clipPath.AsShape()._0;
+ const nsRect refBox =
+ nsLayoutUtils::ComputeGeometryBox(frame, clipPath.AsShape()._1);
+
+ // We only fill polygon data for polygons that are below a complexity
+ // limit.
+ nsTArray<nsPoint> vertices =
+ ShapeUtils::ComputePolygonVertices(shape, refBox);
+ if (vertices.Length() > wr::POLYGON_CLIP_VERTEX_MAX) {
+ return;
+ }
+
+ auto appUnitsPerDevPixel = frame->PresContext()->AppUnitsPerDevPixel();
+
+ for (size_t i = 0; i < vertices.Length(); ++i) {
+ wr::LayoutPoint point = wr::ToLayoutPoint(
+ LayoutDevicePoint::FromAppUnits(vertices[i], appUnitsPerDevPixel));
+ aPoints.AppendElement(point);
+ }
+
+ aFillRule = (shape.AsPolygon().fill == StyleFillRule::Nonzero)
+ ? wr::FillRule::Nonzero
+ : wr::FillRule::Evenodd;
+}
+
+static Maybe<wr::WrClipChainId> CreateWRClipPathAndMasks(
+ nsDisplayMasksAndClipPaths* aDisplayItem, const LayoutDeviceRect& aBounds,
+ wr::IpcResourceUpdateQueue& aResources, wr::DisplayListBuilder& aBuilder,
+ const StackingContextHelper& aSc, RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) {
+ if (auto clip = CreateSimpleClipRegion(*aDisplayItem, aBuilder)) {
+ return clip;
+ }
+
+ Maybe<wr::ImageMask> mask = aManager->CommandBuilder().BuildWrMaskImage(
+ aDisplayItem, aBuilder, aResources, aSc, aDisplayListBuilder, aBounds);
+ if (!mask) {
+ return Nothing();
+ }
+
+ // We couldn't create a simple clip region, but before we create an image
+ // mask clip, see if we can get a polygon clip to add to it.
+ nsTArray<wr::LayoutPoint> points;
+ wr::FillRule fillRule = wr::FillRule::Nonzero;
+ FillPolygonDataForDisplayItem(*aDisplayItem, points, fillRule);
+
+ wr::WrClipId clipId =
+ aBuilder.DefineImageMaskClip(mask.ref(), points, fillRule);
+
+ wr::WrClipChainId clipChainId = aBuilder.DefineClipChain({clipId}, true);
+
+ return Some(clipChainId);
+}
+
+bool nsDisplayMasksAndClipPaths::CreateWebRenderCommands(
+ wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc, RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) {
+ bool snap;
+ auto appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel();
+ nsRect displayBounds = GetBounds(aDisplayListBuilder, &snap);
+ LayoutDeviceRect bounds =
+ LayoutDeviceRect::FromAppUnits(displayBounds, appUnitsPerDevPixel);
+
+ Maybe<wr::WrClipChainId> clip = CreateWRClipPathAndMasks(
+ this, bounds, aResources, aBuilder, aSc, aManager, aDisplayListBuilder);
+
+ float oldOpacity = aBuilder.GetInheritedOpacity();
+
+ Maybe<StackingContextHelper> layer;
+ const StackingContextHelper* sc = &aSc;
+ if (clip) {
+ // Create a new stacking context to attach the mask to, ensuring the mask is
+ // applied to the aggregate, and not the individual elements.
+
+ // The stacking context shouldn't have any offset.
+ bounds.MoveTo(0, 0);
+
+ Maybe<float> opacity =
+ (SVGIntegrationUtils::UsingSimpleClipPathForFrame(mFrame) &&
+ aBuilder.GetInheritedOpacity() != 1.0f)
+ ? Some(aBuilder.GetInheritedOpacity())
+ : Nothing();
+
+ wr::StackingContextParams params;
+ params.clip = wr::WrStackingContextClip::ClipChain(clip->id);
+ params.opacity = opacity.ptrOr(nullptr);
+ if (mWrapsBackdropFilter) {
+ params.flags |= wr::StackingContextFlags::WRAPS_BACKDROP_FILTER;
+ }
+ layer.emplace(aSc, GetActiveScrolledRoot(), mFrame, this, aBuilder, params,
+ bounds);
+ sc = layer.ptr();
+ }
+
+ aBuilder.SetInheritedOpacity(1.0f);
+ const DisplayItemClipChain* oldClipChain = aBuilder.GetInheritedClipChain();
+ aBuilder.SetInheritedClipChain(nullptr);
+ CreateWebRenderCommandsNewClipListOption(aBuilder, aResources, *sc, aManager,
+ aDisplayListBuilder, layer.isSome());
+ aBuilder.SetInheritedOpacity(oldOpacity);
+ aBuilder.SetInheritedClipChain(oldClipChain);
+
+ return true;
+}
+
+Maybe<nsRect> nsDisplayMasksAndClipPaths::GetClipWithRespectToASR(
+ nsDisplayListBuilder* aBuilder, const ActiveScrolledRoot* aASR) const {
+ if (const DisplayItemClip* clip =
+ DisplayItemClipChain::ClipForASR(GetClipChain(), aASR)) {
+ return Some(clip->GetClipRect());
+ }
+ // This item does not have a clip with respect to |aASR|. However, we
+ // might still have finite bounds with respect to |aASR|. Check our
+ // children.
+ nsDisplayList* childList = GetSameCoordinateSystemChildren();
+ if (childList) {
+ return Some(childList->GetClippedBoundsWithRespectToASR(aBuilder, aASR));
+ }
+#ifdef DEBUG
+ NS_ASSERTION(false, "item should have finite clip with respect to aASR");
+#endif
+ return Nothing();
+}
+
+#ifdef MOZ_DUMP_PAINTING
+void nsDisplayMasksAndClipPaths::PrintEffects(nsACString& aTo) {
+ nsIFrame* firstFrame =
+ nsLayoutUtils::FirstContinuationOrIBSplitSibling(mFrame);
+ bool first = true;
+ aTo += " effects=(";
+ SVGClipPathFrame* clipPathFrame;
+ // XXX Check return value?
+ SVGObserverUtils::GetAndObserveClipPath(firstFrame, &clipPathFrame);
+ if (clipPathFrame) {
+ if (!first) {
+ aTo += ", ";
+ }
+ aTo += nsPrintfCString(
+ "clip(%s)", clipPathFrame->IsTrivial() ? "trivial" : "non-trivial");
+ first = false;
+ } else if (mFrame->StyleSVGReset()->HasClipPath()) {
+ if (!first) {
+ aTo += ", ";
+ }
+ aTo += "clip(basic-shape)";
+ first = false;
+ }
+
+ nsTArray<SVGMaskFrame*> masks;
+ // XXX check return value?
+ SVGObserverUtils::GetAndObserveMasks(firstFrame, &masks);
+ if (!masks.IsEmpty() && masks[0]) {
+ if (!first) {
+ aTo += ", ";
+ }
+ aTo += "mask";
+ }
+ aTo += ")";
+}
+#endif
+
+bool nsDisplayBackdropFilters::CreateWebRenderCommands(
+ wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc, RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) {
+ WrFiltersHolder wrFilters;
+ const ComputedStyle& style = mStyle ? *mStyle : *mFrame->Style();
+ auto filterChain = style.StyleEffects()->mBackdropFilters.AsSpan();
+ bool initialized = true;
+ if (!SVGIntegrationUtils::CreateWebRenderCSSFilters(filterChain, mFrame,
+ wrFilters) &&
+ !SVGIntegrationUtils::BuildWebRenderFilters(mFrame, filterChain,
+ wrFilters, initialized)) {
+ // TODO: If painting backdrop-filters on the content side is implemented,
+ // consider returning false to fall back to that.
+ wrFilters = {};
+ }
+
+ if (!initialized) {
+ wrFilters = {};
+ }
+
+ nsCSSRendering::ImageLayerClipState clip;
+ nsCSSRendering::GetImageLayerClip(
+ style.StyleBackground()->BottomLayer(), mFrame, *style.StyleBorder(),
+ mBackdropRect, mBackdropRect, false,
+ mFrame->PresContext()->AppUnitsPerDevPixel(), &clip);
+
+ LayoutDeviceRect bounds = LayoutDeviceRect::FromAppUnits(
+ mBackdropRect, mFrame->PresContext()->AppUnitsPerDevPixel());
+
+ wr::ComplexClipRegion region =
+ wr::ToComplexClipRegion(clip.mBGClipArea, clip.mRadii,
+ mFrame->PresContext()->AppUnitsPerDevPixel());
+
+ aBuilder.PushBackdropFilter(wr::ToLayoutRect(bounds), region,
+ wrFilters.filters, wrFilters.filter_datas,
+ !BackfaceIsHidden());
+
+ wr::StackingContextParams params;
+ params.clip =
+ wr::WrStackingContextClip::ClipChain(aBuilder.CurrentClipChainId());
+ StackingContextHelper sc(aSc, GetActiveScrolledRoot(), mFrame, this, aBuilder,
+ params);
+
+ nsDisplayWrapList::CreateWebRenderCommands(aBuilder, aResources, sc, aManager,
+ aDisplayListBuilder);
+ return true;
+}
+
+void nsDisplayBackdropFilters::Paint(nsDisplayListBuilder* aBuilder,
+ gfxContext* aCtx) {
+ // TODO: Implement backdrop filters
+ GetChildren()->Paint(aBuilder, aCtx,
+ mFrame->PresContext()->AppUnitsPerDevPixel());
+}
+
+nsRect nsDisplayBackdropFilters::GetBounds(nsDisplayListBuilder* aBuilder,
+ bool* aSnap) const {
+ nsRect childBounds = nsDisplayWrapList::GetBounds(aBuilder, aSnap);
+
+ *aSnap = false;
+
+ return mBackdropRect.Union(childBounds);
+}
+
+/* static */
+nsDisplayFilters::nsDisplayFilters(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame, nsDisplayList* aList,
+ nsIFrame* aStyleFrame,
+ bool aWrapsBackdropFilter)
+ : nsDisplayEffectsBase(aBuilder, aFrame, aList),
+ mStyle(aFrame == aStyleFrame ? nullptr : aStyleFrame->Style()),
+ mEffectsBounds(aFrame->InkOverflowRectRelativeToSelf()),
+ mWrapsBackdropFilter(aWrapsBackdropFilter) {
+ MOZ_COUNT_CTOR(nsDisplayFilters);
+ mVisibleRect = aBuilder->GetVisibleRect() +
+ aBuilder->GetCurrentFrameOffsetToReferenceFrame();
+}
+
+void nsDisplayFilters::Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) {
+ PaintWithContentsPaintCallback(aBuilder, aCtx, [&](gfxContext* aContext) {
+ GetChildren()->Paint(aBuilder, aContext,
+ mFrame->PresContext()->AppUnitsPerDevPixel());
+ });
+}
+
+void nsDisplayFilters::PaintWithContentsPaintCallback(
+ nsDisplayListBuilder* aBuilder, gfxContext* aCtx,
+ const std::function<void(gfxContext* aContext)>& aPaintChildren) {
+ imgDrawingParams imgParams(aBuilder->GetImageDecodeFlags());
+ nsRect borderArea = nsRect(ToReferenceFrame(), mFrame->GetSize());
+ PaintFramesParams params(*aCtx, mFrame, mVisibleRect, borderArea, aBuilder,
+ false, imgParams);
+
+ gfxPoint userSpaceToFrameSpaceOffset =
+ SVGIntegrationUtils::GetOffsetToUserSpaceInDevPx(mFrame, params);
+
+ auto filterChain = mStyle ? mStyle->StyleEffects()->mFilters.AsSpan()
+ : mFrame->StyleEffects()->mFilters.AsSpan();
+ SVGIntegrationUtils::PaintFilter(
+ params, filterChain,
+ [&](gfxContext& aContext, imgDrawingParams&, const gfxMatrix*,
+ const nsIntRect*) {
+ gfxContextMatrixAutoSaveRestore autoSR(&aContext);
+ aContext.SetMatrixDouble(aContext.CurrentMatrixDouble().PreTranslate(
+ -userSpaceToFrameSpaceOffset));
+ aPaintChildren(&aContext);
+ });
+}
+
+bool nsDisplayFilters::CanCreateWebRenderCommands() const {
+ return SVGIntegrationUtils::CanCreateWebRenderFiltersForFrame(mFrame);
+}
+
+bool nsDisplayFilters::CreateWebRenderCommands(
+ wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc, RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) {
+ WrFiltersHolder wrFilters;
+ const ComputedStyle& style = mStyle ? *mStyle : *mFrame->Style();
+ auto filterChain = style.StyleEffects()->mFilters.AsSpan();
+ bool initialized = true;
+ if (!SVGIntegrationUtils::CreateWebRenderCSSFilters(filterChain, mFrame,
+ wrFilters) &&
+ !SVGIntegrationUtils::BuildWebRenderFilters(mFrame, filterChain,
+ wrFilters, initialized)) {
+ if (mStyle) {
+ // TODO(bug 1769223): Support fallback filters in the root code-path,
+ // perhaps. For now treat it the same way as invalid filters.
+ wrFilters = {};
+ } else {
+ // Draw using fallback.
+ return false;
+ }
+ }
+
+ if (!initialized) {
+ // https://drafts.fxtf.org/filter-effects/#typedef-filter-url:
+ //
+ // If the filter references a non-existent object or the referenced object
+ // is not a filter element, then the whole filter chain is ignored. No
+ // filter is applied to the object.
+ //
+ // Note that other engines have a weird discrepancy between SVG and HTML
+ // content here, but the spec is clear.
+ wrFilters = {};
+ }
+
+ uint64_t clipChainId;
+ if (wrFilters.post_filters_clip) {
+ auto devPxRect = LayoutDeviceRect::FromAppUnits(
+ wrFilters.post_filters_clip.value() + ToReferenceFrame(),
+ mFrame->PresContext()->AppUnitsPerDevPixel());
+ auto clipId =
+ aBuilder.DefineRectClip(Nothing(), wr::ToLayoutRect(devPxRect));
+ clipChainId = aBuilder.DefineClipChain({clipId}, true).id;
+ } else {
+ clipChainId = aBuilder.CurrentClipChainId();
+ }
+ wr::WrStackingContextClip clip =
+ wr::WrStackingContextClip::ClipChain(clipChainId);
+
+ float opacity = aBuilder.GetInheritedOpacity();
+ aBuilder.SetInheritedOpacity(1.0f);
+ const DisplayItemClipChain* oldClipChain = aBuilder.GetInheritedClipChain();
+ aBuilder.SetInheritedClipChain(nullptr);
+ wr::StackingContextParams params;
+ params.mFilters = std::move(wrFilters.filters);
+ params.mFilterDatas = std::move(wrFilters.filter_datas);
+ params.opacity = opacity != 1.0f ? &opacity : nullptr;
+ params.clip = clip;
+ if (mWrapsBackdropFilter) {
+ params.flags |= wr::StackingContextFlags::WRAPS_BACKDROP_FILTER;
+ }
+ StackingContextHelper sc(aSc, GetActiveScrolledRoot(), mFrame, this, aBuilder,
+ params);
+
+ nsDisplayEffectsBase::CreateWebRenderCommands(aBuilder, aResources, sc,
+ aManager, aDisplayListBuilder);
+ aBuilder.SetInheritedOpacity(opacity);
+ aBuilder.SetInheritedClipChain(oldClipChain);
+
+ return true;
+}
+
+#ifdef MOZ_DUMP_PAINTING
+void nsDisplayFilters::PrintEffects(nsACString& aTo) {
+ nsIFrame* firstFrame =
+ nsLayoutUtils::FirstContinuationOrIBSplitSibling(mFrame);
+ bool first = true;
+ aTo += " effects=(";
+ // We may exist for a mix of CSS filter functions and/or references to SVG
+ // filters. If we have invalid references to SVG filters then we paint
+ // nothing, but otherwise we will apply one or more filters.
+ if (SVGObserverUtils::GetAndObserveFilters(firstFrame, nullptr) !=
+ SVGObserverUtils::eHasRefsSomeInvalid) {
+ if (!first) {
+ aTo += ", ";
+ }
+ aTo += "filter";
+ }
+ aTo += ")";
+}
+#endif
+
+nsDisplaySVGWrapper::nsDisplaySVGWrapper(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame, nsDisplayList* aList)
+ : nsDisplayWrapList(aBuilder, aFrame, aList) {
+ MOZ_COUNT_CTOR(nsDisplaySVGWrapper);
+}
+
+bool nsDisplaySVGWrapper::ShouldFlattenAway(nsDisplayListBuilder* aBuilder) {
+ return !aBuilder->GetWidgetLayerManager();
+}
+
+bool nsDisplaySVGWrapper::CreateWebRenderCommands(
+ wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc, RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) {
+ return CreateWebRenderCommandsNewClipListOption(
+ aBuilder, aResources, aSc, aManager, aDisplayListBuilder, false);
+}
+
+nsDisplayForeignObject::nsDisplayForeignObject(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame,
+ nsDisplayList* aList)
+ : nsDisplayWrapList(aBuilder, aFrame, aList) {
+ MOZ_COUNT_CTOR(nsDisplayForeignObject);
+}
+
+#ifdef NS_BUILD_REFCNT_LOGGING
+nsDisplayForeignObject::~nsDisplayForeignObject() {
+ MOZ_COUNT_DTOR(nsDisplayForeignObject);
+}
+#endif
+
+bool nsDisplayForeignObject::ShouldFlattenAway(nsDisplayListBuilder* aBuilder) {
+ return !aBuilder->GetWidgetLayerManager();
+}
+
+bool nsDisplayForeignObject::CreateWebRenderCommands(
+ wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc, RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) {
+ AutoRestore<bool> restoreDoGrouping(aManager->CommandBuilder().mDoGrouping);
+ aManager->CommandBuilder().mDoGrouping = false;
+ return CreateWebRenderCommandsNewClipListOption(
+ aBuilder, aResources, aSc, aManager, aDisplayListBuilder, false);
+}
+
+void nsDisplayLink::Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) {
+ auto appPerDev = mFrame->PresContext()->AppUnitsPerDevPixel();
+ aCtx->GetDrawTarget()->Link(
+ mLinkSpec.get(), NSRectToRect(GetPaintRect(aBuilder, aCtx), appPerDev));
+}
+
+void nsDisplayDestination::Paint(nsDisplayListBuilder* aBuilder,
+ gfxContext* aCtx) {
+ auto appPerDev = mFrame->PresContext()->AppUnitsPerDevPixel();
+ aCtx->GetDrawTarget()->Destination(
+ mDestinationName.get(),
+ NSPointToPoint(GetPaintRect(aBuilder, aCtx).TopLeft(), appPerDev));
+}
+
+void nsDisplayListCollection::SerializeWithCorrectZOrder(
+ nsDisplayList* aOutResultList, nsIContent* aContent) {
+ // Sort PositionedDescendants() in CSS 'z-order' order. The list is already
+ // in content document order and SortByZOrder is a stable sort which
+ // guarantees that boxes produced by the same element are placed together
+ // in the sort. Consider a position:relative inline element that breaks
+ // across lines and has absolutely positioned children; all the abs-pos
+ // children should be z-ordered after all the boxes for the position:relative
+ // element itself.
+ PositionedDescendants()->SortByZOrder();
+
+ // Now follow the rules of http://www.w3.org/TR/CSS21/zindex.html
+ // 1,2: backgrounds and borders
+ aOutResultList->AppendToTop(BorderBackground());
+ // 3: negative z-index children.
+ for (auto* item : PositionedDescendants()->TakeItems()) {
+ if (item->ZIndex() < 0) {
+ aOutResultList->AppendToTop(item);
+ } else {
+ PositionedDescendants()->AppendToTop(item);
+ }
+ }
+
+ // 4: block backgrounds
+ aOutResultList->AppendToTop(BlockBorderBackgrounds());
+ // 5: floats
+ aOutResultList->AppendToTop(Floats());
+ // 7: general content
+ aOutResultList->AppendToTop(Content());
+ // 7.5: outlines, in content tree order. We need to sort by content order
+ // because an element with outline that breaks and has children with outline
+ // might have placed child outline items between its own outline items.
+ // The element's outline items need to all come before any child outline
+ // items.
+ if (aContent) {
+ Outlines()->SortByContentOrder(aContent);
+ }
+ aOutResultList->AppendToTop(Outlines());
+ // 8, 9: non-negative z-index children
+ aOutResultList->AppendToTop(PositionedDescendants());
+}
+
+uint32_t PaintTelemetry::sPaintLevel = 0;
+
+PaintTelemetry::AutoRecordPaint::AutoRecordPaint() {
+ // Don't record nested paints.
+ if (sPaintLevel++ > 0) {
+ return;
+ }
+
+ mStart = TimeStamp::Now();
+}
+
+PaintTelemetry::AutoRecordPaint::~AutoRecordPaint() {
+ MOZ_ASSERT(sPaintLevel != 0);
+ if (--sPaintLevel > 0) {
+ return;
+ }
+
+ // If we're in multi-process mode, don't include paint times for the parent
+ // process.
+ if (gfxVars::BrowserTabsRemoteAutostart() && XRE_IsParentProcess()) {
+ return;
+ }
+
+ double totalMs = (TimeStamp::Now() - mStart).ToMilliseconds();
+
+ // Record the total time.
+ Telemetry::Accumulate(Telemetry::CONTENT_PAINT_TIME,
+ static_cast<uint32_t>(totalMs));
+}
+
+static nsIFrame* GetSelfOrPlaceholderFor(nsIFrame* aFrame) {
+ if (aFrame->HasAnyStateBits(NS_FRAME_IS_PUSHED_FLOAT)) {
+ return aFrame;
+ }
+
+ if (aFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW) &&
+ !aFrame->GetPrevInFlow()) {
+ return aFrame->GetPlaceholderFrame();
+ }
+
+ return aFrame;
+}
+
+static nsIFrame* GetAncestorFor(nsIFrame* aFrame) {
+ nsIFrame* f = GetSelfOrPlaceholderFor(aFrame);
+ MOZ_ASSERT(f);
+ return nsLayoutUtils::GetCrossDocParentFrameInProcess(f);
+}
+
+nsDisplayListBuilder::AutoBuildingDisplayList::AutoBuildingDisplayList(
+ nsDisplayListBuilder* aBuilder, nsIFrame* aForChild,
+ const nsRect& aVisibleRect, const nsRect& aDirtyRect,
+ const bool aIsTransformed)
+ : mBuilder(aBuilder),
+ mPrevFrame(aBuilder->mCurrentFrame),
+ mPrevReferenceFrame(aBuilder->mCurrentReferenceFrame),
+ mPrevOffset(aBuilder->mCurrentOffsetToReferenceFrame),
+ mPrevAdditionalOffset(aBuilder->mAdditionalOffset),
+ mPrevVisibleRect(aBuilder->mVisibleRect),
+ mPrevDirtyRect(aBuilder->mDirtyRect),
+ mPrevCompositorHitTestInfo(aBuilder->mCompositorHitTestInfo),
+ mPrevAncestorHasApzAwareEventHandler(
+ aBuilder->mAncestorHasApzAwareEventHandler),
+ mPrevBuildingInvisibleItems(aBuilder->mBuildingInvisibleItems),
+ mPrevInInvalidSubtree(aBuilder->mInInvalidSubtree) {
+ if (aIsTransformed) {
+ aBuilder->mCurrentOffsetToReferenceFrame =
+ aBuilder->AdditionalOffset().refOr(nsPoint());
+ aBuilder->mCurrentReferenceFrame = aForChild;
+ } else if (aBuilder->mCurrentFrame == aForChild->GetParent()) {
+ aBuilder->mCurrentOffsetToReferenceFrame += aForChild->GetPosition();
+ } else {
+ aBuilder->mCurrentReferenceFrame = aBuilder->FindReferenceFrameFor(
+ aForChild, &aBuilder->mCurrentOffsetToReferenceFrame);
+ }
+
+ // If aForChild is being visited from a frame other than it's ancestor frame,
+ // mInInvalidSubtree will need to be recalculated the slow way.
+ if (aForChild == mPrevFrame || GetAncestorFor(aForChild) == mPrevFrame) {
+ aBuilder->mInInvalidSubtree =
+ aBuilder->mInInvalidSubtree || aForChild->IsFrameModified();
+ } else {
+ aBuilder->mInInvalidSubtree = AnyContentAncestorModified(aForChild);
+ }
+
+ aBuilder->mCurrentFrame = aForChild;
+ aBuilder->mVisibleRect = aVisibleRect;
+ aBuilder->mDirtyRect =
+ aBuilder->mInInvalidSubtree ? aVisibleRect : aDirtyRect;
+}
+
+} // namespace mozilla
diff --git a/layout/painting/nsDisplayList.h b/layout/painting/nsDisplayList.h
new file mode 100644
index 0000000000..501644867b
--- /dev/null
+++ b/layout/painting/nsDisplayList.h
@@ -0,0 +1,6863 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+/*
+ * structures that represent things to be painted (ordered in z-order),
+ * used during painting and hit testing
+ */
+
+#ifndef NSDISPLAYLIST_H_
+#define NSDISPLAYLIST_H_
+
+#include "DisplayItemClipChain.h"
+#include "DisplayListClipState.h"
+#include "FrameMetrics.h"
+#include "HitTestInfo.h"
+#include "ImgDrawResult.h"
+#include "RetainedDisplayListHelpers.h"
+#include "Units.h"
+#include "gfxContext.h"
+#include "mozilla/ArenaAllocator.h"
+#include "mozilla/Array.h"
+#include "mozilla/ArrayIterator.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/EffectCompositor.h"
+#include "mozilla/EnumSet.h"
+#include "mozilla/EnumeratedArray.h"
+#include "mozilla/Logging.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/MotionPathUtils.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/TemplateLib.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/dom/EffectsInfo.h"
+#include "mozilla/gfx/UserData.h"
+#include "mozilla/layers/BSPTree.h"
+#include "mozilla/layers/ScrollableLayerGuid.h"
+#include "mozilla/layers/ScrollbarData.h"
+#include "nsAutoLayoutPhase.h"
+#include "nsCOMPtr.h"
+#include "nsCSSRenderingBorders.h"
+#include "nsContainerFrame.h"
+#include "nsDisplayItemTypes.h"
+#include "nsDisplayListInvalidation.h"
+#include "nsPoint.h"
+#include "nsPresArena.h"
+#include "nsRect.h"
+#include "nsRegion.h"
+#include "nsClassHashtable.h"
+#include "nsTHashSet.h"
+#include "nsTHashMap.h"
+
+#include <algorithm>
+#include <unordered_set>
+
+// XXX Includes that could be avoided by moving function implementations to the
+// cpp file.
+#include "gfxPlatform.h"
+
+class gfxContext;
+class nsIContent;
+class nsIScrollableFrame;
+class nsSubDocumentFrame;
+class nsCaret;
+struct WrFiltersHolder;
+
+namespace nsStyleTransformMatrix {
+class TransformReferenceBox;
+}
+
+namespace mozilla {
+
+enum class nsDisplayOwnLayerFlags;
+class nsDisplayCompositorHitTestInfo;
+class nsDisplayScrollInfoLayer;
+class PresShell;
+class StickyScrollContainer;
+
+namespace layers {
+struct FrameMetrics;
+class RenderRootStateManager;
+class Layer;
+class ImageContainer;
+class StackingContextHelper;
+class WebRenderScrollData;
+class WebRenderLayerScrollData;
+class WebRenderLayerManager;
+} // namespace layers
+
+namespace wr {
+class DisplayListBuilder;
+} // namespace wr
+
+namespace dom {
+class RemoteBrowser;
+class Selection;
+} // namespace dom
+
+enum class DisplayListArenaObjectId {
+#define DISPLAY_LIST_ARENA_OBJECT(name_) name_,
+#include "nsDisplayListArenaTypes.h"
+#undef DISPLAY_LIST_ARENA_OBJECT
+ COUNT
+};
+
+extern LazyLogModule sContentDisplayListLog;
+extern LazyLogModule sParentDisplayListLog;
+
+LazyLogModule& GetLoggerByProcess();
+
+#define DL_LOG(lvl, ...) MOZ_LOG(GetLoggerByProcess(), lvl, (__VA_ARGS__))
+#define DL_LOGI(...) DL_LOG(LogLevel::Info, __VA_ARGS__)
+#define DL_LOG_TEST(lvl) MOZ_LOG_TEST(GetLoggerByProcess(), lvl)
+
+#ifdef DEBUG
+# define DL_LOGD(...) DL_LOG(LogLevel::Debug, __VA_ARGS__)
+# define DL_LOGV(...) DL_LOG(LogLevel::Verbose, __VA_ARGS__)
+#else
+// Disable Debug and Verbose logs for release builds.
+# define DL_LOGD(...)
+# define DL_LOGV(...)
+#endif
+
+/*
+ * An nsIFrame can have many different visual parts. For example an image frame
+ * can have a background, border, and outline, the image itself, and a
+ * translucent selection overlay. In general these parts can be drawn at
+ * discontiguous z-levels; see CSS2.1 appendix E:
+ * http://www.w3.org/TR/CSS21/zindex.html
+ *
+ * We construct a display list for a frame tree that contains one item
+ * for each visual part. The display list is itself a tree since some items
+ * are containers for other items; however, its structure does not match
+ * the structure of its source frame tree. The display list items are sorted
+ * by z-order. A display list can be used to paint the frames, to determine
+ * which frame is the target of a mouse event, and to determine what areas
+ * need to be repainted when scrolling. The display lists built for each task
+ * may be different for efficiency; in particular some frames need special
+ * display list items only for event handling, and do not create these items
+ * when the display list will be used for painting (the common case). For
+ * example, when painting we avoid creating nsDisplayBackground items for
+ * frames that don't display a visible background, but for event handling
+ * we need those backgrounds because they are not transparent to events.
+ *
+ * We could avoid constructing an explicit display list by traversing the
+ * frame tree multiple times in clever ways. However, reifying the display list
+ * reduces code complexity and reduces the number of times each frame must be
+ * traversed to one, which seems to be good for performance. It also means
+ * we can share code for painting, event handling and scroll analysis.
+ *
+ * Display lists are short-lived; content and frame trees cannot change
+ * between a display list being created and destroyed. Display lists should
+ * not be created during reflow because the frame tree may be in an
+ * inconsistent state (e.g., a frame's stored overflow-area may not include
+ * the bounds of all its children). However, it should be fine to create
+ * a display list while a reflow is pending, before it starts.
+ *
+ * A display list covers the "extended" frame tree; the display list for
+ * a frame tree containing FRAME/IFRAME elements can include frames from
+ * the subdocuments.
+ *
+ * Display item's coordinates are relative to their nearest reference frame
+ * ancestor. Both the display root and any frame with a transform act as a
+ * reference frame for their frame subtrees.
+ */
+
+/**
+ * An active scrolled root (ASR) is similar to an animated geometry root (AGR).
+ * The differences are:
+ * - ASRs are only created for async-scrollable scroll frames. This is a
+ * (hopefully) temporary restriction. In the future we will want to create
+ * ASRs for all the things that are currently creating AGRs, and then
+ * replace AGRs with ASRs and rename them from "active scrolled root" to
+ * "animated geometry root".
+ * - ASR objects are created during display list construction by the nsIFrames
+ * that induce ASRs. This is done using AutoCurrentActiveScrolledRootSetter.
+ * The current ASR is returned by
+ * nsDisplayListBuilder::CurrentActiveScrolledRoot().
+ * - There is no way to go from an nsIFrame pointer to the ASR of that frame.
+ * If you need to look up an ASR after display list construction, you need
+ * to store it while the AutoCurrentActiveScrolledRootSetter that creates it
+ * is on the stack.
+ */
+struct ActiveScrolledRoot {
+ static already_AddRefed<ActiveScrolledRoot> CreateASRForFrame(
+ const ActiveScrolledRoot* aParent, nsIScrollableFrame* aScrollableFrame,
+ bool aIsRetained);
+
+ static const ActiveScrolledRoot* PickAncestor(
+ const ActiveScrolledRoot* aOne, const ActiveScrolledRoot* aTwo) {
+ MOZ_ASSERT(IsAncestor(aOne, aTwo) || IsAncestor(aTwo, aOne));
+ return Depth(aOne) <= Depth(aTwo) ? aOne : aTwo;
+ }
+
+ static const ActiveScrolledRoot* PickDescendant(
+ const ActiveScrolledRoot* aOne, const ActiveScrolledRoot* aTwo) {
+ MOZ_ASSERT(IsAncestor(aOne, aTwo) || IsAncestor(aTwo, aOne));
+ return Depth(aOne) >= Depth(aTwo) ? aOne : aTwo;
+ }
+
+ static bool IsAncestor(const ActiveScrolledRoot* aAncestor,
+ const ActiveScrolledRoot* aDescendant);
+ static bool IsProperAncestor(const ActiveScrolledRoot* aAncestor,
+ const ActiveScrolledRoot* aDescendant);
+
+ static nsCString ToString(const ActiveScrolledRoot* aActiveScrolledRoot);
+
+ // Call this when inserting an ancestor.
+ void IncrementDepth() { mDepth++; }
+
+ /**
+ * Find the view ID (or generate a new one) for the content element
+ * corresponding to the ASR.
+ */
+ layers::ScrollableLayerGuid::ViewID GetViewId() const {
+ if (!mViewId.isSome()) {
+ mViewId = Some(ComputeViewId());
+ }
+ return *mViewId;
+ }
+
+ RefPtr<const ActiveScrolledRoot> mParent;
+ nsIScrollableFrame* mScrollableFrame;
+
+ NS_INLINE_DECL_REFCOUNTING(ActiveScrolledRoot)
+
+ private:
+ ActiveScrolledRoot()
+ : mScrollableFrame(nullptr), mDepth(0), mRetained(false) {}
+
+ ~ActiveScrolledRoot();
+
+ static void DetachASR(ActiveScrolledRoot* aASR) {
+ aASR->mParent = nullptr;
+ aASR->mScrollableFrame = nullptr;
+ NS_RELEASE(aASR);
+ }
+ NS_DECLARE_FRAME_PROPERTY_WITH_DTOR(ActiveScrolledRootCache,
+ ActiveScrolledRoot, DetachASR)
+
+ static uint32_t Depth(const ActiveScrolledRoot* aActiveScrolledRoot) {
+ return aActiveScrolledRoot ? aActiveScrolledRoot->mDepth : 0;
+ }
+
+ layers::ScrollableLayerGuid::ViewID ComputeViewId() const;
+
+ // This field is lazily populated in GetViewId(). We don't want to do the
+ // work of populating if webrender is disabled, because it is often not
+ // needed.
+ mutable Maybe<layers::ScrollableLayerGuid::ViewID> mViewId;
+
+ uint32_t mDepth;
+ bool mRetained;
+};
+
+enum class nsDisplayListBuilderMode : uint8_t {
+ Painting,
+ PaintForPrinting,
+ EventDelivery,
+ FrameVisibility,
+ GenerateGlyph,
+};
+
+using ListArenaAllocator = ArenaAllocator<4096, 8>;
+
+class nsDisplayItem;
+class nsPaintedDisplayItem;
+class nsDisplayList;
+class nsDisplayWrapList;
+class nsDisplayTableBackgroundSet;
+class nsDisplayTableItem;
+
+class RetainedDisplayList;
+
+/**
+ * This manages a display list and is passed as a parameter to
+ * nsIFrame::BuildDisplayList.
+ * It contains the parameters that don't change from frame to frame and manages
+ * the display list memory using an arena. It also establishes the reference
+ * coordinate system for all display list items. Some of the parameters are
+ * available from the prescontext/presshell, but we copy them into the builder
+ * for faster/more convenient access.
+ */
+class nsDisplayListBuilder {
+ /**
+ * This manages status of a 3d context to collect visible rects of
+ * descendants and passing a dirty rect.
+ *
+ * Since some transforms maybe singular, passing visible rects or
+ * the dirty rect level by level from parent to children may get a
+ * wrong result, being different from the result of appling with
+ * effective transform directly.
+ *
+ * nsIFrame::BuildDisplayListForStackingContext() uses
+ * AutoPreserves3DContext to install an instance on the builder.
+ *
+ * \see AutoAccumulateTransform, AutoAccumulateRect,
+ * AutoPreserves3DContext, Accumulate, GetCurrentTransform,
+ * StartRoot.
+ */
+ class Preserves3DContext {
+ public:
+ Preserves3DContext()
+ : mAccumulatedRectLevels(0), mAllowAsyncAnimation(true) {}
+
+ Preserves3DContext(const Preserves3DContext& aOther)
+ : mAccumulatedTransform(),
+ mAccumulatedRect(),
+ mAccumulatedRectLevels(0),
+ mVisibleRect(aOther.mVisibleRect),
+ mAllowAsyncAnimation(aOther.mAllowAsyncAnimation) {}
+
+ // Accmulate transforms of ancestors on the preserves-3d chain.
+ gfx::Matrix4x4 mAccumulatedTransform;
+ // Accmulate visible rect of descendants in the preserves-3d context.
+ nsRect mAccumulatedRect;
+ // How far this frame is from the root of the current 3d context.
+ int mAccumulatedRectLevels;
+ nsRect mVisibleRect;
+ // Allow async animation for this 3D context.
+ bool mAllowAsyncAnimation;
+ };
+
+ public:
+ using ViewID = layers::ScrollableLayerGuid::ViewID;
+
+ /**
+ * @param aReferenceFrame the frame at the root of the subtree; its origin
+ * is the origin of the reference coordinate system for this display list
+ * @param aMode encodes what the builder is being used for.
+ * @param aBuildCaret whether or not we should include the caret in any
+ * display lists that we make.
+ */
+ nsDisplayListBuilder(nsIFrame* aReferenceFrame,
+ nsDisplayListBuilderMode aMode, bool aBuildCaret,
+ bool aRetainingDisplayList = false);
+ ~nsDisplayListBuilder();
+
+ void BeginFrame();
+ void EndFrame();
+
+ void AddTemporaryItem(nsDisplayItem* aItem) {
+ mTemporaryItems.AppendElement(aItem);
+ }
+
+ WindowRenderer* GetWidgetWindowRenderer(nsView** aView = nullptr);
+ layers::WebRenderLayerManager* GetWidgetLayerManager(
+ nsView** aView = nullptr);
+
+ /**
+ * @return true if the display is being built in order to determine which
+ * frame is under the mouse position.
+ */
+ bool IsForEventDelivery() const {
+ return mMode == nsDisplayListBuilderMode::EventDelivery;
+ }
+
+ /**
+ * @return true if the display list is being built for painting. This
+ * includes both painting to a window or other buffer and painting to
+ * a print/pdf destination.
+ */
+ bool IsForPainting() const {
+ return mMode == nsDisplayListBuilderMode::Painting ||
+ mMode == nsDisplayListBuilderMode::PaintForPrinting;
+ }
+
+ /**
+ * @return true if the display list is being built specifically for printing.
+ */
+ bool IsForPrinting() const {
+ return mMode == nsDisplayListBuilderMode::PaintForPrinting;
+ }
+
+ /**
+ * @return true if the display list is being built for determining frame
+ * visibility.
+ */
+ bool IsForFrameVisibility() const {
+ return mMode == nsDisplayListBuilderMode::FrameVisibility;
+ }
+
+ /**
+ * @return true if the display list is being built for creating the glyph
+ * mask from text items.
+ */
+ bool IsForGenerateGlyphMask() const {
+ return mMode == nsDisplayListBuilderMode::GenerateGlyph;
+ }
+
+ bool BuildCompositorHitTestInfo() const {
+ return mBuildCompositorHitTestInfo;
+ }
+
+ /**
+ * @return true if "painting is suppressed" during page load and we
+ * should paint only the background of the document.
+ */
+ bool IsBackgroundOnly() {
+ NS_ASSERTION(mPresShellStates.Length() > 0,
+ "don't call this if we're not in a presshell");
+ return CurrentPresShellState()->mIsBackgroundOnly;
+ }
+
+ /**
+ * @return the root of given frame's (sub)tree, whose origin
+ * establishes the coordinate system for the child display items.
+ */
+ const nsIFrame* FindReferenceFrameFor(const nsIFrame* aFrame,
+ nsPoint* aOffset = nullptr) const;
+
+ const Maybe<nsPoint>& AdditionalOffset() const { return mAdditionalOffset; }
+
+ /**
+ * @return the root of the display list's frame (sub)tree, whose origin
+ * establishes the coordinate system for the display list
+ */
+ nsIFrame* RootReferenceFrame() const { return mReferenceFrame; }
+
+ /**
+ * @return a point pt such that adding pt to a coordinate relative to aFrame
+ * makes it relative to ReferenceFrame(), i.e., returns
+ * aFrame->GetOffsetToCrossDoc(ReferenceFrame()). The returned point is in
+ * the appunits of aFrame.
+ */
+ const nsPoint ToReferenceFrame(const nsIFrame* aFrame) const {
+ nsPoint result;
+ FindReferenceFrameFor(aFrame, &result);
+ return result;
+ }
+ /**
+ * When building the display list, the scrollframe aFrame will be "ignored"
+ * for the purposes of clipping, and its scrollbars will be hidden. We use
+ * this to allow RenderOffscreen to render a whole document without beign
+ * clipped by the viewport or drawing the viewport scrollbars.
+ */
+ void SetIgnoreScrollFrame(nsIFrame* aFrame) { mIgnoreScrollFrame = aFrame; }
+ /**
+ * Get the scrollframe to ignore, if any.
+ */
+ nsIFrame* GetIgnoreScrollFrame() { return mIgnoreScrollFrame; }
+ void SetIsRelativeToLayoutViewport();
+ bool IsRelativeToLayoutViewport() const {
+ return mIsRelativeToLayoutViewport;
+ }
+ /**
+ * Get the ViewID of the nearest scrolling ancestor frame.
+ */
+ ViewID GetCurrentScrollParentId() const { return mCurrentScrollParentId; }
+ /**
+ * Get and set the flag that indicates if scroll parents should have layers
+ * forcibly created. This flag is set when a deeply nested scrollframe has
+ * a displayport, and for scroll handoff to work properly the ancestor
+ * scrollframes should also get their own scrollable layers.
+ */
+ void ForceLayerForScrollParent() { mForceLayerForScrollParent = true; }
+ /**
+ * Set the flag that indicates there is a non-minimal display port in the
+ * current subtree. This is used to determine display port expiry.
+ */
+ void SetContainsNonMinimalDisplayPort() {
+ mContainsNonMinimalDisplayPort = true;
+ }
+ /**
+ * Get the ViewID and the scrollbar flags corresponding to the scrollbar for
+ * which we are building display items at the moment.
+ */
+ ViewID GetCurrentScrollbarTarget() const { return mCurrentScrollbarTarget; }
+ Maybe<layers::ScrollDirection> GetCurrentScrollbarDirection() const {
+ return mCurrentScrollbarDirection;
+ }
+ /**
+ * Returns true if building a scrollbar, and the scrollbar will not be
+ * layerized.
+ */
+ bool IsBuildingNonLayerizedScrollbar() const {
+ return mIsBuildingScrollbar && !mCurrentScrollbarWillHaveLayer;
+ }
+ /**
+ * Calling this setter makes us include all out-of-flow descendant
+ * frames in the display list, wherever they may be positioned (even
+ * outside the dirty rects).
+ */
+ void SetIncludeAllOutOfFlows() { mIncludeAllOutOfFlows = true; }
+ bool GetIncludeAllOutOfFlows() const { return mIncludeAllOutOfFlows; }
+ /**
+ * Calling this setter makes us exclude all leaf frames that aren't
+ * selected.
+ */
+ void SetSelectedFramesOnly() { mSelectedFramesOnly = true; }
+ bool GetSelectedFramesOnly() { return mSelectedFramesOnly; }
+ /**
+ * @return Returns true if we should include the caret in any display lists
+ * that we make.
+ */
+ bool IsBuildingCaret() const { return mBuildCaret; }
+
+ bool IsRetainingDisplayList() const { return mRetainingDisplayList; }
+
+ bool IsPartialUpdate() const { return mPartialUpdate; }
+ void SetPartialUpdate(bool aPartial) { mPartialUpdate = aPartial; }
+
+ bool IsBuilding() const { return mIsBuilding; }
+ void SetIsBuilding(bool aIsBuilding) { mIsBuilding = aIsBuilding; }
+
+ bool InInvalidSubtree() const { return mInInvalidSubtree; }
+
+ /**
+ * Allows callers to selectively override the regular paint suppression
+ * checks, so that methods like GetFrameForPoint work when painting is
+ * suppressed.
+ */
+ void IgnorePaintSuppression() { mIgnoreSuppression = true; }
+ /**
+ * @return Returns if this builder will ignore paint suppression.
+ */
+ bool IsIgnoringPaintSuppression() { return mIgnoreSuppression; }
+ /**
+ * Call this if we're doing normal painting to the window.
+ */
+ void SetPaintingToWindow(bool aToWindow) { mIsPaintingToWindow = aToWindow; }
+ bool IsPaintingToWindow() const { return mIsPaintingToWindow; }
+ /**
+ * Call this if we're using high quality scaling for image decoding.
+ * It is also implied by IsPaintingToWindow.
+ */
+ void SetUseHighQualityScaling(bool aUseHighQualityScaling) {
+ mUseHighQualityScaling = aUseHighQualityScaling;
+ }
+ bool UseHighQualityScaling() const {
+ return mIsPaintingToWindow || mUseHighQualityScaling;
+ }
+ /**
+ * Call this if we're doing painting for WebRender
+ */
+ void SetPaintingForWebRender(bool aForWebRender) {
+ mIsPaintingForWebRender = true;
+ }
+ bool IsPaintingForWebRender() const { return mIsPaintingForWebRender; }
+ /**
+ * Call this to prevent descending into subdocuments.
+ */
+ void SetDescendIntoSubdocuments(bool aDescend) {
+ mDescendIntoSubdocuments = aDescend;
+ }
+
+ bool GetDescendIntoSubdocuments() { return mDescendIntoSubdocuments; }
+
+ /**
+ * Get dirty rect relative to current frame (the frame that we're calling
+ * BuildDisplayList on right now).
+ */
+ const nsRect& GetVisibleRect() { return mVisibleRect; }
+ const nsRect& GetDirtyRect() { return mDirtyRect; }
+
+ void SetVisibleRect(const nsRect& aVisibleRect) {
+ mVisibleRect = aVisibleRect;
+ }
+
+ void IntersectVisibleRect(const nsRect& aVisibleRect) {
+ mVisibleRect.IntersectRect(mVisibleRect, aVisibleRect);
+ }
+
+ void SetDirtyRect(const nsRect& aDirtyRect) { mDirtyRect = aDirtyRect; }
+
+ void IntersectDirtyRect(const nsRect& aDirtyRect) {
+ mDirtyRect.IntersectRect(mDirtyRect, aDirtyRect);
+ }
+
+ const nsIFrame* GetCurrentFrame() { return mCurrentFrame; }
+ const nsIFrame* GetCurrentReferenceFrame() { return mCurrentReferenceFrame; }
+
+ const nsPoint& GetCurrentFrameOffsetToReferenceFrame() const {
+ return mCurrentOffsetToReferenceFrame;
+ }
+
+ void Check() { mPool.Check(); }
+
+ /*
+ * Get the paint sequence number of the current paint.
+ */
+ static uint32_t GetPaintSequenceNumber() { return sPaintSequenceNumber; }
+
+ /*
+ * Increment the paint sequence number.
+ */
+ static void IncrementPaintSequenceNumber() { ++sPaintSequenceNumber; }
+
+ /**
+ * Returns true if merging and flattening of display lists should be
+ * performed while computing visibility.
+ */
+ bool AllowMergingAndFlattening() { return mAllowMergingAndFlattening; }
+ void SetAllowMergingAndFlattening(bool aAllow) {
+ mAllowMergingAndFlattening = aAllow;
+ }
+
+ void SetCompositorHitTestInfo(const gfx::CompositorHitTestInfo& aInfo) {
+ mCompositorHitTestInfo = aInfo;
+ }
+
+ const gfx::CompositorHitTestInfo& GetCompositorHitTestInfo() const {
+ return mCompositorHitTestInfo;
+ }
+
+ /**
+ * Builds a new nsDisplayCompositorHitTestInfo for the frame |aFrame| if
+ * needed, and adds it to the top of |aList|.
+ */
+ void BuildCompositorHitTestInfoIfNeeded(nsIFrame* aFrame,
+ nsDisplayList* aList);
+
+ bool IsInsidePointerEventsNoneDoc() {
+ return CurrentPresShellState()->mInsidePointerEventsNoneDoc;
+ }
+
+ bool IsTouchEventPrefEnabledDoc() {
+ return CurrentPresShellState()->mTouchEventPrefEnabledDoc;
+ }
+
+ bool GetAncestorHasApzAwareEventHandler() const {
+ return mAncestorHasApzAwareEventHandler;
+ }
+
+ void SetAncestorHasApzAwareEventHandler(bool aValue) {
+ mAncestorHasApzAwareEventHandler = aValue;
+ }
+
+ bool HaveScrollableDisplayPort() const { return mHaveScrollableDisplayPort; }
+ void SetHaveScrollableDisplayPort() { mHaveScrollableDisplayPort = true; }
+ void ClearHaveScrollableDisplayPort() { mHaveScrollableDisplayPort = false; }
+
+ bool SetIsCompositingCheap(bool aCompositingCheap) {
+ bool temp = mIsCompositingCheap;
+ mIsCompositingCheap = aCompositingCheap;
+ return temp;
+ }
+
+ bool IsCompositingCheap() const { return mIsCompositingCheap; }
+ /**
+ * Display the caret if needed.
+ */
+ bool DisplayCaret(nsIFrame* aFrame, nsDisplayList* aList) {
+ nsIFrame* frame = GetCaretFrame();
+ if (aFrame == frame && !IsBackgroundOnly()) {
+ frame->DisplayCaret(this, aList);
+ return true;
+ }
+ return false;
+ }
+ /**
+ * Get the frame that the caret is supposed to draw in.
+ * If the caret is currently invisible, this will be null.
+ */
+ nsIFrame* GetCaretFrame() { return mCaretFrame; }
+ /**
+ * Get the rectangle we're supposed to draw the caret into.
+ */
+ const nsRect& GetCaretRect() { return mCaretRect; }
+ /**
+ * Get the caret associated with the current presshell.
+ */
+ nsCaret* GetCaret();
+
+ /**
+ * Returns the root scroll frame for the current PresShell, if the PresShell
+ * is ignoring viewport scrolling.
+ */
+ nsIFrame* GetPresShellIgnoreScrollFrame() {
+ return CurrentPresShellState()->mPresShellIgnoreScrollFrame;
+ }
+
+ /**
+ * Notify the display list builder that we're entering a presshell.
+ * aReferenceFrame should be a frame in the new presshell.
+ * aPointerEventsNoneDoc should be set to true if the frame generating this
+ * document is pointer-events:none.
+ */
+ void EnterPresShell(const nsIFrame* aReferenceFrame,
+ bool aPointerEventsNoneDoc = false);
+ /**
+ * For print-preview documents, we sometimes need to build display items for
+ * the same frames multiple times in the same presentation, with different
+ * clipping. Between each such batch of items, call
+ * ResetMarkedFramesForDisplayList to make sure that the results of
+ * MarkFramesForDisplayList do not carry over between batches.
+ */
+ void ResetMarkedFramesForDisplayList(const nsIFrame* aReferenceFrame);
+ /**
+ * Notify the display list builder that we're leaving a presshell.
+ */
+ void LeavePresShell(const nsIFrame* aReferenceFrame,
+ nsDisplayList* aPaintedContents);
+
+ void IncrementPresShellPaintCount(PresShell* aPresShell);
+
+ /**
+ * Returns true if we're currently building a display list that's
+ * directly or indirectly under an nsDisplayTransform.
+ */
+ bool IsInTransform() const { return mInTransform; }
+
+ bool InEventsOnly() const { return mInEventsOnly; }
+ /**
+ * Indicate whether or not we're directly or indirectly under and
+ * nsDisplayTransform or SVG foreignObject.
+ */
+ void SetInTransform(bool aInTransform) { mInTransform = aInTransform; }
+
+ /**
+ * Returns true if we're currently building a display list that's
+ * under an nsDisplayFilters.
+ */
+ bool IsInFilter() const { return mInFilter; }
+
+ /**
+ * Return true if we're currently building a display list for a
+ * nested presshell.
+ */
+ bool IsInSubdocument() const { return mPresShellStates.Length() > 1; }
+
+ void SetDisablePartialUpdates(bool aDisable) {
+ mDisablePartialUpdates = aDisable;
+ }
+ bool DisablePartialUpdates() const { return mDisablePartialUpdates; }
+
+ void SetPartialBuildFailed(bool aFailed) { mPartialBuildFailed = aFailed; }
+ bool PartialBuildFailed() const { return mPartialBuildFailed; }
+
+ bool IsInActiveDocShell() const { return mIsInActiveDocShell; }
+ void SetInActiveDocShell(bool aActive) { mIsInActiveDocShell = aActive; }
+
+ /**
+ * Return true if we're currently building a display list for the presshell
+ * of a chrome document, or if we're building the display list for a popup.
+ */
+ bool IsInChromeDocumentOrPopup() const {
+ return mIsInChromePresContext || mIsBuildingForPopup;
+ }
+
+ /**
+ * @return true if images have been set to decode synchronously.
+ */
+ bool ShouldSyncDecodeImages() const { return mSyncDecodeImages; }
+
+ /**
+ * Indicates whether we should synchronously decode images. If true, we decode
+ * and draw whatever image data has been loaded. If false, we just draw
+ * whatever has already been decoded.
+ */
+ void SetSyncDecodeImages(bool aSyncDecodeImages) {
+ mSyncDecodeImages = aSyncDecodeImages;
+ }
+
+ nsDisplayTableBackgroundSet* SetTableBackgroundSet(
+ nsDisplayTableBackgroundSet* aTableSet) {
+ nsDisplayTableBackgroundSet* old = mTableBackgroundSet;
+ mTableBackgroundSet = aTableSet;
+ return old;
+ }
+ nsDisplayTableBackgroundSet* GetTableBackgroundSet() const {
+ return mTableBackgroundSet;
+ }
+
+ void FreeClipChains();
+
+ /*
+ * Frees the temporary display items created during merging.
+ */
+ void FreeTemporaryItems();
+
+ /**
+ * Helper method to generate background painting flags based on the
+ * information available in the display list builder.
+ */
+ uint32_t GetBackgroundPaintFlags();
+
+ /**
+ * Helper method to generate nsImageRenderer flags based on the information
+ * available in the display list builder.
+ */
+ uint32_t GetImageRendererFlags() const;
+
+ /**
+ * Helper method to generate image decoding flags based on the
+ * information available in the display list builder.
+ */
+ uint32_t GetImageDecodeFlags() const;
+
+ /**
+ * Subtracts aRegion from *aVisibleRegion. We avoid letting
+ * aVisibleRegion become overcomplex by simplifying it if necessary.
+ */
+ void SubtractFromVisibleRegion(nsRegion* aVisibleRegion,
+ const nsRegion& aRegion);
+
+ /**
+ * Mark the frames in aFrames to be displayed if they intersect aDirtyRect
+ * (which is relative to aDirtyFrame). If the frames have placeholders
+ * that might not be displayed, we mark the placeholders and their ancestors
+ * to ensure that display list construction descends into them
+ * anyway. nsDisplayListBuilder will take care of unmarking them when it is
+ * destroyed.
+ */
+ void MarkFramesForDisplayList(nsIFrame* aDirtyFrame,
+ const nsFrameList& aFrames);
+ void MarkFrameForDisplay(nsIFrame* aFrame, const nsIFrame* aStopAtFrame);
+ void MarkFrameForDisplayIfVisible(nsIFrame* aFrame,
+ const nsIFrame* aStopAtFrame);
+ void AddFrameMarkedForDisplayIfVisible(nsIFrame* aFrame);
+
+ void ClearFixedBackgroundDisplayData();
+ /**
+ * Mark all child frames that Preserve3D() as needing display.
+ * Because these frames include transforms set on their parent, dirty rects
+ * for intermediate frames may be empty, yet child frames could still be
+ * visible.
+ */
+ void MarkPreserve3DFramesForDisplayList(nsIFrame* aDirtyFrame);
+
+ /**
+ * Returns true if we need to descend into this frame when building
+ * the display list, even though it doesn't intersect the dirty
+ * rect, because it may have out-of-flows that do so.
+ */
+ bool ShouldDescendIntoFrame(nsIFrame* aFrame, bool aVisible) const {
+ return aFrame->HasAnyStateBits(NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO) ||
+ (aVisible && aFrame->ForceDescendIntoIfVisible()) ||
+ GetIncludeAllOutOfFlows();
+ }
+
+ /**
+ * Returns the list of registered theme geometries.
+ */
+ nsTArray<nsIWidget::ThemeGeometry> GetThemeGeometries() const {
+ nsTArray<nsIWidget::ThemeGeometry> geometries;
+
+ for (const auto& data : mThemeGeometries.Values()) {
+ geometries.AppendElements(*data);
+ }
+
+ return geometries;
+ }
+
+ /**
+ * Notifies the builder that a particular themed widget exists
+ * at the given rectangle within the currently built display list.
+ * For certain appearance values (currently only StyleAppearance::Toolbar and
+ * StyleAppearance::WindowTitlebar) this gets called during every display list
+ * construction, for every themed widget of the right type within the
+ * display list, except for themed widgets which are transformed or have
+ * effects applied to them (e.g. CSS opacity or filters).
+ *
+ * @param aWidgetType the -moz-appearance value for the themed widget
+ * @param aItem the item associated with the theme geometry
+ * @param aRect the device-pixel rect relative to the widget's displayRoot
+ * for the themed widget
+ */
+ void RegisterThemeGeometry(uint8_t aWidgetType, nsDisplayItem* aItem,
+ const LayoutDeviceIntRect& aRect) {
+ if (!mIsPaintingToWindow) {
+ return;
+ }
+
+ nsTArray<nsIWidget::ThemeGeometry>* geometries =
+ mThemeGeometries.GetOrInsertNew(aItem);
+ geometries->AppendElement(nsIWidget::ThemeGeometry(aWidgetType, aRect));
+ }
+
+ /**
+ * Removes theme geometries associated with the given display item |aItem|.
+ */
+ void UnregisterThemeGeometry(nsDisplayItem* aItem) {
+ mThemeGeometries.Remove(aItem);
+ }
+
+ /**
+ * Adjusts mWindowDraggingRegion to take into account aFrame. If aFrame's
+ * -moz-window-dragging value is |drag|, its border box is added to the
+ * collected dragging region; if the value is |no-drag|, the border box is
+ * subtracted from the region; if the value is |default|, that frame does
+ * not influence the window dragging region.
+ */
+ void AdjustWindowDraggingRegion(nsIFrame* aFrame);
+
+ LayoutDeviceIntRegion GetWindowDraggingRegion() const;
+
+ void RemoveModifiedWindowRegions();
+ void ClearRetainedWindowRegions();
+
+ const nsTHashMap<nsPtrHashKey<dom::RemoteBrowser>, dom::EffectsInfo>&
+ GetEffectUpdates() const {
+ return mEffectsUpdates;
+ }
+
+ void AddEffectUpdate(dom::RemoteBrowser* aBrowser,
+ const dom::EffectsInfo& aUpdate);
+
+ /**
+ * Allocate memory in our arena. It will only be freed when this display list
+ * builder is destroyed. This memory holds nsDisplayItems and
+ * DisplayItemClipChain objects.
+ *
+ * Destructors are called as soon as the item is no longer used.
+ */
+ void* Allocate(size_t aSize, DisplayListArenaObjectId aId) {
+ return mPool.Allocate(aId, aSize);
+ }
+ void* Allocate(size_t aSize, DisplayItemType aType) {
+#define DECLARE_DISPLAY_ITEM_TYPE(name_, ...) \
+ static_assert(size_t(DisplayItemType::TYPE_##name_) == \
+ size_t(DisplayListArenaObjectId::name_), \
+ "");
+#include "nsDisplayItemTypesList.h"
+ static_assert(size_t(DisplayItemType::TYPE_MAX) ==
+ size_t(DisplayListArenaObjectId::CLIPCHAIN),
+ "");
+ static_assert(size_t(DisplayItemType::TYPE_MAX) + 1 ==
+ size_t(DisplayListArenaObjectId::LISTNODE),
+ "");
+#undef DECLARE_DISPLAY_ITEM_TYPE
+ return Allocate(aSize, DisplayListArenaObjectId(size_t(aType)));
+ }
+
+ void Destroy(DisplayListArenaObjectId aId, void* aPtr) {
+ return mPool.Free(aId, aPtr);
+ }
+ void Destroy(DisplayItemType aType, void* aPtr) {
+ return Destroy(DisplayListArenaObjectId(size_t(aType)), aPtr);
+ }
+
+ /**
+ * Allocate a new ActiveScrolledRoot in the arena. Will be cleaned up
+ * automatically when the arena goes away.
+ */
+ ActiveScrolledRoot* AllocateActiveScrolledRoot(
+ const ActiveScrolledRoot* aParent, nsIScrollableFrame* aScrollableFrame);
+
+ /**
+ * Allocate a new DisplayItemClipChain object in the arena. Will be cleaned
+ * up automatically when the arena goes away.
+ */
+ const DisplayItemClipChain* AllocateDisplayItemClipChain(
+ const DisplayItemClip& aClip, const ActiveScrolledRoot* aASR,
+ const DisplayItemClipChain* aParent);
+
+ /**
+ * Intersect two clip chains, allocating the new clip chain items in this
+ * builder's arena. The result is parented to aAncestor, and no intersections
+ * happen past aAncestor's ASR.
+ * That means aAncestor has to be living in this builder's arena already.
+ * aLeafClip1 and aLeafClip2 only need to outlive the call to this function,
+ * their values are copied into the newly-allocated intersected clip chain
+ * and this function does not hold on to any pointers to them.
+ */
+ const DisplayItemClipChain* CreateClipChainIntersection(
+ const DisplayItemClipChain* aAncestor,
+ const DisplayItemClipChain* aLeafClip1,
+ const DisplayItemClipChain* aLeafClip2);
+
+ /**
+ * Same as above, except aAncestor is computed as the nearest common
+ * ancestor of the two provided clips.
+ */
+ const DisplayItemClipChain* CreateClipChainIntersection(
+ const DisplayItemClipChain* aLeafClip1,
+ const DisplayItemClipChain* aLeafClip2);
+
+ /**
+ * Clone the supplied clip chain's chain items into this builder's arena.
+ */
+ const DisplayItemClipChain* CopyWholeChain(
+ const DisplayItemClipChain* aClipChain);
+
+ const ActiveScrolledRoot* GetFilterASR() const { return mFilterASR; }
+
+ /**
+ * Merges the display items in |aMergedItems| and returns a new temporary
+ * display item.
+ * The display items in |aMergedItems| have to be mergeable with each other.
+ */
+ nsDisplayWrapList* MergeItems(nsTArray<nsDisplayItem*>& aItems);
+
+ /**
+ * A helper class used to temporarily set nsDisplayListBuilder properties for
+ * building display items.
+ * aVisibleRect and aDirtyRect are relative to aForChild.
+ */
+ class AutoBuildingDisplayList {
+ public:
+ AutoBuildingDisplayList(nsDisplayListBuilder* aBuilder, nsIFrame* aForChild,
+ const nsRect& aVisibleRect,
+ const nsRect& aDirtyRect)
+ : AutoBuildingDisplayList(aBuilder, aForChild, aVisibleRect, aDirtyRect,
+ aForChild->IsTransformed()) {}
+
+ AutoBuildingDisplayList(nsDisplayListBuilder* aBuilder, nsIFrame* aForChild,
+ const nsRect& aVisibleRect,
+ const nsRect& aDirtyRect,
+ const bool aIsTransformed);
+
+ void SetReferenceFrameAndCurrentOffset(const nsIFrame* aFrame,
+ const nsPoint& aOffset) {
+ mBuilder->mCurrentReferenceFrame = aFrame;
+ mBuilder->mCurrentOffsetToReferenceFrame = aOffset;
+ }
+
+ void SetAdditionalOffset(const nsPoint& aOffset) {
+ MOZ_ASSERT(!mBuilder->mAdditionalOffset);
+ mBuilder->mAdditionalOffset = Some(aOffset);
+
+ mBuilder->mCurrentOffsetToReferenceFrame += aOffset;
+ }
+
+ void RestoreBuildingInvisibleItemsValue() {
+ mBuilder->mBuildingInvisibleItems = mPrevBuildingInvisibleItems;
+ }
+
+ ~AutoBuildingDisplayList() {
+ mBuilder->mCurrentFrame = mPrevFrame;
+ mBuilder->mCurrentReferenceFrame = mPrevReferenceFrame;
+ mBuilder->mCurrentOffsetToReferenceFrame = mPrevOffset;
+ mBuilder->mVisibleRect = mPrevVisibleRect;
+ mBuilder->mDirtyRect = mPrevDirtyRect;
+ mBuilder->mAncestorHasApzAwareEventHandler =
+ mPrevAncestorHasApzAwareEventHandler;
+ mBuilder->mBuildingInvisibleItems = mPrevBuildingInvisibleItems;
+ mBuilder->mInInvalidSubtree = mPrevInInvalidSubtree;
+ mBuilder->mAdditionalOffset = mPrevAdditionalOffset;
+ mBuilder->mCompositorHitTestInfo = mPrevCompositorHitTestInfo;
+ }
+
+ private:
+ nsDisplayListBuilder* mBuilder;
+ const nsIFrame* mPrevFrame;
+ const nsIFrame* mPrevReferenceFrame;
+ nsPoint mPrevOffset;
+ Maybe<nsPoint> mPrevAdditionalOffset;
+ nsRect mPrevVisibleRect;
+ nsRect mPrevDirtyRect;
+ gfx::CompositorHitTestInfo mPrevCompositorHitTestInfo;
+ bool mPrevAncestorHasApzAwareEventHandler;
+ bool mPrevBuildingInvisibleItems;
+ bool mPrevInInvalidSubtree;
+ };
+
+ /**
+ * A helper class to temporarily set the value of mInTransform.
+ */
+ class AutoInTransformSetter {
+ public:
+ AutoInTransformSetter(nsDisplayListBuilder* aBuilder, bool aInTransform)
+ : mBuilder(aBuilder), mOldValue(aBuilder->mInTransform) {
+ aBuilder->mInTransform = aInTransform;
+ }
+
+ ~AutoInTransformSetter() { mBuilder->mInTransform = mOldValue; }
+
+ private:
+ nsDisplayListBuilder* mBuilder;
+ bool mOldValue;
+ };
+
+ class AutoInEventsOnly {
+ public:
+ AutoInEventsOnly(nsDisplayListBuilder* aBuilder, bool aInEventsOnly)
+ : mBuilder(aBuilder), mOldValue(aBuilder->mInEventsOnly) {
+ aBuilder->mInEventsOnly |= aInEventsOnly;
+ }
+
+ ~AutoInEventsOnly() { mBuilder->mInEventsOnly = mOldValue; }
+
+ private:
+ nsDisplayListBuilder* mBuilder;
+ bool mOldValue;
+ };
+
+ /**
+ * A helper class to temporarily set the value of mFilterASR and
+ * mInFilter.
+ */
+ class AutoEnterFilter {
+ public:
+ AutoEnterFilter(nsDisplayListBuilder* aBuilder, bool aUsingFilter)
+ : mBuilder(aBuilder),
+ mOldValue(aBuilder->mFilterASR),
+ mOldInFilter(aBuilder->mInFilter) {
+ if (!aBuilder->mFilterASR && aUsingFilter) {
+ aBuilder->mFilterASR = aBuilder->CurrentActiveScrolledRoot();
+ aBuilder->mInFilter = true;
+ }
+ }
+
+ ~AutoEnterFilter() {
+ mBuilder->mFilterASR = mOldValue;
+ mBuilder->mInFilter = mOldInFilter;
+ }
+
+ private:
+ nsDisplayListBuilder* mBuilder;
+ const ActiveScrolledRoot* mOldValue;
+ bool mOldInFilter;
+ };
+
+ /**
+ * Used to update the current active scrolled root on the display list
+ * builder, and to create new active scrolled roots.
+ */
+ class AutoCurrentActiveScrolledRootSetter {
+ public:
+ explicit AutoCurrentActiveScrolledRootSetter(nsDisplayListBuilder* aBuilder)
+ : mBuilder(aBuilder),
+ mSavedActiveScrolledRoot(aBuilder->mCurrentActiveScrolledRoot),
+ mContentClipASR(aBuilder->ClipState().GetContentClipASR()),
+ mDescendantsStartIndex(aBuilder->mActiveScrolledRoots.Length()),
+ mUsed(false),
+ mOldScrollParentId(aBuilder->mCurrentScrollParentId),
+ mOldForceLayer(aBuilder->mForceLayerForScrollParent),
+ mOldContainsNonMinimalDisplayPort(
+ mBuilder->mContainsNonMinimalDisplayPort),
+ mCanBeScrollParent(false) {}
+
+ void SetCurrentScrollParentId(ViewID aScrollId) {
+ // Update the old scroll parent id.
+ mOldScrollParentId = mBuilder->mCurrentScrollParentId;
+ // If this AutoCurrentActiveScrolledRootSetter has the same aScrollId as
+ // the previous one on the stack, then that means the scrollframe that
+ // created this isn't actually scrollable and cannot participate in
+ // scroll handoff. We set mCanBeScrollParent to false to indicate this.
+ mCanBeScrollParent = (mOldScrollParentId != aScrollId);
+ mBuilder->mCurrentScrollParentId = aScrollId;
+ mBuilder->mForceLayerForScrollParent = false;
+ mBuilder->mContainsNonMinimalDisplayPort = false;
+ }
+
+ bool ShouldForceLayerForScrollParent() const {
+ // Only scrollframes participating in scroll handoff can be forced to
+ // layerize
+ return mCanBeScrollParent && mBuilder->mForceLayerForScrollParent;
+ }
+
+ bool GetContainsNonMinimalDisplayPort() const {
+ // Only for scrollframes participating in scroll handoff can we return
+ // true.
+ return mCanBeScrollParent && mBuilder->mContainsNonMinimalDisplayPort;
+ }
+
+ ~AutoCurrentActiveScrolledRootSetter() {
+ mBuilder->mCurrentActiveScrolledRoot = mSavedActiveScrolledRoot;
+ mBuilder->mCurrentScrollParentId = mOldScrollParentId;
+ if (mCanBeScrollParent) {
+ // If this flag is set, caller code is responsible for having dealt
+ // with the current value of mBuilder->mForceLayerForScrollParent, so
+ // we can just restore the old value.
+ mBuilder->mForceLayerForScrollParent = mOldForceLayer;
+ } else {
+ // Otherwise we need to keep propagating the force-layerization flag
+ // upwards to the next ancestor scrollframe that does participate in
+ // scroll handoff.
+ mBuilder->mForceLayerForScrollParent |= mOldForceLayer;
+ }
+ mBuilder->mContainsNonMinimalDisplayPort |=
+ mOldContainsNonMinimalDisplayPort;
+ }
+
+ void SetCurrentActiveScrolledRoot(
+ const ActiveScrolledRoot* aActiveScrolledRoot);
+
+ void EnterScrollFrame(nsIScrollableFrame* aScrollableFrame) {
+ MOZ_ASSERT(!mUsed);
+ ActiveScrolledRoot* asr = mBuilder->AllocateActiveScrolledRoot(
+ mBuilder->mCurrentActiveScrolledRoot, aScrollableFrame);
+ mBuilder->mCurrentActiveScrolledRoot = asr;
+ mUsed = true;
+ }
+
+ void InsertScrollFrame(nsIScrollableFrame* aScrollableFrame);
+
+ private:
+ nsDisplayListBuilder* mBuilder;
+ /**
+ * The builder's mCurrentActiveScrolledRoot at construction time which
+ * needs to be restored at destruction time.
+ */
+ const ActiveScrolledRoot* mSavedActiveScrolledRoot;
+ /**
+ * If there's a content clip on the builder at construction time, then
+ * mContentClipASR is that content clip's ASR, otherwise null. The
+ * assumption is that the content clip doesn't get relaxed while this
+ * object is on the stack.
+ */
+ const ActiveScrolledRoot* mContentClipASR;
+ /**
+ * InsertScrollFrame needs to mutate existing ASRs (those that were
+ * created while this object was on the stack), and mDescendantsStartIndex
+ * makes it easier to skip ASRs that were created in the past.
+ */
+ size_t mDescendantsStartIndex;
+ /**
+ * Flag to make sure that only one of SetCurrentActiveScrolledRoot /
+ * EnterScrollFrame / InsertScrollFrame is called per instance of this
+ * class.
+ */
+ bool mUsed;
+ ViewID mOldScrollParentId;
+ bool mOldForceLayer;
+ bool mOldContainsNonMinimalDisplayPort;
+ bool mCanBeScrollParent;
+ };
+
+ /**
+ * Keeps track of the innermost ASR that can be used as the ASR for a
+ * container item that wraps all items that were created while this
+ * object was on the stack.
+ * The rule is: all child items of the container item need to have
+ * clipped bounds with respect to the container ASR.
+ */
+ class AutoContainerASRTracker {
+ public:
+ explicit AutoContainerASRTracker(nsDisplayListBuilder* aBuilder);
+
+ const ActiveScrolledRoot* GetContainerASR() {
+ return mBuilder->mCurrentContainerASR;
+ }
+
+ ~AutoContainerASRTracker() {
+ mBuilder->mCurrentContainerASR = ActiveScrolledRoot::PickAncestor(
+ mBuilder->mCurrentContainerASR, mSavedContainerASR);
+ }
+
+ private:
+ nsDisplayListBuilder* mBuilder;
+ const ActiveScrolledRoot* mSavedContainerASR;
+ };
+
+ /**
+ * A helper class to temporarily set the value of mCurrentScrollbarTarget
+ * and mCurrentScrollbarFlags.
+ */
+ class AutoCurrentScrollbarInfoSetter {
+ public:
+ AutoCurrentScrollbarInfoSetter(
+ nsDisplayListBuilder* aBuilder, ViewID aScrollTargetID,
+ const Maybe<layers::ScrollDirection>& aScrollbarDirection,
+ bool aWillHaveLayer)
+ : mBuilder(aBuilder) {
+ aBuilder->mIsBuildingScrollbar = true;
+ aBuilder->mCurrentScrollbarTarget = aScrollTargetID;
+ aBuilder->mCurrentScrollbarDirection = aScrollbarDirection;
+ aBuilder->mCurrentScrollbarWillHaveLayer = aWillHaveLayer;
+ }
+
+ ~AutoCurrentScrollbarInfoSetter() {
+ // No need to restore old values because scrollbars cannot be nested.
+ mBuilder->mIsBuildingScrollbar = false;
+ mBuilder->mCurrentScrollbarTarget =
+ layers::ScrollableLayerGuid::NULL_SCROLL_ID;
+ mBuilder->mCurrentScrollbarDirection.reset();
+ mBuilder->mCurrentScrollbarWillHaveLayer = false;
+ }
+
+ private:
+ nsDisplayListBuilder* mBuilder;
+ };
+
+ /**
+ * A helper class to temporarily set mBuildingExtraPagesForPageNum.
+ */
+ class MOZ_RAII AutoPageNumberSetter {
+ public:
+ AutoPageNumberSetter(nsDisplayListBuilder* aBuilder, const uint8_t aPageNum)
+ : mBuilder(aBuilder),
+ mOldPageNum(aBuilder->GetBuildingExtraPagesForPageNum()) {
+ mBuilder->SetBuildingExtraPagesForPageNum(aPageNum);
+ }
+ ~AutoPageNumberSetter() {
+ mBuilder->SetBuildingExtraPagesForPageNum(mOldPageNum);
+ }
+
+ private:
+ nsDisplayListBuilder* mBuilder;
+ uint8_t mOldPageNum;
+ };
+
+ /**
+ * A helper class to track current effective transform for items.
+ *
+ * For frames that is Combines3DTransformWithAncestors(), we need to
+ * apply all transforms of ancestors on the same preserves3D chain
+ * on the bounds of current frame to the coordination of the 3D
+ * context root. The 3D context root computes it's bounds from
+ * these transformed bounds.
+ */
+ class AutoAccumulateTransform {
+ public:
+ explicit AutoAccumulateTransform(nsDisplayListBuilder* aBuilder)
+ : mBuilder(aBuilder),
+ mSavedTransform(aBuilder->mPreserves3DCtx.mAccumulatedTransform) {}
+
+ ~AutoAccumulateTransform() {
+ mBuilder->mPreserves3DCtx.mAccumulatedTransform = mSavedTransform;
+ }
+
+ void Accumulate(const gfx::Matrix4x4& aTransform) {
+ mBuilder->mPreserves3DCtx.mAccumulatedTransform =
+ aTransform * mBuilder->mPreserves3DCtx.mAccumulatedTransform;
+ }
+
+ const gfx::Matrix4x4& GetCurrentTransform() {
+ return mBuilder->mPreserves3DCtx.mAccumulatedTransform;
+ }
+
+ void StartRoot() {
+ mBuilder->mPreserves3DCtx.mAccumulatedTransform = gfx::Matrix4x4();
+ }
+
+ private:
+ nsDisplayListBuilder* mBuilder;
+ gfx::Matrix4x4 mSavedTransform;
+ };
+
+ /**
+ * A helper class to collect bounds rects of descendants.
+ *
+ * For a 3D context root, it's bounds is computed from the bounds of
+ * descendants. If we transform bounds frame by frame applying
+ * transforms, the bounds may turn to empty for any singular
+ * transform on the path, but it is not empty for the accumulated
+ * transform.
+ */
+ class AutoAccumulateRect {
+ public:
+ explicit AutoAccumulateRect(nsDisplayListBuilder* aBuilder)
+ : mBuilder(aBuilder),
+ mSavedRect(aBuilder->mPreserves3DCtx.mAccumulatedRect) {
+ aBuilder->mPreserves3DCtx.mAccumulatedRect = nsRect();
+ aBuilder->mPreserves3DCtx.mAccumulatedRectLevels++;
+ }
+
+ ~AutoAccumulateRect() {
+ mBuilder->mPreserves3DCtx.mAccumulatedRect = mSavedRect;
+ mBuilder->mPreserves3DCtx.mAccumulatedRectLevels--;
+ }
+
+ private:
+ nsDisplayListBuilder* mBuilder;
+ nsRect mSavedRect;
+ };
+
+ void AccumulateRect(const nsRect& aRect) {
+ mPreserves3DCtx.mAccumulatedRect.UnionRect(mPreserves3DCtx.mAccumulatedRect,
+ aRect);
+ }
+
+ const nsRect& GetAccumulatedRect() {
+ return mPreserves3DCtx.mAccumulatedRect;
+ }
+
+ /**
+ * The level is increased by one for items establishing 3D rendering
+ * context and starting a new accumulation.
+ */
+ int GetAccumulatedRectLevels() {
+ return mPreserves3DCtx.mAccumulatedRectLevels;
+ }
+
+ struct OutOfFlowDisplayData {
+ OutOfFlowDisplayData(
+ const DisplayItemClipChain* aContainingBlockClipChain,
+ const DisplayItemClipChain* aCombinedClipChain,
+ const ActiveScrolledRoot* aContainingBlockActiveScrolledRoot,
+ const ViewID& aScrollParentId, const nsRect& aVisibleRect,
+ const nsRect& aDirtyRect)
+ : mContainingBlockClipChain(aContainingBlockClipChain),
+ mCombinedClipChain(aCombinedClipChain),
+ mContainingBlockActiveScrolledRoot(
+ aContainingBlockActiveScrolledRoot),
+ mVisibleRect(aVisibleRect),
+ mDirtyRect(aDirtyRect),
+ mScrollParentId(aScrollParentId) {}
+ const DisplayItemClipChain* mContainingBlockClipChain;
+ const DisplayItemClipChain*
+ mCombinedClipChain; // only necessary for the special case of top layer
+ const ActiveScrolledRoot* mContainingBlockActiveScrolledRoot;
+
+ // If this OutOfFlowDisplayData is associated with the ViewportFrame
+ // of a document that has a resolution (creating separate visual and
+ // layout viewports with their own coordinate spaces), these rects
+ // are in layout coordinates. Similarly, GetVisibleRectForFrame() in
+ // such a case returns a quantity in layout coordinates.
+ nsRect mVisibleRect;
+ nsRect mDirtyRect;
+ ViewID mScrollParentId;
+
+ static nsRect ComputeVisibleRectForFrame(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame,
+ const nsRect& aVisibleRect,
+ const nsRect& aDirtyRect,
+ nsRect* aOutDirtyRect);
+
+ nsRect GetVisibleRectForFrame(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame, nsRect* aDirtyRect) {
+ return ComputeVisibleRectForFrame(aBuilder, aFrame, mVisibleRect,
+ mDirtyRect, aDirtyRect);
+ }
+ };
+
+ NS_DECLARE_FRAME_PROPERTY_DELETABLE(OutOfFlowDisplayDataProperty,
+ OutOfFlowDisplayData)
+
+ struct DisplayListBuildingData {
+ nsIFrame* mModifiedAGR = nullptr;
+ nsRect mDirtyRect;
+ };
+ NS_DECLARE_FRAME_PROPERTY_DELETABLE(DisplayListBuildingRect,
+ DisplayListBuildingData)
+
+ NS_DECLARE_FRAME_PROPERTY_DELETABLE(DisplayListBuildingDisplayPortRect,
+ nsRect)
+
+ static OutOfFlowDisplayData* GetOutOfFlowData(nsIFrame* aFrame) {
+ if (!aFrame->GetParent()) {
+ return nullptr;
+ }
+ return aFrame->GetParent()->GetProperty(OutOfFlowDisplayDataProperty());
+ }
+
+ nsPresContext* CurrentPresContext();
+
+ OutOfFlowDisplayData* GetCurrentFixedBackgroundDisplayData() {
+ auto& displayData = CurrentPresShellState()->mFixedBackgroundDisplayData;
+ return displayData ? displayData.ptr() : nullptr;
+ }
+
+ /**
+ * Accumulates the bounds of box frames that have moz-appearance
+ * -moz-win-exclude-glass style. Used in setting glass margins on
+ * Windows.
+ *
+ * We set the window opaque region (from which glass margins are computed)
+ * to the intersection of the glass region specified here and the opaque
+ * region computed during painting. So the excluded glass region actually
+ * *limits* the extent of the opaque area reported to Windows. We limit it
+ * so that changes to the computed opaque region (which can vary based on
+ * region optimizations and the placement of UI elements) outside the
+ * -moz-win-exclude-glass area don't affect the glass margins reported to
+ * Windows; changing those margins willy-nilly can cause the Windows 7 glass
+ * haze effect to jump around disconcertingly.
+ */
+ void AddWindowExcludeGlassRegion(nsIFrame* aFrame, const nsRect& aBounds) {
+ mWindowExcludeGlassRegion.Add(aFrame, aBounds);
+ }
+
+ /**
+ * Returns the window exclude glass region.
+ */
+ nsRegion GetWindowExcludeGlassRegion() const {
+ return mWindowExcludeGlassRegion.ToRegion();
+ }
+
+ /**
+ * Accumulates opaque stuff into the window opaque region.
+ */
+ void AddWindowOpaqueRegion(nsIFrame* aFrame, const nsRect& aBounds) {
+ if (IsRetainingDisplayList()) {
+ mRetainedWindowOpaqueRegion.Add(aFrame, aBounds);
+ return;
+ }
+ mWindowOpaqueRegion.Or(mWindowOpaqueRegion, aBounds);
+ }
+ /**
+ * Returns the window opaque region built so far. This may be incomplete
+ * since the opaque region is built during layer construction.
+ */
+ const nsRegion GetWindowOpaqueRegion() {
+ return IsRetainingDisplayList() ? mRetainedWindowOpaqueRegion.ToRegion()
+ : mWindowOpaqueRegion;
+ }
+
+ void SetGlassDisplayItem(nsDisplayItem* aItem);
+ void ClearGlassDisplayItem() { mGlassDisplayItem = nullptr; }
+ nsDisplayItem* GetGlassDisplayItem() { return mGlassDisplayItem; }
+
+ bool NeedToForceTransparentSurfaceForItem(nsDisplayItem* aItem);
+
+ /**
+ * mContainsBlendMode is true if we processed a display item that
+ * has a blend mode attached. We do this so we can insert a
+ * nsDisplayBlendContainer in the parent stacking context.
+ */
+ void SetContainsBlendMode(bool aContainsBlendMode) {
+ mContainsBlendMode = aContainsBlendMode;
+ }
+ bool ContainsBlendMode() const { return mContainsBlendMode; }
+
+ DisplayListClipState& ClipState() { return mClipState; }
+ const ActiveScrolledRoot* CurrentActiveScrolledRoot() {
+ return mCurrentActiveScrolledRoot;
+ }
+ const ActiveScrolledRoot* CurrentAncestorASRStackingContextContents() {
+ return mCurrentContainerASR;
+ }
+
+ /**
+ * Add the current frame to the will-change budget if possible and
+ * remeber the outcome. Subsequent calls to IsInWillChangeBudget
+ * will return the same value as return here.
+ */
+ bool AddToWillChangeBudget(nsIFrame* aFrame, const nsSize& aSize);
+
+ /**
+ * This will add the current frame to the will-change budget the first
+ * time it is seen. On subsequent calls this will return the same
+ * answer. This effectively implements a first-come, first-served
+ * allocation of the will-change budget.
+ */
+ bool IsInWillChangeBudget(nsIFrame* aFrame, const nsSize& aSize);
+
+ /**
+ * Clears the will-change budget status for the given |aFrame|.
+ * This will also remove the frame from will-change budgets.
+ */
+ void ClearWillChangeBudgetStatus(nsIFrame* aFrame);
+
+ /**
+ * Removes the given |aFrame| from will-change budgets.
+ */
+ void RemoveFromWillChangeBudgets(const nsIFrame* aFrame);
+
+ /**
+ * Clears the will-change budgets.
+ */
+ void ClearWillChangeBudgets();
+
+ void EnterSVGEffectsContents(nsIFrame* aEffectsFrame,
+ nsDisplayList* aHoistedItemsStorage);
+ void ExitSVGEffectsContents();
+
+ bool ShouldBuildScrollInfoItemsForHoisting() const;
+
+ void AppendNewScrollInfoItemForHoisting(
+ nsDisplayScrollInfoLayer* aScrollInfoItem);
+
+ /**
+ * A helper class to install/restore nsDisplayListBuilder::mPreserves3DCtx.
+ *
+ * mPreserves3DCtx is used by class AutoAccumulateTransform &
+ * AutoAccumulateRect to passing data between frames in the 3D
+ * context. If a frame create a new 3D context, it should restore
+ * the value of mPreserves3DCtx before returning back to the parent.
+ * This class do it for the users.
+ */
+ class AutoPreserves3DContext {
+ public:
+ explicit AutoPreserves3DContext(nsDisplayListBuilder* aBuilder)
+ : mBuilder(aBuilder), mSavedCtx(aBuilder->mPreserves3DCtx) {}
+
+ ~AutoPreserves3DContext() { mBuilder->mPreserves3DCtx = mSavedCtx; }
+
+ private:
+ nsDisplayListBuilder* mBuilder;
+ Preserves3DContext mSavedCtx;
+ };
+
+ const nsRect GetPreserves3DRect() const {
+ return mPreserves3DCtx.mVisibleRect;
+ }
+
+ void SavePreserves3DRect() { mPreserves3DCtx.mVisibleRect = mVisibleRect; }
+
+ void SavePreserves3DAllowAsyncAnimation(bool aValue) {
+ mPreserves3DCtx.mAllowAsyncAnimation = aValue;
+ }
+
+ bool GetPreserves3DAllowAsyncAnimation() const {
+ return mPreserves3DCtx.mAllowAsyncAnimation;
+ }
+
+ bool IsBuildingInvisibleItems() const { return mBuildingInvisibleItems; }
+
+ void SetBuildingInvisibleItems(bool aBuildingInvisibleItems) {
+ mBuildingInvisibleItems = aBuildingInvisibleItems;
+ }
+
+ void SetBuildingExtraPagesForPageNum(uint8_t aPageNum) {
+ mBuildingExtraPagesForPageNum = aPageNum;
+ }
+ uint8_t GetBuildingExtraPagesForPageNum() const {
+ return mBuildingExtraPagesForPageNum;
+ }
+
+ bool HitTestIsForVisibility() const { return mVisibleThreshold.isSome(); }
+
+ float VisibilityThreshold() const {
+ MOZ_DIAGNOSTIC_ASSERT(HitTestIsForVisibility());
+ return mVisibleThreshold.valueOr(1.0f);
+ }
+
+ void SetHitTestIsForVisibility(float aVisibleThreshold) {
+ mVisibleThreshold = Some(aVisibleThreshold);
+ }
+
+ bool ShouldBuildAsyncZoomContainer() const {
+ return mBuildAsyncZoomContainer;
+ }
+ void UpdateShouldBuildAsyncZoomContainer();
+
+ void UpdateShouldBuildBackdropRootContainer();
+
+ bool ShouldRebuildDisplayListDueToPrefChange();
+
+ /**
+ * Represents a region composed of frame/rect pairs.
+ * WeakFrames are used to track whether a rect still belongs to the region.
+ * Modified frames and rects are removed and re-added to the region if needed.
+ */
+ struct WeakFrameRegion {
+ /**
+ * A wrapper to store WeakFrame and the pointer to the underlying frame.
+ * This is needed because WeakFrame does not store the frame pointer after
+ * the frame has been deleted.
+ */
+ struct WeakFrameWrapper {
+ explicit WeakFrameWrapper(nsIFrame* aFrame)
+ : mWeakFrame(new WeakFrame(aFrame)), mFrame(aFrame) {}
+
+ UniquePtr<WeakFrame> mWeakFrame;
+ void* mFrame;
+ };
+
+ nsTHashSet<void*> mFrameSet;
+ nsTArray<WeakFrameWrapper> mFrames;
+ nsTArray<pixman_box32_t> mRects;
+
+ template <typename RectType>
+ void Add(nsIFrame* aFrame, const RectType& aRect) {
+ if (mFrameSet.Contains(aFrame)) {
+ return;
+ }
+
+ mFrameSet.Insert(aFrame);
+ mFrames.AppendElement(WeakFrameWrapper(aFrame));
+ mRects.AppendElement(nsRegion::RectToBox(aRect));
+ }
+
+ void Clear() {
+ mFrameSet.Clear();
+ mFrames.Clear();
+ mRects.Clear();
+ }
+
+ void RemoveModifiedFramesAndRects();
+
+ size_t SizeOfExcludingThis(MallocSizeOf) const;
+
+ typedef gfx::ArrayView<pixman_box32_t> BoxArrayView;
+
+ nsRegion ToRegion() const { return nsRegion(BoxArrayView(mRects)); }
+
+ LayoutDeviceIntRegion ToLayoutDeviceIntRegion() const {
+ return LayoutDeviceIntRegion(BoxArrayView(mRects));
+ }
+ };
+
+ void AddScrollFrameToNotify(nsIScrollableFrame* aScrollFrame);
+ void NotifyAndClearScrollFrames();
+
+ // Helper class to find what link spec (if any) to associate with a frame,
+ // recording it in the builder, and generate the corresponding DisplayItem.
+ // This also takes care of generating a named destination for internal links
+ // if the element has an id or name attribute.
+ class Linkifier {
+ public:
+ Linkifier(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ nsDisplayList* aList);
+
+ ~Linkifier() {
+ if (mBuilderToReset) {
+ mBuilderToReset->mLinkSpec.Truncate(0);
+ }
+ }
+
+ void MaybeAppendLink(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame);
+
+ private:
+ nsDisplayListBuilder* mBuilderToReset = nullptr;
+ nsDisplayList* mList;
+ };
+
+ /**
+ * Returns the nearest ancestor frame to aFrame that is considered to have
+ * (or will have) animated geometry. This can return aFrame.
+ */
+ nsIFrame* FindAnimatedGeometryRootFrameFor(nsIFrame* aFrame);
+
+ /**
+ * Returns true if this is a retained builder and reuse stacking contexts
+ * mode is enabled by pref.
+ */
+ bool IsReusingStackingContextItems() const {
+ return mIsReusingStackingContextItems;
+ }
+
+ /**
+ * Adds display item |aItem| to the reuseable display items set.
+ */
+ void AddReusableDisplayItem(nsDisplayItem* aItem);
+
+ /**
+ * Removes display item |aItem| from the reuseable display items set.
+ * This is needed because display items are sometimes deleted during
+ * display list building.
+ * Called by |nsDisplayItem::Destroy()| when the item has been reused.
+ */
+ void RemoveReusedDisplayItem(nsDisplayItem* aItem);
+
+ /**
+ * Clears the reuseable display items set.
+ */
+ void ClearReuseableDisplayItems();
+
+ /**
+ * Marks the given display item |aItem| as reused, and updates the necessary
+ * display list builder state.
+ */
+ void ReuseDisplayItem(nsDisplayItem* aItem);
+
+ private:
+ bool MarkOutOfFlowFrameForDisplay(nsIFrame* aDirtyFrame, nsIFrame* aFrame,
+ const nsRect& aVisibleRect,
+ const nsRect& aDirtyRect);
+
+ friend class nsDisplayBackgroundImage;
+ friend class RetainedDisplayListBuilder;
+
+ /**
+ * Returns whether a frame acts as an animated geometry root, optionally
+ * returning the next ancestor to check.
+ */
+ bool IsAnimatedGeometryRoot(nsIFrame* aFrame, nsIFrame** aParent = nullptr);
+
+ struct PresShellState {
+ PresShell* mPresShell;
+#ifdef DEBUG
+ Maybe<nsAutoLayoutPhase> mAutoLayoutPhase;
+#endif
+ Maybe<OutOfFlowDisplayData> mFixedBackgroundDisplayData;
+ uint32_t mFirstFrameMarkedForDisplay;
+ uint32_t mFirstFrameWithOOFData;
+ bool mIsBackgroundOnly;
+ // This is a per-document flag turning off event handling for all content
+ // in the document, and is set when we enter a subdocument for a pointer-
+ // events:none frame.
+ bool mInsidePointerEventsNoneDoc;
+ bool mTouchEventPrefEnabledDoc;
+ nsIFrame* mPresShellIgnoreScrollFrame;
+ };
+
+ PresShellState* CurrentPresShellState() {
+ NS_ASSERTION(mPresShellStates.Length() > 0,
+ "Someone forgot to enter a presshell");
+ return &mPresShellStates[mPresShellStates.Length() - 1];
+ }
+
+ void AddSizeOfExcludingThis(nsWindowSizes&) const;
+
+ struct FrameWillChangeBudget {
+ FrameWillChangeBudget() : mPresContext(nullptr), mUsage(0) {}
+
+ FrameWillChangeBudget(const nsPresContext* aPresContext, uint32_t aUsage)
+ : mPresContext(aPresContext), mUsage(aUsage) {}
+
+ const nsPresContext* mPresContext;
+ uint32_t mUsage;
+ };
+
+ // will-change budget tracker
+ typedef uint32_t DocumentWillChangeBudget;
+
+ nsIFrame* const mReferenceFrame;
+ nsIFrame* mIgnoreScrollFrame;
+
+ const ActiveScrolledRoot* mCurrentActiveScrolledRoot;
+ const ActiveScrolledRoot* mCurrentContainerASR;
+ // mCurrentFrame is the frame that we're currently calling (or about to call)
+ // BuildDisplayList on.
+ const nsIFrame* mCurrentFrame;
+ // The reference frame for mCurrentFrame.
+ const nsIFrame* mCurrentReferenceFrame;
+
+ // The display item for the Windows window glass background, if any
+ // Set during full display list builds or during display list merging only,
+ // partial display list builds don't touch this.
+ nsDisplayItem* mGlassDisplayItem;
+
+ nsIFrame* mCaretFrame;
+ // A temporary list that we append scroll info items to while building
+ // display items for the contents of frames with SVG effects.
+ // Only non-null when ShouldBuildScrollInfoItemsForHoisting() is true.
+ // This is a pointer and not a real nsDisplayList value because the
+ // nsDisplayList class is defined below this class, so we can't use it here.
+ nsDisplayList* mScrollInfoItemsForHoisting;
+ nsTArray<RefPtr<ActiveScrolledRoot>> mActiveScrolledRoots;
+ DisplayItemClipChain* mFirstClipChainToDestroy;
+ nsTArray<nsDisplayItem*> mTemporaryItems;
+ nsDisplayTableBackgroundSet* mTableBackgroundSet;
+ ViewID mCurrentScrollParentId;
+ ViewID mCurrentScrollbarTarget;
+
+ nsTArray<nsIFrame*> mSVGEffectsFrames;
+ // When we are inside a filter, the current ASR at the time we entered the
+ // filter. Otherwise nullptr.
+ const ActiveScrolledRoot* mFilterASR;
+ nsCString mLinkSpec; // Destination of link currently being emitted, if any.
+
+ // Optimized versions for non-retained display list.
+ LayoutDeviceIntRegion mWindowDraggingRegion;
+ LayoutDeviceIntRegion mWindowNoDraggingRegion;
+ nsRegion mWindowOpaqueRegion;
+
+ nsClassHashtable<nsPtrHashKey<nsDisplayItem>,
+ nsTArray<nsIWidget::ThemeGeometry>>
+ mThemeGeometries;
+ DisplayListClipState mClipState;
+ nsTHashMap<nsPtrHashKey<const nsPresContext>, DocumentWillChangeBudget>
+ mDocumentWillChangeBudgets;
+
+ // Any frame listed in this set is already counted in the budget
+ // and thus is in-budget.
+ nsTHashMap<nsPtrHashKey<const nsIFrame>, FrameWillChangeBudget>
+ mFrameWillChangeBudgets;
+
+ nsTHashMap<nsPtrHashKey<dom::RemoteBrowser>, dom::EffectsInfo>
+ mEffectsUpdates;
+
+ nsTHashSet<nsCString> mDestinations; // Destination names emitted.
+
+ // Stores reusable items collected during display list preprocessing.
+ nsTHashSet<nsDisplayItem*> mReuseableItems;
+
+ // Tracked regions used for retained display list.
+ WeakFrameRegion mWindowExcludeGlassRegion;
+ WeakFrameRegion mRetainedWindowDraggingRegion;
+ WeakFrameRegion mRetainedWindowNoDraggingRegion;
+
+ // Window opaque region is calculated during layer building.
+ WeakFrameRegion mRetainedWindowOpaqueRegion;
+
+ std::unordered_set<const DisplayItemClipChain*, DisplayItemClipChainHasher,
+ DisplayItemClipChainEqualer>
+ mClipDeduplicator;
+ std::unordered_set<nsIScrollableFrame*> mScrollFramesToNotify;
+
+ AutoTArray<nsIFrame*, 20> mFramesWithOOFData;
+ AutoTArray<nsIFrame*, 40> mFramesMarkedForDisplayIfVisible;
+ AutoTArray<PresShellState, 8> mPresShellStates;
+
+ using Arena = nsPresArena<32768, DisplayListArenaObjectId,
+ size_t(DisplayListArenaObjectId::COUNT)>;
+ Arena mPool;
+
+ AutoTArray<nsIFrame*, 400> mFramesMarkedForDisplay;
+
+ gfx::CompositorHitTestInfo mCompositorHitTestInfo;
+
+ // The offset from mCurrentFrame to mCurrentReferenceFrame.
+ nsPoint mCurrentOffsetToReferenceFrame;
+
+ Maybe<float> mVisibleThreshold;
+
+ Maybe<nsPoint> mAdditionalOffset;
+
+ // Relative to mCurrentFrame.
+ nsRect mVisibleRect;
+ nsRect mDirtyRect;
+ nsRect mCaretRect;
+
+ Preserves3DContext mPreserves3DCtx;
+
+ uint8_t mBuildingExtraPagesForPageNum;
+
+ // If we've encountered a glass item yet, only used during partial display
+ // list builds.
+ bool mHasGlassItemDuringPartial;
+
+ nsDisplayListBuilderMode mMode;
+ static uint32_t sPaintSequenceNumber;
+
+ bool mContainsBlendMode;
+ bool mIsBuildingScrollbar;
+ bool mCurrentScrollbarWillHaveLayer;
+ bool mBuildCaret;
+ bool mRetainingDisplayList;
+ bool mPartialUpdate;
+ bool mIgnoreSuppression;
+ bool mIncludeAllOutOfFlows;
+ bool mDescendIntoSubdocuments;
+ bool mSelectedFramesOnly;
+ bool mAllowMergingAndFlattening;
+ // True when we're building a display list that's directly or indirectly
+ // under an nsDisplayTransform
+ bool mInTransform;
+ bool mInEventsOnly;
+ bool mInFilter;
+ bool mInPageSequence;
+ bool mIsInChromePresContext;
+ bool mSyncDecodeImages;
+ bool mIsPaintingToWindow;
+ bool mUseHighQualityScaling;
+ bool mIsPaintingForWebRender;
+ bool mIsCompositingCheap;
+ bool mAncestorHasApzAwareEventHandler;
+ // True when the first async-scrollable scroll frame for which we build a
+ // display list has a display port. An async-scrollable scroll frame is one
+ // which WantsAsyncScroll().
+ bool mHaveScrollableDisplayPort;
+ bool mWindowDraggingAllowed;
+ bool mIsBuildingForPopup;
+ bool mForceLayerForScrollParent;
+ bool mContainsNonMinimalDisplayPort;
+ bool mAsyncPanZoomEnabled;
+ bool mBuildingInvisibleItems;
+ bool mIsBuilding;
+ bool mInInvalidSubtree;
+ bool mBuildCompositorHitTestInfo;
+ bool mDisablePartialUpdates;
+ bool mPartialBuildFailed;
+ bool mIsInActiveDocShell;
+ bool mBuildAsyncZoomContainer;
+ bool mIsRelativeToLayoutViewport;
+ bool mUseOverlayScrollbars;
+ bool mAlwaysLayerizeScrollbars;
+
+ bool mIsReusingStackingContextItems;
+
+ Maybe<layers::ScrollDirection> mCurrentScrollbarDirection;
+};
+
+// All types are defined in nsDisplayItemTypes.h
+#define NS_DISPLAY_DECL_NAME(n, e) \
+ const char* Name() const override { return n; } \
+ constexpr static DisplayItemType ItemType() { return DisplayItemType::e; } \
+ \
+ private: \
+ void* operator new(size_t aSize, nsDisplayListBuilder* aBuilder) { \
+ return aBuilder->Allocate(aSize, DisplayItemType::e); \
+ } \
+ \
+ template <typename T, typename F, typename... Args> \
+ friend T* mozilla::MakeDisplayItemWithIndex( \
+ nsDisplayListBuilder* aBuilder, F* aFrame, const uint16_t aIndex, \
+ Args&&... aArgs); \
+ \
+ public:
+
+#define NS_DISPLAY_ALLOW_CLONING() \
+ template <typename T> \
+ friend T* mozilla::MakeClone(nsDisplayListBuilder* aBuilder, \
+ const T* aItem); \
+ \
+ nsDisplayWrapList* Clone(nsDisplayListBuilder* aBuilder) const override { \
+ return MakeClone(aBuilder, this); \
+ }
+
+template <typename T>
+MOZ_ALWAYS_INLINE T* MakeClone(nsDisplayListBuilder* aBuilder, const T* aItem) {
+ static_assert(std::is_base_of<nsDisplayWrapList, T>::value,
+ "Display item type should be derived from nsDisplayWrapList");
+ T* item = new (aBuilder) T(aBuilder, *aItem);
+ item->SetType(T::ItemType());
+ return item;
+}
+
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+void AssertUniqueItem(nsDisplayItem* aItem);
+#endif
+
+/**
+ * Returns true, if a display item of given |aType| needs to be built within
+ * opacity:0 container.
+ */
+bool ShouldBuildItemForEvents(const DisplayItemType aType);
+
+/**
+ * Initializes the hit test information of |aItem| if the item type supports it.
+ */
+void InitializeHitTestInfo(nsDisplayListBuilder* aBuilder,
+ nsPaintedDisplayItem* aItem,
+ const DisplayItemType aType);
+
+template <typename T, typename F, typename... Args>
+MOZ_ALWAYS_INLINE T* MakeDisplayItemWithIndex(nsDisplayListBuilder* aBuilder,
+ F* aFrame, const uint16_t aIndex,
+ Args&&... aArgs) {
+ static_assert(std::is_base_of<nsDisplayItem, T>::value,
+ "Display item type should be derived from nsDisplayItem");
+ static_assert(std::is_base_of<nsIFrame, F>::value,
+ "Frame type should be derived from nsIFrame");
+
+ const DisplayItemType type = T::ItemType();
+ if (aBuilder->InEventsOnly() && !ShouldBuildItemForEvents(type)) {
+ // This item is not needed for events.
+ return nullptr;
+ }
+
+ T* item = new (aBuilder) T(aBuilder, aFrame, std::forward<Args>(aArgs)...);
+
+ if (type != DisplayItemType::TYPE_GENERIC) {
+ item->SetType(type);
+ }
+
+ item->SetPerFrameIndex(aIndex);
+ item->SetExtraPageForPageNum(aBuilder->GetBuildingExtraPagesForPageNum());
+
+ nsPaintedDisplayItem* paintedItem = item->AsPaintedDisplayItem();
+ if (paintedItem) {
+ InitializeHitTestInfo(aBuilder, paintedItem, type);
+ }
+
+ if (aBuilder->InInvalidSubtree() ||
+ item->FrameForInvalidation()->IsFrameModified()) {
+ item->SetModifiedFrame(true);
+ }
+
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ if (aBuilder->IsRetainingDisplayList() && aBuilder->IsBuilding()) {
+ AssertUniqueItem(item);
+ }
+
+ // Verify that InInvalidSubtree matches invalidation frame's modified state.
+ if (aBuilder->InInvalidSubtree()) {
+ MOZ_DIAGNOSTIC_ASSERT(
+ AnyContentAncestorModified(item->FrameForInvalidation()));
+ }
+
+ DebugOnly<bool> isContainerType =
+ (GetDisplayItemFlagsForType(type) & TYPE_IS_CONTAINER);
+
+ MOZ_ASSERT(item->HasChildren() == isContainerType,
+ "Container items must have container display item flag set.");
+#endif
+
+ DL_LOGV("Created display item %p (%s) (frame: %p)", item, item->Name(),
+ aFrame);
+
+ return item;
+}
+
+template <typename T, typename F, typename... Args>
+MOZ_ALWAYS_INLINE T* MakeDisplayItem(nsDisplayListBuilder* aBuilder, F* aFrame,
+ Args&&... aArgs) {
+ return MakeDisplayItemWithIndex<T>(aBuilder, aFrame, 0,
+ std::forward<Args>(aArgs)...);
+}
+
+/*
+ * nsDisplayItemBase is a base-class for all display items. It is mainly
+ * responsible for handling the frame-display item 1:n relationship, as well as
+ * storing the state needed for display list merging.
+ *
+ * Display items are arena-allocated during display list construction.
+ *
+ * Display items can be containers --- i.e., they can perform hit testing
+ * and painting by recursively traversing a list of child items.
+ *
+ * Display items belong to a list at all times (except temporarily as they
+ * move from one list to another).
+ */
+class nsDisplayItem {
+ public:
+ using LayerManager = layers::LayerManager;
+ using WebRenderLayerManager = layers::WebRenderLayerManager;
+ using StackingContextHelper = layers::StackingContextHelper;
+ using ViewID = layers::ScrollableLayerGuid::ViewID;
+
+ /**
+ * Downcasts this item to nsPaintedDisplayItem, if possible.
+ */
+ virtual nsPaintedDisplayItem* AsPaintedDisplayItem() { return nullptr; }
+ virtual const nsPaintedDisplayItem* AsPaintedDisplayItem() const {
+ return nullptr;
+ }
+
+ /**
+ * Downcasts this item to nsDisplayWrapList, if possible.
+ */
+ virtual nsDisplayWrapList* AsDisplayWrapList() { return nullptr; }
+ virtual const nsDisplayWrapList* AsDisplayWrapList() const { return nullptr; }
+
+ /**
+ * Create a clone of this item.
+ */
+ virtual nsDisplayWrapList* Clone(nsDisplayListBuilder* aBuilder) const {
+ return nullptr;
+ }
+
+ /**
+ * Checks if the given display item can be merged with this item.
+ * @return true if the merging is possible, otherwise false.
+ */
+ virtual bool CanMerge(const nsDisplayItem* aItem) const { return false; }
+
+ /**
+ * Frees the memory allocated for this display item.
+ * The given display list builder must have allocated this display item.
+ */
+ virtual void Destroy(nsDisplayListBuilder* aBuilder) {
+ const DisplayItemType type = GetType();
+ DL_LOGV("Destroying display item %p (%s)", this, Name());
+
+ if (IsReusedItem()) {
+ aBuilder->RemoveReusedDisplayItem(this);
+ }
+
+ this->~nsDisplayItem();
+ aBuilder->Destroy(type, this);
+ }
+
+ /**
+ * Returns the frame that this display item was created for.
+ * Never returns null.
+ */
+ inline nsIFrame* Frame() const {
+ MOZ_ASSERT(mFrame, "Trying to use display item after frame deletion!");
+ return mFrame;
+ }
+
+ /**
+ * Called when the display item is prepared for deletion. The display item
+ * should not be used after calling this function.
+ */
+ virtual void RemoveFrame(nsIFrame* aFrame) {
+ MOZ_ASSERT(aFrame);
+
+ if (mFrame && aFrame == mFrame) {
+ mFrame = nullptr;
+ SetDeletedFrame();
+ }
+ }
+
+ /**
+ * A display item can depend on multiple different frames for invalidation.
+ */
+ virtual nsIFrame* GetDependentFrame() { return nullptr; }
+
+ /**
+ * Returns the frame that provides the style data, and should
+ * be checked when deciding if this display item can be reused.
+ */
+ virtual nsIFrame* FrameForInvalidation() const { return Frame(); }
+
+ /**
+ * Display items can override this to communicate that they won't
+ * contribute any visual information (for example fully transparent).
+ */
+ virtual bool IsInvisible() const { return false; }
+
+ /**
+ * Returns the printable name of this display item.
+ */
+ virtual const char* Name() const = 0;
+
+ /**
+ * Some consecutive items should be rendered together as a unit, e.g.,
+ * outlines for the same element. For this, we need a way for items to
+ * identify their type. We use the type for other purposes too.
+ */
+ DisplayItemType GetType() const {
+ MOZ_ASSERT(mType != DisplayItemType::TYPE_ZERO,
+ "Display item should have a valid type!");
+ return mType;
+ }
+
+ /**
+ * Pairing this with the Frame() pointer gives a key that
+ * uniquely identifies this display item in the display item tree.
+ */
+ uint32_t GetPerFrameKey() const {
+ // The top 8 bits are the page index
+ // The middle 16 bits of the per frame key uniquely identify the display
+ // item when there are more than one item of the same type for a frame.
+ // The low 8 bits are the display item type.
+ return (static_cast<uint32_t>(mExtraPageForPageNum)
+ << (TYPE_BITS + (sizeof(mPerFrameIndex) * 8))) |
+ (static_cast<uint32_t>(mPerFrameIndex) << TYPE_BITS) |
+ static_cast<uint32_t>(mType);
+ }
+
+ /**
+ * Returns true if this item was reused during display list merging.
+ */
+ bool IsReused() const { return mItemFlags.contains(ItemFlag::ReusedItem); }
+
+ void SetReused(bool aReused) { SetItemFlag(ItemFlag::ReusedItem, aReused); }
+
+ /**
+ * Returns true if this item can be reused during display list merging.
+ */
+ bool CanBeReused() const {
+ return !mItemFlags.contains(ItemFlag::CantBeReused);
+ }
+
+ void SetCantBeReused() { mItemFlags += ItemFlag::CantBeReused; }
+
+ bool CanBeCached() const {
+ return !mItemFlags.contains(ItemFlag::CantBeCached);
+ }
+
+ void SetCantBeCached() { mItemFlags += ItemFlag::CantBeCached; }
+
+ bool IsOldItem() const { return !!mOldList; }
+
+ /**
+ * Returns true if the frame of this display item is in a modified subtree.
+ */
+ bool HasModifiedFrame() const {
+ return mItemFlags.contains(ItemFlag::ModifiedFrame);
+ }
+
+ void SetModifiedFrame(bool aModified) {
+ SetItemFlag(ItemFlag::ModifiedFrame, aModified);
+ }
+
+ bool HasDeletedFrame() const;
+
+ /**
+ * Set the nsDisplayList that this item belongs to, and what index it is
+ * within that list.
+ * Temporary state for merging used by RetainedDisplayListBuilder.
+ */
+ void SetOldListIndex(nsDisplayList* aList, OldListIndex aIndex,
+ uint32_t aListKey, uint32_t aNestingDepth) {
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ mOldListKey = aListKey;
+ mOldNestingDepth = aNestingDepth;
+#endif
+ mOldList = reinterpret_cast<uintptr_t>(aList);
+ mOldListIndex = aIndex;
+ }
+
+ bool GetOldListIndex(nsDisplayList* aList, uint32_t aListKey,
+ OldListIndex* aOutIndex) {
+ if (mOldList != reinterpret_cast<uintptr_t>(aList)) {
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ MOZ_CRASH_UNSAFE_PRINTF(
+ "Item found was in the wrong list! type %d "
+ "(outer type was %d at depth %d, now is %d)",
+ GetPerFrameKey(), mOldListKey, mOldNestingDepth, aListKey);
+#endif
+ return false;
+ }
+ *aOutIndex = mOldListIndex;
+ return true;
+ }
+
+ /**
+ * Returns the display list containing the children of this display item.
+ * The children may be in a different coordinate system than this item.
+ */
+ virtual RetainedDisplayList* GetChildren() const { return nullptr; }
+ bool HasChildren() const { return GetChildren(); }
+
+ /**
+ * Display items with children may return true here. This causes the
+ * display list iterator to descend into the child display list.
+ */
+ virtual bool ShouldFlattenAway(nsDisplayListBuilder* aBuilder) {
+ return false;
+ }
+
+ virtual bool CreatesStackingContextHelper() { return false; }
+
+ /**
+ * Returns true if this item can be moved asynchronously on the compositor,
+ * see RetainedDisplayListBuilder.cpp comments.
+ */
+ virtual bool CanMoveAsync() { return false; }
+
+ protected:
+ // This is never instantiated directly (it has pure virtual methods), so no
+ // need to count constructors and destructors.
+ nsDisplayItem(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame);
+ nsDisplayItem(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ const ActiveScrolledRoot* aActiveScrolledRoot);
+
+ /**
+ * The custom copy-constructor is implemented to prevent copying the saved
+ * state of the item.
+ * This is currently only used when creating temporary items for merging.
+ */
+ nsDisplayItem(nsDisplayListBuilder* aBuilder, const nsDisplayItem& aOther)
+ : mFrame(aOther.mFrame),
+ mItemFlags(aOther.mItemFlags),
+ mType(aOther.mType),
+ mExtraPageForPageNum(aOther.mExtraPageForPageNum),
+ mPerFrameIndex(aOther.mPerFrameIndex),
+ mBuildingRect(aOther.mBuildingRect),
+ mToReferenceFrame(aOther.mToReferenceFrame),
+ mActiveScrolledRoot(aOther.mActiveScrolledRoot),
+ mClipChain(aOther.mClipChain) {
+ MOZ_COUNT_CTOR(nsDisplayItem);
+ // TODO: It might be better to remove the flags that aren't copied.
+ if (aOther.ForceNotVisible()) {
+ mItemFlags += ItemFlag::ForceNotVisible;
+ }
+ if (mFrame->In3DContextAndBackfaceIsHidden()) {
+ mItemFlags += ItemFlag::BackfaceHidden;
+ }
+ if (aOther.Combines3DTransformWithAncestors()) {
+ mItemFlags += ItemFlag::Combines3DTransformWithAncestors;
+ }
+ }
+
+ virtual ~nsDisplayItem() {
+ MOZ_COUNT_DTOR(nsDisplayItem);
+ if (mFrame) {
+ mFrame->RemoveDisplayItem(this);
+ }
+ }
+
+ void SetType(const DisplayItemType aType) { mType = aType; }
+
+ void SetPerFrameIndex(const uint16_t aIndex) { mPerFrameIndex = aIndex; }
+
+ // Display list building for printing can build duplicate
+ // container display items when they contain a mixture of
+ // OOF and normal content that is spread across multiple
+ // pages. We include the page number for the duplicates
+ // to make our GetPerFrameKey unique.
+ void SetExtraPageForPageNum(const uint8_t aPageNum) {
+ mExtraPageForPageNum = aPageNum;
+ }
+
+ void SetDeletedFrame();
+
+ public:
+ nsDisplayItem() = delete;
+ nsDisplayItem(const nsDisplayItem&) = delete;
+
+ /**
+ * Invalidate cached information that depends on this node's contents, after
+ * a mutation of those contents.
+ *
+ * Specifically, if you mutate an |nsDisplayItem| in a way that would change
+ * the WebRender display list items generated for it, you should call this
+ * method.
+ *
+ * If a |RestoreState| method exists to restore some piece of state, that's a
+ * good indication that modifications to said state should be accompanied by a
+ * call to this method. Opacity flattening's effects on
+ * |nsDisplayBackgroundColor| items are one example.
+ */
+ virtual void InvalidateItemCacheEntry() {}
+
+ struct HitTestState {
+ explicit HitTestState() = default;
+
+ ~HitTestState() {
+ NS_ASSERTION(mItemBuffer.Length() == 0,
+ "mItemBuffer should have been cleared");
+ }
+
+ // Handling transform items for preserve 3D frames.
+ bool mInPreserves3D = false;
+ // When hit-testing for visibility, we may hit an fully opaque item in a
+ // nested display list. We want to stop at that point, without looking
+ // further on other items.
+ bool mHitOccludingItem = false;
+
+ float mCurrentOpacity = 1.0f;
+
+ AutoTArray<nsDisplayItem*, 100> mItemBuffer;
+ };
+
+ uint8_t GetFlags() const { return GetDisplayItemFlagsForType(GetType()); }
+
+ virtual bool IsContentful() const { return GetFlags() & TYPE_IS_CONTENTFUL; }
+
+ /**
+ * This is called after we've constructed a display list for event handling.
+ * When this is called, we've already ensured that aRect intersects the
+ * item's bounds and that clipping has been taking into account.
+ *
+ * @param aRect the point or rect being tested, relative to the reference
+ * frame. If the width and height are both 1 app unit, it indicates we're
+ * hit testing a point, not a rect.
+ * @param aState must point to a HitTestState. If you don't have one,
+ * just create one with the default constructor and pass it in.
+ * @param aOutFrames each item appends the frame(s) in this display item that
+ * the rect is considered over (if any) to aOutFrames.
+ */
+ virtual void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
+ HitTestState* aState, nsTArray<nsIFrame*>* aOutFrames) {}
+
+ virtual nsIFrame* StyleFrame() const { return mFrame; }
+
+ /**
+ * Compute the used z-index of our frame; returns zero for elements to which
+ * z-index does not apply, and for z-index:auto.
+ * @note This can be overridden, @see nsDisplayWrapList::SetOverrideZIndex.
+ */
+ virtual int32_t ZIndex() const;
+ /**
+ * The default bounds is the frame border rect.
+ * @param aSnap *aSnap is set to true if the returned rect will be
+ * snapped to nearest device pixel edges during actual drawing.
+ * It might be set to false and snap anyway, so code computing the set of
+ * pixels affected by this display item needs to round outwards to pixel
+ * boundaries when *aSnap is set to false.
+ * This does not take the item's clipping into account.
+ * @return a rectangle relative to aBuilder->ReferenceFrame() that
+ * contains the area drawn by this display item
+ */
+ virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) const {
+ *aSnap = false;
+ return nsRect(ToReferenceFrame(), Frame()->GetSize());
+ }
+
+ /**
+ * Returns the untransformed bounds of this display item.
+ */
+ virtual nsRect GetUntransformedBounds(nsDisplayListBuilder* aBuilder,
+ bool* aSnap) const {
+ return GetBounds(aBuilder, aSnap);
+ }
+
+ virtual nsRegion GetTightBounds(nsDisplayListBuilder* aBuilder,
+ bool* aSnap) const {
+ *aSnap = false;
+ return nsRegion();
+ }
+
+ /**
+ * Returns true if nothing will be rendered inside aRect, false if uncertain.
+ * aRect is assumed to be contained in this item's bounds.
+ */
+ virtual bool IsInvisibleInRect(const nsRect& aRect) const { return false; }
+
+ /**
+ * Returns the result of GetBounds intersected with the item's clip.
+ * The intersection is approximate since rounded corners are not taking into
+ * account.
+ */
+ nsRect GetClippedBounds(nsDisplayListBuilder* aBuilder) const;
+
+ nsRect GetBorderRect() const {
+ return nsRect(ToReferenceFrame(), Frame()->GetSize());
+ }
+
+ nsRect GetPaddingRect() const {
+ return Frame()->GetPaddingRectRelativeToSelf() + ToReferenceFrame();
+ }
+
+ nsRect GetContentRect() const {
+ return Frame()->GetContentRectRelativeToSelf() + ToReferenceFrame();
+ }
+
+ /**
+ * Checks if the frame(s) owning this display item have been marked as
+ * invalid, and needing repainting.
+ */
+ virtual bool IsInvalid(nsRect& aRect) const {
+ bool result = mFrame ? mFrame->IsInvalid(aRect) : false;
+ aRect += ToReferenceFrame();
+ return result;
+ }
+
+ /**
+ * Creates and initializes an nsDisplayItemGeometry object that retains the
+ * current areas covered by this display item. These need to retain enough
+ * information such that they can be compared against a future nsDisplayItem
+ * of the same type, and determine if repainting needs to happen.
+ *
+ * Subclasses wishing to store more information need to override both this
+ * and ComputeInvalidationRegion, as well as implementing an
+ * nsDisplayItemGeometry subclass.
+ *
+ * The default implementation tracks both the display item bounds, and the
+ * frame's border rect.
+ */
+ virtual nsDisplayItemGeometry* AllocateGeometry(
+ nsDisplayListBuilder* aBuilder) {
+ return new nsDisplayItemGenericGeometry(this, aBuilder);
+ }
+
+ /**
+ * Compares an nsDisplayItemGeometry object from a previous paint against the
+ * current item. Computes if the geometry of the item has changed, and the
+ * invalidation area required for correct repainting.
+ *
+ * The existing geometry will have been created from a display item with a
+ * matching GetPerFrameKey()/mFrame pair to the current item.
+ *
+ * The default implementation compares the display item bounds, and the
+ * frame's border rect, and invalidates the entire bounds if either rect
+ * changes.
+ *
+ * @param aGeometry The geometry of the matching display item from the
+ * previous paint.
+ * @param aInvalidRegion Output param, the region to invalidate, or
+ * unchanged if none.
+ */
+ virtual void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
+ const nsDisplayItemGeometry* aGeometry,
+ nsRegion* aInvalidRegion) const {
+ const nsDisplayItemGenericGeometry* geometry =
+ static_cast<const nsDisplayItemGenericGeometry*>(aGeometry);
+ bool snap;
+ if (!geometry->mBounds.IsEqualInterior(GetBounds(aBuilder, &snap)) ||
+ !geometry->mBorderRect.IsEqualInterior(GetBorderRect())) {
+ aInvalidRegion->Or(GetBounds(aBuilder, &snap), geometry->mBounds);
+ }
+ }
+
+ /**
+ * An alternative default implementation of ComputeInvalidationRegion,
+ * that instead invalidates only the changed area between the two items.
+ */
+ void ComputeInvalidationRegionDifference(
+ nsDisplayListBuilder* aBuilder,
+ const nsDisplayItemBoundsGeometry* aGeometry,
+ nsRegion* aInvalidRegion) const {
+ bool snap;
+ nsRect bounds = GetBounds(aBuilder, &snap);
+
+ if (!aGeometry->mBounds.IsEqualInterior(bounds)) {
+ nscoord radii[8];
+ if (aGeometry->mHasRoundedCorners || Frame()->GetBorderRadii(radii)) {
+ aInvalidRegion->Or(aGeometry->mBounds, bounds);
+ } else {
+ aInvalidRegion->Xor(aGeometry->mBounds, bounds);
+ }
+ }
+ }
+
+ /**
+ * This function is called when an item's list of children has been modified
+ * by RetainedDisplayListBuilder.
+ */
+ virtual void InvalidateCachedChildInfo(nsDisplayListBuilder* aBuilder) {}
+
+ virtual void AddSizeOfExcludingThis(nsWindowSizes&) const {}
+
+ /**
+ * @param aSnap set to true if the edges of the rectangles of the opaque
+ * region would be snapped to device pixels when drawing
+ * @return a region of the item that is opaque --- that is, every pixel
+ * that is visible is painted with an opaque
+ * color. This is useful for determining when one piece
+ * of content completely obscures another so that we can do occlusion
+ * culling.
+ * This does not take clipping into account.
+ * This must return a simple region (1 rect) for painting display lists.
+ * It is only allowed to be a complex region for hit testing.
+ */
+ virtual nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
+ bool* aSnap) const {
+ *aSnap = false;
+ return nsRegion();
+ }
+ /**
+ * @return Some(nscolor) if the item is guaranteed to paint every pixel in its
+ * bounds with the same (possibly translucent) color
+ */
+ virtual Maybe<nscolor> IsUniform(nsDisplayListBuilder* aBuilder) const {
+ return Nothing();
+ }
+
+ /**
+ * @return true if the contents of this item are rendered fixed relative
+ * to the nearest viewport.
+ */
+ virtual bool ShouldFixToViewport(nsDisplayListBuilder* aBuilder) const {
+ return false;
+ }
+
+ /**
+ * Returns true if all layers that can be active should be forced to be
+ * active. Requires setting the pref layers.force-active=true.
+ */
+ static bool ForceActiveLayers();
+
+#ifdef MOZ_DUMP_PAINTING
+ /**
+ * Mark this display item as being painted via
+ * FrameLayerBuilder::DrawPaintedLayer.
+ */
+ bool Painted() const { return mItemFlags.contains(ItemFlag::Painted); }
+
+ /**
+ * Check if this display item has been painted.
+ */
+ void SetPainted() { mItemFlags += ItemFlag::Painted; }
+#endif
+
+ void SetIsGlassItem() { mItemFlags += ItemFlag::IsGlassItem; }
+ bool IsGlassItem() { return mItemFlags.contains(ItemFlag::IsGlassItem); }
+
+ /**
+ * Function to create the WebRenderCommands.
+ * We should check if the layer state is
+ * active first and have an early return if the layer state is
+ * not active.
+ *
+ * @return true if successfully creating webrender commands.
+ */
+ virtual bool CreateWebRenderCommands(
+ wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) {
+ return false;
+ }
+
+ /**
+ * Updates the provided aLayerData with any APZ-relevant scroll data
+ * that is specific to this display item. This is stuff that would normally
+ * be put on the layer during BuildLayer, but this is only called in
+ * layers-free webrender mode, where we don't have layers.
+ *
+ * This function returns true if and only if it has APZ-relevant scroll data
+ * to provide. Note that the arguments passed in may be nullptr, in which case
+ * the function should still return true if and only if it has APZ-relevant
+ * scroll data, but obviously in this case it can't actually put the
+ * data onto aLayerData, because there isn't one.
+ *
+ * This function assumes that aData and aLayerData will either both be null,
+ * or will both be non-null. The caller is responsible for enforcing this.
+ */
+ virtual bool UpdateScrollData(layers::WebRenderScrollData* aData,
+ layers::WebRenderLayerScrollData* aLayerData) {
+ return false;
+ }
+
+ /**
+ * Returns true if this item needs to have its geometry updated, despite
+ * returning empty invalidation region.
+ */
+ virtual bool NeedsGeometryUpdates() const { return false; }
+
+ /**
+ * Some items such as those calling into the native themed widget machinery
+ * have to be painted on the content process. In this case it is best to avoid
+ * allocating layers that serializes and forwards the work to the compositor.
+ */
+ virtual bool MustPaintOnContentSide() const { return false; }
+
+ /**
+ * If this has a child list where the children are in the same coordinate
+ * system as this item (i.e., they have the same reference frame),
+ * return the list.
+ */
+ virtual RetainedDisplayList* GetSameCoordinateSystemChildren() const {
+ return nullptr;
+ }
+
+ virtual void UpdateBounds(nsDisplayListBuilder* aBuilder) {}
+ /**
+ * Do UpdateBounds() for items with frames establishing or extending
+ * 3D rendering context.
+ *
+ * This function is called by UpdateBoundsFor3D() of
+ * nsDisplayTransform(), and it is called by
+ * BuildDisplayListForStackingContext() on transform items
+ * establishing 3D rendering context.
+ *
+ * The bounds of a transform item with the frame establishing 3D
+ * rendering context should be computed by calling
+ * DoUpdateBoundsPreserves3D() on all descendants that participate
+ * the same 3d rendering context.
+ */
+ virtual void DoUpdateBoundsPreserves3D(nsDisplayListBuilder* aBuilder) {}
+
+ /**
+ * Returns the building rectangle used by nsDisplayListBuilder when
+ * this item was constructed.
+ */
+ const nsRect& GetBuildingRect() const { return mBuildingRect; }
+
+ void SetBuildingRect(const nsRect& aBuildingRect) {
+ mBuildingRect = aBuildingRect;
+ }
+
+ /**
+ * Returns the building rect for the children, relative to their
+ * reference frame. Can be different from mBuildingRect for
+ * nsDisplayTransform, since the reference frame for the children is different
+ * from the reference frame for the item itself.
+ */
+ virtual const nsRect& GetBuildingRectForChildren() const {
+ return mBuildingRect;
+ }
+
+ virtual void WriteDebugInfo(std::stringstream& aStream) {}
+
+ /**
+ * Returns the result of aBuilder->ToReferenceFrame(GetUnderlyingFrame())
+ */
+ const nsPoint& ToReferenceFrame() const {
+ NS_ASSERTION(mFrame, "No frame?");
+ return mToReferenceFrame;
+ }
+
+ /**
+ * Returns the reference frame for display item children of this item.
+ */
+ virtual const nsIFrame* ReferenceFrameForChildren() const { return nullptr; }
+
+ /**
+ * Checks if this display item (or any children) contains content that might
+ * be rendered with component alpha (e.g. subpixel antialiasing). Returns the
+ * bounds of the area that needs component alpha, or an empty rect if nothing
+ * in the item does.
+ */
+ virtual nsRect GetComponentAlphaBounds(nsDisplayListBuilder* aBuilder) const {
+ return nsRect();
+ }
+
+ /**
+ * Check if we can add async animations to the layer for this display item.
+ */
+ virtual bool CanUseAsyncAnimations(nsDisplayListBuilder* aBuilder) {
+ return false;
+ }
+
+ virtual bool SupportsOptimizingToImage() const { return false; }
+
+ virtual const DisplayItemClip& GetClip() const;
+ void IntersectClip(nsDisplayListBuilder* aBuilder,
+ const DisplayItemClipChain* aOther, bool aStore);
+
+ virtual void SetActiveScrolledRoot(
+ const ActiveScrolledRoot* aActiveScrolledRoot) {
+ mActiveScrolledRoot = aActiveScrolledRoot;
+ }
+ const ActiveScrolledRoot* GetActiveScrolledRoot() const {
+ return mActiveScrolledRoot;
+ }
+
+ virtual void SetClipChain(const DisplayItemClipChain* aClipChain,
+ bool aStore);
+ const DisplayItemClipChain* GetClipChain() const { return mClipChain; }
+
+ bool BackfaceIsHidden() const {
+ return mItemFlags.contains(ItemFlag::BackfaceHidden);
+ }
+
+ bool Combines3DTransformWithAncestors() const {
+ return mItemFlags.contains(ItemFlag::Combines3DTransformWithAncestors);
+ }
+
+ bool ForceNotVisible() const {
+ return mItemFlags.contains(ItemFlag::ForceNotVisible);
+ }
+
+ bool In3DContextAndBackfaceIsHidden() const {
+ return mItemFlags.contains(ItemFlag::BackfaceHidden) &&
+ mItemFlags.contains(ItemFlag::Combines3DTransformWithAncestors);
+ }
+
+ bool HasDifferentFrame(const nsDisplayItem* aOther) const {
+ return mFrame != aOther->mFrame;
+ }
+
+ bool HasHitTestInfo() const {
+ return mItemFlags.contains(ItemFlag::HasHitTestInfo);
+ }
+
+ bool HasSameTypeAndClip(const nsDisplayItem* aOther) const {
+ return GetPerFrameKey() == aOther->GetPerFrameKey() &&
+ GetClipChain() == aOther->GetClipChain();
+ }
+
+ bool HasSameContent(const nsDisplayItem* aOther) const {
+ return mFrame->GetContent() == aOther->Frame()->GetContent();
+ }
+
+ virtual void NotifyUsed(nsDisplayListBuilder* aBuilder) {}
+
+ virtual Maybe<nsRect> GetClipWithRespectToASR(
+ nsDisplayListBuilder* aBuilder, const ActiveScrolledRoot* aASR) const;
+
+ virtual const nsRect& GetUntransformedPaintRect() const {
+ return GetBuildingRect();
+ }
+
+ nsRect GetPaintRect(nsDisplayListBuilder* aBuilder, gfxContext* aCtx);
+
+ virtual const HitTestInfo& GetHitTestInfo() { return HitTestInfo::Empty(); }
+
+ enum class ReuseState : uint8_t {
+ None,
+ // Set during display list building.
+ Reusable,
+ // Set during display list preprocessing.
+ PreProcessed,
+ // Set during partial display list build.
+ Reused,
+ };
+
+ void SetReusable() {
+ MOZ_ASSERT(mReuseState == ReuseState::None ||
+ mReuseState == ReuseState::Reused);
+ mReuseState = ReuseState::Reusable;
+ }
+
+ bool IsReusable() const { return mReuseState == ReuseState::Reusable; }
+
+ void SetPreProcessed() {
+ MOZ_ASSERT(mReuseState == ReuseState::Reusable);
+ mReuseState = ReuseState::PreProcessed;
+ }
+
+ bool IsPreProcessed() const {
+ return mReuseState == ReuseState::PreProcessed;
+ }
+
+ void SetReusedItem() {
+ MOZ_ASSERT(mReuseState == ReuseState::PreProcessed);
+ mReuseState = ReuseState::Reused;
+ }
+
+ bool IsReusedItem() const { return mReuseState == ReuseState::Reused; }
+
+ void ResetReuseState() { mReuseState = ReuseState::None; }
+
+ ReuseState GetReuseState() const { return mReuseState; }
+
+ nsIFrame* mFrame; // 8
+
+ private:
+ enum class ItemFlag : uint16_t {
+ CantBeReused,
+ CantBeCached,
+ DeletedFrame,
+ ModifiedFrame,
+ ReusedItem,
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ MergedItem,
+ PreProcessedItem,
+#endif
+ BackfaceHidden,
+ Combines3DTransformWithAncestors,
+ ForceNotVisible,
+ HasHitTestInfo,
+ IsGlassItem,
+#ifdef MOZ_DUMP_PAINTING
+ // True if this frame has been painted.
+ Painted,
+#endif
+ };
+
+ EnumSet<ItemFlag, uint16_t> mItemFlags; // 2
+ DisplayItemType mType = DisplayItemType::TYPE_ZERO; // 1
+ uint8_t mExtraPageForPageNum = 0; // 1
+ uint16_t mPerFrameIndex = 0; // 2
+ ReuseState mReuseState = ReuseState::None;
+ OldListIndex mOldListIndex; // 4
+ uintptr_t mOldList = 0; // 8
+
+ // This is the rectangle that nsDisplayListBuilder was using as the visible
+ // rect to decide which items to construct.
+ nsRect mBuildingRect;
+
+ protected:
+ void SetItemFlag(ItemFlag aFlag, const bool aValue) {
+ if (aValue) {
+ mItemFlags += aFlag;
+ } else {
+ mItemFlags -= aFlag;
+ }
+ }
+
+ void SetHasHitTestInfo() { mItemFlags += ItemFlag::HasHitTestInfo; }
+
+ // Result of ToReferenceFrame(mFrame), if mFrame is non-null
+ nsPoint mToReferenceFrame;
+
+ RefPtr<const ActiveScrolledRoot> mActiveScrolledRoot;
+ RefPtr<const DisplayItemClipChain> mClipChain;
+
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ public:
+ bool IsMergedItem() const {
+ return mItemFlags.contains(ItemFlag::MergedItem);
+ }
+
+ bool IsPreProcessedItem() const {
+ return mItemFlags.contains(ItemFlag::PreProcessedItem);
+ }
+
+ void SetMergedPreProcessed(bool aMerged, bool aPreProcessed) {
+ SetItemFlag(ItemFlag::MergedItem, aMerged);
+ SetItemFlag(ItemFlag::PreProcessedItem, aPreProcessed);
+ }
+
+ uint32_t mOldListKey = 0;
+ uint32_t mOldNestingDepth = 0;
+#endif
+};
+
+class nsPaintedDisplayItem : public nsDisplayItem {
+ public:
+ nsPaintedDisplayItem* AsPaintedDisplayItem() final { return this; }
+ const nsPaintedDisplayItem* AsPaintedDisplayItem() const final {
+ return this;
+ }
+
+ /**
+ * Returns true if this display item would return true from ApplyOpacity
+ * without actually applying the opacity. Otherwise returns false.
+ */
+ virtual bool CanApplyOpacity(WebRenderLayerManager* aManager,
+ nsDisplayListBuilder* aBuilder) const {
+ return false;
+ }
+
+ /**
+ * Returns true if this item supports PaintWithClip, where the clipping
+ * is used directly as the primitive geometry instead of needing an explicit
+ * clip.
+ */
+ virtual bool CanPaintWithClip(const DisplayItemClip& aClip) { return false; }
+
+ /**
+ * Same as |Paint()|, except provides a clip to use the geometry to draw with.
+ * Must not be called unless |CanPaintWithClip()| returned true.
+ */
+ virtual void PaintWithClip(nsDisplayListBuilder* aBuilder, gfxContext* aCtx,
+ const DisplayItemClip& aClip) {
+ MOZ_ASSERT_UNREACHABLE("PaintWithClip() is not implemented!");
+ }
+
+ /**
+ * Paint this item to some rendering context.
+ */
+ virtual void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) = 0;
+
+ /**
+ * External storage used by |DisplayItemCache| to avoid hashmap lookups.
+ * If an item is reused and has the cache index set, it means that
+ * |DisplayItemCache| has assigned a cache slot for the item.
+ */
+ Maybe<uint16_t>& CacheIndex() { return mCacheIndex; }
+
+ void InvalidateItemCacheEntry() override {
+ // |nsPaintedDisplayItem|s may have |DisplayItemCache| entries
+ // that no longer match after a mutation. The cache will notice
+ // on its own that the entry is no longer in use, and free it.
+ mCacheIndex = Nothing();
+ }
+
+ const HitTestInfo& GetHitTestInfo() final { return mHitTestInfo; }
+ void InitializeHitTestInfo(nsDisplayListBuilder* aBuilder) {
+ mHitTestInfo.Initialize(aBuilder, Frame());
+ SetHasHitTestInfo();
+ }
+
+ protected:
+ nsPaintedDisplayItem(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame)
+ : nsPaintedDisplayItem(aBuilder, aFrame,
+ aBuilder->CurrentActiveScrolledRoot()) {}
+
+ nsPaintedDisplayItem(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ const ActiveScrolledRoot* aActiveScrolledRoot)
+ : nsDisplayItem(aBuilder, aFrame, aActiveScrolledRoot) {}
+
+ nsPaintedDisplayItem(nsDisplayListBuilder* aBuilder,
+ const nsPaintedDisplayItem& aOther)
+ : nsDisplayItem(aBuilder, aOther), mHitTestInfo(aOther.mHitTestInfo) {}
+
+ protected:
+ HitTestInfo mHitTestInfo;
+ Maybe<uint16_t> mCacheIndex;
+};
+
+template <typename T>
+struct MOZ_HEAP_CLASS LinkedListNode {
+ explicit LinkedListNode(T aValue) : mNext(nullptr), mValue(aValue) {}
+ LinkedListNode* mNext;
+ T mValue;
+};
+
+template <typename T>
+struct LinkedListIterator {
+ using iterator_category = std::forward_iterator_tag;
+ using difference_type = std::ptrdiff_t;
+ using value_type = T;
+ using pointer = T*;
+ using reference = T&;
+ using Node = LinkedListNode<T>;
+
+ explicit LinkedListIterator(Node* aNode = nullptr) : mNode(aNode) {}
+
+ bool HasNext() const { return mNode != nullptr; }
+
+ LinkedListIterator<T>& operator++() {
+ MOZ_ASSERT(mNode);
+ mNode = mNode->mNext;
+ return *this;
+ }
+
+ bool operator==(const LinkedListIterator<T>& aOther) const {
+ return mNode == aOther.mNode;
+ }
+
+ bool operator!=(const LinkedListIterator<T>& aOther) const {
+ return mNode != aOther.mNode;
+ }
+
+ const T operator*() const {
+ MOZ_ASSERT(mNode);
+ return mNode->mValue;
+ }
+
+ T operator*() {
+ MOZ_ASSERT(mNode);
+ return mNode->mValue;
+ }
+
+ Node* mNode;
+};
+
+/**
+ * Manages a singly-linked list of display list items.
+ *
+ * Stepping upward through this list is very fast. Stepping downward is very
+ * slow so we don't support it. The methods that need to step downward
+ * (HitTest()) internally build a temporary array of all
+ * the items while they do the downward traversal, so overall they're still
+ * linear time. We have optimized for efficient AppendToTop() of both
+ * items and lists, with minimal codesize.
+ *
+ * Internal linked list nodes are allocated using arena allocator.
+ * */
+class nsDisplayList {
+ public:
+ using Node = LinkedListNode<nsDisplayItem*>;
+ using iterator = LinkedListIterator<nsDisplayItem*>;
+ using const_iterator = iterator;
+
+ iterator begin() { return iterator(mBottom); }
+ iterator end() { return iterator(nullptr); }
+ const_iterator begin() const { return iterator(mBottom); }
+ const_iterator end() const { return iterator(nullptr); }
+
+ explicit nsDisplayList(nsDisplayListBuilder* aBuilder) : mBuilder(aBuilder) {}
+
+ nsDisplayList() = delete;
+ nsDisplayList(const nsDisplayList&) = delete;
+ nsDisplayList& operator=(const nsDisplayList&) = delete;
+
+ virtual ~nsDisplayList() {
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ if (!mAllowNonEmptyDestruction) {
+ MOZ_RELEASE_ASSERT(IsEmpty(), "Nonempty list left over?");
+ }
+#endif
+
+ DeallocateNodes();
+ }
+
+ nsDisplayList(nsDisplayList&& aOther)
+ : mBottom(aOther.mBottom),
+ mTop(aOther.mTop),
+ mLength(aOther.mLength),
+ mBuilder(aOther.mBuilder) {
+ aOther.SetEmpty();
+ }
+
+ nsDisplayList& operator=(nsDisplayList&& aOther) {
+ MOZ_RELEASE_ASSERT(mBuilder == aOther.mBuilder);
+
+ if (this != &aOther) {
+ MOZ_RELEASE_ASSERT(IsEmpty());
+ mBottom = std::move(aOther.mBottom);
+ mTop = std::move(aOther.mTop);
+ mLength = std::move(aOther.mLength);
+ aOther.SetEmpty();
+ }
+ return *this;
+ }
+
+ /**
+ * Append an item to the top of the list.
+ **/
+ void AppendToTop(nsDisplayItem* aItem) {
+ if (!aItem) {
+ return;
+ }
+
+ auto* next = Allocate(aItem);
+ MOZ_ASSERT(next);
+
+ if (IsEmpty()) {
+ mBottom = next;
+ mTop = next;
+ } else {
+ mTop->mNext = next;
+ mTop = next;
+ }
+
+ mLength++;
+
+ MOZ_ASSERT(mBottom && mTop);
+ MOZ_ASSERT(mTop->mNext == nullptr);
+ }
+
+ template <typename T, typename F, typename... Args>
+ void AppendNewToTop(nsDisplayListBuilder* aBuilder, F* aFrame,
+ Args&&... aArgs) {
+ AppendNewToTopWithIndex<T>(aBuilder, aFrame, 0,
+ std::forward<Args>(aArgs)...);
+ }
+
+ template <typename T, typename F, typename... Args>
+ void AppendNewToTopWithIndex(nsDisplayListBuilder* aBuilder, F* aFrame,
+ const uint16_t aIndex, Args&&... aArgs) {
+ nsDisplayItem* item = MakeDisplayItemWithIndex<T>(
+ aBuilder, aFrame, aIndex, std::forward<Args>(aArgs)...);
+ AppendToTop(item);
+ }
+
+ /**
+ * Removes all items from aList and appends them to the top of this list.
+ */
+ void AppendToTop(nsDisplayList* aList) {
+ MOZ_ASSERT(aList != this);
+ MOZ_RELEASE_ASSERT(mBuilder == aList->mBuilder);
+
+ if (aList->IsEmpty()) {
+ return;
+ }
+
+ if (IsEmpty()) {
+ std::swap(mBottom, aList->mBottom);
+ std::swap(mTop, aList->mTop);
+ std::swap(mLength, aList->mLength);
+ } else {
+ MOZ_ASSERT(mTop && mTop->mNext == nullptr);
+ mTop->mNext = aList->mBottom;
+ mTop = aList->mTop;
+ mLength += aList->mLength;
+
+ aList->SetEmpty();
+ }
+ }
+
+ /**
+ * Clears the display list.
+ */
+ void Clear() {
+ DeallocateNodes();
+ SetEmpty();
+ }
+
+ /**
+ * Creates a shallow copy of this display list to |aDestination|.
+ */
+ void CopyTo(nsDisplayList* aDestination) const {
+ for (auto* item : *this) {
+ aDestination->AppendToTop(item);
+ }
+ }
+
+ /**
+ * Calls the function |aFn| for each display item in the display list.
+ */
+ void ForEach(const std::function<void(nsDisplayItem*)>& aFn) {
+ for (auto* item : *this) {
+ aFn(item);
+ }
+ }
+ /**
+ * Remove all items from the list and call their destructors.
+ */
+ virtual void DeleteAll(nsDisplayListBuilder* aBuilder);
+
+ /**
+ * @return the item at the bottom of the list, or null if the list is empty
+ */
+ nsDisplayItem* GetBottom() const {
+ return mBottom ? mBottom->mValue : nullptr;
+ }
+
+ /**
+ * @return the item at the top of the list, or null if the list is empty
+ */
+ nsDisplayItem* GetTop() const { return mTop ? mTop->mValue : nullptr; }
+
+ bool IsEmpty() const { return mBottom == nullptr; }
+
+ /**
+ * @return the number of items in the list
+ */
+ size_t Length() const { return mLength; }
+
+ /**
+ * Stable sort the list by the z-order of Frame() on
+ * each item. 'auto' is counted as zero.
+ * It is assumed that the list is already in content document order.
+ */
+ void SortByZOrder();
+
+ /**
+ * Stable sort the list by the tree order of the content of
+ * Frame() on each item. z-index is ignored.
+ * @param aCommonAncestor a common ancestor of all the content elements
+ * associated with the display items, for speeding up tree order
+ * checks, or nullptr if not known; it's only a hint, if it is not an
+ * ancestor of some elements, then we lose performance but not correctness
+ */
+ void SortByContentOrder(nsIContent* aCommonAncestor);
+
+ /**
+ * Sort the display list using a stable sort.
+ * aComparator(Item item1, Item item2) should return true if item1 should go
+ * before item2.
+ * We sort the items into increasing order.
+ */
+ template <typename Item, typename Comparator>
+ void Sort(const Comparator& aComparator) {
+ if (Length() < 2) {
+ // Only sort lists with more than one item.
+ return;
+ }
+
+ // Some casual local browsing testing suggests that a local preallocated
+ // array of 20 items should be able to avoid a lot of dynamic allocations
+ // here.
+ AutoTArray<Item, 20> items;
+
+ for (nsDisplayItem* item : TakeItems()) {
+ items.AppendElement(Item(item));
+ }
+
+ std::stable_sort(items.begin(), items.end(), aComparator);
+
+ for (Item& item : items) {
+ AppendToTop(item);
+ }
+ }
+
+ nsDisplayList TakeItems() {
+ nsDisplayList list = std::move(*this);
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ list.mAllowNonEmptyDestruction = true;
+#endif
+ return list;
+ }
+
+ nsDisplayItem* RemoveBottom() {
+ if (!mBottom) {
+ return nullptr;
+ }
+
+ nsDisplayItem* bottom = mBottom->mValue;
+
+ auto next = mBottom->mNext;
+ Deallocate(mBottom);
+ mBottom = next;
+
+ if (!mBottom) {
+ // No bottom item means no items at all.
+ mTop = nullptr;
+ }
+
+ MOZ_ASSERT(mLength > 0);
+ mLength--;
+
+ return bottom;
+ }
+
+ /**
+ * Paint the list to the rendering context. We assume that (0,0) in aCtx
+ * corresponds to the origin of the reference frame. For best results,
+ * aCtx's current transform should make (0,0) pixel-aligned. The
+ * rectangle in aDirtyRect is painted, which *must* be contained in the
+ * dirty rect used to construct the display list.
+ *
+ * If aFlags contains PAINT_USE_WIDGET_LAYERS and
+ * ShouldUseWidgetLayerManager() is set, then we will paint using
+ * the reference frame's widget's layer manager (and ctx may be null),
+ * otherwise we will use a temporary BasicLayerManager and ctx must
+ * not be null.
+ *
+ * If PAINT_EXISTING_TRANSACTION is set, the reference frame's widget's
+ * layer manager has already had BeginTransaction() called on it and
+ * we should not call it again.
+ *
+ * This must only be called on the root display list of the display list
+ * tree.
+ *
+ * We return the layer manager used for painting --- mainly so that
+ * callers can dump its layer tree if necessary.
+ */
+ enum {
+ PAINT_DEFAULT = 0,
+ PAINT_USE_WIDGET_LAYERS = 0x01,
+ PAINT_EXISTING_TRANSACTION = 0x04,
+ PAINT_IDENTICAL_DISPLAY_LIST = 0x08
+ };
+ void PaintRoot(nsDisplayListBuilder* aBuilder, gfxContext* aCtx,
+ uint32_t aFlags, Maybe<double> aDisplayListBuildTime);
+
+ void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx,
+ int32_t aAppUnitsPerDevPixel);
+
+ /**
+ * Get the bounds. Takes the union of the bounds of all children.
+ * The result is not cached.
+ */
+ nsRect GetClippedBounds(nsDisplayListBuilder* aBuilder) const;
+
+ /**
+ * Get this list's bounds, respecting clips relative to aASR. The result is
+ * the union of each item's clipped bounds with respect to aASR. That means
+ * that if an item can move asynchronously with an ASR that is a descendant
+ * of aASR, then the clipped bounds with respect to aASR will be the clip of
+ * that item for aASR, because the item can move anywhere inside that clip.
+ * If there is an item in this list which is not bounded with respect to
+ * aASR (i.e. which does not have "finite bounds" with respect to aASR),
+ * then this method trigger an assertion failure.
+ * The optional aBuildingRect out argument can be set to non-null if the
+ * caller is also interested to know the building rect. This can be used
+ * to get the visible rect efficiently without traversing the display list
+ * twice.
+ */
+ nsRect GetClippedBoundsWithRespectToASR(
+ nsDisplayListBuilder* aBuilder, const ActiveScrolledRoot* aASR,
+ nsRect* aBuildingRect = nullptr) const;
+
+ /**
+ * Returns the opaque region of this display list.
+ */
+ nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder) {
+ nsRegion result;
+ bool snap;
+ for (nsDisplayItem* item : *this) {
+ result.OrWith(item->GetOpaqueRegion(aBuilder, &snap));
+ }
+ return result;
+ }
+
+ /**
+ * Returns the bounds of the area that needs component alpha.
+ */
+ nsRect GetComponentAlphaBounds(nsDisplayListBuilder* aBuilder) const {
+ nsRect bounds;
+ for (nsDisplayItem* item : *this) {
+ bounds.UnionRect(bounds, item->GetComponentAlphaBounds(aBuilder));
+ }
+ return bounds;
+ }
+
+ /**
+ * Find the topmost display item that returns a non-null frame, and return
+ * the frame.
+ */
+ void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
+ nsDisplayItem::HitTestState* aState,
+ nsTArray<nsIFrame*>* aOutFrames) const;
+ /**
+ * Compute the union of the visible rects of the items in the list. The
+ * result is not cached.
+ */
+ nsRect GetBuildingRect() const;
+
+ private:
+ inline Node* Allocate(nsDisplayItem* aItem) {
+ void* ptr =
+ mBuilder->Allocate(sizeof(Node), DisplayListArenaObjectId::LISTNODE);
+ return new (ptr) Node(aItem);
+ }
+
+ inline void Deallocate(Node* aNode) {
+ aNode->~Node();
+ mBuilder->Destroy(DisplayListArenaObjectId::LISTNODE, aNode);
+ }
+
+ void DeallocateNodes() {
+ Node* current = mBottom;
+ Node* next = nullptr;
+
+ while (current) {
+ next = current->mNext;
+ Deallocate(current);
+ current = next;
+ }
+ }
+
+ inline void SetEmpty() {
+ mBottom = nullptr;
+ mTop = nullptr;
+ mLength = 0;
+ }
+
+ Node* mBottom = nullptr;
+ Node* mTop = nullptr;
+ size_t mLength = 0;
+ nsDisplayListBuilder* mBuilder = nullptr;
+
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ // This checks that the invariant of display lists owning their items is held.
+ bool mAllowNonEmptyDestruction = false;
+#endif
+};
+
+/**
+ * This is passed as a parameter to nsIFrame::BuildDisplayList. That method
+ * will put any generated items onto the appropriate list given here. It's
+ * basically just a collection with one list for each separate stacking layer.
+ * The lists themselves are external to this object and thus can be shared
+ * with others. Some of the list pointers may even refer to the same list.
+ */
+class nsDisplayListSet {
+ public:
+ /**
+ * @return a list where one should place the border and/or background for
+ * this frame (everything from steps 1 and 2 of CSS 2.1 appendix E)
+ */
+ nsDisplayList* BorderBackground() const { return mLists[0]; }
+ /**
+ * @return a list where one should place the borders and/or backgrounds for
+ * block-level in-flow descendants (step 4 of CSS 2.1 appendix E)
+ */
+ nsDisplayList* BlockBorderBackgrounds() const { return mLists[1]; }
+ /**
+ * @return a list where one should place descendant floats (step 5 of
+ * CSS 2.1 appendix E)
+ */
+ nsDisplayList* Floats() const { return mLists[2]; }
+ /**
+ * @return a list where one should place the (pseudo) stacking contexts
+ * for descendants of this frame (everything from steps 3, 7 and 8
+ * of CSS 2.1 appendix E)
+ */
+ nsDisplayList* PositionedDescendants() const { return mLists[3]; }
+ /**
+ * @return a list where one should place the outlines
+ * for this frame and its descendants (step 9 of CSS 2.1 appendix E)
+ */
+ nsDisplayList* Outlines() const { return mLists[4]; }
+ /**
+ * @return a list where one should place all other content
+ */
+ nsDisplayList* Content() const { return mLists[5]; }
+
+ const std::array<nsDisplayList*, 6>& Lists() const { return mLists; }
+
+ /**
+ * Clears all the display lists in the set.
+ */
+ void Clear() {
+ for (auto* list : mLists) {
+ MOZ_ASSERT(list);
+ list->Clear();
+ }
+ }
+
+ /**
+ * Deletes all the display items in the set.
+ */
+ void DeleteAll(nsDisplayListBuilder* aBuilder) {
+ for (auto* list : mLists) {
+ list->DeleteAll(aBuilder);
+ }
+ }
+
+ nsDisplayListSet(nsDisplayList* aBorderBackground,
+ nsDisplayList* aBlockBorderBackgrounds,
+ nsDisplayList* aFloats, nsDisplayList* aContent,
+ nsDisplayList* aPositionedDescendants,
+ nsDisplayList* aOutlines)
+ : mLists{aBorderBackground, aBlockBorderBackgrounds, aFloats,
+ aContent, aPositionedDescendants, aOutlines} {}
+
+ /**
+ * A copy constructor that lets the caller override the BorderBackground
+ * list.
+ */
+ nsDisplayListSet(const nsDisplayListSet& aLists,
+ nsDisplayList* aBorderBackground)
+ : mLists(aLists.mLists) {
+ mLists[0] = aBorderBackground;
+ }
+
+ /**
+ * Returns true if all the display lists in the display list set are empty.
+ */
+ bool IsEmpty() const {
+ for (auto* list : mLists) {
+ if (!list->IsEmpty()) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Calls the function |aFn| for each display item in the display list set.
+ */
+ void ForEach(const std::function<void(nsDisplayItem*)>& aFn) const {
+ for (auto* list : mLists) {
+ list->ForEach(aFn);
+ }
+ }
+
+ /**
+ * Creates a shallow copy of this display list set to |aDestination|.
+ */
+ void CopyTo(const nsDisplayListSet& aDestination) const;
+
+ /**
+ * Move all display items in our lists to top of the corresponding lists in
+ * the destination.
+ */
+ void MoveTo(const nsDisplayListSet& aDestination) const;
+
+ private:
+ // This class is only used on stack, so we don't have to worry about leaking
+ // it. Don't let us be heap-allocated!
+ void* operator new(size_t sz) noexcept(true);
+
+ std::array<nsDisplayList*, 6> mLists;
+};
+
+/**
+ * A specialization of nsDisplayListSet where the lists are actually internal
+ * to the object, and all distinct.
+ */
+struct nsDisplayListCollection : public nsDisplayListSet {
+ explicit nsDisplayListCollection(nsDisplayListBuilder* aBuilder)
+ : nsDisplayListSet(&mLists[0], &mLists[1], &mLists[2], &mLists[3],
+ &mLists[4], &mLists[5]),
+ mLists{nsDisplayList{aBuilder}, nsDisplayList{aBuilder},
+ nsDisplayList{aBuilder}, nsDisplayList{aBuilder},
+ nsDisplayList{aBuilder}, nsDisplayList{aBuilder}} {}
+
+ /**
+ * Sort all lists by content order.
+ */
+ void SortAllByContentOrder(nsIContent* aCommonAncestor) {
+ for (auto& mList : mLists) {
+ mList.SortByContentOrder(aCommonAncestor);
+ }
+ }
+
+ /**
+ * Serialize this display list collection into a display list with the items
+ * in the correct Z order.
+ * @param aOutList the result display list
+ * @param aContent the content element to use for content ordering
+ */
+ void SerializeWithCorrectZOrder(nsDisplayList* aOutResultList,
+ nsIContent* aContent);
+
+ private:
+ // This class is only used on stack, so we don't have to worry about leaking
+ // it. Don't let us be heap-allocated!
+ void* operator new(size_t sz) noexcept(true);
+
+ nsDisplayList mLists[6];
+};
+
+/**
+ * A display list that also retains the partial build
+ * information (in the form of a DAG) used to create it.
+ *
+ * Display lists built from a partial list aren't necessarily
+ * in the same order as a full build, and the DAG retains
+ * the information needing to interpret the current
+ * order correctly.
+ */
+class RetainedDisplayList : public nsDisplayList {
+ public:
+ explicit RetainedDisplayList(nsDisplayListBuilder* aBuilder)
+ : nsDisplayList(aBuilder) {}
+
+ RetainedDisplayList(RetainedDisplayList&& aOther)
+ : nsDisplayList(std::move(aOther)), mDAG(std::move(aOther.mDAG)) {}
+
+ RetainedDisplayList(const RetainedDisplayList&) = delete;
+ RetainedDisplayList& operator=(const RetainedDisplayList&) = delete;
+
+ ~RetainedDisplayList() override {
+ MOZ_ASSERT(mOldItems.IsEmpty(), "Must empty list before destroying");
+ }
+
+ RetainedDisplayList& operator=(RetainedDisplayList&& aOther) {
+ MOZ_ASSERT(IsEmpty(), "Can only move into an empty list!");
+ MOZ_ASSERT(mOldItems.IsEmpty(), "Can only move into an empty list!");
+
+ nsDisplayList::operator=(std::move(aOther));
+ mDAG = std::move(aOther.mDAG);
+ mOldItems = std::move(aOther.mOldItems);
+ return *this;
+ }
+
+ RetainedDisplayList& operator=(nsDisplayList&& aOther) {
+ MOZ_ASSERT(IsEmpty(), "Can only move into an empty list!");
+ MOZ_ASSERT(mOldItems.IsEmpty(), "Can only move into an empty list!");
+ nsDisplayList::operator=(std::move(aOther));
+ return *this;
+ }
+
+ void DeleteAll(nsDisplayListBuilder* aBuilder) override {
+ for (OldItemInfo& i : mOldItems) {
+ if (i.mItem && i.mOwnsItem) {
+ i.mItem->Destroy(aBuilder);
+ MOZ_ASSERT(!GetBottom(),
+ "mOldItems should not be owning items if we also have items "
+ "in the normal list");
+ }
+ }
+ mOldItems.Clear();
+ mDAG.Clear();
+ nsDisplayList::DeleteAll(aBuilder);
+ }
+
+ void AddSizeOfExcludingThis(nsWindowSizes&) const;
+
+ DirectedAcyclicGraph<MergedListUnits> mDAG;
+
+ // Temporary state initialized during the preprocess pass
+ // of RetainedDisplayListBuilder and then used during merging.
+ nsTArray<OldItemInfo> mOldItems;
+};
+
+class nsDisplayContainer final : public nsDisplayItem {
+ public:
+ nsDisplayContainer(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ const ActiveScrolledRoot* aActiveScrolledRoot,
+ nsDisplayList* aList);
+
+ ~nsDisplayContainer() override { MOZ_COUNT_DTOR(nsDisplayContainer); }
+
+ NS_DISPLAY_DECL_NAME("nsDisplayContainer", TYPE_CONTAINER)
+
+ void Destroy(nsDisplayListBuilder* aBuilder) override {
+ mChildren.DeleteAll(aBuilder);
+ nsDisplayItem::Destroy(aBuilder);
+ }
+
+ bool CreateWebRenderCommands(
+ wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) override;
+
+ nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) const override;
+
+ nsRect GetComponentAlphaBounds(nsDisplayListBuilder* aBuilder) const override;
+
+ nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
+ bool* aSnap) const override;
+
+ Maybe<nscolor> IsUniform(nsDisplayListBuilder* aBuilder) const override {
+ return Nothing();
+ }
+
+ RetainedDisplayList* GetChildren() const override { return &mChildren; }
+ RetainedDisplayList* GetSameCoordinateSystemChildren() const override {
+ return GetChildren();
+ }
+
+ Maybe<nsRect> GetClipWithRespectToASR(
+ nsDisplayListBuilder* aBuilder,
+ const ActiveScrolledRoot* aASR) const override;
+
+ void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
+ HitTestState* aState, nsTArray<nsIFrame*>* aOutFrames) override;
+
+ bool ShouldFlattenAway(nsDisplayListBuilder* aBuilder) override {
+ return true;
+ }
+
+ void SetClipChain(const DisplayItemClipChain* aClipChain,
+ bool aStore) override {
+ MOZ_ASSERT_UNREACHABLE("nsDisplayContainer does not support clipping");
+ }
+
+ void UpdateBounds(nsDisplayListBuilder* aBuilder) override;
+
+ private:
+ mutable RetainedDisplayList mChildren;
+ nsRect mBounds;
+};
+
+/**
+ * Use this class to implement not-very-frequently-used display items
+ * that are not opaque, do not receive events, and are bounded by a frame's
+ * border-rect.
+ *
+ * This should not be used for display items which are created frequently,
+ * because each item is one or two pointers bigger than an item from a
+ * custom display item class could be, and fractionally slower. However it does
+ * save code size. We use this for infrequently-used item types.
+ */
+class nsDisplayGeneric : public nsPaintedDisplayItem {
+ public:
+ typedef void (*PaintCallback)(nsIFrame* aFrame, gfx::DrawTarget* aDrawTarget,
+ const nsRect& aDirtyRect, nsPoint aFramePt);
+
+ // XXX: should be removed eventually
+ typedef void (*OldPaintCallback)(nsIFrame* aFrame, gfxContext* aCtx,
+ const nsRect& aDirtyRect, nsPoint aFramePt);
+
+ nsDisplayGeneric(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ PaintCallback aPaint, const char* aName,
+ DisplayItemType aType)
+ : nsPaintedDisplayItem(aBuilder, aFrame),
+ mPaint(aPaint),
+ mOldPaint(nullptr),
+ mName(aName) {
+ MOZ_COUNT_CTOR(nsDisplayGeneric);
+ SetType(aType);
+ }
+
+ // XXX: should be removed eventually
+ nsDisplayGeneric(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ OldPaintCallback aOldPaint, const char* aName,
+ DisplayItemType aType)
+ : nsPaintedDisplayItem(aBuilder, aFrame),
+ mPaint(nullptr),
+ mOldPaint(aOldPaint),
+ mName(aName) {
+ MOZ_COUNT_CTOR(nsDisplayGeneric);
+ SetType(aType);
+ }
+
+ constexpr static DisplayItemType ItemType() {
+ return DisplayItemType::TYPE_GENERIC;
+ }
+
+ MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayGeneric)
+
+ void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override {
+ MOZ_ASSERT(!!mPaint != !!mOldPaint);
+ if (mPaint) {
+ mPaint(mFrame, aCtx->GetDrawTarget(), GetPaintRect(aBuilder, aCtx),
+ ToReferenceFrame());
+ } else {
+ mOldPaint(mFrame, aCtx, GetPaintRect(aBuilder, aCtx), ToReferenceFrame());
+ }
+ }
+
+ const char* Name() const override { return mName; }
+
+ // This override is needed because GetType() for nsDisplayGeneric subclasses
+ // does not match TYPE_GENERIC that was used to allocate the object.
+ void Destroy(nsDisplayListBuilder* aBuilder) override {
+ this->~nsDisplayGeneric();
+ aBuilder->Destroy(DisplayItemType::TYPE_GENERIC, this);
+ }
+
+ protected:
+ void* operator new(size_t aSize, nsDisplayListBuilder* aBuilder) {
+ return aBuilder->Allocate(aSize, DisplayItemType::TYPE_GENERIC);
+ }
+
+ template <typename T, typename F, typename... Args>
+ friend T* MakeDisplayItemWithIndex(nsDisplayListBuilder* aBuilder, F* aFrame,
+ const uint16_t aIndex, Args&&... aArgs);
+
+ PaintCallback mPaint;
+ OldPaintCallback mOldPaint; // XXX: should be removed eventually
+ const char* mName;
+};
+
+#if defined(MOZ_REFLOW_PERF_DSP) && defined(MOZ_REFLOW_PERF)
+/**
+ * This class implements painting of reflow counts. Ideally, we would simply
+ * make all the frame names be those returned by nsIFrame::GetFrameName
+ * (except that tosses in the content tag name!) and support only one color
+ * and eliminate this class altogether in favor of nsDisplayGeneric, but for
+ * the time being we can't pass args to a PaintCallback, so just have a
+ * separate class to do the right thing. Sadly, this alsmo means we need to
+ * hack all leaf frame classes to handle this.
+ *
+ * XXXbz the color thing is a bit of a mess, but 0 basically means "not set"
+ * here... I could switch it all to nscolor, but why bother?
+ */
+class nsDisplayReflowCount : public nsPaintedDisplayItem {
+ public:
+ nsDisplayReflowCount(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ const char* aFrameName, uint32_t aColor = 0)
+ : nsPaintedDisplayItem(aBuilder, aFrame),
+ mFrameName(aFrameName),
+ mColor(aColor) {
+ MOZ_COUNT_CTOR(nsDisplayReflowCount);
+ }
+
+ MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayReflowCount)
+
+ NS_DISPLAY_DECL_NAME("nsDisplayReflowCount", TYPE_REFLOW_COUNT)
+
+ void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override;
+
+ protected:
+ const char* mFrameName;
+ nscolor mColor;
+};
+
+# define DO_GLOBAL_REFLOW_COUNT_DSP(_name) \
+ PR_BEGIN_MACRO \
+ if (!aBuilder->IsBackgroundOnly() && !aBuilder->IsForEventDelivery() && \
+ PresShell()->IsPaintingFrameCounts()) { \
+ aLists.Outlines()->AppendNewToTop<mozilla::nsDisplayReflowCount>( \
+ aBuilder, this, _name); \
+ } \
+ PR_END_MACRO
+
+# define DO_GLOBAL_REFLOW_COUNT_DSP_COLOR(_name, _color) \
+ PR_BEGIN_MACRO \
+ if (!aBuilder->IsBackgroundOnly() && !aBuilder->IsForEventDelivery() && \
+ PresShell()->IsPaintingFrameCounts()) { \
+ aLists.Outlines()->AppendNewToTop<mozilla::nsDisplayReflowCount>( \
+ aBuilder, this, _name, _color); \
+ } \
+ PR_END_MACRO
+
+/*
+ Macro to be used for classes that don't actually implement BuildDisplayList
+ */
+# define DECL_DO_GLOBAL_REFLOW_COUNT_DSP(_class, _super) \
+ void BuildDisplayList(nsDisplayListBuilder* aBuilder, \
+ const nsRect& aDirtyRect, \
+ const nsDisplayListSet& aLists) { \
+ DO_GLOBAL_REFLOW_COUNT_DSP(#_class); \
+ _super::BuildDisplayList(aBuilder, aDirtyRect, aLists); \
+ }
+
+#else // MOZ_REFLOW_PERF_DSP && MOZ_REFLOW_PERF
+
+# define DO_GLOBAL_REFLOW_COUNT_DSP(_name)
+# define DO_GLOBAL_REFLOW_COUNT_DSP_COLOR(_name, _color)
+# define DECL_DO_GLOBAL_REFLOW_COUNT_DSP(_class, _super)
+
+#endif // MOZ_REFLOW_PERF_DSP && MOZ_REFLOW_PERF
+
+class nsDisplayCaret : public nsPaintedDisplayItem {
+ public:
+ nsDisplayCaret(nsDisplayListBuilder* aBuilder, nsIFrame* aCaretFrame);
+
+#ifdef NS_BUILD_REFCNT_LOGGING
+ ~nsDisplayCaret() override;
+#endif
+
+ NS_DISPLAY_DECL_NAME("Caret", TYPE_CARET)
+
+ nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) const override;
+ void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override;
+ bool CreateWebRenderCommands(
+ wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) override;
+
+ protected:
+ RefPtr<nsCaret> mCaret;
+ nsRect mBounds;
+};
+
+/**
+ * The standard display item to paint the CSS borders of a frame.
+ */
+class nsDisplayBorder : public nsPaintedDisplayItem {
+ public:
+ nsDisplayBorder(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame);
+
+ MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayBorder)
+
+ NS_DISPLAY_DECL_NAME("Border", TYPE_BORDER)
+
+ bool IsInvisibleInRect(const nsRect& aRect) const override;
+ nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) const override;
+ bool CreateWebRenderCommands(
+ wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) override;
+ void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override;
+ nsDisplayItemGeometry* AllocateGeometry(
+ nsDisplayListBuilder* aBuilder) override;
+ void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
+ const nsDisplayItemGeometry* aGeometry,
+ nsRegion* aInvalidRegion) const override;
+
+ nsRegion GetTightBounds(nsDisplayListBuilder* aBuilder,
+ bool* aSnap) const override {
+ *aSnap = true;
+ return CalculateBounds<nsRegion>(*mFrame->StyleBorder());
+ }
+
+ protected:
+ template <typename T>
+ T CalculateBounds(const nsStyleBorder& aStyleBorder) const {
+ nsRect borderBounds(ToReferenceFrame(), mFrame->GetSize());
+ if (aStyleBorder.IsBorderImageSizeAvailable()) {
+ borderBounds.Inflate(aStyleBorder.GetImageOutset());
+ return borderBounds;
+ }
+
+ nsMargin border = aStyleBorder.GetComputedBorder();
+ T result;
+ if (border.top > 0) {
+ result = nsRect(borderBounds.X(), borderBounds.Y(), borderBounds.Width(),
+ border.top);
+ }
+ if (border.right > 0) {
+ result.OrWith(nsRect(borderBounds.XMost() - border.right,
+ borderBounds.Y(), border.right,
+ borderBounds.Height()));
+ }
+ if (border.bottom > 0) {
+ result.OrWith(nsRect(borderBounds.X(),
+ borderBounds.YMost() - border.bottom,
+ borderBounds.Width(), border.bottom));
+ }
+ if (border.left > 0) {
+ result.OrWith(nsRect(borderBounds.X(), borderBounds.Y(), border.left,
+ borderBounds.Height()));
+ }
+
+ nscoord radii[8];
+ if (mFrame->GetBorderRadii(radii)) {
+ if (border.left > 0 || border.top > 0) {
+ nsSize cornerSize(radii[eCornerTopLeftX], radii[eCornerTopLeftY]);
+ result.OrWith(nsRect(borderBounds.TopLeft(), cornerSize));
+ }
+ if (border.top > 0 || border.right > 0) {
+ nsSize cornerSize(radii[eCornerTopRightX], radii[eCornerTopRightY]);
+ result.OrWith(
+ nsRect(borderBounds.TopRight() - nsPoint(cornerSize.width, 0),
+ cornerSize));
+ }
+ if (border.right > 0 || border.bottom > 0) {
+ nsSize cornerSize(radii[eCornerBottomRightX],
+ radii[eCornerBottomRightY]);
+ result.OrWith(nsRect(borderBounds.BottomRight() -
+ nsPoint(cornerSize.width, cornerSize.height),
+ cornerSize));
+ }
+ if (border.bottom > 0 || border.left > 0) {
+ nsSize cornerSize(radii[eCornerBottomLeftX], radii[eCornerBottomLeftY]);
+ result.OrWith(
+ nsRect(borderBounds.BottomLeft() - nsPoint(0, cornerSize.height),
+ cornerSize));
+ }
+ }
+ return result;
+ }
+
+ nsRect mBounds;
+};
+
+/**
+ * A simple display item that just renders a solid color across the
+ * specified bounds. For canvas frames (in the CSS sense) we split off the
+ * drawing of the background color into this class (from nsDisplayBackground
+ * via nsDisplayCanvasBackground). This is done so that we can always draw a
+ * background color to avoid ugly flashes of white when we can't draw a full
+ * frame tree (ie when a page is loading). The bounds can differ from the
+ * frame's bounds -- this is needed when a frame/iframe is loading and there
+ * is not yet a frame tree to go in the frame/iframe so we use the subdoc
+ * frame of the parent document as a standin.
+ */
+class nsDisplaySolidColorBase : public nsPaintedDisplayItem {
+ public:
+ nsDisplaySolidColorBase(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ nscolor aColor)
+ : nsPaintedDisplayItem(aBuilder, aFrame), mColor(aColor) {}
+
+ nsDisplayItemGeometry* AllocateGeometry(
+ nsDisplayListBuilder* aBuilder) override {
+ return new nsDisplaySolidColorGeometry(this, aBuilder, mColor);
+ }
+
+ void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
+ const nsDisplayItemGeometry* aGeometry,
+ nsRegion* aInvalidRegion) const override {
+ const nsDisplaySolidColorGeometry* geometry =
+ static_cast<const nsDisplaySolidColorGeometry*>(aGeometry);
+ if (mColor != geometry->mColor) {
+ bool dummy;
+ aInvalidRegion->Or(geometry->mBounds, GetBounds(aBuilder, &dummy));
+ return;
+ }
+ ComputeInvalidationRegionDifference(aBuilder, geometry, aInvalidRegion);
+ }
+
+ nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
+ bool* aSnap) const override {
+ *aSnap = false;
+ nsRegion result;
+ if (NS_GET_A(mColor) == 255) {
+ result = GetBounds(aBuilder, aSnap);
+ }
+ return result;
+ }
+
+ Maybe<nscolor> IsUniform(nsDisplayListBuilder* aBuilder) const override {
+ return Some(mColor);
+ }
+
+ protected:
+ nscolor mColor;
+};
+
+class nsDisplaySolidColor : public nsDisplaySolidColorBase {
+ public:
+ nsDisplaySolidColor(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ const nsRect& aBounds, nscolor aColor,
+ bool aCanBeReused = true)
+ : nsDisplaySolidColorBase(aBuilder, aFrame, aColor),
+ mBounds(aBounds),
+ mIsCheckerboardBackground(false) {
+ NS_ASSERTION(NS_GET_A(aColor) > 0,
+ "Don't create invisible nsDisplaySolidColors!");
+ MOZ_COUNT_CTOR(nsDisplaySolidColor);
+ if (!aCanBeReused) {
+ SetCantBeReused();
+ }
+ }
+
+ MOZ_COUNTED_DTOR_OVERRIDE(nsDisplaySolidColor)
+
+ NS_DISPLAY_DECL_NAME("SolidColor", TYPE_SOLID_COLOR)
+
+ nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) const override;
+ void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override;
+ void WriteDebugInfo(std::stringstream& aStream) override;
+ void SetIsCheckerboardBackground() { mIsCheckerboardBackground = true; }
+ bool CreateWebRenderCommands(
+ wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) override;
+
+ int32_t ZIndex() const override {
+ if (mOverrideZIndex) {
+ return mOverrideZIndex.value();
+ }
+ return nsDisplaySolidColorBase::ZIndex();
+ }
+
+ void SetOverrideZIndex(int32_t aZIndex) { mOverrideZIndex = Some(aZIndex); }
+
+ private:
+ nsRect mBounds;
+ bool mIsCheckerboardBackground;
+ Maybe<int32_t> mOverrideZIndex;
+};
+
+/**
+ * A display item that renders a solid color over a region. This is not
+ * exposed through CSS, its only purpose is efficient invalidation of
+ * the find bar highlighter dimmer.
+ */
+class nsDisplaySolidColorRegion : public nsPaintedDisplayItem {
+ public:
+ nsDisplaySolidColorRegion(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ const nsRegion& aRegion, nscolor aColor)
+ : nsPaintedDisplayItem(aBuilder, aFrame),
+ mRegion(aRegion),
+ mColor(gfx::sRGBColor::FromABGR(aColor)) {
+ NS_ASSERTION(NS_GET_A(aColor) > 0,
+ "Don't create invisible nsDisplaySolidColorRegions!");
+ MOZ_COUNT_CTOR(nsDisplaySolidColorRegion);
+ }
+
+ MOZ_COUNTED_DTOR_OVERRIDE(nsDisplaySolidColorRegion)
+
+ NS_DISPLAY_DECL_NAME("SolidColorRegion", TYPE_SOLID_COLOR_REGION)
+
+ nsDisplayItemGeometry* AllocateGeometry(
+ nsDisplayListBuilder* aBuilder) override {
+ return new nsDisplaySolidColorRegionGeometry(this, aBuilder, mRegion,
+ mColor);
+ }
+
+ void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
+ const nsDisplayItemGeometry* aGeometry,
+ nsRegion* aInvalidRegion) const override {
+ const nsDisplaySolidColorRegionGeometry* geometry =
+ static_cast<const nsDisplaySolidColorRegionGeometry*>(aGeometry);
+ if (mColor == geometry->mColor) {
+ aInvalidRegion->Xor(geometry->mRegion, mRegion);
+ } else {
+ aInvalidRegion->Or(geometry->mRegion.GetBounds(), mRegion.GetBounds());
+ }
+ }
+
+ bool CreateWebRenderCommands(
+ wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) override;
+
+ protected:
+ nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) const override;
+ void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override;
+ void WriteDebugInfo(std::stringstream& aStream) override;
+
+ private:
+ nsRegion mRegion;
+ gfx::sRGBColor mColor;
+};
+
+enum class AppendedBackgroundType : uint8_t {
+ None,
+ Background,
+ ThemedBackground,
+};
+
+/**
+ * A display item to paint one background-image for a frame. Each background
+ * image layer gets its own nsDisplayBackgroundImage.
+ */
+class nsDisplayBackgroundImage : public nsPaintedDisplayItem {
+ public:
+ struct InitData {
+ nsDisplayListBuilder* builder;
+ const ComputedStyle* backgroundStyle;
+ nsCOMPtr<imgIContainer> image;
+ nsRect backgroundRect;
+ nsRect fillArea;
+ nsRect destArea;
+ uint32_t layer;
+ bool isRasterImage;
+ bool shouldFixToViewport;
+ };
+
+ /**
+ * aLayer signifies which background layer this item represents.
+ * aIsThemed should be the value of aFrame->IsThemed.
+ * aBackgroundStyle should be the result of
+ * nsCSSRendering::FindBackground, or null if FindBackground returned false.
+ * aBackgroundRect is relative to aFrame.
+ */
+ static InitData GetInitData(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ uint16_t aLayer, const nsRect& aBackgroundRect,
+ const ComputedStyle* aBackgroundStyle);
+
+ explicit nsDisplayBackgroundImage(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame, const InitData& aInitData,
+ nsIFrame* aFrameForBounds = nullptr);
+ ~nsDisplayBackgroundImage() override;
+
+ NS_DISPLAY_DECL_NAME("Background", TYPE_BACKGROUND)
+
+ /**
+ * This will create and append new items for all the layers of the
+ * background. Returns the type of background that was appended.
+ * aAllowWillPaintBorderOptimization should usually be left at true, unless
+ * aFrame has special border drawing that causes opaque borders to not
+ * actually be opaque.
+ */
+ static AppendedBackgroundType AppendBackgroundItemsToTop(
+ nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ const nsRect& aBackgroundRect, nsDisplayList* aList,
+ bool aAllowWillPaintBorderOptimization = true,
+ const nsRect& aBackgroundOriginRect = nsRect(),
+ nsIFrame* aSecondaryReferenceFrame = nullptr,
+ Maybe<nsDisplayListBuilder::AutoBuildingDisplayList>*
+ aAutoBuildingDisplayList = nullptr);
+
+ bool CreateWebRenderCommands(
+ wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) override;
+ void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
+ HitTestState* aState, nsTArray<nsIFrame*>* aOutFrames) override;
+ nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
+ bool* aSnap) const override;
+ Maybe<nscolor> IsUniform(nsDisplayListBuilder* aBuilder) const override;
+
+ bool CanApplyOpacity(WebRenderLayerManager* aManager,
+ nsDisplayListBuilder* aBuilder) const override;
+
+ /**
+ * GetBounds() returns the background painting area.
+ */
+ nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) const override;
+
+ void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override;
+
+ /**
+ * Return the background positioning area.
+ * (GetBounds() returns the background painting area.)
+ * Can be called only when mBackgroundStyle is non-null.
+ */
+ nsRect GetPositioningArea() const;
+
+ /**
+ * Returns true if existing rendered pixels of this display item may need
+ * to be redrawn if the positioning area size changes but its position does
+ * not.
+ * If false, only the changed painting area needs to be redrawn when the
+ * positioning area size changes but its position does not.
+ */
+ bool RenderingMightDependOnPositioningAreaSizeChange() const;
+
+ nsDisplayItemGeometry* AllocateGeometry(
+ nsDisplayListBuilder* aBuilder) override {
+ return new nsDisplayBackgroundGeometry(this, aBuilder);
+ }
+
+ void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
+ const nsDisplayItemGeometry* aGeometry,
+ nsRegion* aInvalidRegion) const override;
+ bool ShouldFixToViewport(nsDisplayListBuilder* aBuilder) const override {
+ return mShouldFixToViewport;
+ }
+
+ nsRect GetDestRect() const { return mDestRect; }
+
+ nsIFrame* GetDependentFrame() override { return mDependentFrame; }
+
+ void SetDependentFrame(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame) {
+ if (!aBuilder->IsRetainingDisplayList() || mDependentFrame == aFrame) {
+ return;
+ }
+ mDependentFrame = aFrame;
+ if (aFrame) {
+ mDependentFrame->AddDisplayItem(this);
+ }
+ }
+
+ void RemoveFrame(nsIFrame* aFrame) override {
+ if (aFrame == mDependentFrame) {
+ mDependentFrame = nullptr;
+ }
+ nsPaintedDisplayItem::RemoveFrame(aFrame);
+ }
+
+ // Match https://w3c.github.io/paint-timing/#contentful-image
+ bool IsContentful() const override {
+ const auto& styleImage =
+ mBackgroundStyle->StyleBackground()->mImage.mLayers[mLayer].mImage;
+
+ return styleImage.IsSizeAvailable() && styleImage.FinalImage().IsUrl();
+ }
+
+ protected:
+ bool CanBuildWebRenderDisplayItems(layers::WebRenderLayerManager* aManager,
+ nsDisplayListBuilder* aBuilder) const;
+ nsRect GetBoundsInternal(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrameForBounds = nullptr);
+
+ void PaintInternal(nsDisplayListBuilder* aBuilder, gfxContext* aCtx,
+ const nsRect& aBounds, nsRect* aClipRect);
+
+ // Cache the result of nsCSSRendering::FindBackground. Always null if
+ // mIsThemed is true or if FindBackground returned false.
+ RefPtr<const ComputedStyle> mBackgroundStyle;
+ nsCOMPtr<imgIContainer> mImage;
+ nsIFrame* mDependentFrame;
+ nsRect mBackgroundRect; // relative to the reference frame
+ nsRect mFillRect;
+ nsRect mDestRect;
+ /* Bounds of this display item */
+ nsRect mBounds;
+ uint16_t mLayer;
+ bool mIsRasterImage;
+ /* Whether the image should be treated as fixed to the viewport. */
+ bool mShouldFixToViewport;
+};
+
+/**
+ * A display item to paint background image for table. For table parts, such
+ * as row, row group, col, col group, when drawing its background, we'll
+ * create separate background image display item for its containning cell.
+ * Those background image display items will reference to same DisplayItemData
+ * if we keep the mFrame point to cell's ancestor frame. We don't want to this
+ * happened bacause share same DisplatItemData will cause many bugs. So that
+ * we let mFrame point to cell frame and store the table type of the ancestor
+ * frame. And use mFrame and table type as key to generate DisplayItemData to
+ * avoid sharing DisplayItemData.
+ *
+ * Also store ancestor frame as mStyleFrame for all rendering informations.
+ */
+class nsDisplayTableBackgroundImage : public nsDisplayBackgroundImage {
+ public:
+ nsDisplayTableBackgroundImage(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame, const InitData& aData,
+ nsIFrame* aCellFrame);
+ ~nsDisplayTableBackgroundImage() override;
+
+ NS_DISPLAY_DECL_NAME("TableBackgroundImage", TYPE_TABLE_BACKGROUND_IMAGE)
+
+ bool IsInvalid(nsRect& aRect) const override;
+
+ nsIFrame* FrameForInvalidation() const override { return mStyleFrame; }
+
+ void RemoveFrame(nsIFrame* aFrame) override {
+ if (aFrame == mStyleFrame) {
+ mStyleFrame = nullptr;
+ SetDeletedFrame();
+ }
+ nsDisplayBackgroundImage::RemoveFrame(aFrame);
+ }
+
+ protected:
+ nsIFrame* StyleFrame() const override { return mStyleFrame; }
+ nsIFrame* mStyleFrame;
+};
+
+/**
+ * A display item to paint the native theme background for a frame.
+ */
+class nsDisplayThemedBackground : public nsPaintedDisplayItem {
+ public:
+ nsDisplayThemedBackground(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ const nsRect& aBackgroundRect);
+
+ MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayThemedBackground)
+
+ NS_DISPLAY_DECL_NAME("ThemedBackground", TYPE_THEMED_BACKGROUND)
+
+ void Init(nsDisplayListBuilder* aBuilder);
+
+ void Destroy(nsDisplayListBuilder* aBuilder) override {
+ aBuilder->UnregisterThemeGeometry(this);
+ nsPaintedDisplayItem::Destroy(aBuilder);
+ }
+
+ void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
+ HitTestState* aState, nsTArray<nsIFrame*>* aOutFrames) override;
+ nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
+ bool* aSnap) const override;
+ Maybe<nscolor> IsUniform(nsDisplayListBuilder* aBuilder) const override;
+ bool CreateWebRenderCommands(
+ wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) override;
+
+ bool MustPaintOnContentSide() const override { return true; }
+
+ /**
+ * GetBounds() returns the background painting area.
+ */
+ nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) const override;
+
+ void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override;
+
+ /**
+ * Return the background positioning area.
+ * (GetBounds() returns the background painting area.)
+ * Can be called only when mBackgroundStyle is non-null.
+ */
+ nsRect GetPositioningArea() const;
+
+ /**
+ * Return whether our frame's document does not have the state
+ * NS_DOCUMENT_STATE_WINDOW_INACTIVE.
+ */
+ bool IsWindowActive() const;
+
+ nsDisplayItemGeometry* AllocateGeometry(
+ nsDisplayListBuilder* aBuilder) override {
+ return new nsDisplayThemedBackgroundGeometry(this, aBuilder);
+ }
+
+ void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
+ const nsDisplayItemGeometry* aGeometry,
+ nsRegion* aInvalidRegion) const override;
+
+ void WriteDebugInfo(std::stringstream& aStream) override;
+
+ protected:
+ nsRect GetBoundsInternal();
+
+ void PaintInternal(nsDisplayListBuilder* aBuilder, gfxContext* aCtx,
+ const nsRect& aBounds, nsRect* aClipRect);
+
+ nsRect mBackgroundRect;
+ nsRect mBounds;
+ nsITheme::Transparency mThemeTransparency;
+ StyleAppearance mAppearance;
+};
+
+class nsDisplayTableThemedBackground : public nsDisplayThemedBackground {
+ public:
+ nsDisplayTableThemedBackground(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame,
+ const nsRect& aBackgroundRect,
+ nsIFrame* aAncestorFrame)
+ : nsDisplayThemedBackground(aBuilder, aFrame, aBackgroundRect),
+ mAncestorFrame(aAncestorFrame) {
+ if (aBuilder->IsRetainingDisplayList()) {
+ mAncestorFrame->AddDisplayItem(this);
+ }
+ }
+
+ ~nsDisplayTableThemedBackground() override {
+ if (mAncestorFrame) {
+ mAncestorFrame->RemoveDisplayItem(this);
+ }
+ }
+
+ NS_DISPLAY_DECL_NAME("TableThemedBackground",
+ TYPE_TABLE_THEMED_BACKGROUND_IMAGE)
+
+ nsIFrame* FrameForInvalidation() const override { return mAncestorFrame; }
+
+ void RemoveFrame(nsIFrame* aFrame) override {
+ if (aFrame == mAncestorFrame) {
+ mAncestorFrame = nullptr;
+ SetDeletedFrame();
+ }
+ nsDisplayThemedBackground::RemoveFrame(aFrame);
+ }
+
+ protected:
+ nsIFrame* StyleFrame() const override { return mAncestorFrame; }
+ nsIFrame* mAncestorFrame;
+};
+
+class nsDisplayBackgroundColor : public nsPaintedDisplayItem {
+ public:
+ nsDisplayBackgroundColor(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ const nsRect& aBackgroundRect,
+ const ComputedStyle* aBackgroundStyle,
+ const nscolor& aColor)
+ : nsPaintedDisplayItem(aBuilder, aFrame),
+ mBackgroundRect(aBackgroundRect),
+ mHasStyle(aBackgroundStyle),
+ mDependentFrame(nullptr),
+ mColor(gfx::sRGBColor::FromABGR(aColor)) {
+ if (mHasStyle) {
+ mBottomLayerClip =
+ aBackgroundStyle->StyleBackground()->BottomLayer().mClip;
+ } else {
+ MOZ_ASSERT(aBuilder->IsForEventDelivery());
+ }
+ }
+
+ ~nsDisplayBackgroundColor() override {
+ if (mDependentFrame) {
+ mDependentFrame->RemoveDisplayItem(this);
+ }
+ }
+
+ NS_DISPLAY_DECL_NAME("BackgroundColor", TYPE_BACKGROUND_COLOR)
+
+ bool HasBackgroundClipText() const {
+ MOZ_ASSERT(mHasStyle);
+ return mBottomLayerClip == StyleGeometryBox::Text;
+ }
+
+ void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override;
+ void PaintWithClip(nsDisplayListBuilder* aBuilder, gfxContext* aCtx,
+ const DisplayItemClip& aClip) override;
+ bool CreateWebRenderCommands(
+ wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) override;
+ nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
+ bool* aSnap) const override;
+ Maybe<nscolor> IsUniform(nsDisplayListBuilder* aBuilder) const override;
+ void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
+ HitTestState* aState, nsTArray<nsIFrame*>* aOutFrames) override;
+ bool CanApplyOpacity(WebRenderLayerManager* aManager,
+ nsDisplayListBuilder* aBuilder) const override;
+
+ float GetOpacity() const { return mColor.a; }
+
+ nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) const override {
+ *aSnap = true;
+ return mBackgroundRect;
+ }
+
+ bool CanPaintWithClip(const DisplayItemClip& aClip) override {
+ if (HasBackgroundClipText()) {
+ return false;
+ }
+
+ if (aClip.GetRoundedRectCount() > 1) {
+ return false;
+ }
+
+ return true;
+ }
+
+ nsDisplayItemGeometry* AllocateGeometry(
+ nsDisplayListBuilder* aBuilder) override {
+ return new nsDisplaySolidColorGeometry(this, aBuilder, mColor.ToABGR());
+ }
+
+ void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
+ const nsDisplayItemGeometry* aGeometry,
+ nsRegion* aInvalidRegion) const override {
+ const nsDisplaySolidColorGeometry* geometry =
+ static_cast<const nsDisplaySolidColorGeometry*>(aGeometry);
+
+ if (mColor.ToABGR() != geometry->mColor) {
+ bool dummy;
+ aInvalidRegion->Or(geometry->mBounds, GetBounds(aBuilder, &dummy));
+ return;
+ }
+ ComputeInvalidationRegionDifference(aBuilder, geometry, aInvalidRegion);
+ }
+
+ nsIFrame* GetDependentFrame() override { return mDependentFrame; }
+
+ void SetDependentFrame(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame) {
+ if (!aBuilder->IsRetainingDisplayList() || mDependentFrame == aFrame) {
+ return;
+ }
+ mDependentFrame = aFrame;
+ if (aFrame) {
+ mDependentFrame->AddDisplayItem(this);
+ }
+ }
+
+ void RemoveFrame(nsIFrame* aFrame) override {
+ if (aFrame == mDependentFrame) {
+ mDependentFrame = nullptr;
+ }
+
+ nsPaintedDisplayItem::RemoveFrame(aFrame);
+ }
+
+ void WriteDebugInfo(std::stringstream& aStream) override;
+
+ bool CanUseAsyncAnimations(nsDisplayListBuilder* aBuilder) override;
+
+ protected:
+ const nsRect mBackgroundRect;
+ const bool mHasStyle;
+ StyleGeometryBox mBottomLayerClip;
+ nsIFrame* mDependentFrame;
+ gfx::sRGBColor mColor;
+};
+
+class nsDisplayTableBackgroundColor : public nsDisplayBackgroundColor {
+ public:
+ nsDisplayTableBackgroundColor(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame, const nsRect& aBackgroundRect,
+ const ComputedStyle* aBackgroundStyle,
+ const nscolor& aColor, nsIFrame* aAncestorFrame)
+ : nsDisplayBackgroundColor(aBuilder, aFrame, aBackgroundRect,
+ aBackgroundStyle, aColor),
+ mAncestorFrame(aAncestorFrame) {
+ if (aBuilder->IsRetainingDisplayList()) {
+ mAncestorFrame->AddDisplayItem(this);
+ }
+ }
+
+ ~nsDisplayTableBackgroundColor() override {
+ if (mAncestorFrame) {
+ mAncestorFrame->RemoveDisplayItem(this);
+ }
+ }
+
+ NS_DISPLAY_DECL_NAME("TableBackgroundColor", TYPE_TABLE_BACKGROUND_COLOR)
+
+ nsIFrame* FrameForInvalidation() const override { return mAncestorFrame; }
+
+ void RemoveFrame(nsIFrame* aFrame) override {
+ if (aFrame == mAncestorFrame) {
+ mAncestorFrame = nullptr;
+ SetDeletedFrame();
+ }
+ nsDisplayBackgroundColor::RemoveFrame(aFrame);
+ }
+
+ bool CanUseAsyncAnimations(nsDisplayListBuilder* aBuilder) override {
+ return false;
+ }
+
+ protected:
+ nsIFrame* mAncestorFrame;
+};
+
+/**
+ * The standard display item to paint the outer CSS box-shadows of a frame.
+ */
+class nsDisplayBoxShadowOuter final : public nsPaintedDisplayItem {
+ public:
+ nsDisplayBoxShadowOuter(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame)
+ : nsPaintedDisplayItem(aBuilder, aFrame) {
+ MOZ_COUNT_CTOR(nsDisplayBoxShadowOuter);
+ mBounds = GetBoundsInternal();
+ }
+
+ MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayBoxShadowOuter)
+
+ NS_DISPLAY_DECL_NAME("BoxShadowOuter", TYPE_BOX_SHADOW_OUTER)
+
+ void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override;
+ nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) const override;
+ bool IsInvisibleInRect(const nsRect& aRect) const override;
+ void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
+ const nsDisplayItemGeometry* aGeometry,
+ nsRegion* aInvalidRegion) const override;
+
+ bool CanApplyOpacity(WebRenderLayerManager* aManager,
+ nsDisplayListBuilder* aBuilder) const override {
+ return CanBuildWebRenderDisplayItems();
+ }
+
+ bool CanBuildWebRenderDisplayItems() const;
+ bool CreateWebRenderCommands(
+ wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) override;
+ nsRect GetBoundsInternal();
+
+ private:
+ nsRect mBounds;
+};
+
+/**
+ * The standard display item to paint the inner CSS box-shadows of a frame.
+ */
+class nsDisplayBoxShadowInner : public nsPaintedDisplayItem {
+ public:
+ nsDisplayBoxShadowInner(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame)
+ : nsPaintedDisplayItem(aBuilder, aFrame) {
+ MOZ_COUNT_CTOR(nsDisplayBoxShadowInner);
+ }
+
+ MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayBoxShadowInner)
+
+ NS_DISPLAY_DECL_NAME("BoxShadowInner", TYPE_BOX_SHADOW_INNER)
+
+ void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override;
+
+ nsDisplayItemGeometry* AllocateGeometry(
+ nsDisplayListBuilder* aBuilder) override {
+ return new nsDisplayBoxShadowInnerGeometry(this, aBuilder);
+ }
+
+ void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
+ const nsDisplayItemGeometry* aGeometry,
+ nsRegion* aInvalidRegion) const override {
+ const nsDisplayBoxShadowInnerGeometry* geometry =
+ static_cast<const nsDisplayBoxShadowInnerGeometry*>(aGeometry);
+ if (!geometry->mPaddingRect.IsEqualInterior(GetPaddingRect())) {
+ // nsDisplayBoxShadowInner is based around the padding rect, but it can
+ // touch pixels outside of this. We should invalidate the entire bounds.
+ bool snap;
+ aInvalidRegion->Or(geometry->mBounds, GetBounds(aBuilder, &snap));
+ }
+ }
+
+ static bool CanCreateWebRenderCommands(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame,
+ const nsPoint& aReferenceOffset);
+ static void CreateInsetBoxShadowWebRenderCommands(
+ wr::DisplayListBuilder& aBuilder, const StackingContextHelper& aSc,
+ nsRect& aVisibleRect, nsIFrame* aFrame, const nsRect& aBorderRect);
+ bool CreateWebRenderCommands(
+ wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) override;
+};
+
+/**
+ * The standard display item to paint the CSS outline of a frame.
+ */
+class nsDisplayOutline final : public nsPaintedDisplayItem {
+ public:
+ nsDisplayOutline(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame)
+ : nsPaintedDisplayItem(aBuilder, aFrame) {
+ MOZ_COUNT_CTOR(nsDisplayOutline);
+ }
+
+ MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayOutline)
+
+ NS_DISPLAY_DECL_NAME("Outline", TYPE_OUTLINE)
+
+ bool MustPaintOnContentSide() const override {
+ MOZ_ASSERT(IsThemedOutline(),
+ "The only fallback path we have is for themed outlines");
+ return true;
+ }
+
+ bool CreateWebRenderCommands(
+ wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) override;
+ bool IsInvisibleInRect(const nsRect& aRect) const override;
+ nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) const override;
+ void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override;
+
+ private:
+ nsRect GetInnerRect() const;
+ bool IsThemedOutline() const;
+ bool HasRadius() const;
+};
+
+/**
+ * A class that lets you receive events within the frame bounds but never
+ * paints.
+ */
+class nsDisplayEventReceiver final : public nsDisplayItem {
+ public:
+ nsDisplayEventReceiver(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame)
+ : nsDisplayItem(aBuilder, aFrame) {
+ MOZ_COUNT_CTOR(nsDisplayEventReceiver);
+ }
+
+ MOZ_COUNTED_DTOR_FINAL(nsDisplayEventReceiver)
+
+ NS_DISPLAY_DECL_NAME("EventReceiver", TYPE_EVENT_RECEIVER)
+
+ void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
+ HitTestState* aState, nsTArray<nsIFrame*>* aOutFrames) final;
+};
+
+/**
+ * Similar to nsDisplayEventReceiver in that it is used for hit-testing. However
+ * this gets built when we're doing widget painting and we need to send the
+ * compositor some hit-test info for a frame. This is effectively a dummy item
+ * whose sole purpose is to carry the hit-test info to the compositor.
+ */
+class nsDisplayCompositorHitTestInfo final : public nsDisplayItem {
+ public:
+ nsDisplayCompositorHitTestInfo(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame)
+ : nsDisplayItem(aBuilder, aFrame) {
+ MOZ_COUNT_CTOR(nsDisplayCompositorHitTestInfo);
+ mHitTestInfo.Initialize(aBuilder, aFrame);
+ SetHasHitTestInfo();
+ }
+
+ nsDisplayCompositorHitTestInfo(
+ nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, const nsRect& aArea,
+ const gfx::CompositorHitTestInfo& aHitTestFlags)
+ : nsDisplayItem(aBuilder, aFrame) {
+ MOZ_COUNT_CTOR(nsDisplayCompositorHitTestInfo);
+ mHitTestInfo.SetAreaAndInfo(aArea, aHitTestFlags);
+ mHitTestInfo.InitializeScrollTarget(aBuilder);
+ SetHasHitTestInfo();
+ }
+
+ MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayCompositorHitTestInfo)
+
+ NS_DISPLAY_DECL_NAME("CompositorHitTestInfo", TYPE_COMPOSITOR_HITTEST_INFO)
+
+ bool CreateWebRenderCommands(
+ wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) override;
+
+ bool isInvisible() const { return true; }
+
+ int32_t ZIndex() const override;
+ void SetOverrideZIndex(int32_t aZIndex);
+
+ nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) const override {
+ *aSnap = false;
+ return nsRect();
+ }
+
+ const HitTestInfo& GetHitTestInfo() final { return mHitTestInfo; }
+
+ private:
+ HitTestInfo mHitTestInfo;
+ Maybe<int32_t> mOverrideZIndex;
+};
+
+class nsDisplayWrapper;
+
+/**
+ * A class that lets you wrap a display list as a display item.
+ *
+ * GetUnderlyingFrame() is troublesome for wrapped lists because if the wrapped
+ * list has many items, it's not clear which one has the 'underlying frame'.
+ * Thus we force the creator to specify what the underlying frame is. The
+ * underlying frame should be the root of a stacking context, because sorting
+ * a list containing this item will not get at the children.
+ *
+ * In some cases (e.g., clipping) we want to wrap a list but we don't have a
+ * particular underlying frame that is a stacking context root. In that case
+ * we allow the frame to be nullptr. Callers to GetUnderlyingFrame must
+ * detect and handle this case.
+ */
+class nsDisplayWrapList : public nsPaintedDisplayItem {
+ public:
+ /**
+ * Takes all the items from aList and puts them in our list.
+ */
+ nsDisplayWrapList(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ nsDisplayList* aList);
+
+ nsDisplayWrapList(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ nsDisplayItem* aItem);
+
+ nsDisplayWrapList(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ nsDisplayList* aList,
+ const ActiveScrolledRoot* aActiveScrolledRoot,
+ bool aClearClipChain = false);
+
+ nsDisplayWrapList(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame)
+ : nsPaintedDisplayItem(aBuilder, aFrame),
+ mList(aBuilder),
+ mFrameActiveScrolledRoot(aBuilder->CurrentActiveScrolledRoot()),
+ mOverrideZIndex(0),
+ mHasZIndexOverride(false) {
+ MOZ_COUNT_CTOR(nsDisplayWrapList);
+ mBaseBuildingRect = GetBuildingRect();
+ mListPtr = &mList;
+ mOriginalClipChain = mClipChain;
+ }
+
+ nsDisplayWrapList() = delete;
+
+ /**
+ * A custom copy-constructor that does not copy mList, as this would mutate
+ * the other item.
+ */
+ nsDisplayWrapList(const nsDisplayWrapList& aOther) = delete;
+ nsDisplayWrapList(nsDisplayListBuilder* aBuilder,
+ const nsDisplayWrapList& aOther)
+ : nsPaintedDisplayItem(aBuilder, aOther),
+ mList(aBuilder),
+ mListPtr(&mList),
+ mFrameActiveScrolledRoot(aOther.mFrameActiveScrolledRoot),
+ mMergedFrames(aOther.mMergedFrames.Clone()),
+ mBounds(aOther.mBounds),
+ mBaseBuildingRect(aOther.mBaseBuildingRect),
+ mOriginalClipChain(aOther.mClipChain),
+ mOverrideZIndex(aOther.mOverrideZIndex),
+ mHasZIndexOverride(aOther.mHasZIndexOverride),
+ mClearingClipChain(aOther.mClearingClipChain) {
+ MOZ_COUNT_CTOR(nsDisplayWrapList);
+ }
+
+ ~nsDisplayWrapList() override;
+
+ const nsDisplayWrapList* AsDisplayWrapList() const final { return this; }
+ nsDisplayWrapList* AsDisplayWrapList() final { return this; }
+
+ void Destroy(nsDisplayListBuilder* aBuilder) override {
+ mList.DeleteAll(aBuilder);
+ nsPaintedDisplayItem::Destroy(aBuilder);
+ }
+
+ /**
+ * Creates a new nsDisplayWrapper that holds a pointer to the display list
+ * owned by the given nsDisplayItem.
+ */
+ nsDisplayWrapper* CreateShallowCopy(nsDisplayListBuilder* aBuilder);
+
+ /**
+ * Call this if the wrapped list is changed.
+ */
+ void UpdateBounds(nsDisplayListBuilder* aBuilder) override {
+ // Clear the clip chain up to the asr, but don't store it, so that we'll
+ // recover it when we reuse the item.
+ if (mClearingClipChain) {
+ const DisplayItemClipChain* clip = mOriginalClipChain;
+ while (clip && ActiveScrolledRoot::IsAncestor(GetActiveScrolledRoot(),
+ clip->mASR)) {
+ clip = clip->mParent;
+ }
+ SetClipChain(clip, false);
+ }
+
+ nsRect buildingRect;
+ mBounds = mListPtr->GetClippedBoundsWithRespectToASR(
+ aBuilder, mActiveScrolledRoot, &buildingRect);
+ // The display list may contain content that's visible outside the visible
+ // rect (i.e. the current dirty rect) passed in when the item was created.
+ // This happens when the dirty rect has been restricted to the visual
+ // overflow rect of a frame for some reason (e.g. when setting up dirty
+ // rects in nsDisplayListBuilder::MarkOutOfFlowFrameForDisplay), but that
+ // frame contains placeholders for out-of-flows that aren't descendants of
+ // the frame.
+ buildingRect.UnionRect(mBaseBuildingRect, buildingRect);
+ SetBuildingRect(buildingRect);
+ }
+
+ void SetClipChain(const DisplayItemClipChain* aClipChain,
+ bool aStore) override {
+ nsDisplayItem::SetClipChain(aClipChain, aStore);
+
+ if (aStore) {
+ mOriginalClipChain = mClipChain;
+ }
+ }
+
+ void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
+ HitTestState* aState, nsTArray<nsIFrame*>* aOutFrames) override;
+ nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) const override;
+ nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
+ bool* aSnap) const override;
+ Maybe<nscolor> IsUniform(nsDisplayListBuilder* aBuilder) const override;
+
+ /**
+ * Try to merge with the other item (which is below us in the display
+ * list). This gets used by nsDisplayClip to coalesce clipping operations
+ * (optimization), by nsDisplayOpacity to merge rendering for the same
+ * content element into a single opacity group (correctness), and will be
+ * used by nsDisplayOutline to merge multiple outlines for the same element
+ * (also for correctness).
+ */
+ virtual void Merge(const nsDisplayItem* aItem) {
+ MOZ_ASSERT(CanMerge(aItem));
+ MOZ_ASSERT(Frame() != aItem->Frame());
+ MergeFromTrackingMergedFrames(static_cast<const nsDisplayWrapList*>(aItem));
+ }
+
+ /**
+ * Returns the underlying frames of all display items that have been
+ * merged into this one (excluding this item's own underlying frame)
+ * to aFrames.
+ */
+ const nsTArray<nsIFrame*>& GetMergedFrames() const { return mMergedFrames; }
+
+ bool HasMergedFrames() const { return !mMergedFrames.IsEmpty(); }
+
+ bool ShouldFlattenAway(nsDisplayListBuilder* aBuilder) override {
+ return true;
+ }
+
+ bool IsInvalid(nsRect& aRect) const override {
+ if (mFrame->IsInvalid(aRect) && aRect.IsEmpty()) {
+ return true;
+ }
+ nsRect temp;
+ for (uint32_t i = 0; i < mMergedFrames.Length(); i++) {
+ if (mMergedFrames[i]->IsInvalid(temp) && temp.IsEmpty()) {
+ aRect.SetEmpty();
+ return true;
+ }
+ aRect = aRect.Union(temp);
+ }
+ aRect += ToReferenceFrame();
+ return !aRect.IsEmpty();
+ }
+
+ nsRect GetComponentAlphaBounds(nsDisplayListBuilder* aBuilder) const override;
+
+ RetainedDisplayList* GetSameCoordinateSystemChildren() const override {
+ return mListPtr;
+ }
+
+ RetainedDisplayList* GetChildren() const override { return mListPtr; }
+
+ int32_t ZIndex() const override {
+ return (mHasZIndexOverride) ? mOverrideZIndex
+ : nsPaintedDisplayItem::ZIndex();
+ }
+
+ void SetOverrideZIndex(int32_t aZIndex) {
+ mHasZIndexOverride = true;
+ mOverrideZIndex = aZIndex;
+ }
+
+ /**
+ * This creates a copy of this item, but wrapping aItem instead of
+ * our existing list. Only gets called if this item returned nullptr
+ * for GetUnderlyingFrame(). aItem is guaranteed to return non-null from
+ * GetUnderlyingFrame().
+ */
+ nsDisplayWrapList* WrapWithClone(nsDisplayListBuilder* aBuilder,
+ nsDisplayItem* aItem) {
+ MOZ_ASSERT_UNREACHABLE("We never returned nullptr for GetUnderlyingFrame!");
+ return nullptr;
+ }
+
+ bool CreateWebRenderCommands(
+ wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) override {
+ return CreateWebRenderCommandsNewClipListOption(
+ aBuilder, aResources, aSc, aManager, aDisplayListBuilder, true);
+ }
+
+ // Same as the above but with the option to pass the aNewClipList argument to
+ // WebRenderCommandBuilder::CreateWebRenderCommandsFromDisplayList.
+ bool CreateWebRenderCommandsNewClipListOption(
+ wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder, bool aNewClipList);
+
+ const ActiveScrolledRoot* GetFrameActiveScrolledRoot() {
+ return mFrameActiveScrolledRoot;
+ }
+
+ protected:
+ void MergeFromTrackingMergedFrames(const nsDisplayWrapList* aOther) {
+ mBounds.UnionRect(mBounds, aOther->mBounds);
+ nsRect buildingRect;
+ buildingRect.UnionRect(GetBuildingRect(), aOther->GetBuildingRect());
+ SetBuildingRect(buildingRect);
+ mMergedFrames.AppendElement(aOther->mFrame);
+ mMergedFrames.AppendElements(aOther->mMergedFrames.Clone());
+ }
+
+ RetainedDisplayList mList;
+ RetainedDisplayList* mListPtr;
+ // The active scrolled root for the frame that created this
+ // wrap list.
+ RefPtr<const ActiveScrolledRoot> mFrameActiveScrolledRoot;
+ // The frames from items that have been merged into this item, excluding
+ // this item's own frame.
+ nsTArray<nsIFrame*> mMergedFrames;
+ nsRect mBounds;
+ // Displaylist building rect contributed by this display item itself.
+ // Our mBuildingRect may include the visible areas of children.
+ nsRect mBaseBuildingRect;
+ RefPtr<const DisplayItemClipChain> mOriginalClipChain;
+ int32_t mOverrideZIndex;
+ bool mHasZIndexOverride;
+ bool mClearingClipChain = false;
+};
+
+class nsDisplayWrapper : public nsDisplayWrapList {
+ public:
+ NS_DISPLAY_DECL_NAME("WrapList", TYPE_WRAP_LIST)
+
+ nsDisplayWrapper(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ nsDisplayList* aList,
+ const ActiveScrolledRoot* aActiveScrolledRoot,
+ bool aClearClipChain = false)
+ : nsDisplayWrapList(aBuilder, aFrame, aList, aActiveScrolledRoot,
+ aClearClipChain) {}
+
+ nsDisplayWrapper(const nsDisplayWrapper& aOther) = delete;
+ nsDisplayWrapper(nsDisplayListBuilder* aBuilder,
+ const nsDisplayWrapList& aOther)
+ : nsDisplayWrapList(aBuilder, aOther) {}
+
+ void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override;
+
+ private:
+ NS_DISPLAY_ALLOW_CLONING()
+ friend class nsDisplayListBuilder;
+ friend class nsDisplayWrapList;
+};
+
+/**
+ * We call WrapDisplayList on the in-flow lists: BorderBackground(),
+ * BlockBorderBackgrounds() and Content().
+ * We call WrapDisplayItem on each item of Outlines(), PositionedDescendants(),
+ * and Floats(). This is done to support special wrapping processing for frames
+ * that may not be in-flow descendants of the current frame.
+ */
+class nsDisplayItemWrapper {
+ public:
+ // This is never instantiated directly (it has pure virtual methods), so no
+ // need to count constructors and destructors.
+
+ bool WrapBorderBackground() { return true; }
+ virtual nsDisplayItem* WrapList(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame, nsDisplayList* aList) = 0;
+ virtual nsDisplayItem* WrapItem(nsDisplayListBuilder* aBuilder,
+ nsDisplayItem* aItem) = 0;
+
+ nsresult WrapLists(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ const nsDisplayListSet& aIn, const nsDisplayListSet& aOut);
+ nsresult WrapListsInPlace(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ const nsDisplayListSet& aLists);
+
+ protected:
+ nsDisplayItemWrapper() = default;
+};
+
+/**
+ * The standard display item to paint a stacking context with translucency
+ * set by the stacking context root frame's 'opacity' style.
+ */
+class nsDisplayOpacity : public nsDisplayWrapList {
+ public:
+ nsDisplayOpacity(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ nsDisplayList* aList,
+ const ActiveScrolledRoot* aActiveScrolledRoot,
+ bool aForEventsOnly, bool aNeedsActiveLayer,
+ bool aWrapsBackdropFilter);
+
+ nsDisplayOpacity(nsDisplayListBuilder* aBuilder,
+ const nsDisplayOpacity& aOther)
+ : nsDisplayWrapList(aBuilder, aOther),
+ mOpacity(aOther.mOpacity),
+ mForEventsOnly(aOther.mForEventsOnly),
+ mNeedsActiveLayer(aOther.mNeedsActiveLayer),
+ mChildOpacityState(ChildOpacityState::Unknown),
+ mWrapsBackdropFilter(aOther.mWrapsBackdropFilter) {
+ MOZ_COUNT_CTOR(nsDisplayOpacity);
+ // We should not try to merge flattened opacities.
+ MOZ_ASSERT(aOther.mChildOpacityState != ChildOpacityState::Applied);
+ }
+
+ void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
+ HitTestState* aState, nsTArray<nsIFrame*>* aOutFrames) override;
+
+ MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayOpacity)
+
+ NS_DISPLAY_DECL_NAME("Opacity", TYPE_OPACITY)
+
+ void InvalidateCachedChildInfo(nsDisplayListBuilder* aBuilder) override {
+ mChildOpacityState = ChildOpacityState::Unknown;
+ }
+
+ nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
+ bool* aSnap) const override;
+ void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override;
+
+ bool CanMerge(const nsDisplayItem* aItem) const override {
+ // items for the same content element should be merged into a single
+ // compositing group
+ // aItem->GetUnderlyingFrame() returns non-null because it's
+ // nsDisplayOpacity
+ return HasDifferentFrame(aItem) && HasSameTypeAndClip(aItem) &&
+ HasSameContent(aItem);
+ }
+
+ nsDisplayItemGeometry* AllocateGeometry(
+ nsDisplayListBuilder* aBuilder) override {
+ return new nsDisplayOpacityGeometry(this, aBuilder, mOpacity);
+ }
+
+ void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
+ const nsDisplayItemGeometry* aGeometry,
+ nsRegion* aInvalidRegion) const override;
+
+ bool IsInvalid(nsRect& aRect) const override {
+ if (mForEventsOnly) {
+ return false;
+ }
+ return nsDisplayWrapList::IsInvalid(aRect);
+ }
+ bool CanApplyOpacity(WebRenderLayerManager* aManager,
+ nsDisplayListBuilder* aBuilder) const override;
+ bool ShouldFlattenAway(nsDisplayListBuilder* aBuilder) override {
+ return false;
+ }
+
+ bool CanApplyOpacityToChildren(WebRenderLayerManager* aManager,
+ nsDisplayListBuilder* aBuilder,
+ float aInheritedOpacity);
+
+ bool NeedsGeometryUpdates() const override {
+ // For flattened nsDisplayOpacity items, ComputeInvalidationRegion() only
+ // handles invalidation for changed |mOpacity|. In order to keep track of
+ // the current bounds of the item for invalidation, nsDisplayOpacityGeometry
+ // for the corresponding DisplayItemData needs to be updated, even if the
+ // reported invalidation region is empty.
+ return mChildOpacityState == ChildOpacityState::Deferred;
+ }
+
+ /**
+ * Returns true if ShouldFlattenAway() applied opacity to children.
+ */
+ bool OpacityAppliedToChildren() const {
+ return mChildOpacityState == ChildOpacityState::Applied;
+ }
+
+ static bool NeedsActiveLayer(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame);
+ void WriteDebugInfo(std::stringstream& aStream) override;
+ bool CanUseAsyncAnimations(nsDisplayListBuilder* aBuilder) override;
+ bool CreateWebRenderCommands(
+ wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) override;
+
+ float GetOpacity() const { return mOpacity; }
+
+ bool CreatesStackingContextHelper() override { return true; }
+
+ private:
+ NS_DISPLAY_ALLOW_CLONING()
+
+ bool CanApplyToChildren(WebRenderLayerManager* aManager,
+ nsDisplayListBuilder* aBuilder);
+ bool ApplyToMask();
+
+ float mOpacity;
+ bool mForEventsOnly : 1;
+ enum class ChildOpacityState : uint8_t {
+ // Our child list has changed since the last time ApplyToChildren was
+ // called.
+ Unknown,
+ // Our children defer opacity handling to us.
+ Deferred,
+ // Opacity is applied to our children.
+ Applied
+ };
+ bool mNeedsActiveLayer : 1;
+#ifndef __GNUC__
+ ChildOpacityState mChildOpacityState : 2;
+#else
+ ChildOpacityState mChildOpacityState;
+#endif
+ bool mWrapsBackdropFilter;
+};
+
+class nsDisplayBlendMode : public nsDisplayWrapList {
+ public:
+ nsDisplayBlendMode(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ nsDisplayList* aList, StyleBlend aBlendMode,
+ const ActiveScrolledRoot* aActiveScrolledRoot,
+ const bool aIsForBackground);
+ nsDisplayBlendMode(nsDisplayListBuilder* aBuilder,
+ const nsDisplayBlendMode& aOther)
+ : nsDisplayWrapList(aBuilder, aOther),
+ mBlendMode(aOther.mBlendMode),
+ mIsForBackground(aOther.mIsForBackground) {
+ MOZ_COUNT_CTOR(nsDisplayBlendMode);
+ }
+
+ MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayBlendMode)
+
+ NS_DISPLAY_DECL_NAME("BlendMode", TYPE_BLEND_MODE)
+
+ nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
+ bool* aSnap) const override;
+ void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
+ const nsDisplayItemGeometry* aGeometry,
+ nsRegion* aInvalidRegion) const override {
+ // We don't need to compute an invalidation region since we have
+ // LayerTreeInvalidation
+ }
+
+ bool CreateWebRenderCommands(
+ wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) override;
+ void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override;
+
+ bool CanMerge(const nsDisplayItem* aItem) const override;
+
+ bool ShouldFlattenAway(nsDisplayListBuilder* aBuilder) override {
+ return false;
+ }
+
+ gfx::CompositionOp BlendMode();
+
+ bool CreatesStackingContextHelper() override { return true; }
+
+ protected:
+ StyleBlend mBlendMode;
+ bool mIsForBackground;
+
+ private:
+ NS_DISPLAY_ALLOW_CLONING()
+};
+
+class nsDisplayTableBlendMode : public nsDisplayBlendMode {
+ public:
+ nsDisplayTableBlendMode(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ nsDisplayList* aList, StyleBlend aBlendMode,
+ const ActiveScrolledRoot* aActiveScrolledRoot,
+ nsIFrame* aAncestorFrame, const bool aIsForBackground)
+ : nsDisplayBlendMode(aBuilder, aFrame, aList, aBlendMode,
+ aActiveScrolledRoot, aIsForBackground),
+ mAncestorFrame(aAncestorFrame) {
+ if (aBuilder->IsRetainingDisplayList()) {
+ mAncestorFrame->AddDisplayItem(this);
+ }
+ }
+
+ nsDisplayTableBlendMode(nsDisplayListBuilder* aBuilder,
+ const nsDisplayTableBlendMode& aOther)
+ : nsDisplayBlendMode(aBuilder, aOther),
+ mAncestorFrame(aOther.mAncestorFrame) {
+ if (aBuilder->IsRetainingDisplayList()) {
+ mAncestorFrame->AddDisplayItem(this);
+ }
+ }
+
+ ~nsDisplayTableBlendMode() override {
+ if (mAncestorFrame) {
+ mAncestorFrame->RemoveDisplayItem(this);
+ }
+ }
+
+ NS_DISPLAY_DECL_NAME("TableBlendMode", TYPE_TABLE_BLEND_MODE)
+
+ nsIFrame* FrameForInvalidation() const override { return mAncestorFrame; }
+
+ void RemoveFrame(nsIFrame* aFrame) override {
+ if (aFrame == mAncestorFrame) {
+ mAncestorFrame = nullptr;
+ SetDeletedFrame();
+ }
+ nsDisplayBlendMode::RemoveFrame(aFrame);
+ }
+
+ protected:
+ nsIFrame* mAncestorFrame;
+
+ private:
+ NS_DISPLAY_ALLOW_CLONING()
+};
+
+class nsDisplayBlendContainer : public nsDisplayWrapList {
+ public:
+ static nsDisplayBlendContainer* CreateForMixBlendMode(
+ nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayList* aList,
+ const ActiveScrolledRoot* aActiveScrolledRoot);
+
+ static nsDisplayBlendContainer* CreateForBackgroundBlendMode(
+ nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ nsIFrame* aSecondaryFrame, nsDisplayList* aList,
+ const ActiveScrolledRoot* aActiveScrolledRoot);
+
+ MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayBlendContainer)
+
+ NS_DISPLAY_DECL_NAME("BlendContainer", TYPE_BLEND_CONTAINER)
+
+ void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override;
+ bool CreateWebRenderCommands(
+ wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) override;
+
+ bool CanMerge(const nsDisplayItem* aItem) const override {
+ // Items for the same content element should be merged into a single
+ // compositing group.
+ return HasDifferentFrame(aItem) && HasSameTypeAndClip(aItem) &&
+ HasSameContent(aItem) &&
+ mIsForBackground ==
+ static_cast<const nsDisplayBlendContainer*>(aItem)
+ ->mIsForBackground;
+ }
+
+ bool ShouldFlattenAway(nsDisplayListBuilder* aBuilder) override {
+ return false;
+ }
+
+ bool CreatesStackingContextHelper() override { return true; }
+
+ protected:
+ nsDisplayBlendContainer(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ nsDisplayList* aList,
+ const ActiveScrolledRoot* aActiveScrolledRoot,
+ bool aIsForBackground);
+ nsDisplayBlendContainer(nsDisplayListBuilder* aBuilder,
+ const nsDisplayBlendContainer& aOther)
+ : nsDisplayWrapList(aBuilder, aOther),
+ mIsForBackground(aOther.mIsForBackground) {
+ MOZ_COUNT_CTOR(nsDisplayBlendContainer);
+ }
+
+ // Used to distinguish containers created at building stacking
+ // context or appending background.
+ bool mIsForBackground;
+
+ private:
+ NS_DISPLAY_ALLOW_CLONING()
+};
+
+class nsDisplayTableBlendContainer : public nsDisplayBlendContainer {
+ public:
+ NS_DISPLAY_DECL_NAME("TableBlendContainer", TYPE_TABLE_BLEND_CONTAINER)
+
+ nsIFrame* FrameForInvalidation() const override { return mAncestorFrame; }
+
+ void RemoveFrame(nsIFrame* aFrame) override {
+ if (aFrame == mAncestorFrame) {
+ mAncestorFrame = nullptr;
+ SetDeletedFrame();
+ }
+ nsDisplayBlendContainer::RemoveFrame(aFrame);
+ }
+
+ protected:
+ nsDisplayTableBlendContainer(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ nsDisplayList* aList,
+ const ActiveScrolledRoot* aActiveScrolledRoot,
+ bool aIsForBackground, nsIFrame* aAncestorFrame)
+ : nsDisplayBlendContainer(aBuilder, aFrame, aList, aActiveScrolledRoot,
+ aIsForBackground),
+ mAncestorFrame(aAncestorFrame) {
+ if (aBuilder->IsRetainingDisplayList()) {
+ mAncestorFrame->AddDisplayItem(this);
+ }
+ }
+
+ nsDisplayTableBlendContainer(nsDisplayListBuilder* aBuilder,
+ const nsDisplayTableBlendContainer& aOther)
+ : nsDisplayBlendContainer(aBuilder, aOther),
+ mAncestorFrame(aOther.mAncestorFrame) {}
+
+ ~nsDisplayTableBlendContainer() override {
+ if (mAncestorFrame) {
+ mAncestorFrame->RemoveDisplayItem(this);
+ }
+ }
+
+ nsIFrame* mAncestorFrame;
+
+ private:
+ NS_DISPLAY_ALLOW_CLONING()
+};
+
+/**
+ * nsDisplayOwnLayer constructor flags. If we nest this class inside
+ * nsDisplayOwnLayer then we can't forward-declare it up at the top of this
+ * file and that makes it hard to use in all the places that we need to use it.
+ */
+enum class nsDisplayOwnLayerFlags {
+ None = 0,
+ GenerateSubdocInvalidations = 1 << 0,
+ GenerateScrollableLayer = 1 << 1,
+};
+
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(nsDisplayOwnLayerFlags)
+
+/**
+ * A display item that has no purpose but to ensure its contents get
+ * their own layer.
+ */
+class nsDisplayOwnLayer : public nsDisplayWrapList {
+ public:
+ enum OwnLayerType {
+ OwnLayerForTransformWithRoundedClip,
+ OwnLayerForStackingContext,
+ OwnLayerForScrollbar,
+ OwnLayerForScrollThumb,
+ OwnLayerForSubdoc,
+ OwnLayerForBoxFrame
+ };
+
+ /**
+ * @param aFlags eGenerateSubdocInvalidations :
+ * Add UserData to the created ContainerLayer, so that invalidations
+ * for this layer are send to our nsPresContext.
+ * eGenerateScrollableLayer : only valid on nsDisplaySubDocument (and
+ * subclasses), indicates this layer is to be a scrollable layer, so call
+ * ComputeFrameMetrics, etc.
+ * @param aScrollTarget when eVerticalScrollbar or eHorizontalScrollbar
+ * is set in the flags, this parameter should be the ViewID of the
+ * scrollable content this scrollbar is for.
+ */
+ nsDisplayOwnLayer(
+ nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayList* aList,
+ const ActiveScrolledRoot* aActiveScrolledRoot,
+ nsDisplayOwnLayerFlags aFlags = nsDisplayOwnLayerFlags::None,
+ const layers::ScrollbarData& aScrollbarData = layers::ScrollbarData{},
+ bool aForceActive = true, bool aClearClipChain = false);
+
+ nsDisplayOwnLayer(nsDisplayListBuilder* aBuilder,
+ const nsDisplayOwnLayer& aOther)
+ : nsDisplayWrapList(aBuilder, aOther),
+ mFlags(aOther.mFlags),
+ mScrollbarData(aOther.mScrollbarData),
+ mForceActive(aOther.mForceActive),
+ mWrAnimationId(aOther.mWrAnimationId) {
+ MOZ_COUNT_CTOR(nsDisplayOwnLayer);
+ }
+
+ MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayOwnLayer)
+
+ NS_DISPLAY_DECL_NAME("OwnLayer", TYPE_OWN_LAYER)
+
+ bool CreateWebRenderCommands(
+ wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) override;
+ bool UpdateScrollData(layers::WebRenderScrollData* aData,
+ layers::WebRenderLayerScrollData* aLayerData) override;
+ void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override {
+ GetChildren()->Paint(aBuilder, aCtx,
+ mFrame->PresContext()->AppUnitsPerDevPixel());
+ }
+
+ bool CanMerge(const nsDisplayItem* aItem) const override {
+ // Don't allow merging, each sublist must have its own layer
+ return false;
+ }
+
+ bool ShouldFlattenAway(nsDisplayListBuilder* aBuilder) override {
+ return false;
+ }
+
+ void WriteDebugInfo(std::stringstream& aStream) override;
+ nsDisplayOwnLayerFlags GetFlags() { return mFlags; }
+ bool IsScrollThumbLayer() const;
+ bool IsScrollbarContainer() const;
+ bool IsRootScrollbarContainer() const;
+ bool IsZoomingLayer() const;
+ bool IsFixedPositionLayer() const;
+ bool IsStickyPositionLayer() const;
+ bool HasDynamicToolbar() const;
+ bool ShouldFixedAndStickyContentGetAnimationIds() const;
+
+ bool CreatesStackingContextHelper() override { return true; }
+
+ protected:
+ nsDisplayOwnLayerFlags mFlags;
+
+ /**
+ * If this nsDisplayOwnLayer represents a scroll thumb layer or a
+ * scrollbar container layer, mScrollbarData stores information
+ * about the scrollbar. Otherwise, mScrollbarData will be
+ * default-constructed (in particular with mDirection == Nothing())
+ * and can be ignored.
+ */
+ layers::ScrollbarData mScrollbarData;
+ bool mForceActive;
+ uint64_t mWrAnimationId;
+};
+
+/**
+ * A display item for subdocuments. This is more or less the same as
+ * nsDisplayOwnLayer, except that it always populates the FrameMetrics instance
+ * on the ContainerLayer it builds.
+ */
+class nsDisplaySubDocument : public nsDisplayOwnLayer {
+ public:
+ nsDisplaySubDocument(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ nsSubDocumentFrame* aSubDocFrame, nsDisplayList* aList,
+ nsDisplayOwnLayerFlags aFlags);
+ ~nsDisplaySubDocument() override;
+
+ NS_DISPLAY_DECL_NAME("SubDocument", TYPE_SUBDOCUMENT)
+
+ nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) const override;
+
+ virtual nsSubDocumentFrame* SubDocumentFrame() { return mSubDocFrame; }
+
+ bool ShouldFlattenAway(nsDisplayListBuilder* aBuilder) override {
+ return mShouldFlatten;
+ }
+
+ void SetShouldFlattenAway(bool aShouldFlatten) {
+ mShouldFlatten = aShouldFlatten;
+ }
+
+ nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
+ bool* aSnap) const override;
+
+ nsIFrame* FrameForInvalidation() const override;
+ void RemoveFrame(nsIFrame* aFrame) override;
+
+ protected:
+ ViewID mScrollParentId;
+ bool mForceDispatchToContentRegion{};
+ bool mShouldFlatten;
+ nsSubDocumentFrame* mSubDocFrame;
+};
+
+/**
+ * A display item used to represent sticky position elements. The contents
+ * gets its own layer and creates a stacking context, and the layer will have
+ * position-related metadata set on it.
+ */
+class nsDisplayStickyPosition : public nsDisplayOwnLayer {
+ public:
+ nsDisplayStickyPosition(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ nsDisplayList* aList,
+ const ActiveScrolledRoot* aActiveScrolledRoot,
+ const ActiveScrolledRoot* aContainerASR,
+ bool aClippedToDisplayPort);
+ nsDisplayStickyPosition(nsDisplayListBuilder* aBuilder,
+ const nsDisplayStickyPosition& aOther)
+ : nsDisplayOwnLayer(aBuilder, aOther),
+ mContainerASR(aOther.mContainerASR),
+ mClippedToDisplayPort(aOther.mClippedToDisplayPort),
+ mShouldFlatten(false) {
+ MOZ_COUNT_CTOR(nsDisplayStickyPosition);
+ }
+
+ MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayStickyPosition)
+
+ const DisplayItemClip& GetClip() const override {
+ return DisplayItemClip::NoClip();
+ }
+ bool IsClippedToDisplayPort() const { return mClippedToDisplayPort; }
+
+ NS_DISPLAY_DECL_NAME("StickyPosition", TYPE_STICKY_POSITION)
+ void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override {
+ GetChildren()->Paint(aBuilder, aCtx,
+ mFrame->PresContext()->AppUnitsPerDevPixel());
+ }
+
+ bool CreateWebRenderCommands(
+ wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) override;
+
+ bool UpdateScrollData(layers::WebRenderScrollData* aData,
+ layers::WebRenderLayerScrollData* aLayerData) override;
+
+ const ActiveScrolledRoot* GetContainerASR() const { return mContainerASR; }
+
+ bool CreatesStackingContextHelper() override { return true; }
+
+ bool CanMoveAsync() override { return true; }
+
+ void SetShouldFlatten(bool aShouldFlatten) {
+ mShouldFlatten = aShouldFlatten;
+ }
+
+ bool ShouldFlattenAway(nsDisplayListBuilder* aBuilder) final {
+ return mShouldFlatten;
+ }
+
+ private:
+ NS_DISPLAY_ALLOW_CLONING()
+
+ void CalculateLayerScrollRanges(StickyScrollContainer* aStickyScrollContainer,
+ float aAppUnitsPerDevPixel, float aScaleX,
+ float aScaleY,
+ LayerRectAbsolute& aStickyOuter,
+ LayerRectAbsolute& aStickyInner);
+
+ StickyScrollContainer* GetStickyScrollContainer();
+
+ // This stores the ASR that this sticky container item would have assuming it
+ // has no fixed descendants. This may be the same as the ASR returned by
+ // GetActiveScrolledRoot(), or it may be a descendant of that.
+ RefPtr<const ActiveScrolledRoot> mContainerASR;
+ // This flag tracks if this sticky item is just clipped to the enclosing
+ // scrollframe's displayport, or if there are additional clips in play. In
+ // the former case, we can skip setting the displayport clip as the scrolled-
+ // clip of the corresponding layer. This allows sticky items to remain
+ // unclipped when the enclosing scrollframe is scrolled past the displayport.
+ // i.e. when the rest of the scrollframe checkerboards, the sticky item will
+ // not. This makes sense to do because the sticky item has abnormal scrolling
+ // behavior and may still be visible even if the rest of the scrollframe is
+ // checkerboarded. Note that the sticky item will still be subject to the
+ // scrollport clip.
+ bool mClippedToDisplayPort;
+
+ // True if this item should be flattened away.
+ bool mShouldFlatten;
+};
+
+class nsDisplayFixedPosition : public nsDisplayOwnLayer {
+ public:
+ nsDisplayFixedPosition(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ nsDisplayList* aList,
+ const ActiveScrolledRoot* aActiveScrolledRoot,
+ const ActiveScrolledRoot* aScrollTargetASR);
+ nsDisplayFixedPosition(nsDisplayListBuilder* aBuilder,
+ const nsDisplayFixedPosition& aOther)
+ : nsDisplayOwnLayer(aBuilder, aOther),
+ mScrollTargetASR(aOther.mScrollTargetASR),
+ mIsFixedBackground(aOther.mIsFixedBackground) {
+ MOZ_COUNT_CTOR(nsDisplayFixedPosition);
+ }
+
+ static nsDisplayFixedPosition* CreateForFixedBackground(
+ nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ nsIFrame* aSecondaryFrame, nsDisplayBackgroundImage* aImage,
+ const uint16_t aIndex, const ActiveScrolledRoot* aScrollTargetASR);
+
+ MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayFixedPosition)
+
+ NS_DISPLAY_DECL_NAME("FixedPosition", TYPE_FIXED_POSITION)
+
+ void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override {
+ GetChildren()->Paint(aBuilder, aCtx,
+ mFrame->PresContext()->AppUnitsPerDevPixel());
+ }
+
+ bool ShouldFixToViewport(nsDisplayListBuilder* aBuilder) const override {
+ return mIsFixedBackground;
+ }
+
+ bool CreateWebRenderCommands(
+ wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) override;
+ bool UpdateScrollData(layers::WebRenderScrollData* aData,
+ layers::WebRenderLayerScrollData* aLayerData) override;
+ void WriteDebugInfo(std::stringstream& aStream) override;
+
+ protected:
+ // For background-attachment:fixed
+ nsDisplayFixedPosition(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ nsDisplayList* aList,
+ const ActiveScrolledRoot* aScrollTargetASR);
+ ViewID GetScrollTargetId();
+
+ RefPtr<const ActiveScrolledRoot> mScrollTargetASR;
+ bool mIsFixedBackground;
+
+ private:
+ NS_DISPLAY_ALLOW_CLONING()
+};
+
+class nsDisplayTableFixedPosition : public nsDisplayFixedPosition {
+ public:
+ NS_DISPLAY_DECL_NAME("TableFixedPosition", TYPE_TABLE_FIXED_POSITION)
+
+ nsIFrame* FrameForInvalidation() const override { return mAncestorFrame; }
+
+ void RemoveFrame(nsIFrame* aFrame) override {
+ if (aFrame == mAncestorFrame) {
+ mAncestorFrame = nullptr;
+ SetDeletedFrame();
+ }
+ nsDisplayFixedPosition::RemoveFrame(aFrame);
+ }
+
+ protected:
+ nsDisplayTableFixedPosition(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ nsDisplayList* aList, nsIFrame* aAncestorFrame,
+ const ActiveScrolledRoot* aScrollTargetASR);
+
+ nsDisplayTableFixedPosition(nsDisplayListBuilder* aBuilder,
+ const nsDisplayTableFixedPosition& aOther)
+ : nsDisplayFixedPosition(aBuilder, aOther),
+ mAncestorFrame(aOther.mAncestorFrame) {}
+
+ ~nsDisplayTableFixedPosition() override {
+ if (mAncestorFrame) {
+ mAncestorFrame->RemoveDisplayItem(this);
+ }
+ }
+
+ nsIFrame* mAncestorFrame;
+
+ private:
+ NS_DISPLAY_ALLOW_CLONING()
+};
+
+/**
+ * This creates an empty scrollable layer. It has no child layers.
+ * It is used to record the existence of a scrollable frame in the layer
+ * tree.
+ */
+class nsDisplayScrollInfoLayer : public nsDisplayWrapList {
+ public:
+ nsDisplayScrollInfoLayer(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aScrolledFrame, nsIFrame* aScrollFrame,
+ const gfx::CompositorHitTestInfo& aHitInfo,
+ const nsRect& aHitArea);
+
+ MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayScrollInfoLayer)
+
+ NS_DISPLAY_DECL_NAME("ScrollInfoLayer", TYPE_SCROLL_INFO_LAYER)
+
+ nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
+ bool* aSnap) const override {
+ *aSnap = false;
+ return nsRegion();
+ }
+
+ void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override {
+ return;
+ }
+
+ bool ShouldFlattenAway(nsDisplayListBuilder* aBuilder) override {
+ return false;
+ }
+
+ void WriteDebugInfo(std::stringstream& aStream) override;
+ UniquePtr<layers::ScrollMetadata> ComputeScrollMetadata(
+ nsDisplayListBuilder* aBuilder,
+ layers::WebRenderLayerManager* aLayerManager);
+ bool UpdateScrollData(layers::WebRenderScrollData* aData,
+ layers::WebRenderLayerScrollData* aLayerData) override;
+ bool CreateWebRenderCommands(
+ wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) override;
+
+ protected:
+ nsIFrame* mScrollFrame;
+ nsIFrame* mScrolledFrame;
+ ViewID mScrollParentId;
+ gfx::CompositorHitTestInfo mHitInfo;
+ nsRect mHitArea;
+};
+
+/**
+ * nsDisplayZoom is used for subdocuments that have a different full zoom than
+ * their parent documents. This item creates a container layer.
+ */
+class nsDisplayZoom : public nsDisplaySubDocument {
+ public:
+ /**
+ * @param aFrame is the root frame of the subdocument.
+ * @param aList contains the display items for the subdocument.
+ * @param aAPD is the app units per dev pixel ratio of the subdocument.
+ * @param aParentAPD is the app units per dev pixel ratio of the parent
+ * document.
+ * @param aFlags eGenerateSubdocInvalidations :
+ * Add UserData to the created ContainerLayer, so that invalidations
+ * for this layer are send to our nsPresContext.
+ */
+ nsDisplayZoom(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ nsSubDocumentFrame* aSubDocFrame, nsDisplayList* aList,
+ int32_t aAPD, int32_t aParentAPD,
+ nsDisplayOwnLayerFlags aFlags = nsDisplayOwnLayerFlags::None);
+
+ MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayZoom)
+
+ NS_DISPLAY_DECL_NAME("Zoom", TYPE_ZOOM)
+
+ nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) const override;
+ void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
+ HitTestState* aState, nsTArray<nsIFrame*>* aOutFrames) override;
+ // Get the app units per dev pixel ratio of the child document.
+ int32_t GetChildAppUnitsPerDevPixel() { return mAPD; }
+ // Get the app units per dev pixel ratio of the parent document.
+ int32_t GetParentAppUnitsPerDevPixel() { return mParentAPD; }
+
+ private:
+ int32_t mAPD, mParentAPD;
+};
+
+/**
+ * nsDisplayAsyncZoom is used for APZ zooming. It wraps the contents of the
+ * root content document's scroll frame, including fixed position content. It
+ * does not contain the scroll frame's scrollbars. It is clipped to the scroll
+ * frame's scroll port clip. It is not scrolled; only its non-fixed contents
+ * are scrolled. This item creates a container layer.
+ */
+class nsDisplayAsyncZoom : public nsDisplayOwnLayer {
+ public:
+ nsDisplayAsyncZoom(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ nsDisplayList* aList,
+ const ActiveScrolledRoot* aActiveScrolledRoot,
+ layers::FrameMetrics::ViewID aViewID);
+ nsDisplayAsyncZoom(nsDisplayListBuilder* aBuilder,
+ const nsDisplayAsyncZoom& aOther)
+ : nsDisplayOwnLayer(aBuilder, aOther), mViewID(aOther.mViewID) {
+ MOZ_COUNT_CTOR(nsDisplayAsyncZoom);
+ }
+
+#ifdef NS_BUILD_REFCNT_LOGGING
+ virtual ~nsDisplayAsyncZoom();
+#endif
+
+ NS_DISPLAY_DECL_NAME("AsyncZoom", TYPE_ASYNC_ZOOM)
+
+ void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
+ HitTestState* aState, nsTArray<nsIFrame*>* aOutFrames) override;
+ bool UpdateScrollData(layers::WebRenderScrollData* aData,
+ layers::WebRenderLayerScrollData* aLayerData) override;
+
+ protected:
+ layers::FrameMetrics::ViewID mViewID;
+};
+
+/**
+ * A base class for different effects types.
+ */
+class nsDisplayEffectsBase : public nsDisplayWrapList {
+ public:
+ nsDisplayEffectsBase(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ nsDisplayList* aList,
+ const ActiveScrolledRoot* aActiveScrolledRoot,
+ bool aClearClipChain = false);
+ nsDisplayEffectsBase(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ nsDisplayList* aList);
+
+ nsDisplayEffectsBase(nsDisplayListBuilder* aBuilder,
+ const nsDisplayEffectsBase& aOther)
+ : nsDisplayWrapList(aBuilder, aOther),
+ mEffectsBounds(aOther.mEffectsBounds) {
+ MOZ_COUNT_CTOR(nsDisplayEffectsBase);
+ }
+
+ MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayEffectsBase)
+
+ nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
+ bool* aSnap) const override;
+ void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
+ HitTestState* aState, nsTArray<nsIFrame*>* aOutFrames) override;
+
+ bool ShouldFlattenAway(nsDisplayListBuilder* aBuilder) override {
+ return false;
+ }
+
+ gfxRect BBoxInUserSpace() const;
+ gfxPoint UserSpaceOffset() const;
+
+ void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
+ const nsDisplayItemGeometry* aGeometry,
+ nsRegion* aInvalidRegion) const override;
+
+ protected:
+ bool ValidateSVGFrame();
+
+ // relative to mFrame
+ nsRect mEffectsBounds;
+};
+
+/**
+ * A display item to paint a stacking context with 'mask' and 'clip-path'
+ * effects set by the stacking context root frame's style. The 'mask' and
+ * 'clip-path' properties may both contain multiple masks and clip paths,
+ * respectively.
+ *
+ * Note that 'mask' and 'clip-path' may just contain CSS simple-images and CSS
+ * basic shapes, respectively. That is, they don't necessarily reference
+ * resources such as SVG 'mask' and 'clipPath' elements.
+ */
+class nsDisplayMasksAndClipPaths : public nsDisplayEffectsBase {
+ public:
+ nsDisplayMasksAndClipPaths(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ nsDisplayList* aList,
+ const ActiveScrolledRoot* aActiveScrolledRoot,
+ bool aWrapsBackdropFilter);
+ nsDisplayMasksAndClipPaths(nsDisplayListBuilder* aBuilder,
+ const nsDisplayMasksAndClipPaths& aOther)
+ : nsDisplayEffectsBase(aBuilder, aOther),
+ mDestRects(aOther.mDestRects.Clone()),
+ mWrapsBackdropFilter(aOther.mWrapsBackdropFilter) {
+ MOZ_COUNT_CTOR(nsDisplayMasksAndClipPaths);
+ }
+
+ MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayMasksAndClipPaths)
+
+ NS_DISPLAY_DECL_NAME("Mask", TYPE_MASK)
+
+ bool CanMerge(const nsDisplayItem* aItem) const override;
+
+ void Merge(const nsDisplayItem* aItem) override {
+ nsDisplayWrapList::Merge(aItem);
+
+ const nsDisplayMasksAndClipPaths* other =
+ static_cast<const nsDisplayMasksAndClipPaths*>(aItem);
+ mEffectsBounds.UnionRect(
+ mEffectsBounds,
+ other->mEffectsBounds + other->mFrame->GetOffsetTo(mFrame));
+ }
+
+ void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override;
+
+ nsDisplayItemGeometry* AllocateGeometry(
+ nsDisplayListBuilder* aBuilder) override {
+ return new nsDisplayMasksAndClipPathsGeometry(this, aBuilder);
+ }
+
+ void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
+ const nsDisplayItemGeometry* aGeometry,
+ nsRegion* aInvalidRegion) const override;
+#ifdef MOZ_DUMP_PAINTING
+ void PrintEffects(nsACString& aTo);
+#endif
+
+ bool IsValidMask();
+
+ void PaintWithContentsPaintCallback(
+ nsDisplayListBuilder* aBuilder, gfxContext* aCtx,
+ const std::function<void()>& aPaintChildren);
+
+ /*
+ * Paint mask onto aMaskContext in mFrame's coordinate space and
+ * return whether the mask layer was painted successfully.
+ */
+ bool PaintMask(nsDisplayListBuilder* aBuilder, gfxContext* aMaskContext,
+ bool aHandleOpacity, bool* aMaskPainted = nullptr);
+
+ const nsTArray<nsRect>& GetDestRects() { return mDestRects; }
+
+ bool CreateWebRenderCommands(
+ wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) override;
+
+ Maybe<nsRect> GetClipWithRespectToASR(
+ nsDisplayListBuilder* aBuilder,
+ const ActiveScrolledRoot* aASR) const override;
+
+ bool CreatesStackingContextHelper() override { return true; }
+
+ private:
+ NS_DISPLAY_ALLOW_CLONING()
+
+ nsTArray<nsRect> mDestRects;
+ bool mWrapsBackdropFilter;
+};
+
+class nsDisplayBackdropFilters : public nsDisplayWrapList {
+ public:
+ nsDisplayBackdropFilters(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ nsDisplayList* aList, const nsRect& aBackdropRect,
+ nsIFrame* aStyleFrame)
+ : nsDisplayWrapList(aBuilder, aFrame, aList),
+ mStyle(aFrame == aStyleFrame ? nullptr : aStyleFrame->Style()),
+ mBackdropRect(aBackdropRect) {
+ MOZ_COUNT_CTOR(nsDisplayBackdropFilters);
+ }
+
+ MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayBackdropFilters)
+
+ NS_DISPLAY_DECL_NAME("BackdropFilter", TYPE_BACKDROP_FILTER)
+
+ bool CreateWebRenderCommands(
+ wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) override;
+ void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override;
+
+ bool ShouldFlattenAway(nsDisplayListBuilder* aBuilder) override {
+ return !aBuilder->IsPaintingForWebRender();
+ }
+
+ bool CreatesStackingContextHelper() override { return true; }
+
+ nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) const override;
+
+ private:
+ RefPtr<ComputedStyle> mStyle;
+ nsRect mBackdropRect;
+};
+
+/**
+ * A display item to paint a stacking context with filter effects set by the
+ * stacking context root frame's style.
+ *
+ * Note that the filters may just be simple CSS filter functions. That is,
+ * they won't necessarily be references to SVG 'filter' elements.
+ */
+class nsDisplayFilters : public nsDisplayEffectsBase {
+ public:
+ nsDisplayFilters(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ nsDisplayList* aList, nsIFrame* aStyleFrame,
+ bool aWrapsBackdropFilter);
+
+ nsDisplayFilters(nsDisplayListBuilder* aBuilder,
+ const nsDisplayFilters& aOther)
+ : nsDisplayEffectsBase(aBuilder, aOther),
+ mStyle(aOther.mStyle),
+ mEffectsBounds(aOther.mEffectsBounds),
+ mWrapsBackdropFilter(aOther.mWrapsBackdropFilter) {
+ MOZ_COUNT_CTOR(nsDisplayFilters);
+ }
+
+ MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayFilters)
+
+ NS_DISPLAY_DECL_NAME("Filter", TYPE_FILTER)
+
+ bool CanMerge(const nsDisplayItem* aItem) const override {
+ // Items for the same content element should be merged into a single
+ // compositing group.
+ return HasDifferentFrame(aItem) && HasSameTypeAndClip(aItem) &&
+ HasSameContent(aItem);
+ }
+
+ void Merge(const nsDisplayItem* aItem) override {
+ nsDisplayWrapList::Merge(aItem);
+
+ const nsDisplayFilters* other = static_cast<const nsDisplayFilters*>(aItem);
+ mEffectsBounds.UnionRect(
+ mEffectsBounds,
+ other->mEffectsBounds + other->mFrame->GetOffsetTo(mFrame));
+ }
+
+ void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override;
+
+ nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) const override {
+ *aSnap = false;
+ return mEffectsBounds + ToReferenceFrame();
+ }
+
+ nsDisplayItemGeometry* AllocateGeometry(
+ nsDisplayListBuilder* aBuilder) override {
+ return new nsDisplaySVGEffectGeometry(this, aBuilder);
+ }
+
+#ifdef MOZ_DUMP_PAINTING
+ void PrintEffects(nsACString& aTo);
+#endif
+
+ void PaintWithContentsPaintCallback(
+ nsDisplayListBuilder* aBuilder, gfxContext* aCtx,
+ const std::function<void(gfxContext* aContext)>& aPaintChildren);
+
+ bool CreateWebRenderCommands(
+ wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) override;
+ bool CanCreateWebRenderCommands() const;
+
+ bool CanApplyOpacity(WebRenderLayerManager* aManager,
+ nsDisplayListBuilder* aBuilder) const override {
+ return CanCreateWebRenderCommands();
+ }
+
+ bool CreatesStackingContextHelper() override { return true; }
+
+ private:
+ NS_DISPLAY_ALLOW_CLONING()
+
+ RefPtr<ComputedStyle> mStyle;
+ // relative to mFrame
+ nsRect mEffectsBounds;
+ nsRect mVisibleRect;
+ bool mWrapsBackdropFilter;
+};
+
+/* A display item that applies a transformation to all of its descendant
+ * elements. This wrapper should only be used if there is a transform applied
+ * to the root element.
+ *
+ * The reason that a "bounds" rect is involved in transform calculations is
+ * because CSS-transforms allow percentage values for the x and y components
+ * of <translation-value>s, where percentages are percentages of the element's
+ * border box.
+ *
+ * INVARIANT: The wrapped frame is transformed or we supplied a transform getter
+ * function.
+ * INVARIANT: The wrapped frame is non-null.
+ */
+class nsDisplayTransform : public nsPaintedDisplayItem {
+ using Matrix4x4 = gfx::Matrix4x4;
+ using Matrix4x4Flagged = gfx::Matrix4x4Flagged;
+ using TransformReferenceBox = nsStyleTransformMatrix::TransformReferenceBox;
+
+ public:
+ enum class PrerenderDecision : uint8_t { No, Full, Partial };
+
+ enum {
+ WithTransformGetter,
+ };
+
+ /* Constructor accepts a display list, empties it, and wraps it up. It also
+ * ferries the underlying frame to the nsDisplayItem constructor.
+ */
+ nsDisplayTransform(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ nsDisplayList* aList, const nsRect& aChildrenBuildingRect);
+
+ nsDisplayTransform(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ nsDisplayList* aList, const nsRect& aChildrenBuildingRect,
+ PrerenderDecision aPrerenderDecision);
+
+ nsDisplayTransform(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ nsDisplayList* aList, const nsRect& aChildrenBuildingRect,
+ decltype(WithTransformGetter));
+
+ MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayTransform)
+
+ NS_DISPLAY_DECL_NAME("nsDisplayTransform", TYPE_TRANSFORM)
+
+ void UpdateBounds(nsDisplayListBuilder* aBuilder) override;
+
+ /**
+ * This function updates bounds for items with a frame establishing
+ * 3D rendering context.
+ */
+ void UpdateBoundsFor3D(nsDisplayListBuilder* aBuilder);
+
+ void DoUpdateBoundsPreserves3D(nsDisplayListBuilder* aBuilder) override;
+
+ void Destroy(nsDisplayListBuilder* aBuilder) override {
+ GetChildren()->DeleteAll(aBuilder);
+ nsPaintedDisplayItem::Destroy(aBuilder);
+ }
+
+ nsRect GetComponentAlphaBounds(nsDisplayListBuilder* aBuilder) const override;
+
+ RetainedDisplayList* GetChildren() const override { return &mChildren; }
+
+ nsRect GetUntransformedBounds(nsDisplayListBuilder* aBuilder,
+ bool* aSnap) const override {
+ *aSnap = false;
+ return mChildBounds;
+ }
+
+ const nsRect& GetUntransformedPaintRect() const override {
+ return mChildrenBuildingRect;
+ }
+
+ bool ShouldFlattenAway(nsDisplayListBuilder* aBuilder) override;
+
+ void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
+ HitTestState* aState, nsTArray<nsIFrame*>* aOutFrames) override;
+ nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) const override;
+ nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
+ bool* aSnap) const override;
+ void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override;
+ void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx,
+ const Maybe<gfx::Polygon>& aPolygon);
+ bool CreateWebRenderCommands(
+ wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) override;
+ bool UpdateScrollData(layers::WebRenderScrollData* aData,
+ layers::WebRenderLayerScrollData* aLayerData) override;
+
+ nsDisplayItemGeometry* AllocateGeometry(
+ nsDisplayListBuilder* aBuilder) override {
+ return new nsDisplayTransformGeometry(
+ this, aBuilder, GetTransformForRendering(),
+ mFrame->PresContext()->AppUnitsPerDevPixel());
+ }
+
+ void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
+ const nsDisplayItemGeometry* aGeometry,
+ nsRegion* aInvalidRegion) const override {
+ const nsDisplayTransformGeometry* geometry =
+ static_cast<const nsDisplayTransformGeometry*>(aGeometry);
+
+ // This code is only called for flattened, inactive transform items.
+ // Only check if the transform has changed. The bounds invalidation should
+ // be handled by the children themselves.
+ if (!geometry->mTransform.FuzzyEqual(GetTransformForRendering())) {
+ bool snap;
+ aInvalidRegion->Or(GetBounds(aBuilder, &snap), geometry->mBounds);
+ }
+ }
+
+ const nsIFrame* ReferenceFrameForChildren() const override {
+ // If we were created using a transform-getter, then we don't
+ // belong to a transformed frame, and aren't a reference frame
+ // for our children.
+ if (!mHasTransformGetter) {
+ return mFrame;
+ }
+ return nsPaintedDisplayItem::ReferenceFrameForChildren();
+ }
+
+ const nsRect& GetBuildingRectForChildren() const override {
+ return mChildrenBuildingRect;
+ }
+
+ enum { INDEX_MAX = UINT32_MAX >> TYPE_BITS };
+
+ /**
+ * We include the perspective matrix from our containing block for the
+ * purposes of visibility calculations, but we exclude it from the transform
+ * we set on the layer (for rendering), since there will be an
+ * nsDisplayPerspective created for that.
+ */
+ const Matrix4x4Flagged& GetTransform() const;
+ const Matrix4x4Flagged& GetInverseTransform() const;
+
+ bool ShouldSkipTransform(nsDisplayListBuilder* aBuilder) const;
+ Matrix4x4 GetTransformForRendering(
+ LayoutDevicePoint* aOutOrigin = nullptr) const;
+
+ /**
+ * Return the transform that is aggregation of all transform on the
+ * preserves3d chain.
+ */
+ const Matrix4x4& GetAccumulatedPreserved3DTransform(
+ nsDisplayListBuilder* aBuilder);
+
+ float GetHitDepthAtPoint(nsDisplayListBuilder* aBuilder,
+ const nsPoint& aPoint);
+ /**
+ * TransformRect takes in as parameters a rectangle (in aFrame's coordinate
+ * space) and returns the smallest rectangle (in aFrame's coordinate space)
+ * containing the transformed image of that rectangle. That is, it takes
+ * the four corners of the rectangle, transforms them according to the
+ * matrix associated with the specified frame, then returns the smallest
+ * rectangle containing the four transformed points.
+ *
+ * @param untransformedBounds The rectangle (in app units) to transform.
+ * @param aFrame The frame whose transformation should be applied. This
+ * function raises an assertion if aFrame is null or doesn't have a
+ * transform applied to it.
+ * @param aRefBox the reference box to use, which would usually be just
+ * TransformReferemceBox(aFrame), but callers may override it if
+ * needed.
+ */
+ static nsRect TransformRect(const nsRect& aUntransformedBounds,
+ const nsIFrame* aFrame,
+ TransformReferenceBox& aRefBox);
+
+ /* UntransformRect is like TransformRect, except that it inverts the
+ * transform.
+ */
+ static bool UntransformRect(const nsRect& aTransformedBounds,
+ const nsRect& aChildBounds,
+ const nsIFrame* aFrame, nsRect* aOutRect);
+ static bool UntransformRect(const nsRect& aTransformedBounds,
+ const nsRect& aChildBounds,
+ const Matrix4x4& aMatrix, float aAppUnitsPerPixel,
+ nsRect* aOutRect);
+
+ bool UntransformRect(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
+ nsRect* aOutRect) const;
+
+ bool UntransformBuildingRect(nsDisplayListBuilder* aBuilder,
+ nsRect* aOutRect) const {
+ return UntransformRect(aBuilder, GetBuildingRect(), aOutRect);
+ }
+
+ static gfx::Point3D GetDeltaToTransformOrigin(const nsIFrame* aFrame,
+ TransformReferenceBox&,
+ float aAppUnitsPerPixel);
+
+ /*
+ * Returns true if aFrame has perspective applied from its containing
+ * block.
+ * Returns the matrix to append to apply the persective (taking
+ * perspective-origin into account), relative to aFrames coordinate
+ * space).
+ * aOutMatrix is assumed to be the identity matrix, and isn't explicitly
+ * cleared.
+ */
+ static bool ComputePerspectiveMatrix(const nsIFrame* aFrame,
+ float aAppUnitsPerPixel,
+ Matrix4x4& aOutMatrix);
+
+ struct MOZ_STACK_CLASS FrameTransformProperties {
+ FrameTransformProperties(const nsIFrame* aFrame,
+ TransformReferenceBox& aRefBox,
+ float aAppUnitsPerPixel);
+ FrameTransformProperties(const StyleTranslate& aTranslate,
+ const StyleRotate& aRotate,
+ const StyleScale& aScale,
+ const StyleTransform& aTransform,
+ const Maybe<ResolvedMotionPathData>& aMotion,
+ const gfx::Point3D& aToTransformOrigin)
+ : mFrame(nullptr),
+ mTranslate(aTranslate),
+ mRotate(aRotate),
+ mScale(aScale),
+ mTransform(aTransform),
+ mMotion(aMotion),
+ mToTransformOrigin(aToTransformOrigin) {}
+
+ bool HasTransform() const {
+ return !mTranslate.IsNone() || !mRotate.IsNone() || !mScale.IsNone() ||
+ !mTransform.IsNone() || mMotion.isSome();
+ }
+
+ const nsIFrame* mFrame;
+ const StyleTranslate& mTranslate;
+ const StyleRotate& mRotate;
+ const StyleScale& mScale;
+ const StyleTransform& mTransform;
+ const Maybe<ResolvedMotionPathData> mMotion;
+ const gfx::Point3D mToTransformOrigin;
+ };
+
+ /**
+ * Given a frame with the transform property or an SVG transform,
+ * returns the transformation matrix for that frame.
+ *
+ * @param aFrame The frame to get the matrix from.
+ * @param aOrigin Relative to which point this transform should be applied.
+ * @param aAppUnitsPerPixel The number of app units per graphics unit.
+ * @param aBoundsOverride [optional] If this is nullptr (the default), the
+ * computation will use the value of TransformReferenceBox(aFrame).
+ * Otherwise, it will use the value of aBoundsOverride. This is
+ * mostly for internal use and in most cases you will not need to
+ * specify a value.
+ * @param aFlags OFFSET_BY_ORIGIN The resulting matrix will be translated
+ * by aOrigin. This translation is applied *before* the CSS transform.
+ * @param aFlags INCLUDE_PRESERVE3D_ANCESTORS The computed transform will
+ * include the transform of any ancestors participating in the same
+ * 3d rendering context.
+ * @param aFlags INCLUDE_PERSPECTIVE The resulting matrix will include the
+ * perspective transform from the containing block if applicable.
+ */
+ enum {
+ OFFSET_BY_ORIGIN = 1 << 0,
+ INCLUDE_PRESERVE3D_ANCESTORS = 1 << 1,
+ INCLUDE_PERSPECTIVE = 1 << 2,
+ };
+ static constexpr uint32_t kTransformRectFlags =
+ INCLUDE_PERSPECTIVE | OFFSET_BY_ORIGIN | INCLUDE_PRESERVE3D_ANCESTORS;
+ static Matrix4x4 GetResultingTransformMatrix(const nsIFrame* aFrame,
+ const nsPoint& aOrigin,
+ float aAppUnitsPerPixel,
+ uint32_t aFlags);
+ static Matrix4x4 GetResultingTransformMatrix(
+ const FrameTransformProperties& aProperties, TransformReferenceBox&,
+ float aAppUnitsPerPixel);
+
+ struct PrerenderInfo {
+ bool CanUseAsyncAnimations() const {
+ return mDecision != PrerenderDecision::No && mHasAnimations;
+ }
+ PrerenderDecision mDecision = PrerenderDecision::No;
+ bool mHasAnimations = true;
+ };
+ /**
+ * Decide whether we should prerender some or all of the contents of the
+ * transformed frame even when it's not completely visible (yet).
+ * Return PrerenderDecision::Full if the entire contents should be
+ * prerendered, PrerenderDecision::Partial if some but not all of the
+ * contents should be prerendered, or PrerenderDecision::No if only the
+ * visible area should be rendered.
+ * |mNoAffectDecisionInPreserve3D| is set if the prerender decision should not
+ * affect the decision on other frames in the preserve 3d tree.
+ * |aDirtyRect| is updated to the area that should be prerendered.
+ */
+ static PrerenderInfo ShouldPrerenderTransformedContent(
+ nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsRect* aDirtyRect);
+
+ bool CanUseAsyncAnimations(nsDisplayListBuilder* aBuilder) override;
+
+ bool MayBeAnimated(nsDisplayListBuilder* aBuilder) const;
+
+ void WriteDebugInfo(std::stringstream& aStream) override;
+
+ bool CanMoveAsync() override {
+ return EffectCompositor::HasAnimationsForCompositor(
+ mFrame, DisplayItemType::TYPE_TRANSFORM);
+ }
+
+ /**
+ * This item is an additional item as the boundary between parent
+ * and child 3D rendering context.
+ * \see nsIFrame::BuildDisplayListForStackingContext().
+ */
+ bool IsTransformSeparator() const { return mIsTransformSeparator; }
+ /**
+ * This item is the boundary between parent and child 3D rendering
+ * context.
+ */
+ bool IsLeafOf3DContext() const {
+ return (IsTransformSeparator() ||
+ (!mFrame->Extend3DContext() && Combines3DTransformWithAncestors()));
+ }
+ /**
+ * The backing frame of this item participates a 3D rendering
+ * context.
+ */
+ bool IsParticipating3DContext() const {
+ return mFrame->Extend3DContext() || Combines3DTransformWithAncestors();
+ }
+
+ bool IsPartialPrerender() const {
+ return mPrerenderDecision == PrerenderDecision::Partial;
+ }
+
+ /**
+ * Mark this item as created together with `nsDisplayPerspective`.
+ * \see nsIFrame::BuildDisplayListForStackingContext().
+ */
+ void MarkWithAssociatedPerspective() { mHasAssociatedPerspective = true; }
+
+ void AddSizeOfExcludingThis(nsWindowSizes&) const override;
+
+ bool CreatesStackingContextHelper() override { return true; }
+
+ private:
+ void ComputeBounds(nsDisplayListBuilder* aBuilder);
+ nsRect TransformUntransformedBounds(nsDisplayListBuilder* aBuilder,
+ const Matrix4x4Flagged& aMatrix) const;
+ void UpdateUntransformedBounds(nsDisplayListBuilder* aBuilder);
+
+ void SetReferenceFrameToAncestor(nsDisplayListBuilder* aBuilder);
+ void Init(nsDisplayListBuilder* aBuilder, nsDisplayList* aChildren);
+
+ static Matrix4x4 GetResultingTransformMatrixInternal(
+ const FrameTransformProperties& aProperties,
+ TransformReferenceBox& aRefBox, const nsPoint& aOrigin,
+ float aAppUnitsPerPixel, uint32_t aFlags);
+
+ void Collect3DTransformLeaves(nsDisplayListBuilder* aBuilder,
+ nsTArray<nsDisplayTransform*>& aLeaves);
+ using TransformPolygon = layers::BSPPolygon<nsDisplayTransform>;
+ void CollectSorted3DTransformLeaves(nsDisplayListBuilder* aBuilder,
+ nsTArray<TransformPolygon>& aLeaves);
+
+ mutable RetainedDisplayList mChildren;
+ mutable Maybe<Matrix4x4Flagged> mTransform;
+ mutable Maybe<Matrix4x4Flagged> mInverseTransform;
+ // Accumulated transform of ancestors on the preserves-3d chain.
+ UniquePtr<Matrix4x4> mTransformPreserves3D;
+ nsRect mChildrenBuildingRect;
+
+ // The untransformed bounds of |mChildren|.
+ nsRect mChildBounds;
+ // The transformed bounds of this display item.
+ nsRect mBounds;
+ PrerenderDecision mPrerenderDecision : 8;
+ // This item is a separator between 3D rendering contexts, and
+ // mTransform have been presetted by the constructor.
+ // This also forces us not to extend the 3D context. Since we don't create a
+ // transform item, a container layer, for every frame in a preserves3d
+ // context, the transform items of a child preserves3d context may extend the
+ // parent context unintendedly if the root of the child preserves3d context
+ // doesn't create a transform item.
+ bool mIsTransformSeparator : 1;
+ // True if we have a transform getter.
+ bool mHasTransformGetter : 1;
+ // True if this item is created together with `nsDisplayPerspective`
+ // from the same CSS stacking context.
+ bool mHasAssociatedPerspective : 1;
+};
+
+/* A display item that applies a perspective transformation to a single
+ * nsDisplayTransform child item. We keep this as a separate item since the
+ * perspective-origin is relative to an ancestor of the transformed frame, and
+ * APZ can scroll the child separately.
+ */
+class nsDisplayPerspective : public nsPaintedDisplayItem {
+ public:
+ nsDisplayPerspective(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ nsDisplayList* aList);
+ ~nsDisplayPerspective() override = default;
+
+ NS_DISPLAY_DECL_NAME("nsDisplayPerspective", TYPE_PERSPECTIVE)
+
+ void Destroy(nsDisplayListBuilder* aBuilder) override {
+ mList.DeleteAll(aBuilder);
+ nsPaintedDisplayItem::Destroy(aBuilder);
+ }
+
+ void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
+ HitTestState* aState, nsTArray<nsIFrame*>* aOutFrames) override {
+ return GetChildren()->HitTest(aBuilder, aRect, aState, aOutFrames);
+ }
+
+ nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) const override {
+ *aSnap = false;
+ return GetChildren()->GetClippedBoundsWithRespectToASR(aBuilder,
+ mActiveScrolledRoot);
+ }
+
+ void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
+ const nsDisplayItemGeometry* aGeometry,
+ nsRegion* aInvalidRegion) const override {}
+
+ nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
+ bool* aSnap) const override;
+
+ bool CreateWebRenderCommands(
+ wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) override;
+
+ void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override;
+
+ RetainedDisplayList* GetSameCoordinateSystemChildren() const override {
+ return &mList;
+ }
+
+ RetainedDisplayList* GetChildren() const override { return &mList; }
+
+ nsRect GetComponentAlphaBounds(
+ nsDisplayListBuilder* aBuilder) const override {
+ return GetChildren()->GetComponentAlphaBounds(aBuilder);
+ }
+
+ void DoUpdateBoundsPreserves3D(nsDisplayListBuilder* aBuilder) override {
+ if (GetChildren()->GetTop()) {
+ static_cast<nsDisplayTransform*>(GetChildren()->GetTop())
+ ->DoUpdateBoundsPreserves3D(aBuilder);
+ }
+ }
+
+ bool CreatesStackingContextHelper() override { return true; }
+
+ private:
+ mutable RetainedDisplayList mList;
+};
+
+class nsDisplayTextGeometry;
+
+/**
+ * This class adds basic support for limiting the rendering (in the inline axis
+ * of the writing mode) to the part inside the specified edges.
+ * The two members, mVisIStartEdge and mVisIEndEdge, are relative to the edges
+ * of the frame's scrollable overflow rectangle and are the amount to suppress
+ * on each side.
+ *
+ * Setting none, both or only one edge is allowed.
+ * The values must be non-negative.
+ * The default value for both edges is zero, which means everything is painted.
+ */
+class nsDisplayText final : public nsPaintedDisplayItem {
+ public:
+ nsDisplayText(nsDisplayListBuilder* aBuilder, nsTextFrame* aFrame);
+ MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayText)
+
+ NS_DISPLAY_DECL_NAME("Text", TYPE_TEXT)
+
+ nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) const final {
+ *aSnap = false;
+ return mBounds;
+ }
+
+ void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
+ HitTestState* aState, nsTArray<nsIFrame*>* aOutFrames) final {
+ if (nsRect(ToReferenceFrame(), mFrame->GetSize()).Intersects(aRect)) {
+ aOutFrames->AppendElement(mFrame);
+ }
+ }
+
+ bool CreateWebRenderCommands(wr::DisplayListBuilder& aBuilder,
+ wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) final;
+ void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) final;
+
+ nsRect GetComponentAlphaBounds(nsDisplayListBuilder* aBuilder) const final {
+ if (gfxPlatform::GetPlatform()->RespectsFontStyleSmoothing()) {
+ // On OS X, web authors can turn off subpixel text rendering using the
+ // CSS property -moz-osx-font-smoothing. If they do that, we don't need
+ // to use component alpha layers for the affected text.
+ if (mFrame->StyleFont()->mFont.smoothing == NS_FONT_SMOOTHING_GRAYSCALE) {
+ return nsRect();
+ }
+ }
+ bool snap;
+ return GetBounds(aBuilder, &snap);
+ }
+
+ nsDisplayItemGeometry* AllocateGeometry(nsDisplayListBuilder* aBuilder) final;
+
+ void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
+ const nsDisplayItemGeometry* aGeometry,
+ nsRegion* aInvalidRegion) const final;
+
+ void RenderToContext(gfxContext* aCtx, nsDisplayListBuilder* aBuilder,
+ const nsRect& aVisibleRect, float aOpacity = 1.0f,
+ bool aIsRecording = false);
+
+ bool CanApplyOpacity(WebRenderLayerManager* aManager,
+ nsDisplayListBuilder* aBuilder) const final;
+
+ void WriteDebugInfo(std::stringstream& aStream) final;
+
+ static nsDisplayText* CheckCast(nsDisplayItem* aItem) {
+ return (aItem->GetType() == DisplayItemType::TYPE_TEXT)
+ ? static_cast<nsDisplayText*>(aItem)
+ : nullptr;
+ }
+
+ nscoord& VisIStartEdge() { return mVisIStartEdge; }
+ nscoord& VisIEndEdge() { return mVisIEndEdge; }
+
+ private:
+ nsRect mBounds;
+ nsRect mVisibleRect;
+
+ // Lengths measured from the visual inline start and end sides
+ // (i.e. left and right respectively in horizontal writing modes,
+ // regardless of bidi directionality; top and bottom in vertical modes).
+ nscoord mVisIStartEdge;
+ nscoord mVisIEndEdge;
+};
+
+/**
+ * A display item that for webrender to handle SVG
+ */
+class nsDisplaySVGWrapper : public nsDisplayWrapList {
+ public:
+ nsDisplaySVGWrapper(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ nsDisplayList* aList);
+
+ MOZ_COUNTED_DTOR_OVERRIDE(nsDisplaySVGWrapper)
+
+ NS_DISPLAY_DECL_NAME("SVGWrapper", TYPE_SVG_WRAPPER)
+
+ void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override {
+ GetChildren()->Paint(aBuilder, aCtx,
+ mFrame->PresContext()->AppUnitsPerDevPixel());
+ }
+ bool ShouldFlattenAway(nsDisplayListBuilder* aBuilder) override;
+ bool CreateWebRenderCommands(
+ wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) override;
+};
+
+/**
+ * A display item for webrender to handle SVG foreign object
+ */
+class nsDisplayForeignObject : public nsDisplayWrapList {
+ public:
+ nsDisplayForeignObject(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ nsDisplayList* aList);
+#ifdef NS_BUILD_REFCNT_LOGGING
+ virtual ~nsDisplayForeignObject();
+#endif
+
+ NS_DISPLAY_DECL_NAME("ForeignObject", TYPE_FOREIGN_OBJECT)
+
+ virtual bool ShouldFlattenAway(nsDisplayListBuilder* aBuilder) override;
+ void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override {
+ GetChildren()->Paint(aBuilder, aCtx,
+ mFrame->PresContext()->AppUnitsPerDevPixel());
+ }
+
+ bool CreateWebRenderCommands(
+ wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) override;
+};
+
+/**
+ * A display item to represent a hyperlink.
+ */
+class nsDisplayLink : public nsPaintedDisplayItem {
+ public:
+ nsDisplayLink(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ const char* aLinkSpec, const nsRect& aRect)
+ : nsPaintedDisplayItem(aBuilder, aFrame),
+ mLinkSpec(aLinkSpec),
+ mRect(aRect) {}
+
+ NS_DISPLAY_DECL_NAME("Link", TYPE_LINK)
+
+ void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override;
+
+ private:
+ nsCString mLinkSpec;
+ nsRect mRect;
+};
+
+/**
+ * A display item to represent a destination within the document.
+ */
+class nsDisplayDestination : public nsPaintedDisplayItem {
+ public:
+ nsDisplayDestination(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ const char* aDestinationName, const nsPoint& aPosition)
+ : nsPaintedDisplayItem(aBuilder, aFrame),
+ mDestinationName(aDestinationName),
+ mPosition(aPosition) {}
+
+ NS_DISPLAY_DECL_NAME("Destination", TYPE_DESTINATION)
+
+ void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override;
+
+ private:
+ nsCString mDestinationName;
+ nsPoint mPosition;
+};
+
+class MOZ_STACK_CLASS FlattenedDisplayListIterator {
+ public:
+ FlattenedDisplayListIterator(nsDisplayListBuilder* aBuilder,
+ nsDisplayList* aList)
+ : mBuilder(aBuilder), mStart(aList->begin()), mEnd(aList->end()) {
+ ResolveFlattening();
+ }
+
+ bool HasNext() const { return !AtEndOfCurrentList(); }
+
+ nsDisplayItem* GetNextItem() {
+ MOZ_ASSERT(HasNext());
+
+ nsDisplayItem* current = NextItem();
+ Advance();
+
+ if (!AtEndOfCurrentList() && current->CanMerge(NextItem())) {
+ // Since we can merge at least two display items, create an array and
+ // collect mergeable display items there.
+ AutoTArray<nsDisplayItem*, 2> willMerge{current};
+
+ auto it = mStart;
+ while (it != mEnd) {
+ nsDisplayItem* next = *it;
+ if (current->CanMerge(next)) {
+ willMerge.AppendElement(next);
+ ++it;
+ } else {
+ break;
+ }
+ }
+ mStart = it;
+
+ current = mBuilder->MergeItems(willMerge);
+ }
+
+ ResolveFlattening();
+ return current;
+ }
+
+ protected:
+ void Advance() { ++mStart; }
+
+ bool AtEndOfNestedList() const {
+ return AtEndOfCurrentList() && mStack.Length() > 0;
+ }
+
+ bool AtEndOfCurrentList() const { return mStart == mEnd; }
+
+ nsDisplayItem* NextItem() {
+ MOZ_ASSERT(HasNext());
+ return *mStart;
+ }
+
+ bool ShouldFlattenNextItem() {
+ return HasNext() && NextItem()->ShouldFlattenAway(mBuilder);
+ }
+
+ void ResolveFlattening() {
+ // Handle the case where we reach the end of a nested list, or the current
+ // item should start a new nested list. Repeat this until we find an actual
+ // item, or the very end of the outer list.
+ while (AtEndOfNestedList() || ShouldFlattenNextItem()) {
+ if (AtEndOfNestedList()) {
+ // We reached the end of the list, pop the next list from the stack.
+ std::tie(mStart, mEnd) = mStack.PopLastElement();
+ } else {
+ // The next item wants to be flattened. This means that we will skip the
+ // flattened item and directly iterate over its sublist.
+ MOZ_ASSERT(ShouldFlattenNextItem());
+
+ nsDisplayList* sublist = NextItem()->GetChildren();
+ MOZ_ASSERT(sublist);
+
+ // Skip the flattened item.
+ Advance();
+
+ // Store the current position on the stack.
+ if (!AtEndOfCurrentList()) {
+ mStack.AppendElement(std::make_pair(mStart, mEnd));
+ }
+
+ // Iterate over the sublist.
+ mStart = sublist->begin();
+ mEnd = sublist->end();
+ }
+ }
+ }
+
+ private:
+ nsDisplayListBuilder* mBuilder;
+ nsDisplayList::iterator mStart;
+ nsDisplayList::iterator mEnd;
+ AutoTArray<std::pair<nsDisplayList::iterator, nsDisplayList::iterator>, 3>
+ mStack;
+};
+
+class PaintTelemetry {
+ public:
+ class AutoRecordPaint {
+ public:
+ AutoRecordPaint();
+ ~AutoRecordPaint();
+
+ private:
+ TimeStamp mStart;
+ };
+
+ private:
+ static uint32_t sPaintLevel;
+};
+
+} // namespace mozilla
+
+#endif /*NSDISPLAYLIST_H_*/
diff --git a/layout/painting/nsDisplayListArenaTypes.h b/layout/painting/nsDisplayListArenaTypes.h
new file mode 100644
index 0000000000..79d04b298a
--- /dev/null
+++ b/layout/painting/nsDisplayListArenaTypes.h
@@ -0,0 +1,15 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* a list of all types that can be allocated in the display list's nsPresArena,
+ for preprocessing */
+
+#define DECLARE_DISPLAY_ITEM_TYPE(name_, ...) DISPLAY_LIST_ARENA_OBJECT(name_)
+DISPLAY_LIST_ARENA_OBJECT(UNUSED) // DisplayItemType::TYPE_ZERO
+#include "nsDisplayItemTypesList.h"
+#undef DECLARE_DISPLAY_ITEM_TYPE
+DISPLAY_LIST_ARENA_OBJECT(CLIPCHAIN)
+DISPLAY_LIST_ARENA_OBJECT(LISTNODE)
diff --git a/layout/painting/nsDisplayListInvalidation.cpp b/layout/painting/nsDisplayListInvalidation.cpp
new file mode 100644
index 0000000000..e5696d14c7
--- /dev/null
+++ b/layout/painting/nsDisplayListInvalidation.cpp
@@ -0,0 +1,124 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsDisplayListInvalidation.h"
+#include "nsDisplayList.h"
+#include "nsIFrame.h"
+#include "nsTableFrame.h"
+
+namespace mozilla {
+
+nsDisplayItemGeometry::nsDisplayItemGeometry(nsDisplayItem* aItem,
+ nsDisplayListBuilder* aBuilder) {
+ MOZ_COUNT_CTOR(nsDisplayItemGeometry);
+ bool snap;
+ mBounds = aItem->GetBounds(aBuilder, &snap);
+}
+
+nsDisplayItemGeometry::~nsDisplayItemGeometry() {
+ MOZ_COUNT_DTOR(nsDisplayItemGeometry);
+}
+
+nsDisplayItemGenericGeometry::nsDisplayItemGenericGeometry(
+ nsDisplayItem* aItem, nsDisplayListBuilder* aBuilder)
+ : nsDisplayItemGeometry(aItem, aBuilder),
+ mBorderRect(aItem->GetBorderRect()) {}
+
+bool ShouldSyncDecodeImages(nsDisplayListBuilder* aBuilder) {
+ return aBuilder->ShouldSyncDecodeImages();
+}
+
+nsDisplayItemGeometry* GetPreviousGeometry(nsDisplayItem* aItem) {
+ if (RefPtr<layers::WebRenderFallbackData> data =
+ layers::GetWebRenderUserData<layers::WebRenderFallbackData>(
+ aItem->Frame(), aItem->GetPerFrameKey())) {
+ return data->GetGeometry();
+ }
+ return nullptr;
+}
+
+void nsDisplayItemGenericGeometry::MoveBy(const nsPoint& aOffset) {
+ nsDisplayItemGeometry::MoveBy(aOffset);
+ mBorderRect.MoveBy(aOffset);
+}
+
+nsDisplayItemBoundsGeometry::nsDisplayItemBoundsGeometry(
+ nsDisplayItem* aItem, nsDisplayListBuilder* aBuilder)
+ : nsDisplayItemGeometry(aItem, aBuilder) {
+ nscoord radii[8];
+ mHasRoundedCorners = aItem->Frame()->GetBorderRadii(radii);
+}
+
+nsDisplayBorderGeometry::nsDisplayBorderGeometry(nsDisplayItem* aItem,
+ nsDisplayListBuilder* aBuilder)
+ : nsDisplayItemGeometry(aItem, aBuilder) {}
+
+nsDisplayBackgroundGeometry::nsDisplayBackgroundGeometry(
+ nsDisplayBackgroundImage* aItem, nsDisplayListBuilder* aBuilder)
+ : nsDisplayItemGeometry(aItem, aBuilder),
+ mPositioningArea(aItem->GetPositioningArea()),
+ mDestRect(aItem->GetDestRect()) {}
+
+void nsDisplayBackgroundGeometry::MoveBy(const nsPoint& aOffset) {
+ nsDisplayItemGeometry::MoveBy(aOffset);
+ mPositioningArea.MoveBy(aOffset);
+ mDestRect.MoveBy(aOffset);
+}
+
+nsDisplayThemedBackgroundGeometry::nsDisplayThemedBackgroundGeometry(
+ nsDisplayThemedBackground* aItem, nsDisplayListBuilder* aBuilder)
+ : nsDisplayItemGeometry(aItem, aBuilder),
+ mPositioningArea(aItem->GetPositioningArea()),
+ mWindowIsActive(aItem->IsWindowActive()) {}
+
+void nsDisplayThemedBackgroundGeometry::MoveBy(const nsPoint& aOffset) {
+ nsDisplayItemGeometry::MoveBy(aOffset);
+ mPositioningArea.MoveBy(aOffset);
+}
+
+nsDisplayBoxShadowInnerGeometry::nsDisplayBoxShadowInnerGeometry(
+ nsDisplayItem* aItem, nsDisplayListBuilder* aBuilder)
+ : nsDisplayItemGeometry(aItem, aBuilder),
+ mPaddingRect(aItem->GetPaddingRect()) {}
+
+void nsDisplayBoxShadowInnerGeometry::MoveBy(const nsPoint& aOffset) {
+ nsDisplayItemGeometry::MoveBy(aOffset);
+ mPaddingRect.MoveBy(aOffset);
+}
+
+void nsDisplaySolidColorRegionGeometry::MoveBy(const nsPoint& aOffset) {
+ nsDisplayItemGeometry::MoveBy(aOffset);
+ mRegion.MoveBy(aOffset);
+}
+
+nsDisplaySVGEffectGeometry::nsDisplaySVGEffectGeometry(
+ nsDisplayEffectsBase* aItem, nsDisplayListBuilder* aBuilder)
+ : nsDisplayItemGeometry(aItem, aBuilder),
+ mBBox(aItem->BBoxInUserSpace()),
+ mUserSpaceOffset(aItem->UserSpaceOffset()),
+ mFrameOffsetToReferenceFrame(aItem->ToReferenceFrame()) {}
+
+void nsDisplaySVGEffectGeometry::MoveBy(const nsPoint& aOffset) {
+ mBounds.MoveBy(aOffset);
+ mFrameOffsetToReferenceFrame += aOffset;
+}
+
+nsDisplayMasksAndClipPathsGeometry::nsDisplayMasksAndClipPathsGeometry(
+ nsDisplayMasksAndClipPaths* aItem, nsDisplayListBuilder* aBuilder)
+ : nsDisplaySVGEffectGeometry(aItem, aBuilder),
+ mDestRects(aItem->GetDestRects().Clone()) {}
+
+nsDisplayFiltersGeometry::nsDisplayFiltersGeometry(
+ nsDisplayFilters* aItem, nsDisplayListBuilder* aBuilder)
+ : nsDisplaySVGEffectGeometry(aItem, aBuilder) {}
+
+nsDisplayTableItemGeometry::nsDisplayTableItemGeometry(
+ nsDisplayTableItem* aItem, nsDisplayListBuilder* aBuilder,
+ const nsPoint& aFrameOffsetToViewport)
+ : nsDisplayItemGenericGeometry(aItem, aBuilder),
+ mFrameOffsetToViewport(aFrameOffsetToViewport) {}
+
+} // namespace mozilla
diff --git a/layout/painting/nsDisplayListInvalidation.h b/layout/painting/nsDisplayListInvalidation.h
new file mode 100644
index 0000000000..3e07713301
--- /dev/null
+++ b/layout/painting/nsDisplayListInvalidation.h
@@ -0,0 +1,236 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef NSDISPLAYLISTINVALIDATION_H_
+#define NSDISPLAYLISTINVALIDATION_H_
+
+#include "mozilla/Attributes.h"
+#include "mozilla/layers/WebRenderUserData.h"
+#include "ImgDrawResult.h"
+#include "nsRect.h"
+#include "nsColor.h"
+#include "gfxRect.h"
+#include "mozilla/gfx/MatrixFwd.h"
+
+namespace mozilla {
+class nsDisplayBackgroundImage;
+class nsCharClipDisplayItem;
+class nsDisplayItem;
+class nsDisplayListBuilder;
+class nsDisplayTableItem;
+class nsDisplayThemedBackground;
+class nsDisplayEffectsBase;
+class nsDisplayMasksAndClipPaths;
+class nsDisplayFilters;
+
+namespace gfx {
+struct sRGBColor;
+}
+
+/**
+ * This stores the geometry of an nsDisplayItem, and the area
+ * that will be affected when painting the item.
+ *
+ * It is used to retain information about display items so they
+ * can be compared against new display items in the next paint.
+ */
+class nsDisplayItemGeometry {
+ public:
+ nsDisplayItemGeometry(nsDisplayItem* aItem, nsDisplayListBuilder* aBuilder);
+ virtual ~nsDisplayItemGeometry();
+
+ /**
+ * Compute the area required to be invalidated if this
+ * display item is removed.
+ */
+ const nsRect& ComputeInvalidationRegion() { return mBounds; }
+
+ /**
+ * Shifts all retained areas of the nsDisplayItemGeometry by the given offset.
+ *
+ * This is used to compensate for scrolling, since the destination buffer
+ * can scroll without requiring a full repaint.
+ *
+ * @param aOffset Offset to shift by.
+ */
+ virtual void MoveBy(const nsPoint& aOffset) { mBounds.MoveBy(aOffset); }
+
+ /**
+ * Bounds of the display item
+ */
+ nsRect mBounds;
+};
+
+/**
+ * A default geometry implementation, used by nsDisplayItem. Retains
+ * and compares the bounds, and border rect.
+ *
+ * This should be sufficient for the majority of display items.
+ */
+class nsDisplayItemGenericGeometry : public nsDisplayItemGeometry {
+ public:
+ nsDisplayItemGenericGeometry(nsDisplayItem* aItem,
+ nsDisplayListBuilder* aBuilder);
+
+ void MoveBy(const nsPoint& aOffset) override;
+
+ nsRect mBorderRect;
+};
+
+bool ShouldSyncDecodeImages(nsDisplayListBuilder* aBuilder);
+
+nsDisplayItemGeometry* GetPreviousGeometry(nsDisplayItem*);
+
+class nsDisplayItemBoundsGeometry : public nsDisplayItemGeometry {
+ public:
+ nsDisplayItemBoundsGeometry(nsDisplayItem* aItem,
+ nsDisplayListBuilder* aBuilder);
+
+ bool mHasRoundedCorners;
+};
+
+class nsDisplayBorderGeometry : public nsDisplayItemGeometry {
+ public:
+ nsDisplayBorderGeometry(nsDisplayItem* aItem, nsDisplayListBuilder* aBuilder);
+};
+
+class nsDisplayBackgroundGeometry : public nsDisplayItemGeometry {
+ public:
+ nsDisplayBackgroundGeometry(nsDisplayBackgroundImage* aItem,
+ nsDisplayListBuilder* aBuilder);
+
+ void MoveBy(const nsPoint& aOffset) override;
+
+ nsRect mPositioningArea;
+ nsRect mDestRect;
+};
+
+class nsDisplayThemedBackgroundGeometry : public nsDisplayItemGeometry {
+ public:
+ nsDisplayThemedBackgroundGeometry(nsDisplayThemedBackground* aItem,
+ nsDisplayListBuilder* aBuilder);
+
+ void MoveBy(const nsPoint& aOffset) override;
+
+ nsRect mPositioningArea;
+ bool mWindowIsActive;
+};
+
+class nsDisplayTreeBodyGeometry : public nsDisplayItemGenericGeometry {
+ public:
+ nsDisplayTreeBodyGeometry(nsDisplayItem* aItem,
+ nsDisplayListBuilder* aBuilder,
+ bool aWindowIsActive)
+ : nsDisplayItemGenericGeometry(aItem, aBuilder),
+ mWindowIsActive(aWindowIsActive) {}
+
+ bool mWindowIsActive = false;
+};
+
+class nsDisplayBoxShadowInnerGeometry : public nsDisplayItemGeometry {
+ public:
+ nsDisplayBoxShadowInnerGeometry(nsDisplayItem* aItem,
+ nsDisplayListBuilder* aBuilder);
+
+ void MoveBy(const nsPoint& aOffset) override;
+
+ nsRect mPaddingRect;
+};
+
+class nsDisplaySolidColorGeometry : public nsDisplayItemBoundsGeometry {
+ public:
+ nsDisplaySolidColorGeometry(nsDisplayItem* aItem,
+ nsDisplayListBuilder* aBuilder, nscolor aColor)
+ : nsDisplayItemBoundsGeometry(aItem, aBuilder), mColor(aColor) {}
+
+ nscolor mColor;
+};
+
+class nsDisplaySolidColorRegionGeometry : public nsDisplayItemBoundsGeometry {
+ public:
+ nsDisplaySolidColorRegionGeometry(nsDisplayItem* aItem,
+ nsDisplayListBuilder* aBuilder,
+ const nsRegion& aRegion,
+ mozilla::gfx::sRGBColor aColor)
+ : nsDisplayItemBoundsGeometry(aItem, aBuilder),
+ mRegion(aRegion),
+ mColor(aColor) {}
+
+ void MoveBy(const nsPoint& aOffset) override;
+
+ nsRegion mRegion;
+ mozilla::gfx::sRGBColor mColor;
+};
+
+class nsDisplaySVGEffectGeometry : public nsDisplayItemGeometry {
+ public:
+ nsDisplaySVGEffectGeometry(nsDisplayEffectsBase* aItem,
+ nsDisplayListBuilder* aBuilder);
+
+ void MoveBy(const nsPoint& aOffset) override;
+
+ gfxRect mBBox;
+ gfxPoint mUserSpaceOffset;
+ nsPoint mFrameOffsetToReferenceFrame;
+ bool mHandleOpacity;
+};
+
+class nsDisplayMasksAndClipPathsGeometry : public nsDisplaySVGEffectGeometry {
+ public:
+ nsDisplayMasksAndClipPathsGeometry(nsDisplayMasksAndClipPaths* aItem,
+ nsDisplayListBuilder* aBuilder);
+
+ nsTArray<nsRect> mDestRects;
+};
+
+class nsDisplayFiltersGeometry : public nsDisplaySVGEffectGeometry {
+ public:
+ nsDisplayFiltersGeometry(nsDisplayFilters* aItem,
+ nsDisplayListBuilder* aBuilder);
+};
+
+class nsDisplayTableItemGeometry : public nsDisplayItemGenericGeometry {
+ public:
+ nsDisplayTableItemGeometry(nsDisplayTableItem* aItem,
+ nsDisplayListBuilder* aBuilder,
+ const nsPoint& aFrameOffsetToViewport);
+
+ nsPoint mFrameOffsetToViewport;
+};
+
+class nsDisplayOpacityGeometry : public nsDisplayItemGenericGeometry {
+ public:
+ nsDisplayOpacityGeometry(nsDisplayItem* aItem, nsDisplayListBuilder* aBuilder,
+ float aOpacity)
+ : nsDisplayItemGenericGeometry(aItem, aBuilder), mOpacity(aOpacity) {}
+
+ float mOpacity;
+};
+
+class nsDisplayTransformGeometry : public nsDisplayItemGeometry {
+ public:
+ nsDisplayTransformGeometry(nsDisplayItem* aItem,
+ nsDisplayListBuilder* aBuilder,
+ const mozilla::gfx::Matrix4x4Flagged& aTransform,
+ int32_t aAppUnitsPerDevPixel)
+ : nsDisplayItemGeometry(aItem, aBuilder),
+ mTransform(aTransform),
+ mAppUnitsPerDevPixel(aAppUnitsPerDevPixel) {}
+
+ void MoveBy(const nsPoint& aOffset) override {
+ nsDisplayItemGeometry::MoveBy(aOffset);
+ mTransform.PostTranslate(
+ NSAppUnitsToFloatPixels(aOffset.x, mAppUnitsPerDevPixel),
+ NSAppUnitsToFloatPixels(aOffset.y, mAppUnitsPerDevPixel), 0.0f);
+ }
+
+ mozilla::gfx::Matrix4x4Flagged mTransform;
+ int32_t mAppUnitsPerDevPixel;
+};
+
+} // namespace mozilla
+
+#endif /*NSDISPLAYLISTINVALIDATION_H_*/
diff --git a/layout/painting/nsImageRenderer.cpp b/layout/painting/nsImageRenderer.cpp
new file mode 100644
index 0000000000..d2fa024d4d
--- /dev/null
+++ b/layout/painting/nsImageRenderer.cpp
@@ -0,0 +1,1054 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* utility code for drawing images as CSS borders, backgrounds, and shapes. */
+
+#include "nsImageRenderer.h"
+
+#include "mozilla/webrender/WebRenderAPI.h"
+
+#include "gfxContext.h"
+#include "gfxDrawable.h"
+#include "ImageOps.h"
+#include "ImageRegion.h"
+#include "mozilla/image/WebRenderImageProvider.h"
+#include "mozilla/layers/RenderRootStateManager.h"
+#include "mozilla/layers/StackingContextHelper.h"
+#include "mozilla/layers/WebRenderLayerManager.h"
+#include "nsContentUtils.h"
+#include "nsCSSRendering.h"
+#include "nsCSSRenderingGradients.h"
+#include "nsDeviceContext.h"
+#include "nsIFrame.h"
+#include "nsLayoutUtils.h"
+#include "nsStyleStructInlines.h"
+#include "mozilla/StaticPrefs_image.h"
+#include "mozilla/ISVGDisplayableFrame.h"
+#include "mozilla/SVGIntegrationUtils.h"
+#include "mozilla/SVGPaintServerFrame.h"
+#include "mozilla/SVGObserverUtils.h"
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+using namespace mozilla::image;
+using namespace mozilla::layers;
+
+nsSize CSSSizeOrRatio::ComputeConcreteSize() const {
+ NS_ASSERTION(CanComputeConcreteSize(), "Cannot compute");
+ if (mHasWidth && mHasHeight) {
+ return nsSize(mWidth, mHeight);
+ }
+ if (mHasWidth) {
+ return nsSize(mWidth, mRatio.Inverted().ApplyTo(mWidth));
+ }
+
+ MOZ_ASSERT(mHasHeight);
+ return nsSize(mRatio.ApplyTo(mHeight), mHeight);
+}
+
+nsImageRenderer::nsImageRenderer(nsIFrame* aForFrame, const StyleImage* aImage,
+ uint32_t aFlags)
+ : mForFrame(aForFrame),
+ mImage(&aImage->FinalImage()),
+ mImageResolution(aImage->GetResolution()),
+ mType(mImage->tag),
+ mImageContainer(nullptr),
+ mGradientData(nullptr),
+ mPaintServerFrame(nullptr),
+ mPrepareResult(ImgDrawResult::NOT_READY),
+ mSize(0, 0),
+ mFlags(aFlags),
+ mExtendMode(ExtendMode::CLAMP),
+ mMaskOp(StyleMaskMode::MatchSource) {}
+
+bool nsImageRenderer::PrepareImage() {
+ if (mImage->IsNone()) {
+ mPrepareResult = ImgDrawResult::BAD_IMAGE;
+ return false;
+ }
+
+ const bool isImageRequest = mImage->IsImageRequestType();
+ MOZ_ASSERT_IF(!isImageRequest, !mImage->GetImageRequest());
+ imgRequestProxy* request = nullptr;
+ if (isImageRequest) {
+ request = mImage->GetImageRequest();
+ if (!request) {
+ // request could be null here if the StyleImage refused
+ // to load a same-document URL, or the url was invalid, for example.
+ mPrepareResult = ImgDrawResult::BAD_IMAGE;
+ return false;
+ }
+ }
+
+ if (!mImage->IsComplete()) {
+ MOZ_DIAGNOSTIC_ASSERT(isImageRequest);
+
+ // Make sure the image is actually decoding.
+ bool frameComplete = request->StartDecodingWithResult(
+ imgIContainer::FLAG_ASYNC_NOTIFY |
+ imgIContainer::FLAG_AVOID_REDECODE_FOR_SIZE);
+
+ // Boost the loading priority since we know we want to draw the image.
+ if (mFlags & nsImageRenderer::FLAG_PAINTING_TO_WINDOW) {
+ request->BoostPriority(imgIRequest::CATEGORY_DISPLAY);
+ }
+
+ // Check again to see if we finished.
+ // We cannot prepare the image for rendering if it is not fully loaded.
+ if (!frameComplete && !mImage->IsComplete()) {
+ uint32_t imageStatus = 0;
+ request->GetImageStatus(&imageStatus);
+ if (imageStatus & imgIRequest::STATUS_ERROR) {
+ mPrepareResult = ImgDrawResult::BAD_IMAGE;
+ return false;
+ }
+
+ // If not errored, and we requested a sync decode, and the image has
+ // loaded, push on through because the Draw() will do a sync decode then.
+ const bool syncDecodeWillComplete =
+ (mFlags & FLAG_SYNC_DECODE_IMAGES) &&
+ (imageStatus & imgIRequest::STATUS_LOAD_COMPLETE);
+
+ bool canDrawPartial =
+ (mFlags & nsImageRenderer::FLAG_DRAW_PARTIAL_FRAMES) &&
+ isImageRequest && mImage->IsSizeAvailable() && !mImage->IsRect();
+
+ // If we are drawing a partial frame then we want to make sure there are
+ // some pixels to draw, otherwise we waste effort pushing through to draw
+ // nothing.
+ if (!syncDecodeWillComplete && canDrawPartial) {
+ nsCOMPtr<imgIContainer> image;
+ canDrawPartial =
+ canDrawPartial &&
+ NS_SUCCEEDED(request->GetImage(getter_AddRefs(image))) && image &&
+ image->GetType() == imgIContainer::TYPE_RASTER &&
+ image->HasDecodedPixels();
+ }
+
+ // If we can draw partial then proceed if we at least have the size
+ // available.
+ if (!(syncDecodeWillComplete || canDrawPartial)) {
+ mPrepareResult = ImgDrawResult::NOT_READY;
+ return false;
+ }
+ }
+ }
+
+ if (isImageRequest) {
+ nsCOMPtr<imgIContainer> srcImage;
+ nsresult rv = request->GetImage(getter_AddRefs(srcImage));
+ MOZ_ASSERT(NS_SUCCEEDED(rv) && srcImage,
+ "If GetImage() is failing, mImage->IsComplete() "
+ "should have returned false");
+ if (!NS_SUCCEEDED(rv)) {
+ srcImage = nullptr;
+ }
+
+ if (srcImage) {
+ StyleImageOrientation orientation =
+ mForFrame->StyleVisibility()->UsedImageOrientation(request);
+ srcImage = nsLayoutUtils::OrientImage(srcImage, orientation);
+ }
+
+ if (!mImage->IsRect()) {
+ mImageContainer.swap(srcImage);
+ } else {
+ auto croprect = mImage->ComputeActualCropRect();
+ if (!croprect || croprect->mRect.IsEmpty()) {
+ // The cropped image has zero size
+ mPrepareResult = ImgDrawResult::BAD_IMAGE;
+ return false;
+ }
+ if (croprect->mIsEntireImage) {
+ // The cropped image is identical to the source image
+ mImageContainer.swap(srcImage);
+ } else {
+ nsCOMPtr<imgIContainer> subImage =
+ ImageOps::Clip(srcImage, croprect->mRect, Nothing());
+ mImageContainer.swap(subImage);
+ }
+ }
+ mPrepareResult = ImgDrawResult::SUCCESS;
+ } else if (mImage->IsGradient()) {
+ mGradientData = &*mImage->AsGradient();
+ mPrepareResult = ImgDrawResult::SUCCESS;
+ } else if (mImage->IsElement()) {
+ dom::Element* paintElement = // may be null
+ SVGObserverUtils::GetAndObserveBackgroundImage(
+ mForFrame->FirstContinuation(), mImage->AsElement().AsAtom());
+ // If the referenced element is an <img>, <canvas>, or <video> element,
+ // prefer SurfaceFromElement as it's more reliable.
+ mImageElementSurface = nsLayoutUtils::SurfaceFromElement(paintElement);
+
+ if (!mImageElementSurface.GetSourceSurface()) {
+ nsIFrame* paintServerFrame =
+ paintElement ? paintElement->GetPrimaryFrame() : nullptr;
+ // If there's no referenced frame, or the referenced frame is
+ // non-displayable SVG, then we have nothing valid to paint.
+ if (!paintServerFrame ||
+ (paintServerFrame->IsFrameOfType(nsIFrame::eSVG) &&
+ !static_cast<SVGPaintServerFrame*>(
+ do_QueryFrame(paintServerFrame)) &&
+ !static_cast<ISVGDisplayableFrame*>(
+ do_QueryFrame(paintServerFrame)))) {
+ mPrepareResult = ImgDrawResult::BAD_IMAGE;
+ return false;
+ }
+ mPaintServerFrame = paintServerFrame;
+ }
+
+ mPrepareResult = ImgDrawResult::SUCCESS;
+ } else if (mImage->IsCrossFade()) {
+ // See bug 546052 - cross-fade implementation still being worked
+ // on.
+ mPrepareResult = ImgDrawResult::BAD_IMAGE;
+ return false;
+ } else {
+ MOZ_ASSERT(mImage->IsNone(), "Unknown image type?");
+ }
+
+ return IsReady();
+}
+
+CSSSizeOrRatio nsImageRenderer::ComputeIntrinsicSize() {
+ NS_ASSERTION(IsReady(),
+ "Ensure PrepareImage() has returned true "
+ "before calling me");
+
+ CSSSizeOrRatio result;
+ switch (mType) {
+ case StyleImage::Tag::Rect:
+ case StyleImage::Tag::Url: {
+ bool haveWidth, haveHeight;
+ CSSIntSize imageIntSize;
+ nsLayoutUtils::ComputeSizeForDrawing(mImageContainer, mImageResolution,
+ imageIntSize, result.mRatio,
+ haveWidth, haveHeight);
+ if (haveWidth) {
+ result.SetWidth(CSSPixel::ToAppUnits(imageIntSize.width));
+ }
+ if (haveHeight) {
+ result.SetHeight(CSSPixel::ToAppUnits(imageIntSize.height));
+ }
+
+ // If we know the aspect ratio and one of the dimensions,
+ // we can compute the other missing width or height.
+ if (!haveHeight && haveWidth && result.mRatio) {
+ nscoord intrinsicHeight =
+ result.mRatio.Inverted().ApplyTo(imageIntSize.width);
+ result.SetHeight(nsPresContext::CSSPixelsToAppUnits(intrinsicHeight));
+ } else if (haveHeight && !haveWidth && result.mRatio) {
+ nscoord intrinsicWidth = result.mRatio.ApplyTo(imageIntSize.height);
+ result.SetWidth(nsPresContext::CSSPixelsToAppUnits(intrinsicWidth));
+ }
+
+ break;
+ }
+ case StyleImage::Tag::Element: {
+ // XXX element() should have the width/height of the referenced element,
+ // and that element's ratio, if it matches. If it doesn't match, it
+ // should have no width/height or ratio. See element() in CSS images:
+ // <http://dev.w3.org/csswg/css-images-4/#element-notation>.
+ // Make sure to change nsStyleImageLayers::Size::DependsOnFrameSize
+ // when fixing this!
+ if (mPaintServerFrame) {
+ // SVG images have no intrinsic size
+ if (!mPaintServerFrame->IsFrameOfType(nsIFrame::eSVG)) {
+ // The intrinsic image size for a generic nsIFrame paint server is
+ // the union of the border-box rects of all of its continuations,
+ // rounded to device pixels.
+ int32_t appUnitsPerDevPixel =
+ mForFrame->PresContext()->AppUnitsPerDevPixel();
+ result.SetSize(IntSizeToAppUnits(
+ SVGIntegrationUtils::GetContinuationUnionSize(mPaintServerFrame)
+ .ToNearestPixels(appUnitsPerDevPixel),
+ appUnitsPerDevPixel));
+ }
+ } else {
+ NS_ASSERTION(mImageElementSurface.GetSourceSurface(),
+ "Surface should be ready.");
+ IntSize surfaceSize = mImageElementSurface.mSize;
+ result.SetSize(
+ nsSize(nsPresContext::CSSPixelsToAppUnits(surfaceSize.width),
+ nsPresContext::CSSPixelsToAppUnits(surfaceSize.height)));
+ }
+ break;
+ }
+ case StyleImage::Tag::ImageSet:
+ MOZ_FALLTHROUGH_ASSERT("image-set should be resolved already");
+ // Bug 546052 cross-fade not yet implemented.
+ case StyleImage::Tag::CrossFade:
+ // Per <http://dev.w3.org/csswg/css3-images/#gradients>, gradients have no
+ // intrinsic dimensions.
+ case StyleImage::Tag::Gradient:
+ case StyleImage::Tag::None:
+ break;
+ }
+
+ return result;
+}
+
+/* static */
+nsSize nsImageRenderer::ComputeConcreteSize(
+ const CSSSizeOrRatio& aSpecifiedSize, const CSSSizeOrRatio& aIntrinsicSize,
+ const nsSize& aDefaultSize) {
+ // The specified size is fully specified, just use that
+ if (aSpecifiedSize.IsConcrete()) {
+ return aSpecifiedSize.ComputeConcreteSize();
+ }
+
+ MOZ_ASSERT(!aSpecifiedSize.mHasWidth || !aSpecifiedSize.mHasHeight);
+
+ if (!aSpecifiedSize.mHasWidth && !aSpecifiedSize.mHasHeight) {
+ // no specified size, try using the intrinsic size
+ if (aIntrinsicSize.CanComputeConcreteSize()) {
+ return aIntrinsicSize.ComputeConcreteSize();
+ }
+
+ if (aIntrinsicSize.mHasWidth) {
+ return nsSize(aIntrinsicSize.mWidth, aDefaultSize.height);
+ }
+ if (aIntrinsicSize.mHasHeight) {
+ return nsSize(aDefaultSize.width, aIntrinsicSize.mHeight);
+ }
+
+ // couldn't use the intrinsic size either, revert to using the default size
+ return ComputeConstrainedSize(aDefaultSize, aIntrinsicSize.mRatio, CONTAIN);
+ }
+
+ MOZ_ASSERT(aSpecifiedSize.mHasWidth || aSpecifiedSize.mHasHeight);
+
+ // The specified height is partial, try to compute the missing part.
+ if (aSpecifiedSize.mHasWidth) {
+ nscoord height;
+ if (aIntrinsicSize.HasRatio()) {
+ height = aIntrinsicSize.mRatio.Inverted().ApplyTo(aSpecifiedSize.mWidth);
+ } else if (aIntrinsicSize.mHasHeight) {
+ height = aIntrinsicSize.mHeight;
+ } else {
+ height = aDefaultSize.height;
+ }
+ return nsSize(aSpecifiedSize.mWidth, height);
+ }
+
+ MOZ_ASSERT(aSpecifiedSize.mHasHeight);
+ nscoord width;
+ if (aIntrinsicSize.HasRatio()) {
+ width = aIntrinsicSize.mRatio.ApplyTo(aSpecifiedSize.mHeight);
+ } else if (aIntrinsicSize.mHasWidth) {
+ width = aIntrinsicSize.mWidth;
+ } else {
+ width = aDefaultSize.width;
+ }
+ return nsSize(width, aSpecifiedSize.mHeight);
+}
+
+/* static */
+nsSize nsImageRenderer::ComputeConstrainedSize(
+ const nsSize& aConstrainingSize, const AspectRatio& aIntrinsicRatio,
+ FitType aFitType) {
+ if (!aIntrinsicRatio) {
+ return aConstrainingSize;
+ }
+
+ // Suppose we're doing a "contain" fit. If the image's aspect ratio has a
+ // "fatter" shape than the constraint area, then we need to use the
+ // constraint area's full width, and we need to use the aspect ratio to
+ // produce a height. On the other hand, if the aspect ratio is "skinnier", we
+ // use the constraint area's full height, and we use the aspect ratio to
+ // produce a width. (If instead we're doing a "cover" fit, then it can easily
+ // be seen that we should do precisely the opposite.)
+ //
+ // We check if the image's aspect ratio is "fatter" than the constraint area
+ // by simply applying the aspect ratio to the constraint area's height, to
+ // produce a "hypothetical width", and we check whether that
+ // aspect-ratio-provided "hypothetical width" is wider than the constraint
+ // area's actual width. If it is, then the aspect ratio is fatter than the
+ // constraint area.
+ //
+ // This is equivalent to the more descriptive alternative:
+ //
+ // AspectRatio::FromSize(aConstrainingSize) < aIntrinsicRatio
+ //
+ // But gracefully handling the case where one of the two dimensions from
+ // aConstrainingSize is zero. This is easy to prove since:
+ //
+ // aConstrainingSize.width / aConstrainingSize.height < aIntrinsicRatio
+ //
+ // Is trivially equivalent to:
+ //
+ // aIntrinsicRatio.width < aIntrinsicRatio * aConstrainingSize.height
+ //
+ // For the cases where height is not zero.
+ //
+ // We use float math here to avoid losing precision for very large backgrounds
+ // since we use saturating nscoord math otherwise.
+ const float constraintWidth = float(aConstrainingSize.width);
+ const float hypotheticalWidth =
+ aIntrinsicRatio.ApplyToFloat(aConstrainingSize.height);
+
+ nsSize size;
+ if ((aFitType == CONTAIN) == (constraintWidth < hypotheticalWidth)) {
+ size.width = aConstrainingSize.width;
+ size.height = aIntrinsicRatio.Inverted().ApplyTo(aConstrainingSize.width);
+ // If we're reducing the size by less than one css pixel, then just use the
+ // constraining size.
+ if (aFitType == CONTAIN &&
+ aConstrainingSize.height - size.height < AppUnitsPerCSSPixel()) {
+ size.height = aConstrainingSize.height;
+ }
+ } else {
+ size.height = aConstrainingSize.height;
+ size.width = aIntrinsicRatio.ApplyTo(aConstrainingSize.height);
+ if (aFitType == CONTAIN &&
+ aConstrainingSize.width - size.width < AppUnitsPerCSSPixel()) {
+ size.width = aConstrainingSize.width;
+ }
+ }
+ return size;
+}
+
+/**
+ * mSize is the image's "preferred" size for this particular rendering, while
+ * the drawn (aka concrete) size is the actual rendered size after accounting
+ * for background-size etc.. The preferred size is most often the image's
+ * intrinsic dimensions. But for images with incomplete intrinsic dimensions,
+ * the preferred size varies, depending on the specified and default sizes, see
+ * nsImageRenderer::Compute*Size.
+ *
+ * This distinction is necessary because the components of a vector image are
+ * specified with respect to its preferred size for a rendering situation, not
+ * to its actual rendered size. For example, consider a 4px wide background
+ * vector image with no height which contains a left-aligned
+ * 2px wide black rectangle with height 100%. If the background-size width is
+ * auto (or 4px), the vector image will render 4px wide, and the black rectangle
+ * will be 2px wide. If the background-size width is 8px, the vector image will
+ * render 8px wide, and the black rectangle will be 4px wide -- *not* 2px wide.
+ * In both cases mSize.width will be 4px; but in the first case the returned
+ * width will be 4px, while in the second case the returned width will be 8px.
+ */
+void nsImageRenderer::SetPreferredSize(const CSSSizeOrRatio& aIntrinsicSize,
+ const nsSize& aDefaultSize) {
+ mSize.width =
+ aIntrinsicSize.mHasWidth ? aIntrinsicSize.mWidth : aDefaultSize.width;
+ mSize.height =
+ aIntrinsicSize.mHasHeight ? aIntrinsicSize.mHeight : aDefaultSize.height;
+}
+
+// Convert from nsImageRenderer flags to the flags we want to use for drawing in
+// the imgIContainer namespace.
+static uint32_t ConvertImageRendererToDrawFlags(uint32_t aImageRendererFlags) {
+ uint32_t drawFlags = imgIContainer::FLAG_ASYNC_NOTIFY;
+ if (aImageRendererFlags & nsImageRenderer::FLAG_SYNC_DECODE_IMAGES) {
+ drawFlags |= imgIContainer::FLAG_SYNC_DECODE;
+ } else {
+ drawFlags |= imgIContainer::FLAG_SYNC_DECODE_IF_FAST;
+ }
+ if (aImageRendererFlags & (nsImageRenderer::FLAG_PAINTING_TO_WINDOW |
+ nsImageRenderer::FLAG_HIGH_QUALITY_SCALING)) {
+ drawFlags |= imgIContainer::FLAG_HIGH_QUALITY_SCALING;
+ }
+ return drawFlags;
+}
+
+ImgDrawResult nsImageRenderer::Draw(nsPresContext* aPresContext,
+ gfxContext& aRenderingContext,
+ const nsRect& aDirtyRect,
+ const nsRect& aDest, const nsRect& aFill,
+ const nsPoint& aAnchor,
+ const nsSize& aRepeatSize,
+ const CSSIntRect& aSrc, float aOpacity) {
+ if (!IsReady()) {
+ MOZ_ASSERT_UNREACHABLE(
+ "Ensure PrepareImage() has returned true before "
+ "calling me");
+ return ImgDrawResult::TEMPORARY_ERROR;
+ }
+
+ if (aDest.IsEmpty() || aFill.IsEmpty() || mSize.width <= 0 ||
+ mSize.height <= 0) {
+ return ImgDrawResult::SUCCESS;
+ }
+
+ SamplingFilter samplingFilter =
+ nsLayoutUtils::GetSamplingFilterForFrame(mForFrame);
+ ImgDrawResult result = ImgDrawResult::SUCCESS;
+ gfxContext* ctx = &aRenderingContext;
+ Maybe<gfxContext> tempCtx;
+ IntRect tmpDTRect;
+
+ if (ctx->CurrentOp() != CompositionOp::OP_OVER ||
+ mMaskOp == StyleMaskMode::Luminance) {
+ gfxRect clipRect = ctx->GetClipExtents(gfxContext::eDeviceSpace);
+ tmpDTRect = RoundedOut(ToRect(clipRect));
+ if (tmpDTRect.IsEmpty()) {
+ return ImgDrawResult::SUCCESS;
+ }
+ RefPtr<DrawTarget> tempDT = ctx->GetDrawTarget()->CreateSimilarDrawTarget(
+ tmpDTRect.Size(), SurfaceFormat::B8G8R8A8);
+ if (!tempDT || !tempDT->IsValid()) {
+ gfxDevCrash(LogReason::InvalidContext)
+ << "ImageRenderer::Draw problem " << gfx::hexa(tempDT);
+ return ImgDrawResult::TEMPORARY_ERROR;
+ }
+ tempDT->SetTransform(ctx->GetDrawTarget()->GetTransform() *
+ Matrix::Translation(-tmpDTRect.TopLeft()));
+ tempCtx.emplace(tempDT, /* aPreserveTransform */ true);
+ ctx = &tempCtx.ref();
+ if (!ctx) {
+ gfxDevCrash(LogReason::InvalidContext)
+ << "ImageRenderer::Draw problem " << gfx::hexa(tempDT);
+ return ImgDrawResult::TEMPORARY_ERROR;
+ }
+ }
+
+ switch (mType) {
+ case StyleImage::Tag::Rect:
+ case StyleImage::Tag::Url: {
+ result = nsLayoutUtils::DrawBackgroundImage(
+ *ctx, mForFrame, aPresContext, mImageContainer, samplingFilter, aDest,
+ aFill, aRepeatSize, aAnchor, aDirtyRect,
+ ConvertImageRendererToDrawFlags(mFlags), mExtendMode, aOpacity);
+ break;
+ }
+ case StyleImage::Tag::Gradient: {
+ nsCSSGradientRenderer renderer = nsCSSGradientRenderer::Create(
+ aPresContext, mForFrame->Style(), *mGradientData, mSize);
+
+ renderer.Paint(*ctx, aDest, aFill, aRepeatSize, aSrc, aDirtyRect,
+ aOpacity);
+ break;
+ }
+ case StyleImage::Tag::Element: {
+ RefPtr<gfxDrawable> drawable = DrawableForElement(aDest, *ctx);
+ if (!drawable) {
+ NS_WARNING("Could not create drawable for element");
+ return ImgDrawResult::TEMPORARY_ERROR;
+ }
+
+ nsCOMPtr<imgIContainer> image(ImageOps::CreateFromDrawable(drawable));
+ result = nsLayoutUtils::DrawImage(
+ *ctx, mForFrame->Style(), aPresContext, image, samplingFilter, aDest,
+ aFill, aAnchor, aDirtyRect, ConvertImageRendererToDrawFlags(mFlags),
+ aOpacity);
+ break;
+ }
+ case StyleImage::Tag::ImageSet:
+ MOZ_FALLTHROUGH_ASSERT("image-set should be resolved already");
+ // See bug 546052 - cross-fade implementation still being worked
+ // on.
+ case StyleImage::Tag::CrossFade:
+ case StyleImage::Tag::None:
+ break;
+ }
+
+ if (!tmpDTRect.IsEmpty()) {
+ DrawTarget* dt = aRenderingContext.GetDrawTarget();
+ Matrix oldTransform = dt->GetTransform();
+ dt->SetTransform(Matrix());
+ if (mMaskOp == StyleMaskMode::Luminance) {
+ RefPtr<SourceSurface> surf = ctx->GetDrawTarget()->IntoLuminanceSource(
+ LuminanceType::LUMINANCE, 1.0f);
+ dt->MaskSurface(ColorPattern(DeviceColor(0, 0, 0, 1.0f)), surf,
+ tmpDTRect.TopLeft(),
+ DrawOptions(1.0f, aRenderingContext.CurrentOp()));
+ } else {
+ RefPtr<SourceSurface> surf = ctx->GetDrawTarget()->Snapshot();
+ dt->DrawSurface(
+ surf,
+ Rect(tmpDTRect.x, tmpDTRect.y, tmpDTRect.width, tmpDTRect.height),
+ Rect(0, 0, tmpDTRect.width, tmpDTRect.height),
+ DrawSurfaceOptions(SamplingFilter::POINT),
+ DrawOptions(1.0f, aRenderingContext.CurrentOp()));
+ }
+
+ dt->SetTransform(oldTransform);
+ }
+
+ if (!mImage->IsComplete()) {
+ result &= ImgDrawResult::SUCCESS_NOT_COMPLETE;
+ }
+
+ return result;
+}
+
+ImgDrawResult nsImageRenderer::BuildWebRenderDisplayItems(
+ nsPresContext* aPresContext, mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const mozilla::layers::StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager, nsDisplayItem* aItem,
+ const nsRect& aDirtyRect, const nsRect& aDest, const nsRect& aFill,
+ const nsPoint& aAnchor, const nsSize& aRepeatSize, const CSSIntRect& aSrc,
+ float aOpacity) {
+ if (!IsReady()) {
+ MOZ_ASSERT_UNREACHABLE(
+ "Ensure PrepareImage() has returned true before "
+ "calling me");
+ return ImgDrawResult::NOT_READY;
+ }
+
+ if (aDest.IsEmpty() || aFill.IsEmpty() || mSize.width <= 0 ||
+ mSize.height <= 0) {
+ return ImgDrawResult::SUCCESS;
+ }
+
+ ImgDrawResult drawResult = ImgDrawResult::SUCCESS;
+ switch (mType) {
+ case StyleImage::Tag::Gradient: {
+ nsCSSGradientRenderer renderer = nsCSSGradientRenderer::Create(
+ aPresContext, mForFrame->Style(), *mGradientData, mSize);
+
+ renderer.BuildWebRenderDisplayItems(aBuilder, aSc, aDest, aFill,
+ aRepeatSize, aSrc,
+ !aItem->BackfaceIsHidden(), aOpacity);
+ break;
+ }
+ case StyleImage::Tag::Rect:
+ case StyleImage::Tag::Url: {
+ ExtendMode extendMode = mExtendMode;
+ if (aDest.Contains(aFill)) {
+ extendMode = ExtendMode::CLAMP;
+ }
+
+ uint32_t containerFlags = ConvertImageRendererToDrawFlags(mFlags);
+ if (extendMode == ExtendMode::CLAMP &&
+ StaticPrefs::image_svg_blob_image() &&
+ mImageContainer->GetType() == imgIContainer::TYPE_VECTOR) {
+ containerFlags |= imgIContainer::FLAG_RECORD_BLOB;
+ }
+
+ CSSIntSize destCSSSize{
+ nsPresContext::AppUnitsToIntCSSPixels(aDest.width),
+ nsPresContext::AppUnitsToIntCSSPixels(aDest.height)};
+
+ SVGImageContext svgContext(Some(destCSSSize));
+ Maybe<ImageIntRegion> region;
+
+ const int32_t appUnitsPerDevPixel =
+ mForFrame->PresContext()->AppUnitsPerDevPixel();
+ LayoutDeviceRect destRect =
+ LayoutDeviceRect::FromAppUnits(aDest, appUnitsPerDevPixel);
+ LayoutDeviceRect clipRect =
+ LayoutDeviceRect::FromAppUnits(aFill, appUnitsPerDevPixel);
+ auto stretchSize = wr::ToLayoutSize(destRect.Size());
+
+ gfx::IntSize decodeSize =
+ nsLayoutUtils::ComputeImageContainerDrawingParameters(
+ mImageContainer, mForFrame, destRect, clipRect, aSc,
+ containerFlags, svgContext, region);
+
+ RefPtr<image::WebRenderImageProvider> provider;
+ drawResult = mImageContainer->GetImageProvider(
+ aManager->LayerManager(), decodeSize, svgContext, region,
+ containerFlags, getter_AddRefs(provider));
+
+ Maybe<wr::ImageKey> key =
+ aManager->CommandBuilder().CreateImageProviderKey(
+ aItem, provider, drawResult, aResources);
+ if (key.isNothing()) {
+ break;
+ }
+
+ auto rendering =
+ wr::ToImageRendering(aItem->Frame()->UsedImageRendering());
+ wr::LayoutRect clip = wr::ToLayoutRect(clipRect);
+
+ // If we provided a region to the provider, then it already took the
+ // dest rect into account when it did the recording.
+ wr::LayoutRect dest = region ? clip : wr::ToLayoutRect(destRect);
+
+ if (extendMode == ExtendMode::CLAMP) {
+ // The image is not repeating. Just push as a regular image.
+ aBuilder.PushImage(dest, clip, !aItem->BackfaceIsHidden(), false,
+ rendering, key.value(), true,
+ wr::ColorF{1.0f, 1.0f, 1.0f, aOpacity});
+ } else {
+ nsPoint firstTilePos = nsLayoutUtils::GetBackgroundFirstTilePos(
+ aDest.TopLeft(), aFill.TopLeft(), aRepeatSize);
+ LayoutDeviceRect fillRect = LayoutDeviceRect::FromAppUnits(
+ nsRect(firstTilePos.x, firstTilePos.y,
+ aFill.XMost() - firstTilePos.x,
+ aFill.YMost() - firstTilePos.y),
+ appUnitsPerDevPixel);
+ wr::LayoutRect fill = wr::ToLayoutRect(fillRect);
+
+ switch (extendMode) {
+ case ExtendMode::REPEAT_Y:
+ fill.min.x = dest.min.x;
+ fill.max.x = dest.max.x;
+ stretchSize.width = dest.width();
+ break;
+ case ExtendMode::REPEAT_X:
+ fill.min.y = dest.min.y;
+ fill.max.y = dest.max.y;
+ stretchSize.height = dest.height();
+ break;
+ default:
+ break;
+ }
+
+ LayoutDeviceSize gapSize = LayoutDeviceSize::FromAppUnits(
+ aRepeatSize - aDest.Size(), appUnitsPerDevPixel);
+
+ aBuilder.PushRepeatingImage(fill, clip, !aItem->BackfaceIsHidden(),
+ stretchSize, wr::ToLayoutSize(gapSize),
+ rendering, key.value(), true,
+ wr::ColorF{1.0f, 1.0f, 1.0f, aOpacity});
+ }
+ break;
+ }
+ default:
+ break;
+ }
+
+ if (!mImage->IsComplete() && drawResult == ImgDrawResult::SUCCESS) {
+ return ImgDrawResult::SUCCESS_NOT_COMPLETE;
+ }
+ return drawResult;
+}
+
+already_AddRefed<gfxDrawable> nsImageRenderer::DrawableForElement(
+ const nsRect& aImageRect, gfxContext& aContext) {
+ NS_ASSERTION(mType == StyleImage::Tag::Element,
+ "DrawableForElement only makes sense if backed by an element");
+ if (mPaintServerFrame) {
+ // XXX(seth): In order to not pass FLAG_SYNC_DECODE_IMAGES here,
+ // DrawableFromPaintServer would have to return a ImgDrawResult indicating
+ // whether any images could not be painted because they weren't fully
+ // decoded. Even always passing FLAG_SYNC_DECODE_IMAGES won't eliminate all
+ // problems, as it won't help if there are image which haven't finished
+ // loading, but it's better than nothing.
+ int32_t appUnitsPerDevPixel =
+ mForFrame->PresContext()->AppUnitsPerDevPixel();
+ nsRect destRect = aImageRect - aImageRect.TopLeft();
+ nsIntSize roundedOut = destRect.ToOutsidePixels(appUnitsPerDevPixel).Size();
+ IntSize imageSize(roundedOut.width, roundedOut.height);
+
+ RefPtr<gfxDrawable> drawable;
+
+ SurfaceFormat format = aContext.GetDrawTarget()->GetFormat();
+ // Don't allow creating images that are too big
+ if (aContext.GetDrawTarget()->CanCreateSimilarDrawTarget(imageSize,
+ format)) {
+ drawable = SVGIntegrationUtils::DrawableFromPaintServer(
+ mPaintServerFrame, mForFrame, mSize, imageSize,
+ aContext.GetDrawTarget(), aContext.CurrentMatrixDouble(),
+ SVGIntegrationUtils::FLAG_SYNC_DECODE_IMAGES);
+ }
+
+ return drawable.forget();
+ }
+ NS_ASSERTION(mImageElementSurface.GetSourceSurface(),
+ "Surface should be ready.");
+ RefPtr<gfxDrawable> drawable =
+ new gfxSurfaceDrawable(mImageElementSurface.GetSourceSurface().get(),
+ mImageElementSurface.mSize);
+ return drawable.forget();
+}
+
+ImgDrawResult nsImageRenderer::DrawLayer(
+ nsPresContext* aPresContext, gfxContext& aRenderingContext,
+ const nsRect& aDest, const nsRect& aFill, const nsPoint& aAnchor,
+ const nsRect& aDirty, const nsSize& aRepeatSize, float aOpacity) {
+ if (!IsReady()) {
+ MOZ_ASSERT_UNREACHABLE(
+ "Ensure PrepareImage() has returned true before "
+ "calling me");
+ return ImgDrawResult::TEMPORARY_ERROR;
+ }
+
+ if (aDest.IsEmpty() || aFill.IsEmpty() || mSize.width <= 0 ||
+ mSize.height <= 0) {
+ return ImgDrawResult::SUCCESS;
+ }
+
+ return Draw(
+ aPresContext, aRenderingContext, aDirty, aDest, aFill, aAnchor,
+ aRepeatSize,
+ CSSIntRect(0, 0, nsPresContext::AppUnitsToIntCSSPixels(mSize.width),
+ nsPresContext::AppUnitsToIntCSSPixels(mSize.height)),
+ aOpacity);
+}
+
+ImgDrawResult nsImageRenderer::BuildWebRenderDisplayItemsForLayer(
+ nsPresContext* aPresContext, mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const mozilla::layers::StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager, nsDisplayItem* aItem,
+ const nsRect& aDest, const nsRect& aFill, const nsPoint& aAnchor,
+ const nsRect& aDirty, const nsSize& aRepeatSize, float aOpacity) {
+ if (!IsReady()) {
+ MOZ_ASSERT_UNREACHABLE(
+ "Ensure PrepareImage() has returned true before "
+ "calling me");
+ return mPrepareResult;
+ }
+
+ CSSIntRect srcRect(0, 0, nsPresContext::AppUnitsToIntCSSPixels(mSize.width),
+ nsPresContext::AppUnitsToIntCSSPixels(mSize.height));
+
+ if (aDest.IsEmpty() || aFill.IsEmpty() || srcRect.IsEmpty()) {
+ return ImgDrawResult::SUCCESS;
+ }
+ return BuildWebRenderDisplayItems(aPresContext, aBuilder, aResources, aSc,
+ aManager, aItem, aDirty, aDest, aFill,
+ aAnchor, aRepeatSize, srcRect, aOpacity);
+}
+
+/**
+ * Compute the size and position of the master copy of the image. I.e., a single
+ * tile used to fill the dest rect.
+ * aFill The destination rect to be filled
+ * aHFill and aVFill are the repeat patterns for the component -
+ * StyleBorderImageRepeat - i.e., how a tiling unit is used to fill aFill
+ * aUnitSize The size of the source rect in dest coords.
+ */
+static nsRect ComputeTile(nsRect& aFill, StyleBorderImageRepeat aHFill,
+ StyleBorderImageRepeat aVFill,
+ const nsSize& aUnitSize, nsSize& aRepeatSize) {
+ nsRect tile;
+ switch (aHFill) {
+ case StyleBorderImageRepeat::Stretch:
+ tile.x = aFill.x;
+ tile.width = aFill.width;
+ aRepeatSize.width = tile.width;
+ break;
+ case StyleBorderImageRepeat::Repeat:
+ tile.x = aFill.x + aFill.width / 2 - aUnitSize.width / 2;
+ tile.width = aUnitSize.width;
+ aRepeatSize.width = tile.width;
+ break;
+ case StyleBorderImageRepeat::Round:
+ tile.x = aFill.x;
+ tile.width =
+ nsCSSRendering::ComputeRoundedSize(aUnitSize.width, aFill.width);
+ aRepeatSize.width = tile.width;
+ break;
+ case StyleBorderImageRepeat::Space: {
+ nscoord space;
+ aRepeatSize.width = nsCSSRendering::ComputeBorderSpacedRepeatSize(
+ aUnitSize.width, aFill.width, space);
+ tile.x = aFill.x + space;
+ tile.width = aUnitSize.width;
+ aFill.x = tile.x;
+ aFill.width = aFill.width - space * 2;
+ } break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("unrecognized border-image fill style");
+ }
+
+ switch (aVFill) {
+ case StyleBorderImageRepeat::Stretch:
+ tile.y = aFill.y;
+ tile.height = aFill.height;
+ aRepeatSize.height = tile.height;
+ break;
+ case StyleBorderImageRepeat::Repeat:
+ tile.y = aFill.y + aFill.height / 2 - aUnitSize.height / 2;
+ tile.height = aUnitSize.height;
+ aRepeatSize.height = tile.height;
+ break;
+ case StyleBorderImageRepeat::Round:
+ tile.y = aFill.y;
+ tile.height =
+ nsCSSRendering::ComputeRoundedSize(aUnitSize.height, aFill.height);
+ aRepeatSize.height = tile.height;
+ break;
+ case StyleBorderImageRepeat::Space: {
+ nscoord space;
+ aRepeatSize.height = nsCSSRendering::ComputeBorderSpacedRepeatSize(
+ aUnitSize.height, aFill.height, space);
+ tile.y = aFill.y + space;
+ tile.height = aUnitSize.height;
+ aFill.y = tile.y;
+ aFill.height = aFill.height - space * 2;
+ } break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("unrecognized border-image fill style");
+ }
+
+ return tile;
+}
+
+/**
+ * Returns true if the given set of arguments will require the tiles which fill
+ * the dest rect to be scaled from the source tile. See comment on ComputeTile
+ * for argument descriptions.
+ */
+static bool RequiresScaling(const nsRect& aFill, StyleBorderImageRepeat aHFill,
+ StyleBorderImageRepeat aVFill,
+ const nsSize& aUnitSize) {
+ // If we have no tiling in either direction, we can skip the intermediate
+ // scaling step.
+ return (aHFill != StyleBorderImageRepeat::Stretch ||
+ aVFill != StyleBorderImageRepeat::Stretch) &&
+ (aUnitSize.width != aFill.width || aUnitSize.height != aFill.height);
+}
+
+ImgDrawResult nsImageRenderer::DrawBorderImageComponent(
+ nsPresContext* aPresContext, gfxContext& aRenderingContext,
+ const nsRect& aDirtyRect, const nsRect& aFill, const CSSIntRect& aSrc,
+ StyleBorderImageRepeat aHFill, StyleBorderImageRepeat aVFill,
+ const nsSize& aUnitSize, uint8_t aIndex,
+ const Maybe<nsSize>& aSVGViewportSize, const bool aHasIntrinsicRatio) {
+ if (!IsReady()) {
+ MOZ_ASSERT_UNREACHABLE(
+ "Ensure PrepareImage() has returned true before "
+ "calling me");
+ return ImgDrawResult::BAD_ARGS;
+ }
+
+ if (aFill.IsEmpty() || aSrc.IsEmpty()) {
+ return ImgDrawResult::SUCCESS;
+ }
+
+ const bool isRequestBacked =
+ mType == StyleImage::Tag::Url || mType == StyleImage::Tag::Rect;
+ MOZ_ASSERT(isRequestBacked == mImage->IsImageRequestType());
+
+ if (isRequestBacked || mType == StyleImage::Tag::Element) {
+ nsCOMPtr<imgIContainer> subImage;
+
+ // To draw one portion of an image into a border component, we stretch that
+ // portion to match the size of that border component and then draw onto.
+ // However, preserveAspectRatio attribute of a SVG image may break this
+ // rule. To get correct rendering result, we add
+ // FLAG_FORCE_PRESERVEASPECTRATIO_NONE flag here, to tell mImage to ignore
+ // preserveAspectRatio attribute, and always do non-uniform stretch.
+ uint32_t drawFlags = ConvertImageRendererToDrawFlags(mFlags) |
+ imgIContainer::FLAG_FORCE_PRESERVEASPECTRATIO_NONE;
+ // For those SVG image sources which don't have fixed aspect ratio (i.e.
+ // without viewport size and viewBox), we should scale the source uniformly
+ // after the viewport size is decided by "Default Sizing Algorithm".
+ if (!aHasIntrinsicRatio) {
+ drawFlags = drawFlags | imgIContainer::FLAG_FORCE_UNIFORM_SCALING;
+ }
+ // Retrieve or create the subimage we'll draw.
+ nsIntRect srcRect(aSrc.x, aSrc.y, aSrc.width, aSrc.height);
+ if (isRequestBacked) {
+ subImage = ImageOps::Clip(mImageContainer, srcRect, aSVGViewportSize);
+ } else {
+ // This path, for eStyleImageType_Element, is currently slower than it
+ // needs to be because we don't cache anything. (In particular, if we have
+ // to draw to a temporary surface inside ClippedImage, we don't cache that
+ // temporary surface since we immediately throw the ClippedImage we create
+ // here away.) However, if we did cache, we'd need to know when to
+ // invalidate that cache, and it's not clear that it's worth the trouble
+ // since using border-image with -moz-element is rare.
+
+ RefPtr<gfxDrawable> drawable =
+ DrawableForElement(nsRect(nsPoint(), mSize), aRenderingContext);
+ if (!drawable) {
+ NS_WARNING("Could not create drawable for element");
+ return ImgDrawResult::TEMPORARY_ERROR;
+ }
+
+ nsCOMPtr<imgIContainer> image(ImageOps::CreateFromDrawable(drawable));
+ subImage = ImageOps::Clip(image, srcRect, aSVGViewportSize);
+ }
+
+ MOZ_ASSERT(!aSVGViewportSize ||
+ subImage->GetType() == imgIContainer::TYPE_VECTOR);
+
+ SamplingFilter samplingFilter =
+ nsLayoutUtils::GetSamplingFilterForFrame(mForFrame);
+
+ if (!RequiresScaling(aFill, aHFill, aVFill, aUnitSize)) {
+ ImgDrawResult result = nsLayoutUtils::DrawSingleImage(
+ aRenderingContext, aPresContext, subImage, samplingFilter, aFill,
+ aDirtyRect, SVGImageContext(), drawFlags);
+
+ if (!mImage->IsComplete()) {
+ result &= ImgDrawResult::SUCCESS_NOT_COMPLETE;
+ }
+
+ return result;
+ }
+
+ nsSize repeatSize;
+ nsRect fillRect(aFill);
+ nsRect tile = ComputeTile(fillRect, aHFill, aVFill, aUnitSize, repeatSize);
+
+ ImgDrawResult result = nsLayoutUtils::DrawBackgroundImage(
+ aRenderingContext, mForFrame, aPresContext, subImage, samplingFilter,
+ tile, fillRect, repeatSize, tile.TopLeft(), aDirtyRect, drawFlags,
+ ExtendMode::CLAMP, 1.0);
+
+ if (!mImage->IsComplete()) {
+ result &= ImgDrawResult::SUCCESS_NOT_COMPLETE;
+ }
+
+ return result;
+ }
+
+ nsSize repeatSize(aFill.Size());
+ nsRect fillRect(aFill);
+ nsRect destTile =
+ RequiresScaling(fillRect, aHFill, aVFill, aUnitSize)
+ ? ComputeTile(fillRect, aHFill, aVFill, aUnitSize, repeatSize)
+ : fillRect;
+
+ return Draw(aPresContext, aRenderingContext, aDirtyRect, destTile, fillRect,
+ destTile.TopLeft(), repeatSize, aSrc);
+}
+
+ImgDrawResult nsImageRenderer::DrawShapeImage(nsPresContext* aPresContext,
+ gfxContext& aRenderingContext) {
+ if (!IsReady()) {
+ MOZ_ASSERT_UNREACHABLE(
+ "Ensure PrepareImage() has returned true before "
+ "calling me");
+ return ImgDrawResult::NOT_READY;
+ }
+
+ if (mSize.width <= 0 || mSize.height <= 0) {
+ return ImgDrawResult::SUCCESS;
+ }
+
+ if (mImage->IsImageRequestType()) {
+ uint32_t drawFlags =
+ ConvertImageRendererToDrawFlags(mFlags) | imgIContainer::FRAME_FIRST;
+ nsRect dest(nsPoint(0, 0), mSize);
+ // We have a tricky situation in our choice of SamplingFilter. Shape
+ // images define a float area based on the alpha values in the rendered
+ // pixels. When multiple device pixels are used for one css pixel, the
+ // sampling can change crisp edges into aliased edges. For visual pixels,
+ // that's usually the right choice. For defining a float area, it can
+ // cause problems. If a style is using a shape-image-threshold value that
+ // is less than the alpha of the edge pixels, any filtering may smear the
+ // alpha into adjacent pixels and expand the float area in a confusing
+ // way. Since the alpha threshold can be set precisely in CSS, and since a
+ // web author may be counting on that threshold to define a precise float
+ // area from an image, it is least confusing to have the rendered pixels
+ // have unfiltered alpha. We use SamplingFilter::POINT to ensure that each
+ // rendered pixel has an alpha that precisely matches the alpha of the
+ // closest pixel in the image.
+ return nsLayoutUtils::DrawSingleImage(
+ aRenderingContext, aPresContext, mImageContainer, SamplingFilter::POINT,
+ dest, dest, SVGImageContext(), drawFlags);
+ }
+
+ if (mImage->IsGradient()) {
+ nsCSSGradientRenderer renderer = nsCSSGradientRenderer::Create(
+ aPresContext, mForFrame->Style(), *mGradientData, mSize);
+ nsRect dest(nsPoint(0, 0), mSize);
+ renderer.Paint(aRenderingContext, dest, dest, mSize,
+ CSSIntRect::FromAppUnitsRounded(dest), dest, 1.0);
+ return ImgDrawResult::SUCCESS;
+ }
+
+ // Unsupported image type.
+ return ImgDrawResult::BAD_IMAGE;
+}
+
+bool nsImageRenderer::IsRasterImage() {
+ return mImageContainer &&
+ mImageContainer->GetType() == imgIContainer::TYPE_RASTER;
+}
+
+already_AddRefed<imgIContainer> nsImageRenderer::GetImage() {
+ return do_AddRef(mImageContainer);
+}
diff --git a/layout/painting/nsImageRenderer.h b/layout/painting/nsImageRenderer.h
new file mode 100644
index 0000000000..122564f886
--- /dev/null
+++ b/layout/painting/nsImageRenderer.h
@@ -0,0 +1,313 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsImageRenderer_h__
+#define nsImageRenderer_h__
+
+#include "nsStyleStruct.h"
+#include "Units.h"
+#include "mozilla/AspectRatio.h"
+#include "mozilla/SurfaceFromElementResult.h"
+
+class gfxDrawable;
+
+namespace mozilla {
+class nsDisplayItem;
+
+namespace layers {
+class StackingContextHelper;
+class WebRenderParentCommand;
+class RenderRootStateManager;
+} // namespace layers
+
+namespace wr {
+class DisplayListBuilder;
+class IpcResourceUpdateQueue;
+} // namespace wr
+
+// A CSSSizeOrRatio represents a (possibly partially specified) size for use
+// in computing image sizes. Either or both of the width and height might be
+// given. A ratio of width to height may also be given. If we at least two
+// of these then we can compute a concrete size, that is a width and height.
+struct CSSSizeOrRatio {
+ CSSSizeOrRatio()
+ : mWidth(0), mHeight(0), mHasWidth(false), mHasHeight(false) {}
+
+ bool CanComputeConcreteSize() const {
+ return mHasWidth + mHasHeight + HasRatio() >= 2;
+ }
+ bool IsConcrete() const { return mHasWidth && mHasHeight; }
+ bool HasRatio() const { return !!mRatio; }
+ bool IsEmpty() const {
+ return (mHasWidth && mWidth <= 0) || (mHasHeight && mHeight <= 0) ||
+ !mRatio;
+ }
+
+ // CanComputeConcreteSize must return true when ComputeConcreteSize is
+ // called.
+ nsSize ComputeConcreteSize() const;
+
+ void SetWidth(nscoord aWidth) {
+ mWidth = aWidth;
+ mHasWidth = true;
+ if (mHasHeight) {
+ mRatio = AspectRatio::FromSize(mWidth, mHeight);
+ }
+ }
+ void SetHeight(nscoord aHeight) {
+ mHeight = aHeight;
+ mHasHeight = true;
+ if (mHasWidth) {
+ mRatio = AspectRatio::FromSize(mWidth, mHeight);
+ }
+ }
+ void SetSize(const nsSize& aSize) {
+ mWidth = aSize.width;
+ mHeight = aSize.height;
+ mHasWidth = true;
+ mHasHeight = true;
+ mRatio = AspectRatio::FromSize(mWidth, mHeight);
+ }
+ void SetRatio(const AspectRatio& aRatio) {
+ MOZ_ASSERT(
+ !mHasWidth || !mHasHeight,
+ "Probably shouldn't be setting a ratio if we have a concrete size");
+ mRatio = aRatio;
+ }
+
+ AspectRatio mRatio;
+ nscoord mWidth;
+ nscoord mHeight;
+ bool mHasWidth;
+ bool mHasHeight;
+};
+
+/**
+ * This is a small wrapper class to encapsulate image drawing that can draw an
+ * StyleImage image, which may internally be a real image, a sub image, or a CSS
+ * gradient, etc...
+ *
+ * @note Always call the member functions in the order of PrepareImage(),
+ * SetSize(), and Draw*().
+ */
+class nsImageRenderer {
+ public:
+ typedef mozilla::image::ImgDrawResult ImgDrawResult;
+ typedef mozilla::layers::ImageContainer ImageContainer;
+
+ enum {
+ FLAG_SYNC_DECODE_IMAGES = 0x01,
+ FLAG_PAINTING_TO_WINDOW = 0x02,
+ FLAG_HIGH_QUALITY_SCALING = 0x04,
+ FLAG_DRAW_PARTIAL_FRAMES = 0x08
+ };
+ enum FitType { CONTAIN, COVER };
+
+ nsImageRenderer(nsIFrame* aForFrame, const mozilla::StyleImage* aImage,
+ uint32_t aFlags);
+ ~nsImageRenderer() = default;
+ /**
+ * Populates member variables to get ready for rendering.
+ * @return true iff the image is ready, and there is at least a pixel to
+ * draw.
+ */
+ bool PrepareImage();
+
+ /**
+ * The three Compute*Size functions correspond to the sizing algorthms and
+ * definitions from the CSS Image Values and Replaced Content spec. See
+ * http://dev.w3.org/csswg/css-images-3/#sizing .
+ */
+
+ /**
+ * Compute the intrinsic size of the image as defined in the CSS Image Values
+ * spec. The intrinsic size is the unscaled size which the image would ideally
+ * like to be in app units.
+ */
+ mozilla::CSSSizeOrRatio ComputeIntrinsicSize();
+
+ /**
+ * Computes the placement for a background image, or for the image data
+ * inside of a replaced element.
+ *
+ * @param aPos The CSS <position> value that specifies the image's position.
+ * @param aOriginBounds The box to which the tiling position should be
+ * relative. For background images, this should correspond to
+ * 'background-origin' for the frame, except when painting on the
+ * canvas, in which case the origin bounds should be the bounds
+ * of the root element's frame. For a replaced element, this should
+ * be the element's content-box.
+ * @param aTopLeft [out] The top-left corner where an image tile should be
+ * drawn.
+ * @param aAnchorPoint [out] A point which should be pixel-aligned by
+ * nsLayoutUtils::DrawImage. This is the same as aTopLeft, unless
+ * CSS specifies a percentage (including 'right' or 'bottom'), in
+ * which case it's that percentage within of aOriginBounds. So
+ * 'right' would set aAnchorPoint.x to aOriginBounds.XMost().
+ *
+ * Points are returned relative to aOriginBounds.
+ */
+ static void ComputeObjectAnchorPoint(const mozilla::Position& aPos,
+ const nsSize& aOriginBounds,
+ const nsSize& aImageSize,
+ nsPoint* aTopLeft,
+ nsPoint* aAnchorPoint);
+
+ /**
+ * Compute the size of the rendered image using either the 'cover' or
+ * 'contain' constraints (aFitType).
+ */
+ static nsSize ComputeConstrainedSize(
+ const nsSize& aConstrainingSize,
+ const mozilla::AspectRatio& aIntrinsicRatio, FitType aFitType);
+ /**
+ * Compute the size of the rendered image (the concrete size) where no cover/
+ * contain constraints are given. The 'default algorithm' from the CSS Image
+ * Values spec.
+ */
+ static nsSize ComputeConcreteSize(
+ const mozilla::CSSSizeOrRatio& aSpecifiedSize,
+ const mozilla::CSSSizeOrRatio& aIntrinsicSize,
+ const nsSize& aDefaultSize);
+
+ /**
+ * Set this image's preferred size. This will be its intrinsic size where
+ * specified and the default size where it is not. Used as the unscaled size
+ * when rendering the image.
+ */
+ void SetPreferredSize(const mozilla::CSSSizeOrRatio& aIntrinsicSize,
+ const nsSize& aDefaultSize);
+
+ /**
+ * Draws the image to the target rendering context using
+ * {background|mask}-specific arguments.
+ * @see nsLayoutUtils::DrawImage() for parameters.
+ */
+ ImgDrawResult DrawLayer(nsPresContext* aPresContext,
+ gfxContext& aRenderingContext, const nsRect& aDest,
+ const nsRect& aFill, const nsPoint& aAnchor,
+ const nsRect& aDirty, const nsSize& aRepeatSize,
+ float aOpacity);
+
+ /**
+ * Builds WebRender DisplayItems for an image using
+ * {background|mask}-specific arguments.
+ * @see nsLayoutUtils::DrawImage() for parameters.
+ */
+ ImgDrawResult BuildWebRenderDisplayItemsForLayer(
+ nsPresContext* aPresContext, mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResource,
+ const mozilla::layers::StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager, nsDisplayItem* aItem,
+ const nsRect& aDest, const nsRect& aFill, const nsPoint& aAnchor,
+ const nsRect& aDirty, const nsSize& aRepeatSize, float aOpacity);
+
+ /**
+ * Draw the image to a single component of a border-image style rendering.
+ * aFill The destination rect to be drawn into
+ * aSrc is the part of the image to be rendered into a tile (aUnitSize in
+ * aFill), if aSrc and the dest tile are different sizes, the image will be
+ * scaled to map aSrc onto the dest tile.
+ * aHFill and aVFill are the repeat patterns for the component -
+ * NS_STYLE_BORDER_IMAGE_REPEAT_*
+ * aUnitSize The scaled size of a single source rect (in destination coords)
+ * aIndex identifies the component: 0 1 2
+ * 3 4 5
+ * 6 7 8
+ * aSVGViewportSize The image size evaluated by default sizing algorithm.
+ * Pass Nothing() if we can read a valid viewport size or aspect-ratio from
+ * the drawing image directly, otherwise, pass Some() with viewport size
+ * evaluated from default sizing algorithm.
+ * aHasIntrinsicRatio is used to record if the source image has fixed
+ * intrinsic ratio.
+ */
+ ImgDrawResult DrawBorderImageComponent(
+ nsPresContext* aPresContext, gfxContext& aRenderingContext,
+ const nsRect& aDirtyRect, const nsRect& aFill,
+ const mozilla::CSSIntRect& aSrc, mozilla::StyleBorderImageRepeat aHFill,
+ mozilla::StyleBorderImageRepeat aVFill, const nsSize& aUnitSize,
+ uint8_t aIndex, const mozilla::Maybe<nsSize>& aSVGViewportSize,
+ const bool aHasIntrinsicRatio);
+
+ /**
+ * Draw the image to aRenderingContext which can be used to define the
+ * float area in the presence of "shape-outside: <image>".
+ */
+ ImgDrawResult DrawShapeImage(nsPresContext* aPresContext,
+ gfxContext& aRenderingContext);
+
+ bool IsRasterImage();
+
+ /// Retrieves the image associated with this nsImageRenderer, if there is one.
+ already_AddRefed<imgIContainer> GetImage();
+
+ bool IsReady() const { return mPrepareResult == ImgDrawResult::SUCCESS; }
+ ImgDrawResult PrepareResult() const { return mPrepareResult; }
+ void SetExtendMode(mozilla::gfx::ExtendMode aMode) { mExtendMode = aMode; }
+ void SetMaskOp(mozilla::StyleMaskMode aMaskOp) { mMaskOp = aMaskOp; }
+ const nsSize& GetSize() const { return mSize; }
+ mozilla::StyleImage::Tag GetType() const { return mType; }
+ const mozilla::StyleGradient* GetGradientData() const {
+ return mGradientData;
+ }
+
+ private:
+ /**
+ * Draws the image to the target rendering context.
+ * aSrc is a rect on the source image which will be mapped to aDest; it's
+ * currently only used for gradients.
+ *
+ * @see nsLayoutUtils::DrawImage() for other parameters.
+ */
+ ImgDrawResult Draw(nsPresContext* aPresContext, gfxContext& aRenderingContext,
+ const nsRect& aDirtyRect, const nsRect& aDest,
+ const nsRect& aFill, const nsPoint& aAnchor,
+ const nsSize& aRepeatSize, const mozilla::CSSIntRect& aSrc,
+ float aOpacity = 1.0);
+
+ /**
+ * Builds WebRender DisplayItems for the image.
+ * aSrc is a rect on the source image which will be mapped to aDest; it's
+ * currently only used for gradients.
+ *
+ * @see nsLayoutUtils::DrawImage() for other parameters.
+ */
+ ImgDrawResult BuildWebRenderDisplayItems(
+ nsPresContext* aPresContext, mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const mozilla::layers::StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager, nsDisplayItem* aItem,
+ const nsRect& aDirtyRect, const nsRect& aDest, const nsRect& aFill,
+ const nsPoint& aAnchor, const nsSize& aRepeatSize,
+ const mozilla::CSSIntRect& aSrc, float aOpacity = 1.0);
+
+ /**
+ * Helper method for creating a gfxDrawable from mPaintServerFrame or
+ * mImageElementSurface.
+ * Requires mType to be Element.
+ * Returns null if we cannot create the drawable.
+ */
+ already_AddRefed<gfxDrawable> DrawableForElement(const nsRect& aImageRect,
+ gfxContext& aContext);
+
+ nsIFrame* mForFrame;
+ const mozilla::StyleImage* mImage;
+ ImageResolution mImageResolution;
+ mozilla::StyleImage::Tag mType;
+ nsCOMPtr<imgIContainer> mImageContainer;
+ const mozilla::StyleGradient* mGradientData;
+ nsIFrame* mPaintServerFrame;
+ SurfaceFromElementResult mImageElementSurface;
+ ImgDrawResult mPrepareResult;
+ nsSize mSize; // unscaled size of the image, in app units
+ uint32_t mFlags;
+ mozilla::gfx::ExtendMode mExtendMode;
+ mozilla::StyleMaskMode mMaskOp;
+};
+
+} // namespace mozilla
+
+#endif /* nsImageRenderer_h__ */