diff options
Diffstat (limited to '')
69 files changed, 46858 insertions, 0 deletions
diff --git a/layout/painting/ActiveLayerTracker.cpp b/layout/painting/ActiveLayerTracker.cpp new file mode 100644 index 0000000000..4000ee9191 --- /dev/null +++ b/layout/painting/ActiveLayerTracker.cpp @@ -0,0 +1,614 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ActiveLayerTracker.h" + +#include "mozilla/AnimationUtils.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/gfx/gfxVars.h" +#include "mozilla/gfx/Matrix.h" +#include "mozilla/EffectSet.h" +#include "mozilla/MotionPathUtils.h" +#include "mozilla/PodOperations.h" +#include "gfx2DGlue.h" +#include "nsExpirationTracker.h" +#include "nsContainerFrame.h" +#include "nsIContent.h" +#include "nsIScrollableFrame.h" +#include "nsRefreshDriver.h" +#include "nsPIDOMWindow.h" +#include "mozilla/dom/Document.h" +#include "nsAnimationManager.h" +#include "nsStyleTransformMatrix.h" +#include "nsTransitionManager.h" +#include "nsDisplayList.h" +#include "nsDOMCSSDeclaration.h" +#include "nsLayoutUtils.h" + +namespace mozilla { + +using namespace gfx; + +/** + * This tracks the state of a frame that may need active layers due to + * ongoing content changes or style changes that indicate animation. + * + * When no changes of *any* kind are detected after 75-100ms we remove this + * object. Because we only track all kinds of activity with a single + * nsExpirationTracker, it's possible a frame might remain active somewhat + * spuriously if different kinds of changes kept happening, but that almost + * certainly doesn't matter. + */ +class LayerActivity { + public: + enum ActivityIndex { + ACTIVITY_OPACITY, + ACTIVITY_TRANSFORM, + ACTIVITY_LEFT, + ACTIVITY_TOP, + ACTIVITY_RIGHT, + ACTIVITY_BOTTOM, + ACTIVITY_BACKGROUND_POSITION, + + ACTIVITY_SCALE, + ACTIVITY_TRIGGERED_REPAINT, + + // keep as last item + ACTIVITY_COUNT + }; + + explicit LayerActivity(nsIFrame* aFrame) + : mFrame(aFrame), mContent(nullptr), mContentActive(false) { + PodArrayZero(mRestyleCounts); + } + ~LayerActivity(); + nsExpirationState* GetExpirationState() { return &mState; } + uint8_t& RestyleCountForProperty(nsCSSPropertyID aProperty) { + return mRestyleCounts[GetActivityIndexForProperty(aProperty)]; + } + + static ActivityIndex GetActivityIndexForProperty(nsCSSPropertyID aProperty) { + switch (aProperty) { + case eCSSProperty_opacity: + return ACTIVITY_OPACITY; + case eCSSProperty_transform: + case eCSSProperty_translate: + case eCSSProperty_rotate: + case eCSSProperty_scale: + case eCSSProperty_offset_path: + case eCSSProperty_offset_distance: + case eCSSProperty_offset_rotate: + case eCSSProperty_offset_anchor: + // TODO: Bug 1559232: Add offset-position. + return ACTIVITY_TRANSFORM; + case eCSSProperty_left: + return ACTIVITY_LEFT; + case eCSSProperty_top: + return ACTIVITY_TOP; + case eCSSProperty_right: + return ACTIVITY_RIGHT; + case eCSSProperty_bottom: + return ACTIVITY_BOTTOM; + case eCSSProperty_background_position: + return ACTIVITY_BACKGROUND_POSITION; + case eCSSProperty_background_position_x: + return ACTIVITY_BACKGROUND_POSITION; + case eCSSProperty_background_position_y: + return ACTIVITY_BACKGROUND_POSITION; + default: + MOZ_ASSERT(false); + return ACTIVITY_OPACITY; + } + } + + static ActivityIndex GetActivityIndexForPropertySet( + const nsCSSPropertyIDSet& aPropertySet) { + if (aPropertySet.IsSubsetOf( + nsCSSPropertyIDSet::TransformLikeProperties())) { + return ACTIVITY_TRANSFORM; + } + MOZ_ASSERT( + aPropertySet.IsSubsetOf(nsCSSPropertyIDSet::OpacityProperties())); + return ACTIVITY_OPACITY; + } + + // While tracked, exactly one of mFrame or mContent is non-null, depending + // on whether this property is stored on a frame or on a content node. + // When this property is expired by the layer activity tracker, both mFrame + // and mContent are nulled-out and the property is deleted. + nsIFrame* mFrame; + nsIContent* mContent; + + nsExpirationState mState; + + // Previous scale due to the CSS transform property. + Maybe<Size> mPreviousTransformScale; + + // The scroll frame during for which we most recently received a call to + // NotifyAnimatedFromScrollHandler. + WeakFrame mAnimatingScrollHandlerFrame; + // The set of activities that were triggered during + // mAnimatingScrollHandlerFrame's scroll event handler. + EnumSet<ActivityIndex> mScrollHandlerInducedActivity; + + // Number of restyle operations detected + uint8_t mRestyleCounts[ACTIVITY_COUNT]; + bool mContentActive; +}; + +class LayerActivityTracker final + : public nsExpirationTracker<LayerActivity, 4> { + public: + // 75-100ms is a good timeout period. We use 4 generations of 25ms each. + enum { GENERATION_MS = 100 }; + + explicit LayerActivityTracker(nsIEventTarget* aEventTarget) + : nsExpirationTracker<LayerActivity, 4>( + GENERATION_MS, "LayerActivityTracker", aEventTarget), + mDestroying(false) {} + ~LayerActivityTracker() override { + mDestroying = true; + AgeAllGenerations(); + } + + void NotifyExpired(LayerActivity* aObject) override; + + public: + WeakFrame mCurrentScrollHandlerFrame; + + private: + bool mDestroying; +}; + +static LayerActivityTracker* gLayerActivityTracker = nullptr; + +LayerActivity::~LayerActivity() { + if (mFrame || mContent) { + NS_ASSERTION(gLayerActivityTracker, "Should still have a tracker"); + gLayerActivityTracker->RemoveObject(this); + } +} + +// Frames with this property have NS_FRAME_HAS_LAYER_ACTIVITY_PROPERTY set +NS_DECLARE_FRAME_PROPERTY_DELETABLE(LayerActivityProperty, LayerActivity) + +void LayerActivityTracker::NotifyExpired(LayerActivity* aObject) { + if (!mDestroying && aObject->mAnimatingScrollHandlerFrame.IsAlive()) { + // Reset the restyle counts, but let the layer activity survive. + PodArrayZero(aObject->mRestyleCounts); + MarkUsed(aObject); + return; + } + + RemoveObject(aObject); + + nsIFrame* f = aObject->mFrame; + nsIContent* c = aObject->mContent; + aObject->mFrame = nullptr; + aObject->mContent = nullptr; + + MOZ_ASSERT((f == nullptr) != (c == nullptr), + "A LayerActivity object should always have a reference to either " + "its frame or its content"); + + if (f) { + // The pres context might have been detached during the delay - + // that's fine, just skip the paint. + if (f->PresContext()->GetContainerWeak() && !gfxVars::UseWebRender()) { + f->SchedulePaint(nsIFrame::PAINT_DEFAULT, false); + } + f->RemoveStateBits(NS_FRAME_HAS_LAYER_ACTIVITY_PROPERTY); + f->RemoveProperty(LayerActivityProperty()); + } else { + c->RemoveProperty(nsGkAtoms::LayerActivity); + } +} + +static LayerActivity* GetLayerActivity(nsIFrame* aFrame) { + if (!aFrame->HasAnyStateBits(NS_FRAME_HAS_LAYER_ACTIVITY_PROPERTY)) { + return nullptr; + } + return aFrame->GetProperty(LayerActivityProperty()); +} + +static LayerActivity* GetLayerActivityForUpdate(nsIFrame* aFrame) { + LayerActivity* layerActivity = GetLayerActivity(aFrame); + if (layerActivity) { + gLayerActivityTracker->MarkUsed(layerActivity); + } else { + if (!gLayerActivityTracker) { + gLayerActivityTracker = + new LayerActivityTracker(GetMainThreadSerialEventTarget()); + } + layerActivity = new LayerActivity(aFrame); + gLayerActivityTracker->AddObject(layerActivity); + aFrame->AddStateBits(NS_FRAME_HAS_LAYER_ACTIVITY_PROPERTY); + aFrame->SetProperty(LayerActivityProperty(), layerActivity); + } + return layerActivity; +} + +static void IncrementMutationCount(uint8_t* aCount) { + *aCount = uint8_t(std::min(0xFF, *aCount + 1)); +} + +/* static */ +void ActiveLayerTracker::TransferActivityToContent(nsIFrame* aFrame, + nsIContent* aContent) { + if (!aFrame->HasAnyStateBits(NS_FRAME_HAS_LAYER_ACTIVITY_PROPERTY)) { + return; + } + LayerActivity* layerActivity = aFrame->TakeProperty(LayerActivityProperty()); + aFrame->RemoveStateBits(NS_FRAME_HAS_LAYER_ACTIVITY_PROPERTY); + if (!layerActivity) { + return; + } + layerActivity->mFrame = nullptr; + layerActivity->mContent = aContent; + aContent->SetProperty(nsGkAtoms::LayerActivity, layerActivity, + nsINode::DeleteProperty<LayerActivity>, true); +} + +/* static */ +void ActiveLayerTracker::TransferActivityToFrame(nsIContent* aContent, + nsIFrame* aFrame) { + auto* layerActivity = static_cast<LayerActivity*>( + aContent->TakeProperty(nsGkAtoms::LayerActivity)); + if (!layerActivity) { + return; + } + layerActivity->mContent = nullptr; + layerActivity->mFrame = aFrame; + aFrame->AddStateBits(NS_FRAME_HAS_LAYER_ACTIVITY_PROPERTY); + aFrame->SetProperty(LayerActivityProperty(), layerActivity); +} + +static void IncrementScaleRestyleCountIfNeeded(nsIFrame* aFrame, + LayerActivity* aActivity) { + const nsStyleDisplay* display = aFrame->StyleDisplay(); + if (!display->HasTransformProperty() && !display->HasIndividualTransform() && + display->mOffsetPath.IsNone()) { + // The transform was removed. + aActivity->mPreviousTransformScale = Nothing(); + IncrementMutationCount( + &aActivity->mRestyleCounts[LayerActivity::ACTIVITY_SCALE]); + return; + } + + // Compute the new scale due to the CSS transform property. + // Note: Motion path doesn't contribute to scale factor. (It only has 2d + // translate and 2d rotate, so we use Nothing() for it.) + nsStyleTransformMatrix::TransformReferenceBox refBox(aFrame); + Matrix4x4 transform = nsStyleTransformMatrix::ReadTransforms( + display->mTranslate, display->mRotate, display->mScale, Nothing(), + display->mTransform, refBox, AppUnitsPerCSSPixel()); + Matrix transform2D; + if (!transform.Is2D(&transform2D)) { + // We don't attempt to handle 3D transforms; just assume the scale changed. + aActivity->mPreviousTransformScale = Nothing(); + IncrementMutationCount( + &aActivity->mRestyleCounts[LayerActivity::ACTIVITY_SCALE]); + return; + } + + Size scale = transform2D.ScaleFactors(); + if (aActivity->mPreviousTransformScale == Some(scale)) { + return; // Nothing changed. + } + + aActivity->mPreviousTransformScale = Some(scale); + IncrementMutationCount( + &aActivity->mRestyleCounts[LayerActivity::ACTIVITY_SCALE]); +} + +/* static */ +void ActiveLayerTracker::NotifyRestyle(nsIFrame* aFrame, + nsCSSPropertyID aProperty) { + LayerActivity* layerActivity = GetLayerActivityForUpdate(aFrame); + uint8_t& mutationCount = layerActivity->RestyleCountForProperty(aProperty); + IncrementMutationCount(&mutationCount); + + if (nsCSSPropertyIDSet::TransformLikeProperties().HasProperty(aProperty)) { + IncrementScaleRestyleCountIfNeeded(aFrame, layerActivity); + } +} + +/* static */ +void ActiveLayerTracker::NotifyOffsetRestyle(nsIFrame* aFrame) { + LayerActivity* layerActivity = GetLayerActivityForUpdate(aFrame); + IncrementMutationCount( + &layerActivity->mRestyleCounts[LayerActivity::ACTIVITY_LEFT]); + IncrementMutationCount( + &layerActivity->mRestyleCounts[LayerActivity::ACTIVITY_TOP]); + IncrementMutationCount( + &layerActivity->mRestyleCounts[LayerActivity::ACTIVITY_RIGHT]); + IncrementMutationCount( + &layerActivity->mRestyleCounts[LayerActivity::ACTIVITY_BOTTOM]); +} + +/* static */ +void ActiveLayerTracker::NotifyAnimated(nsIFrame* aFrame, + nsCSSPropertyID aProperty, + const nsACString& aNewValue, + nsDOMCSSDeclaration* aDOMCSSDecl) { + LayerActivity* layerActivity = GetLayerActivityForUpdate(aFrame); + uint8_t& mutationCount = layerActivity->RestyleCountForProperty(aProperty); + if (mutationCount != 0xFF) { + nsAutoCString oldValue; + aDOMCSSDecl->GetPropertyValue(aProperty, oldValue); + if (oldValue != aNewValue) { + // We know this is animated, so just hack the mutation count. + mutationCount = 0xFF; + } + } +} + +/* static */ +void ActiveLayerTracker::NotifyAnimatedFromScrollHandler( + nsIFrame* aFrame, nsCSSPropertyID aProperty, nsIFrame* aScrollFrame) { + if (aFrame->PresContext() != aScrollFrame->PresContext()) { + // Don't allow cross-document dependencies. + return; + } + LayerActivity* layerActivity = GetLayerActivityForUpdate(aFrame); + LayerActivity::ActivityIndex activityIndex = + LayerActivity::GetActivityIndexForProperty(aProperty); + + if (layerActivity->mAnimatingScrollHandlerFrame.GetFrame() != aScrollFrame) { + // Discard any activity of a different scroll frame. We only track the + // most recent scroll handler induced activity. + layerActivity->mScrollHandlerInducedActivity.clear(); + layerActivity->mAnimatingScrollHandlerFrame = aScrollFrame; + } + + layerActivity->mScrollHandlerInducedActivity += activityIndex; +} + +static bool IsPresContextInScriptAnimationCallback( + nsPresContext* aPresContext) { + if (aPresContext->RefreshDriver()->IsInRefresh()) { + return true; + } + // Treat timeouts/setintervals as scripted animation callbacks for our + // purposes. + nsPIDOMWindowInner* win = aPresContext->Document()->GetInnerWindow(); + return win && win->IsRunningTimeout(); +} + +/* static */ +void ActiveLayerTracker::NotifyInlineStyleRuleModified( + nsIFrame* aFrame, nsCSSPropertyID aProperty, const nsACString& aNewValue, + nsDOMCSSDeclaration* aDOMCSSDecl) { + if (IsPresContextInScriptAnimationCallback(aFrame->PresContext())) { + NotifyAnimated(aFrame, aProperty, aNewValue, aDOMCSSDecl); + } + if (gLayerActivityTracker && + gLayerActivityTracker->mCurrentScrollHandlerFrame.IsAlive()) { + NotifyAnimatedFromScrollHandler( + aFrame, aProperty, + gLayerActivityTracker->mCurrentScrollHandlerFrame.GetFrame()); + } +} + +/* static */ +void ActiveLayerTracker::NotifyNeedsRepaint(nsIFrame* aFrame) { + LayerActivity* layerActivity = GetLayerActivityForUpdate(aFrame); + if (IsPresContextInScriptAnimationCallback(aFrame->PresContext())) { + // This is mirroring NotifyInlineStyleRuleModified's NotifyAnimated logic. + // Just max out the restyle count if we're in an animation callback. + layerActivity->mRestyleCounts[LayerActivity::ACTIVITY_TRIGGERED_REPAINT] = + 0xFF; + } else { + IncrementMutationCount( + &layerActivity + ->mRestyleCounts[LayerActivity::ACTIVITY_TRIGGERED_REPAINT]); + } +} + +static bool CheckScrollInducedActivity( + LayerActivity* aLayerActivity, LayerActivity::ActivityIndex aActivityIndex, + nsDisplayListBuilder* aBuilder) { + if (!aLayerActivity->mScrollHandlerInducedActivity.contains(aActivityIndex) || + !aLayerActivity->mAnimatingScrollHandlerFrame.IsAlive()) { + return false; + } + + nsIScrollableFrame* scrollFrame = + do_QueryFrame(aLayerActivity->mAnimatingScrollHandlerFrame.GetFrame()); + if (scrollFrame && (!aBuilder || scrollFrame->IsScrollingActive(aBuilder))) { + return true; + } + + // The scroll frame has been destroyed or has become inactive. Clear it from + // the layer activity so that it can expire. + aLayerActivity->mAnimatingScrollHandlerFrame = nullptr; + aLayerActivity->mScrollHandlerInducedActivity.clear(); + return false; +} + +/* static */ +bool ActiveLayerTracker::IsBackgroundPositionAnimated( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame) { + LayerActivity* layerActivity = GetLayerActivity(aFrame); + if (layerActivity) { + LayerActivity::ActivityIndex activityIndex = + LayerActivity::ActivityIndex::ACTIVITY_BACKGROUND_POSITION; + if (layerActivity->mRestyleCounts[activityIndex] >= 2) { + // If the frame needs to be repainted frequently, we probably don't get + // much from treating the property as animated, *unless* this frame's + // 'scale' (which includes the bounds changes of a rotation) is changing. + // Marking a scaling transform as animating allows us to avoid resizing + // the texture, even if we have to repaint the contents of that texture. + if (layerActivity + ->mRestyleCounts[LayerActivity::ACTIVITY_TRIGGERED_REPAINT] < 2) { + return true; + } + } + if (CheckScrollInducedActivity(layerActivity, activityIndex, aBuilder)) { + return true; + } + } + return nsLayoutUtils::HasEffectiveAnimation( + aFrame, nsCSSPropertyIDSet({eCSSProperty_background_position_x, + eCSSProperty_background_position_y})); +} + +static bool IsMotionPathAnimated(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame) { + return ActiveLayerTracker::IsStyleAnimated( + aBuilder, aFrame, nsCSSPropertyIDSet{eCSSProperty_offset_path}) || + (!aFrame->StyleDisplay()->mOffsetPath.IsNone() && + ActiveLayerTracker::IsStyleAnimated( + aBuilder, aFrame, + nsCSSPropertyIDSet{eCSSProperty_offset_distance, + eCSSProperty_offset_rotate, + eCSSProperty_offset_anchor})); +} + +/* static */ +bool ActiveLayerTracker::IsTransformAnimated(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame) { + return IsStyleAnimated(aBuilder, aFrame, + nsCSSPropertyIDSet::CSSTransformProperties()) || + IsMotionPathAnimated(aBuilder, aFrame); +} + +/* static */ +bool ActiveLayerTracker::IsTransformMaybeAnimated(nsIFrame* aFrame) { + return IsStyleAnimated(nullptr, aFrame, + nsCSSPropertyIDSet::CSSTransformProperties()) || + IsMotionPathAnimated(nullptr, aFrame); +} + +/* static */ +bool ActiveLayerTracker::IsStyleAnimated( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + const nsCSSPropertyIDSet& aPropertySet) { + MOZ_ASSERT( + aPropertySet.IsSubsetOf(nsCSSPropertyIDSet::TransformLikeProperties()) || + aPropertySet.IsSubsetOf(nsCSSPropertyIDSet::OpacityProperties()), + "Only subset of opacity or transform-like properties set calls this"); + + // For display:table content, transforms are applied to the table wrapper + // (primary frame) but their will-change style will be specified on the style + // frame and, unlike other transform properties, not inherited. + // As a result, for transform properties only we need to be careful to look up + // the will-change style on the _style_ frame. + const nsIFrame* styleFrame = nsLayoutUtils::GetStyleFrame(aFrame); + const nsCSSPropertyIDSet transformSet = + nsCSSPropertyIDSet::TransformLikeProperties(); + if ((styleFrame && (styleFrame->StyleDisplay()->mWillChange.bits & + StyleWillChangeBits::TRANSFORM)) && + aPropertySet.Intersects(transformSet) && + (!aBuilder || + aBuilder->IsInWillChangeBudget(aFrame, aFrame->GetSize()))) { + return true; + } + if ((aFrame->StyleDisplay()->mWillChange.bits & + StyleWillChangeBits::OPACITY) && + aPropertySet.Intersects(nsCSSPropertyIDSet::OpacityProperties()) && + (!aBuilder || + aBuilder->IsInWillChangeBudget(aFrame, aFrame->GetSize()))) { + return true; + } + + LayerActivity* layerActivity = GetLayerActivity(aFrame); + if (layerActivity) { + LayerActivity::ActivityIndex activityIndex = + LayerActivity::GetActivityIndexForPropertySet(aPropertySet); + if (layerActivity->mRestyleCounts[activityIndex] >= 2) { + // If the frame needs to be repainted frequently, we probably don't get + // much from treating the property as animated, *unless* this frame's + // 'scale' (which includes the bounds changes of a rotation) is changing. + // Marking a scaling transform as animating allows us to avoid resizing + // the texture, even if we have to repaint the contents of that texture. + if (layerActivity + ->mRestyleCounts[LayerActivity::ACTIVITY_TRIGGERED_REPAINT] < + 2 || + (aPropertySet.Intersects(transformSet) && + IsScaleSubjectToAnimation(aFrame))) { + return true; + } + } + if (CheckScrollInducedActivity(layerActivity, activityIndex, aBuilder)) { + return true; + } + } + + if (nsLayoutUtils::HasEffectiveAnimation(aFrame, aPropertySet)) { + return true; + } + + if (!aPropertySet.Intersects(transformSet) || + !aFrame->Combines3DTransformWithAncestors()) { + return false; + } + + // For preserve-3d, we check if there is any transform animation on its parent + // frames in the 3d rendering context. If there is one, this function will + // return true. + return IsStyleAnimated(aBuilder, aFrame->GetParent(), aPropertySet); +} + +/* static */ +bool ActiveLayerTracker::IsOffsetStyleAnimated(nsIFrame* aFrame) { + LayerActivity* layerActivity = GetLayerActivity(aFrame); + if (layerActivity) { + if (layerActivity->mRestyleCounts[LayerActivity::ACTIVITY_LEFT] >= 2 || + layerActivity->mRestyleCounts[LayerActivity::ACTIVITY_TOP] >= 2 || + layerActivity->mRestyleCounts[LayerActivity::ACTIVITY_RIGHT] >= 2 || + layerActivity->mRestyleCounts[LayerActivity::ACTIVITY_BOTTOM] >= 2) { + return true; + } + } + // We should also check for running CSS animations of these properties once + // bug 1009693 is fixed. Until that happens, layerization isn't useful for + // animations of these properties because we'll invalidate the layer contents + // on every change anyway. + // See bug 1151346 for a patch that adds a check for CSS animations. + return false; +} + +/* static */ +bool ActiveLayerTracker::IsScaleSubjectToAnimation(nsIFrame* aFrame) { + // Check whether JavaScript is animating this frame's scale. + LayerActivity* layerActivity = GetLayerActivity(aFrame); + if (layerActivity && + layerActivity->mRestyleCounts[LayerActivity::ACTIVITY_SCALE] >= 2) { + return true; + } + + return AnimationUtils::FrameHasAnimatedScale(aFrame); +} + +/* static */ +void ActiveLayerTracker::NotifyContentChange(nsIFrame* aFrame) { + LayerActivity* layerActivity = GetLayerActivityForUpdate(aFrame); + layerActivity->mContentActive = true; +} + +/* static */ +bool ActiveLayerTracker::IsContentActive(nsIFrame* aFrame) { + LayerActivity* layerActivity = GetLayerActivity(aFrame); + return layerActivity && layerActivity->mContentActive; +} + +/* static */ +void ActiveLayerTracker::SetCurrentScrollHandlerFrame(nsIFrame* aFrame) { + if (!gLayerActivityTracker) { + gLayerActivityTracker = + new LayerActivityTracker(GetMainThreadSerialEventTarget()); + } + gLayerActivityTracker->mCurrentScrollHandlerFrame = aFrame; +} + +/* static */ +void ActiveLayerTracker::Shutdown() { + delete gLayerActivityTracker; + gLayerActivityTracker = nullptr; +} + +} // namespace mozilla diff --git a/layout/painting/ActiveLayerTracker.h b/layout/painting/ActiveLayerTracker.h new file mode 100644 index 0000000000..1bcd2b0e62 --- /dev/null +++ b/layout/painting/ActiveLayerTracker.h @@ -0,0 +1,163 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef ACTIVELAYERTRACKER_H_ +#define ACTIVELAYERTRACKER_H_ + +#include "nsCSSPropertyID.h" + +class nsIFrame; +class nsIContent; +class nsCSSPropertyIDSet; +class nsDisplayListBuilder; +class nsDOMCSSDeclaration; + +namespace mozilla { + +/** + * This class receives various notifications about style changes and content + * changes that affect layerization decisions, and implements the heuristics + * that drive those decisions. It manages per-frame state to support those + * heuristics. + */ +class ActiveLayerTracker { + public: + static void Shutdown(); + + /* + * We track style changes to selected styles: + * eCSSProperty_transform, eCSSProperty_translate, + * eCSSProperty_rotate, eCSSProperty_scale + * eCSSProperty_offset_path, eCSSProperty_offset_distance, + * eCSSProperty_offset_rotate, eCSSProperty_offset_anchor, + * eCSSProperty_opacity + * eCSSProperty_left, eCSSProperty_top, + * eCSSProperty_right, eCSSProperty_bottom + * and use that information to guess whether style changes are animated. + */ + + /** + * Notify aFrame's style property as having changed due to a restyle, + * and therefore possibly wanting an active layer to render that style. + * Any such marking will time out after a short period. + * @param aProperty the property that has changed + */ + static void NotifyRestyle(nsIFrame* aFrame, nsCSSPropertyID aProperty); + /** + * Notify aFrame's left/top/right/bottom properties as having (maybe) + * changed due to a restyle, and therefore possibly wanting an active layer + * to render that style. Any such marking will time out after a short period. + */ + static void NotifyOffsetRestyle(nsIFrame* aFrame); + /** + * Mark aFrame as being known to have an animation of aProperty. + * Any such marking will time out after a short period. + * aNewValue and aDOMCSSDecl are used to determine whether the property's + * value has changed. + */ + static void NotifyAnimated(nsIFrame* aFrame, nsCSSPropertyID aProperty, + const nsACString& aNewValue, + nsDOMCSSDeclaration* aDOMCSSDecl); + /** + * Notify aFrame as being known to have an animation of aProperty through an + * inline style modification during aScrollFrame's scroll event handler. + */ + static void NotifyAnimatedFromScrollHandler(nsIFrame* aFrame, + nsCSSPropertyID aProperty, + nsIFrame* aScrollFrame); + /** + * Notify that a property in the inline style rule of aFrame's element + * has been modified. + * This notification is incomplete --- not all modifications to inline + * style will trigger this. + * aNewValue and aDOMCSSDecl are used to determine whether the property's + * value has changed. + */ + static void NotifyInlineStyleRuleModified(nsIFrame* aFrame, + nsCSSPropertyID aProperty, + const nsACString& aNewValue, + nsDOMCSSDeclaration* aDOMCSSDecl); + /** + * Notify that a frame needs to be repainted. This is important for layering + * decisions where, say, aFrame's transform is updated from JS, but we need + * to repaint aFrame anyway, so we get no benefit from giving it its own + * layer. + */ + static void NotifyNeedsRepaint(nsIFrame* aFrame); + /** + * Return true if aFrame's property style in |aPropertySet| should be + * considered as being animated for constructing active layers. + */ + static bool IsStyleAnimated(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + const nsCSSPropertyIDSet& aPropertySet); + /** + * Return true if any of aFrame's offset property styles should be considered + * as being animated for constructing active layers. + */ + static bool IsOffsetStyleAnimated(nsIFrame* aFrame); + /** + * Return true if aFrame's background-position-x or background-position-y + * property is animated. + */ + static bool IsBackgroundPositionAnimated(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame); + /** + * Return true if aFrame's transform-like property, + * i.e. transform/translate/rotate/scale, is animated. + */ + static bool IsTransformAnimated(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame); + /** + * Return true if aFrame's transform style should be considered as being + * animated for pre-rendering. + */ + static bool IsTransformMaybeAnimated(nsIFrame* aFrame); + /** + * Return true if aFrame either has an animated scale now, or is likely to + * have one in the future because it has a CSS animation or transition + * (which may not be playing right now) that affects its scale. + */ + static bool IsScaleSubjectToAnimation(nsIFrame* aFrame); + + /** + * Transfer the LayerActivity property to the frame's content node when the + * frame is about to be destroyed so that layer activity can be tracked + * throughout reframes of an element. Only call this when aFrame is the + * primary frame of aContent. + */ + static void TransferActivityToContent(nsIFrame* aFrame, nsIContent* aContent); + /** + * Transfer the LayerActivity property back to the content node's primary + * frame after the frame has been created. + */ + static void TransferActivityToFrame(nsIContent* aContent, nsIFrame* aFrame); + + /* + * We track modifications to the content of certain frames (i.e. canvas + * frames) and use that to make layering decisions. + */ + + /** + * Mark aFrame's content as being active. This marking will time out after + * a short period. + */ + static void NotifyContentChange(nsIFrame* aFrame); + /** + * Return true if this frame's content is still marked as active. + */ + static bool IsContentActive(nsIFrame* aFrame); + + /** + * Called before and after a scroll event handler is executed, with the + * scrollframe or nullptr, respectively. This acts as a hint to treat + * inline style changes during the handler differently. + */ + static void SetCurrentScrollHandlerFrame(nsIFrame* aFrame); +}; + +} // namespace mozilla + +#endif /* ACTIVELAYERTRACKER_H_ */ diff --git a/layout/painting/BorderCache.h b/layout/painting/BorderCache.h new file mode 100644 index 0000000000..58b2dca9ba --- /dev/null +++ b/layout/painting/BorderCache.h @@ -0,0 +1,68 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_BorderCache_h_ +#define mozilla_BorderCache_h_ + +#include "mozilla/gfx/2D.h" +#include "mozilla/HashFunctions.h" +#include "nsDataHashtable.h" +#include "PLDHashTable.h" + +namespace mozilla { +// Cache for best overlap and best dashLength. + +struct FourFloats { + typedef mozilla::gfx::Float Float; + + Float n[4]; + + FourFloats() { + n[0] = 0.0f; + n[1] = 0.0f; + n[2] = 0.0f; + n[3] = 0.0f; + } + + FourFloats(Float a, Float b, Float c, Float d) { + n[0] = a; + n[1] = b; + n[2] = c; + n[3] = d; + } + + bool operator==(const FourFloats& aOther) const { + return n[0] == aOther.n[0] && n[1] == aOther.n[1] && n[2] == aOther.n[2] && + n[3] == aOther.n[3]; + } +}; + +class FourFloatsHashKey : public PLDHashEntryHdr { + public: + typedef const FourFloats& KeyType; + typedef const FourFloats* KeyTypePointer; + + explicit FourFloatsHashKey(KeyTypePointer aKey) : mValue(*aKey) {} + FourFloatsHashKey(const FourFloatsHashKey& aToCopy) + : mValue(aToCopy.mValue) {} + ~FourFloatsHashKey() = default; + + KeyType GetKey() const { return mValue; } + bool KeyEquals(KeyTypePointer aKey) const { return *aKey == mValue; } + + static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; } + static PLDHashNumber HashKey(KeyTypePointer aKey) { + return HashBytes(aKey->n, sizeof(mozilla::gfx::Float) * 4); + } + enum { ALLOW_MEMMOVE = true }; + + private: + const FourFloats mValue; +}; + +} // namespace mozilla + +#endif /* mozilla_BorderCache_h_ */ diff --git a/layout/painting/BorderConsts.h b/layout/painting/BorderConsts.h new file mode 100644 index 0000000000..e25c697101 --- /dev/null +++ b/layout/painting/BorderConsts.h @@ -0,0 +1,22 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_BorderConsts_h_ +#define mozilla_BorderConsts_h_ + +// thickness of dashed line relative to dotted line +#define DOT_LENGTH 1 // square +#define DASH_LENGTH 3 // 3 times longer than dot + +#define C_TL mozilla::eCornerTopLeft +#define C_TR mozilla::eCornerTopRight +#define C_BR mozilla::eCornerBottomRight +#define C_BL mozilla::eCornerBottomLeft + +#define BORDER_SEGMENT_COUNT_MAX 100 +#define BORDER_DOTTED_CORNER_MAX_RADIUS 100000 + +#endif /* mozilla_BorderConsts_h_ */ diff --git a/layout/painting/DashedCornerFinder.cpp b/layout/painting/DashedCornerFinder.cpp new file mode 100644 index 0000000000..2fe9ddcb82 --- /dev/null +++ b/layout/painting/DashedCornerFinder.cpp @@ -0,0 +1,414 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "DashedCornerFinder.h" + +#include <utility> + +#include "BorderCache.h" +#include "BorderConsts.h" + +namespace mozilla { + +using namespace gfx; + +struct BestDashLength { + typedef mozilla::gfx::Float Float; + + Float dashLength; + size_t count; + + BestDashLength() : dashLength(0.0f), count(0) {} + + BestDashLength(Float aDashLength, size_t aCount) + : dashLength(aDashLength), count(aCount) {} +}; + +static const size_t DashedCornerCacheSize = 256; +nsDataHashtable<FourFloatsHashKey, BestDashLength> DashedCornerCache; + +DashedCornerFinder::DashedCornerFinder(const Bezier& aOuterBezier, + const Bezier& aInnerBezier, + Float aBorderWidthH, Float aBorderWidthV, + const Size& aCornerDim) + : mOuterBezier(aOuterBezier), + mInnerBezier(aInnerBezier), + mLastOuterP(aOuterBezier.mPoints[0]), + mLastInnerP(aInnerBezier.mPoints[0]), + mLastOuterT(0.0f), + mLastInnerT(0.0f), + mBestDashLength(DOT_LENGTH * DASH_LENGTH), + mHasZeroBorderWidth(false), + mHasMore(true), + mMaxCount(aCornerDim.width + aCornerDim.height), + mType(OTHER), + mI(0), + mCount(0) { + NS_ASSERTION(aBorderWidthH > 0.0f || aBorderWidthV > 0.0f, + "At least one side should have non-zero width."); + + DetermineType(aBorderWidthH, aBorderWidthV); + + Reset(); +} + +void DashedCornerFinder::DetermineType(Float aBorderWidthH, + Float aBorderWidthV) { + if (aBorderWidthH < aBorderWidthV) { + // Always draw from wider side to thinner side. + std::swap(mInnerBezier.mPoints[0], mInnerBezier.mPoints[3]); + std::swap(mInnerBezier.mPoints[1], mInnerBezier.mPoints[2]); + std::swap(mOuterBezier.mPoints[0], mOuterBezier.mPoints[3]); + std::swap(mOuterBezier.mPoints[1], mOuterBezier.mPoints[2]); + mLastOuterP = mOuterBezier.mPoints[0]; + mLastInnerP = mInnerBezier.mPoints[0]; + } + + // See the comment at mType declaration for each condition. + + Float borderRadiusA = + fabs(mOuterBezier.mPoints[0].x - mOuterBezier.mPoints[3].x); + Float borderRadiusB = + fabs(mOuterBezier.mPoints[0].y - mOuterBezier.mPoints[3].y); + if (aBorderWidthH == aBorderWidthV && borderRadiusA == borderRadiusB && + borderRadiusA > aBorderWidthH * 2.0f) { + Float curveHeight = borderRadiusA - aBorderWidthH / 2.0; + + mType = PERFECT; + Float borderLength = M_PI * curveHeight / 2.0f; + + Float dashWidth = aBorderWidthH * DOT_LENGTH * DASH_LENGTH; + size_t count = ceil(borderLength / dashWidth); + if (count % 2) { + count++; + } + mCount = count / 2 + 1; + mBestDashLength = borderLength / (aBorderWidthH * count); + } + + Float minBorderWidth = std::min(aBorderWidthH, aBorderWidthV); + if (minBorderWidth == 0.0f) { + mHasZeroBorderWidth = true; + } + + if (mType == OTHER && !mHasZeroBorderWidth) { + Float minBorderRadius = std::min(borderRadiusA, borderRadiusB); + Float maxBorderRadius = std::max(borderRadiusA, borderRadiusB); + Float maxBorderWidth = std::max(aBorderWidthH, aBorderWidthV); + + FindBestDashLength(minBorderWidth, maxBorderWidth, minBorderRadius, + maxBorderRadius); + } +} + +bool DashedCornerFinder::HasMore(void) const { + if (mHasZeroBorderWidth) { + return mI < mMaxCount && mHasMore; + } + + return mI < mCount; +} + +DashedCornerFinder::Result DashedCornerFinder::Next(void) { + Float lastOuterT, lastInnerT, outerT, innerT; + + if (mI == 0) { + lastOuterT = 0.0f; + lastInnerT = 0.0f; + } else { + if (mType == PERFECT) { + lastOuterT = lastInnerT = (mI * 2.0f - 0.5f) / ((mCount - 1) * 2.0f); + } else { + Float last2OuterT = mLastOuterT; + Float last2InnerT = mLastInnerT; + + (void)FindNext(mBestDashLength); + + // + // mLastOuterT lastOuterT + // | | + // v v + // +---+---+---+---+ <- last2OuterT + // | |###|###| | + // | |###|###| | + // | |###|###| | + // +---+---+---+---+ <- last2InnerT + // ^ ^ + // | | + // mLastInnerT lastInnerT + lastOuterT = (mLastOuterT + last2OuterT) / 2.0f; + lastInnerT = (mLastInnerT + last2InnerT) / 2.0f; + } + } + + if ((!mHasZeroBorderWidth && mI == mCount - 1) || + (mHasZeroBorderWidth && !mHasMore)) { + outerT = 1.0f; + innerT = 1.0f; + } else { + if (mType == PERFECT) { + outerT = innerT = (mI * 2.0f + 0.5f) / ((mCount - 1) * 2.0f); + } else { + Float last2OuterT = mLastOuterT; + Float last2InnerT = mLastInnerT; + + (void)FindNext(mBestDashLength); + + // + // outerT last2OuterT + // | | + // v v + // mLastOuterT -> +---+---+---+---+ + // | |###|###| | + // | |###|###| | + // | |###|###| | + // mLastInnerT -> +---+---+---+---+ + // ^ ^ + // | | + // innerT last2InnerT + outerT = (mLastOuterT + last2OuterT) / 2.0f; + innerT = (mLastInnerT + last2InnerT) / 2.0f; + } + } + + mI++; + + Bezier outerSectionBezier; + Bezier innerSectionBezier; + GetSubBezier(&outerSectionBezier, mOuterBezier, lastOuterT, outerT); + GetSubBezier(&innerSectionBezier, mInnerBezier, lastInnerT, innerT); + return DashedCornerFinder::Result(outerSectionBezier, innerSectionBezier); +} + +void DashedCornerFinder::Reset(void) { + mLastOuterP = mOuterBezier.mPoints[0]; + mLastInnerP = mInnerBezier.mPoints[0]; + mLastOuterT = 0.0f; + mLastInnerT = 0.0f; + mHasMore = true; +} + +Float DashedCornerFinder::FindNext(Float dashLength) { + Float upper = 1.0f; + Float lower = mLastOuterT; + + Point OuterP, InnerP; + // Start from upper bound to check if this is the last segment. + Float outerT = upper; + Float innerT; + Float W = 0.0f; + Float L = 0.0f; + + const Float LENGTH_MARGIN = 0.1f; + for (size_t i = 0; i < MAX_LOOP; i++) { + OuterP = GetBezierPoint(mOuterBezier, outerT); + InnerP = FindBezierNearestPoint(mInnerBezier, OuterP, outerT, &innerT); + + // Calculate approximate dash length. + // + // W = (W1 + W2) / 2 + // L = (OuterL + InnerL) / 2 + // dashLength = L / W + // + // ____----+----____ + // OuterP ___--- | ---___ mLastOuterP + // +--- | ---+ + // | | | + // | | | + // | W | W1 | + // | | | + // W2 | | | + // | | ______------+ + // | ____+---- mLastInnerP + // | ___--- + // | __--- + // +-- + // InnerP + // OuterL + // ____---------____ + // OuterP ___--- ---___ mLastOuterP + // +--- ---+ + // | L | + // | ___----------______ | + // | __--- -----+ + // | __-- | + // +-- | + // | InnerL ______------+ + // | ____----- mLastInnerP + // | ___--- + // | __--- + // +-- + // InnerP + Float W1 = (mLastOuterP - mLastInnerP).Length(); + Float W2 = (OuterP - InnerP).Length(); + Float OuterL = GetBezierLength(mOuterBezier, mLastOuterT, outerT); + Float InnerL = GetBezierLength(mInnerBezier, mLastInnerT, innerT); + W = (W1 + W2) / 2.0f; + L = (OuterL + InnerL) / 2.0f; + if (L > W * dashLength + LENGTH_MARGIN) { + if (i > 0) { + upper = outerT; + } + } else if (L < W * dashLength - LENGTH_MARGIN) { + if (i == 0) { + // This is the last segment with shorter dashLength. + mHasMore = false; + break; + } + lower = outerT; + } else { + break; + } + + outerT = (upper + lower) / 2.0f; + } + + mLastOuterP = OuterP; + mLastInnerP = InnerP; + mLastOuterT = outerT; + mLastInnerT = innerT; + + if (W == 0.0f) { + return 1.0f; + } + + return L / W; +} + +void DashedCornerFinder::FindBestDashLength(Float aMinBorderWidth, + Float aMaxBorderWidth, + Float aMinBorderRadius, + Float aMaxBorderRadius) { + // If dashLength is not calculateable, find it with binary search, + // such that there exists i that OuterP_i == OuterP_n and + // InnerP_i == InnerP_n with given dashLength. + + FourFloats key(aMinBorderWidth, aMaxBorderWidth, aMinBorderRadius, + aMaxBorderRadius); + BestDashLength best; + if (DashedCornerCache.Get(key, &best)) { + mCount = best.count; + mBestDashLength = best.dashLength; + return; + } + + Float lower = 1.0f; + Float upper = DOT_LENGTH * DASH_LENGTH; + Float dashLength = upper; + size_t targetCount = 0; + + const Float LENGTH_MARGIN = 0.1f; + for (size_t j = 0; j < MAX_LOOP; j++) { + size_t count; + Float actualDashLength; + if (!GetCountAndLastDashLength(dashLength, &count, &actualDashLength)) { + if (j == 0) { + mCount = mMaxCount; + break; + } + } + + if (j == 0) { + if (count == 1) { + // If only 1 segment fits, fill entire region + // + // count = 1 + // mCount = 1 + // | 1 | + // +---+---+ + // |###|###| + // |###|###| + // |###|###| + // +---+---+ + // 1 + mCount = 1; + break; + } + + // targetCount should be 2n. + // + // targetCount = 2 + // mCount = 2 + // | 1 | 2 | + // +---+---+---+---+ + // |###| | |###| + // |###| | |###| + // |###| | |###| + // +---+---+---+---+ + // 1 2 + // + // targetCount = 6 + // mCount = 4 + // | 1 | 2 | 3 | 4 | 5 | 6 | + // +---+---+---+---+---+---+---+---+---+---+---+---+ + // |###| | |###|###| | |###|###| | |###| + // |###| | |###|###| | |###|###| | |###| + // |###| | |###|###| | |###|###| | |###| + // +---+---+---+---+---+---+---+---+---+---+---+---+ + // 1 2 3 4 + if (count % 2) { + targetCount = count + 1; + } else { + targetCount = count; + } + + mCount = targetCount / 2 + 1; + } + + if (count == targetCount) { + mBestDashLength = dashLength; + + // actualDashLength won't be greater than dashLength. + if (actualDashLength > dashLength - LENGTH_MARGIN) { + break; + } + + // We started from upper bound, no need to update range when j == 0. + if (j > 0) { + upper = dashLength; + } + } else { + // |j == 0 && count != targetCount| means that |targetCount = count + 1|, + // and we started from upper bound, no need to update range when j == 0. + if (j > 0) { + if (count > targetCount) { + lower = dashLength; + } else { + upper = dashLength; + } + } + } + + dashLength = (upper + lower) / 2.0f; + } + + if (DashedCornerCache.Count() > DashedCornerCacheSize) { + DashedCornerCache.Clear(); + } + DashedCornerCache.Put(key, BestDashLength(mBestDashLength, mCount)); +} + +bool DashedCornerFinder::GetCountAndLastDashLength(Float aDashLength, + size_t* aCount, + Float* aActualDashLength) { + // Return the number of segments and the last segment's dashLength for + // the given dashLength. + + Reset(); + + for (size_t i = 0; i < mMaxCount; i++) { + Float actualDashLength = FindNext(aDashLength); + if (mLastOuterT >= 1.0f) { + *aCount = i + 1; + *aActualDashLength = actualDashLength; + return true; + } + } + + return false; +} + +} // namespace mozilla diff --git a/layout/painting/DashedCornerFinder.h b/layout/painting/DashedCornerFinder.h new file mode 100644 index 0000000000..3a409d19f7 --- /dev/null +++ b/layout/painting/DashedCornerFinder.h @@ -0,0 +1,274 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_DashedCornerFinder_h_ +#define mozilla_DashedCornerFinder_h_ + +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/BezierUtils.h" + +namespace mozilla { + +// Calculate {OuterT_i, InnerT_i} for each 1 < i < n, that +// (OuterL_i + InnerL_i) / 2 == dashLength * (W_i + W_{i-1}) / 2 +// where +// OuterP_i: OuterCurve(OuterT_i) +// InnerP_i: InnerCurve(OuterT_i) +// OuterL_i: Elliptic arc length between OuterP_i - OuterP_{i-1} +// InnerL_i: Elliptic arc length between InnerP_i - InnerP_{i-1} +// W_i = |OuterP_i - InnerP_i| +// 1.0 < dashLength < 3.0 +// +// OuterP_1 OuterP_0 +// _+__-----------+ OuterCurve +// OuterP_2 __---- | OuterL_1 | +// __+--- | | +// __--- | OuterL_2 | | +// OuterP_3 _-- | | W_1 | W_0 +// _+ | | | +// / \ W_2 | | | +// / \ | | InnerL_1 | +// | \ | InnerL_2|____-------+ InnerCurve +// | \ |____----+ InnerP_0 +// | . \ __---+ InnerP_1 +// | \ / InnerP_2 +// | . /+ InnerP_3 +// | | +// | . | +// | | +// | | +// | | +// OuterP_{n-1} +--------+ InnerP_{n-1} +// | | +// | | +// | | +// | | +// | | +// OuterP_n +--------+ InnerP_n +// +// Returns region with [OuterCurve((OuterT_{2j} + OuterT_{2j-1}) / 2), +// OuterCurve((OuterT_{2j} + OuterT_{2j-1}) / 2), +// InnerCurve((OuterT_{2j} + OuterT_{2j+1}) / 2), +// InnerCurve((OuterT_{2j} + OuterT_{2j+1}) / 2)], +// to start and end with half segment. +// +// _+__----+------+ OuterCurve +// _+---- | |######| +// __+---#| | |######| +// _+---##|####| | |######| +// _-- |#####|#####| | |#####| +// _+ |#####|#####| | |#####| +// / \ |#####|####| | |#####| +// / \ |####|#####| | |#####| +// | \ |####|####| |____-+-----+ InnerCurve +// | \ |####|____+---+ +// | . \ __+---+ +// | \ / +// | . /+ +// | | +// | . | +// | | +// | | +// | | +// +--------+ +// | | +// | | +// +--------+ +// |########| +// |########| +// +--------+ + +class DashedCornerFinder { + typedef mozilla::gfx::Bezier Bezier; + typedef mozilla::gfx::Float Float; + typedef mozilla::gfx::Point Point; + typedef mozilla::gfx::Size Size; + + public: + struct Result { + // Control points for the outer curve and the inner curve. + // + // outerSectionBezier + // | + // v _+ 3 + // ___---#| + // 0 +---#######| + // |###########| + // |###########| + // |##########| + // |##########| + // |#########| + // |#####____+ 3 + // 0 +---- + // ^ + // | + // innerSectionBezier + Bezier outerSectionBezier; + Bezier innerSectionBezier; + + Result(const Bezier& aOuterSectionBezier, const Bezier& aInnerSectionBezier) + : outerSectionBezier(aOuterSectionBezier), + innerSectionBezier(aInnerSectionBezier) {} + }; + + // aCornerDim.width + // |<----------------->| + // | | + // --+-------------___---+-- + // ^ | __-- | ^ + // | | _- | | + // | | / | | aBorderWidthH + // | | / | | + // | | | | v + // | | | __--+-- + // aCornerDim.height | || _- + // | || / + // | | / + // | | | + // | | | + // | | | + // | | | + // v | | + // --+---------+ + // | | + // |<------->| + // aBorderWidthV + DashedCornerFinder(const Bezier& aOuterBezier, const Bezier& aInnerBezier, + Float aBorderWidthH, Float aBorderWidthV, + const Size& aCornerDim); + + bool HasMore(void) const; + Result Next(void); + + private: + static const size_t MAX_LOOP = 32; + + // Bezier control points for the outer curve and the inner curve. + // + // ___---+ outer curve + // __-- | + // _- | + // / | + // / | + // | | + // | __--+ inner curve + // | _- + // | / + // | / + // | | + // | | + // | | + // | | + // | | + // +---------+ + Bezier mOuterBezier; + Bezier mInnerBezier; + + Point mLastOuterP; + Point mLastInnerP; + Float mLastOuterT; + Float mLastInnerT; + + // Length for each segment, ratio of the border width at that point. + Float mBestDashLength; + + // If one of border-widths is 0, do not calculate mBestDashLength, and draw + // segments until it reaches the other side or exceeds mMaxCount. + bool mHasZeroBorderWidth; + bool mHasMore; + + // The maximum number of segments. + size_t mMaxCount; + + enum { + // radius.width + // |<----------------->| + // | | + // --+-------------___---+-- + // ^ | __-- | ^ + // | | _- | | + // | | / + | top-width + // | | / | | + // | | | | v + // | | | __--+-- + // radius.height | || _- + // | || / + // | | / + // | | | + // | | | + // | | | + // | | | + // v | | + // --+----+----+ + // | | + // |<------->| + // left-width + + // * top-width == left-width + // * radius.width == radius.height + // * top-width < radius.width * 2 + // + // Split the perfect circle's arc into 2n segments, each segment's length is + // top-width * dashLength. Then split the inner curve and the outer curve + // with same angles. + // + // radius.width + // |<---------------------->| + // | | v + // --+------------------------+-- + // ^ | | | top-width / 2 + // | | perfect | | + // | | circle's ___---+-- + // | | arc __-+ | ^ + // | | | _- | | + // radius.height | | | + | +-- + // | | | / \ | | + // | | | | \ | | + // | | | | \ | | + // | | +->| \ | | + // | | +---__ \ | | + // | | | --__ \ | | + // | | | ---__ \ | | + // v | | --_\|| + // --+----+----+--------------+ + // | | | + // |<-->| | + // left-width / 2 + PERFECT, + + // Other cases. + // + // Split the outer curve and the inner curve into 2n segments, each segment + // satisfies following: + // (OuterL_i + InnerL_i) / 2 == dashLength * (W_i + W_{i-1}) / 2 + OTHER + } mType; + + size_t mI; + size_t mCount; + + // Determine mType from parameters. + void DetermineType(Float aBorderWidthH, Float aBorderWidthV); + + // Reset calculation. + void Reset(void); + + // Find next segment. + Float FindNext(Float dashLength); + + // Find mBestDashLength for parameters. + void FindBestDashLength(Float aMinBorderWidth, Float aMaxBorderWidth, + Float aMinBorderRadius, Float aMaxBorderRadius); + + // Fill corner with dashes with given dash length, and return the number of + // segments and last segment's dash length. + bool GetCountAndLastDashLength(Float aDashLength, size_t* aCount, + Float* aActualDashLength); +}; + +} // namespace mozilla + +#endif /* mozilla_DashedCornerFinder_h_ */ diff --git a/layout/painting/DisplayItemClip.cpp b/layout/painting/DisplayItemClip.cpp new file mode 100644 index 0000000000..7572130ac9 --- /dev/null +++ b/layout/painting/DisplayItemClip.cpp @@ -0,0 +1,488 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "DisplayItemClip.h" + +#include "gfxContext.h" +#include "gfxUtils.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/PathHelpers.h" +#include "mozilla/layers/StackingContextHelper.h" +#include "mozilla/webrender/WebRenderTypes.h" +#include "nsPresContext.h" +#include "nsCSSRendering.h" +#include "nsLayoutUtils.h" +#include "nsRegion.h" + +using namespace mozilla::gfx; + +namespace mozilla { + +void DisplayItemClip::SetTo(const nsRect& aRect) { SetTo(aRect, nullptr); } + +void DisplayItemClip::SetTo(const nsRect& aRect, const nscoord* aRadii) { + mHaveClipRect = true; + mClipRect = aRect; + if (aRadii) { + mRoundedClipRects.SetLength(1); + mRoundedClipRects[0].mRect = aRect; + memcpy(mRoundedClipRects[0].mRadii, aRadii, sizeof(nscoord) * 8); + } else { + mRoundedClipRects.Clear(); + } +} + +void DisplayItemClip::SetTo(const nsRect& aRect, const nsRect& aRoundedRect, + const nscoord* aRadii) { + mHaveClipRect = true; + mClipRect = aRect; + mRoundedClipRects.SetLength(1); + mRoundedClipRects[0].mRect = aRoundedRect; + memcpy(mRoundedClipRects[0].mRadii, aRadii, sizeof(nscoord) * 8); +} + +bool DisplayItemClip::MayIntersect(const nsRect& aRect) const { + if (!mHaveClipRect) { + return !aRect.IsEmpty(); + } + nsRect r = aRect.Intersect(mClipRect); + if (r.IsEmpty()) { + return false; + } + for (uint32_t i = 0; i < mRoundedClipRects.Length(); ++i) { + const RoundedRect& rr = mRoundedClipRects[i]; + if (!nsLayoutUtils::RoundedRectIntersectsRect(rr.mRect, rr.mRadii, r)) { + return false; + } + } + return true; +} + +void DisplayItemClip::IntersectWith(const DisplayItemClip& aOther) { + if (!aOther.mHaveClipRect) { + return; + } + if (!mHaveClipRect) { + *this = aOther; + return; + } + if (!mClipRect.IntersectRect(mClipRect, aOther.mClipRect)) { + mRoundedClipRects.Clear(); + return; + } + mRoundedClipRects.AppendElements(aOther.mRoundedClipRects); +} + +void DisplayItemClip::ApplyTo(gfxContext* aContext, int32_t A2D) const { + ApplyRectTo(aContext, A2D); + ApplyRoundedRectClipsTo(aContext, A2D, 0, mRoundedClipRects.Length()); +} + +void DisplayItemClip::ApplyRectTo(gfxContext* aContext, int32_t A2D) const { + aContext->NewPath(); + gfxRect clip = nsLayoutUtils::RectToGfxRect(mClipRect, A2D); + aContext->SnappedRectangle(clip); + aContext->Clip(); +} + +void DisplayItemClip::ApplyRoundedRectClipsTo(gfxContext* aContext, int32_t A2D, + uint32_t aBegin, + uint32_t aEnd) const { + DrawTarget& aDrawTarget = *aContext->GetDrawTarget(); + + aEnd = std::min<uint32_t>(aEnd, mRoundedClipRects.Length()); + + for (uint32_t i = aBegin; i < aEnd; ++i) { + RefPtr<Path> roundedRect = + MakeRoundedRectPath(aDrawTarget, A2D, mRoundedClipRects[i]); + aContext->Clip(roundedRect); + } +} + +void DisplayItemClip::FillIntersectionOfRoundedRectClips( + gfxContext* aContext, const DeviceColor& aColor, + int32_t aAppUnitsPerDevPixel) const { + DrawTarget& aDrawTarget = *aContext->GetDrawTarget(); + + uint32_t end = mRoundedClipRects.Length(); + if (!end) { + return; + } + + // Push clips for any rects that come BEFORE the rect at |aEnd - 1|, if any: + ApplyRoundedRectClipsTo(aContext, aAppUnitsPerDevPixel, 0, end - 1); + + // Now fill the rect at |aEnd - 1|: + RefPtr<Path> roundedRect = MakeRoundedRectPath( + aDrawTarget, aAppUnitsPerDevPixel, mRoundedClipRects[end - 1]); + aDrawTarget.Fill(roundedRect, ColorPattern(aColor)); + + // Finally, pop any clips that we may have pushed: + for (uint32_t i = 0; i < end - 1; ++i) { + aContext->PopClip(); + } +} + +already_AddRefed<Path> DisplayItemClip::MakeRoundedRectPath( + DrawTarget& aDrawTarget, int32_t A2D, const RoundedRect& aRoundRect) const { + RectCornerRadii pixelRadii; + nsCSSRendering::ComputePixelRadii(aRoundRect.mRadii, A2D, &pixelRadii); + + Rect rect = NSRectToSnappedRect(aRoundRect.mRect, A2D, aDrawTarget); + + return MakePathForRoundedRect(aDrawTarget, rect, pixelRadii); +} + +nsRect DisplayItemClip::ApproximateIntersectInward(const nsRect& aRect) const { + nsRect r = aRect; + if (mHaveClipRect) { + r.IntersectRect(r, mClipRect); + } + for (uint32_t i = 0, iEnd = mRoundedClipRects.Length(); i < iEnd; ++i) { + const RoundedRect& rr = mRoundedClipRects[i]; + nsRegion rgn = + nsLayoutUtils::RoundedRectIntersectRect(rr.mRect, rr.mRadii, r); + r = rgn.GetLargestRectangle(); + } + return r; +} + +// Test if (aXPoint, aYPoint) is in the ellipse with center (aXCenter, aYCenter) +// and radii aXRadius, aYRadius. +static bool IsInsideEllipse(nscoord aXRadius, nscoord aXCenter, nscoord aXPoint, + nscoord aYRadius, nscoord aYCenter, + nscoord aYPoint) { + float scaledX = float(aXPoint - aXCenter) / float(aXRadius); + float scaledY = float(aYPoint - aYCenter) / float(aYRadius); + return scaledX * scaledX + scaledY * scaledY < 1.0f; +} + +bool DisplayItemClip::IsRectClippedByRoundedCorner(const nsRect& aRect) const { + if (mRoundedClipRects.IsEmpty()) return false; + + nsRect rect; + rect.IntersectRect(aRect, NonRoundedIntersection()); + for (uint32_t i = 0, iEnd = mRoundedClipRects.Length(); i < iEnd; ++i) { + const RoundedRect& rr = mRoundedClipRects[i]; + // top left + if (rect.x < rr.mRect.x + rr.mRadii[eCornerTopLeftX] && + rect.y < rr.mRect.y + rr.mRadii[eCornerTopLeftY]) { + if (!IsInsideEllipse(rr.mRadii[eCornerTopLeftX], + rr.mRect.x + rr.mRadii[eCornerTopLeftX], rect.x, + rr.mRadii[eCornerTopLeftY], + rr.mRect.y + rr.mRadii[eCornerTopLeftY], rect.y)) { + return true; + } + } + // top right + if (rect.XMost() > rr.mRect.XMost() - rr.mRadii[eCornerTopRightX] && + rect.y < rr.mRect.y + rr.mRadii[eCornerTopRightY]) { + if (!IsInsideEllipse(rr.mRadii[eCornerTopRightX], + rr.mRect.XMost() - rr.mRadii[eCornerTopRightX], + rect.XMost(), rr.mRadii[eCornerTopRightY], + rr.mRect.y + rr.mRadii[eCornerTopRightY], rect.y)) { + return true; + } + } + // bottom left + if (rect.x < rr.mRect.x + rr.mRadii[eCornerBottomLeftX] && + rect.YMost() > rr.mRect.YMost() - rr.mRadii[eCornerBottomLeftY]) { + if (!IsInsideEllipse(rr.mRadii[eCornerBottomLeftX], + rr.mRect.x + rr.mRadii[eCornerBottomLeftX], rect.x, + rr.mRadii[eCornerBottomLeftY], + rr.mRect.YMost() - rr.mRadii[eCornerBottomLeftY], + rect.YMost())) { + return true; + } + } + // bottom right + if (rect.XMost() > rr.mRect.XMost() - rr.mRadii[eCornerBottomRightX] && + rect.YMost() > rr.mRect.YMost() - rr.mRadii[eCornerBottomRightY]) { + if (!IsInsideEllipse(rr.mRadii[eCornerBottomRightX], + rr.mRect.XMost() - rr.mRadii[eCornerBottomRightX], + rect.XMost(), rr.mRadii[eCornerBottomRightY], + rr.mRect.YMost() - rr.mRadii[eCornerBottomRightY], + rect.YMost())) { + return true; + } + } + } + return false; +} + +nsRect DisplayItemClip::NonRoundedIntersection() const { + NS_ASSERTION(mHaveClipRect, "Must have a clip rect!"); + nsRect result = mClipRect; + for (uint32_t i = 0, iEnd = mRoundedClipRects.Length(); i < iEnd; ++i) { + result.IntersectRect(result, mRoundedClipRects[i].mRect); + } + return result; +} + +bool DisplayItemClip::IsRectAffectedByClip(const nsRect& aRect) const { + if (mHaveClipRect && !mClipRect.Contains(aRect)) { + return true; + } + for (uint32_t i = 0, iEnd = mRoundedClipRects.Length(); i < iEnd; ++i) { + const RoundedRect& rr = mRoundedClipRects[i]; + nsRegion rgn = + nsLayoutUtils::RoundedRectIntersectRect(rr.mRect, rr.mRadii, aRect); + if (!rgn.Contains(aRect)) { + return true; + } + } + return false; +} + +bool DisplayItemClip::IsRectAffectedByClip(const nsIntRect& aRect, + float aXScale, float aYScale, + int32_t A2D) const { + if (mHaveClipRect) { + nsIntRect pixelClipRect = + mClipRect.ScaleToNearestPixels(aXScale, aYScale, A2D); + if (!pixelClipRect.Contains(aRect)) { + return true; + } + } + + // Rounded rect clipping only snaps to user-space pixels, not device space. + nsIntRect unscaled = aRect; + unscaled.Scale(1 / aXScale, 1 / aYScale); + + for (uint32_t i = 0, iEnd = mRoundedClipRects.Length(); i < iEnd; ++i) { + const RoundedRect& rr = mRoundedClipRects[i]; + + nsIntRect pixelRect = rr.mRect.ToNearestPixels(A2D); + + RectCornerRadii pixelRadii; + nsCSSRendering::ComputePixelRadii(rr.mRadii, A2D, &pixelRadii); + + nsIntRegion rgn = nsLayoutUtils::RoundedRectIntersectIntRect( + pixelRect, pixelRadii, unscaled); + if (!rgn.Contains(unscaled)) { + return true; + } + } + return false; +} + +nsRect DisplayItemClip::ApplyNonRoundedIntersection(const nsRect& aRect) const { + if (!mHaveClipRect) { + return aRect; + } + + nsRect result = aRect.Intersect(mClipRect); + for (uint32_t i = 0, iEnd = mRoundedClipRects.Length(); i < iEnd; ++i) { + result = result.Intersect(mRoundedClipRects[i].mRect); + } + return result; +} + +void DisplayItemClip::RemoveRoundedCorners() { + if (mRoundedClipRects.IsEmpty()) return; + + mClipRect = NonRoundedIntersection(); + mRoundedClipRects.Clear(); +} + +// Computes the difference between aR1 and aR2, limited to aBounds. +static void AccumulateRectDifference(const nsRect& aR1, const nsRect& aR2, + const nsRect& aBounds, nsRegion* aOut) { + if (aR1.IsEqualInterior(aR2)) return; + nsRegion r; + r.Xor(aR1, aR2); + r.And(r, aBounds); + aOut->Or(*aOut, r); +} + +static void AccumulateRoundedRectDifference( + const DisplayItemClip::RoundedRect& aR1, + const DisplayItemClip::RoundedRect& aR2, const nsRect& aBounds, + const nsRect& aOtherBounds, nsRegion* aOut) { + const nsRect& rect1 = aR1.mRect; + const nsRect& rect2 = aR2.mRect; + + // If the two rectangles are totally disjoint, just add them both - otherwise + // we'd end up adding one big enclosing rect + if (!rect1.Intersects(rect2) || + memcmp(aR1.mRadii, aR2.mRadii, sizeof(aR1.mRadii))) { + aOut->Or(*aOut, rect1.Intersect(aBounds)); + aOut->Or(*aOut, rect2.Intersect(aOtherBounds)); + return; + } + + nscoord lowestBottom = std::max(rect1.YMost(), rect2.YMost()); + nscoord highestTop = std::min(rect1.Y(), rect2.Y()); + nscoord maxRight = std::max(rect1.XMost(), rect2.XMost()); + nscoord minLeft = std::min(rect1.X(), rect2.X()); + + // At this point, we know that the radii haven't changed, and that the bounds + // are different in some way. To explain how this works, consider the case + // where the rounded rect has just been translated along the X direction. + // | ______________________ _ _ _ _ _ _ | + // | / / \ \ | + // | | | | + // | | aR1 | | aR2 | | + // | | | | + // | \ __________\___________ / _ _ _ _ _ / | + // | | + // The invalidation region will be as if we lopped off the left rounded part + // of aR2, and the right rounded part of aR1, and XOR'd them: + // | ______________________ _ _ _ _ _ _ | + // | -/-----------/- -\-----------\- | + // | |-------------- --|------------ | + // | |-----aR1---|-- --|-----aR2---| | + // | |-------------- --|------------ | + // | -\ __________\-__________-/ _ _ _ _ _ /- | + // | | + // The logic below just implements this idea, but generalized to both the + // X and Y dimensions. The "(...)Adjusted(...)" values represent the lopped + // off sides. + nscoord highestAdjustedBottom = std::min( + rect1.YMost() - aR1.mRadii[eCornerBottomLeftY], + std::min(rect1.YMost() - aR1.mRadii[eCornerBottomRightY], + std::min(rect2.YMost() - aR2.mRadii[eCornerBottomLeftY], + rect2.YMost() - aR2.mRadii[eCornerBottomRightY]))); + nscoord lowestAdjustedTop = + std::max(rect1.Y() + aR1.mRadii[eCornerTopLeftY], + std::max(rect1.Y() + aR1.mRadii[eCornerTopRightY], + std::max(rect2.Y() + aR2.mRadii[eCornerTopLeftY], + rect2.Y() + aR2.mRadii[eCornerTopRightY]))); + + nscoord minAdjustedRight = std::min( + rect1.XMost() - aR1.mRadii[eCornerTopRightX], + std::min(rect1.XMost() - aR1.mRadii[eCornerBottomRightX], + std::min(rect2.XMost() - aR2.mRadii[eCornerTopRightX], + rect2.XMost() - aR2.mRadii[eCornerBottomRightX]))); + nscoord maxAdjustedLeft = + std::max(rect1.X() + aR1.mRadii[eCornerTopLeftX], + std::max(rect1.X() + aR1.mRadii[eCornerBottomLeftX], + std::max(rect2.X() + aR2.mRadii[eCornerTopLeftX], + rect2.X() + aR2.mRadii[eCornerBottomLeftX]))); + + // We only want to add an invalidation rect if the bounds have changed. If we + // always added all of the 4 rects below, we would always be invalidating a + // border around the rects, even in cases where we just translated along the X + // or Y axis. + nsRegion r; + // First, or with the Y delta rects, wide along the X axis + if (rect1.Y() != rect2.Y()) { + r.Or(r, nsRect(minLeft, highestTop, maxRight - minLeft, + lowestAdjustedTop - highestTop)); + } + if (rect1.YMost() != rect2.YMost()) { + r.Or(r, nsRect(minLeft, highestAdjustedBottom, maxRight - minLeft, + lowestBottom - highestAdjustedBottom)); + } + // Then, or with the X delta rects, wide along the Y axis + if (rect1.X() != rect2.X()) { + r.Or(r, nsRect(minLeft, highestTop, maxAdjustedLeft - minLeft, + lowestBottom - highestTop)); + } + if (rect1.XMost() != rect2.XMost()) { + r.Or(r, nsRect(minAdjustedRight, highestTop, maxRight - minAdjustedRight, + lowestBottom - highestTop)); + } + + r.And(r, aBounds.Union(aOtherBounds)); + aOut->Or(*aOut, r); +} + +void DisplayItemClip::AddOffsetAndComputeDifference( + const nsPoint& aOffset, const nsRect& aBounds, + const DisplayItemClip& aOther, const nsRect& aOtherBounds, + nsRegion* aDifference) { + if (mHaveClipRect != aOther.mHaveClipRect || + mRoundedClipRects.Length() != aOther.mRoundedClipRects.Length()) { + aDifference->Or(*aDifference, aBounds); + aDifference->Or(*aDifference, aOtherBounds); + return; + } + if (mHaveClipRect) { + AccumulateRectDifference(mClipRect + aOffset, aOther.mClipRect, + aBounds.Union(aOtherBounds), aDifference); + } + for (uint32_t i = 0; i < mRoundedClipRects.Length(); ++i) { + if (mRoundedClipRects[i] + aOffset != aOther.mRoundedClipRects[i]) { + AccumulateRoundedRectDifference(mRoundedClipRects[i] + aOffset, + aOther.mRoundedClipRects[i], aBounds, + aOtherBounds, aDifference); + } + } +} + +void DisplayItemClip::AppendRoundedRects(nsTArray<RoundedRect>* aArray) const { + aArray->AppendElements(mRoundedClipRects.Elements(), + mRoundedClipRects.Length()); +} + +bool DisplayItemClip::ComputeRegionInClips(const DisplayItemClip* aOldClip, + const nsPoint& aShift, + nsRegion* aCombined) const { + if (!mHaveClipRect || (aOldClip && !aOldClip->mHaveClipRect)) { + return false; + } + + if (aOldClip) { + *aCombined = aOldClip->NonRoundedIntersection(); + aCombined->MoveBy(aShift); + aCombined->Or(*aCombined, NonRoundedIntersection()); + } else { + *aCombined = NonRoundedIntersection(); + } + return true; +} + +void DisplayItemClip::MoveBy(const nsPoint& aPoint) { + if (!mHaveClipRect) { + return; + } + mClipRect += aPoint; + for (uint32_t i = 0; i < mRoundedClipRects.Length(); ++i) { + mRoundedClipRects[i].mRect += aPoint; + } +} + +static DisplayItemClip* gNoClip; + +const DisplayItemClip& DisplayItemClip::NoClip() { + if (!gNoClip) { + gNoClip = new DisplayItemClip(); + } + return *gNoClip; +} + +void DisplayItemClip::Shutdown() { + delete gNoClip; + gNoClip = nullptr; +} + +nsCString DisplayItemClip::ToString() const { + nsAutoCString str; + if (mHaveClipRect) { + str.AppendPrintf("%d,%d,%d,%d", mClipRect.x, mClipRect.y, mClipRect.width, + mClipRect.height); + for (uint32_t i = 0; i < mRoundedClipRects.Length(); ++i) { + const RoundedRect& r = mRoundedClipRects[i]; + str.AppendPrintf(" [%d,%d,%d,%d corners %d,%d,%d,%d,%d,%d,%d,%d]", + r.mRect.x, r.mRect.y, r.mRect.width, r.mRect.height, + r.mRadii[0], r.mRadii[1], r.mRadii[2], r.mRadii[3], + r.mRadii[4], r.mRadii[5], r.mRadii[6], r.mRadii[7]); + } + } + return std::move(str); +} + +void DisplayItemClip::ToComplexClipRegions( + int32_t aAppUnitsPerDevPixel, + nsTArray<wr::ComplexClipRegion>& aOutArray) const { + for (const auto& clipRect : mRoundedClipRects) { + aOutArray.AppendElement(wr::ToComplexClipRegion( + clipRect.mRect, clipRect.mRadii, aAppUnitsPerDevPixel)); + } +} + +} // namespace mozilla diff --git a/layout/painting/DisplayItemClip.h b/layout/painting/DisplayItemClip.h new file mode 100644 index 0000000000..ff9df26603 --- /dev/null +++ b/layout/painting/DisplayItemClip.h @@ -0,0 +1,193 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DISPLAYITEMCLIP_H_ +#define DISPLAYITEMCLIP_H_ + +#include "mozilla/RefPtr.h" +#include "nsRect.h" +#include "nsTArray.h" + +class gfxContext; +class nsPresContext; +class nsRegion; + +namespace mozilla { +namespace gfx { +class DrawTarget; +class Path; +} // namespace gfx +namespace layers { +class StackingContextHelper; +} // namespace layers +namespace wr { +struct ComplexClipRegion; +} // namespace wr +} // namespace mozilla + +namespace mozilla { + +/** + * An DisplayItemClip represents the intersection of an optional rectangle + * with a list of rounded rectangles (which is often empty), all in appunits. + * It can represent everything CSS clipping can do to an element (except for + * SVG clip-path), including no clipping at all. + */ +class DisplayItemClip { + typedef mozilla::gfx::DeviceColor DeviceColor; + typedef mozilla::gfx::DrawTarget DrawTarget; + typedef mozilla::gfx::Path Path; + + public: + struct RoundedRect { + nsRect mRect; + // Indices into mRadii are the HalfCorner values in gfx/2d/Types.h + nscoord mRadii[8]; + + RoundedRect operator+(const nsPoint& aOffset) const { + RoundedRect r = *this; + r.mRect += aOffset; + return r; + } + bool operator==(const RoundedRect& aOther) const { + if (!mRect.IsEqualInterior(aOther.mRect)) { + return false; + } + + for (const auto corner : mozilla::AllPhysicalHalfCorners()) { + if (mRadii[corner] != aOther.mRadii[corner]) { + return false; + } + } + return true; + } + bool operator!=(const RoundedRect& aOther) const { + return !(*this == aOther); + } + }; + + // Constructs a DisplayItemClip that does no clipping at all. + DisplayItemClip() : mHaveClipRect(false) {} + + void SetTo(const nsRect& aRect); + void SetTo(const nsRect& aRect, const nscoord* aRadii); + void SetTo(const nsRect& aRect, const nsRect& aRoundedRect, + const nscoord* aRadii); + void IntersectWith(const DisplayItemClip& aOther); + + // Apply this |DisplayItemClip| to the given gfxContext. Any saving of state + // or clearing of other clips must be done by the caller. + // See aBegin/aEnd note on ApplyRoundedRectsTo. + void ApplyTo(gfxContext* aContext, int32_t A2D) const; + + void ApplyRectTo(gfxContext* aContext, int32_t A2D) const; + // Applies the rounded rects in this Clip to aContext + // Will only apply rounded rects from aBegin (inclusive) to aEnd + // (exclusive) or the number of rounded rects, whichever is smaller. + void ApplyRoundedRectClipsTo(gfxContext* aContext, int32_t A2DPRInt32, + uint32_t aBegin, uint32_t aEnd) const; + + // Draw (fill) the rounded rects in this clip to aContext + void FillIntersectionOfRoundedRectClips(gfxContext* aContext, + const DeviceColor& aColor, + int32_t aAppUnitsPerDevPixel) const; + // 'Draw' (create as a path, does not stroke or fill) aRoundRect to aContext + already_AddRefed<Path> MakeRoundedRectPath( + DrawTarget& aDrawTarget, int32_t A2D, + const RoundedRect& aRoundRect) const; + + // Returns true if the intersection of aRect and this clip region is + // non-empty. This is precise for DisplayItemClips with at most one + // rounded rectangle. When multiple rounded rectangles are present, we just + // check that the rectangle intersects all of them (but possibly in different + // places). So it may return true when the correct answer is false. + bool MayIntersect(const nsRect& aRect) const; + + // Return a rectangle contained in the intersection of aRect with this + // clip region. Tries to return the largest possible rectangle, but may + // not succeed. + nsRect ApproximateIntersectInward(const nsRect& aRect) const; + + /* + * Computes a region which contains the clipped area of this DisplayItemClip, + * or if aOldClip is non-null, the union of the clipped area of this + * DisplayItemClip with the clipped area of aOldClip translated by aShift. + * The result is stored in aCombined. If the result would be infinite + * (because one or both of the clips does no clipping), returns false. + */ + bool ComputeRegionInClips(const DisplayItemClip* aOldClip, + const nsPoint& aShift, nsRegion* aCombined) const; + + // Returns false if aRect is definitely not clipped by a rounded corner in + // this clip. Returns true if aRect is clipped by a rounded corner in this + // clip or it can not be quickly determined that it is not clipped by a + // rounded corner in this clip. + bool IsRectClippedByRoundedCorner(const nsRect& aRect) const; + + // Returns false if aRect is definitely not clipped by anything in this clip. + // Fast but not necessarily accurate. + bool IsRectAffectedByClip(const nsRect& aRect) const; + bool IsRectAffectedByClip(const nsIntRect& aRect, float aXScale, + float aYScale, int32_t A2D) const; + + // Intersection of all rects in this clip ignoring any rounded corners. + nsRect NonRoundedIntersection() const; + + // Intersect the given rects with all rects in this clip, ignoring any + // rounded corners. + nsRect ApplyNonRoundedIntersection(const nsRect& aRect) const; + + // Gets rid of any rounded corners in this clip. + void RemoveRoundedCorners(); + + // Adds the difference between Intersect(*this + aPoint, aBounds) and + // Intersect(aOther, aOtherBounds) to aDifference (or a bounding-box thereof). + void AddOffsetAndComputeDifference(const nsPoint& aPoint, + const nsRect& aBounds, + const DisplayItemClip& aOther, + const nsRect& aOtherBounds, + nsRegion* aDifference); + + bool operator==(const DisplayItemClip& aOther) const { + return mHaveClipRect == aOther.mHaveClipRect && + (!mHaveClipRect || mClipRect.IsEqualInterior(aOther.mClipRect)) && + mRoundedClipRects == aOther.mRoundedClipRects; + } + bool operator!=(const DisplayItemClip& aOther) const { + return !(*this == aOther); + } + + bool HasClip() const { return mHaveClipRect; } + const nsRect& GetClipRect() const { + NS_ASSERTION(HasClip(), "No clip rect!"); + return mClipRect; + } + + void MoveBy(const nsPoint& aPoint); + + nsCString ToString() const; + + uint32_t GetRoundedRectCount() const { return mRoundedClipRects.Length(); } + void AppendRoundedRects(nsTArray<RoundedRect>* aArray) const; + + void ToComplexClipRegions(int32_t aAppUnitsPerDevPixel, + nsTArray<wr::ComplexClipRegion>& aOutArray) const; + + static const DisplayItemClip& NoClip(); + + static void Shutdown(); + + private: + nsRect mClipRect; + CopyableTArray<RoundedRect> mRoundedClipRects; + // If mHaveClipRect is false then this object represents no clipping at all + // and mRoundedClipRects must be empty. + bool mHaveClipRect; +}; + +} // namespace mozilla + +#endif /* DISPLAYITEMCLIP_H_ */ diff --git a/layout/painting/DisplayItemClipChain.cpp b/layout/painting/DisplayItemClipChain.cpp new file mode 100644 index 0000000000..537c3615a1 --- /dev/null +++ b/layout/painting/DisplayItemClipChain.cpp @@ -0,0 +1,88 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "DisplayItemClipChain.h" + +#include "nsDisplayList.h" + +namespace mozilla { + +/* static */ const DisplayItemClip* DisplayItemClipChain::ClipForASR( + const DisplayItemClipChain* aClipChain, const ActiveScrolledRoot* aASR) { + while (aClipChain && + !ActiveScrolledRoot::IsAncestor(aClipChain->mASR, aASR)) { + aClipChain = aClipChain->mParent; + } + return (aClipChain && aClipChain->mASR == aASR) ? &aClipChain->mClip + : nullptr; +} + +bool DisplayItemClipChain::Equal(const DisplayItemClipChain* aClip1, + const DisplayItemClipChain* aClip2) { + if (aClip1 == aClip2) { + return true; + } + + if (!aClip1 || !aClip2) { + return false; + } + + bool ret = aClip1->mASR == aClip2->mASR && aClip1->mClip == aClip2->mClip && + Equal(aClip1->mParent, aClip2->mParent); + // Sanity check: if two clip chains are equal they must hash to the same + // thing too, or Bad Things (TM) will happen. + MOZ_ASSERT(!ret || (Hash(aClip1) == Hash(aClip2))); + return ret; +} + +uint32_t DisplayItemClipChain::Hash(const DisplayItemClipChain* aClip) { + if (!aClip) { + return 0; + } + + // We include the number of rounded rects in the hash but not their contents. + // This is to keep the hash fast, because most clips will not have rounded + // rects and including them will slow down the hash in the common case. Note + // that the ::Equal check still checks the rounded rect contents, so in case + // of hash collisions the clip chains can still be distinguished using that. + uint32_t hash = HashGeneric(aClip->mASR, aClip->mClip.GetRoundedRectCount()); + if (aClip->mClip.HasClip()) { + const nsRect& rect = aClip->mClip.GetClipRect(); + // empty rects are considered equal in DisplayItemClipChain::Equal, even + // though they may have different x and y coordinates. So make sure they + // hash to the same thing in those cases too. + if (!rect.IsEmpty()) { + hash = AddToHash(hash, rect.x, rect.y, rect.width, rect.height); + } + } + + return hash; +} + +/* static */ +nsCString DisplayItemClipChain::ToString( + const DisplayItemClipChain* aClipChain) { + nsAutoCString str; + for (auto* sc = aClipChain; sc; sc = sc->mParent) { + if (sc->mASR) { + str.AppendPrintf("0x%p <%s> [0x%p]", sc, sc->mClip.ToString().get(), + sc->mASR->mScrollableFrame); + } else { + str.AppendPrintf("0x%p <%s> [root asr]", sc, sc->mClip.ToString().get()); + } + if (sc->mParent) { + str.AppendLiteral(", "); + } + } + return std::move(str); +} + +bool DisplayItemClipChain::HasRoundedCorners() const { + return mClip.GetRoundedRectCount() > 0 || + (mParent && mParent->HasRoundedCorners()); +} + +} // namespace mozilla diff --git a/layout/painting/DisplayItemClipChain.h b/layout/painting/DisplayItemClipChain.h new file mode 100644 index 0000000000..32779e09c0 --- /dev/null +++ b/layout/painting/DisplayItemClipChain.h @@ -0,0 +1,117 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DISPLAYITEMCLIPCHAIN_H_ +#define DISPLAYITEMCLIPCHAIN_H_ + +#include "mozilla/Assertions.h" +#include "DisplayItemClip.h" +#include "nsString.h" + +class nsIScrollableFrame; + +namespace mozilla { + +struct ActiveScrolledRoot; + +/** + * A DisplayItemClipChain is a linked list of DisplayItemClips where each clip + * is associated with an active scrolled root that describes what the clip + * moves with. + * We use a chain instead of just one intersected clip due to async scrolling: + * A clip that moves along with a display item can be fused to the item's + * contents when drawing the layer contents, but all other clips in the chain + * need to be kept separate so that they can be applied at composition time, + * after any async scroll offsets have been applied. + * The clip chain is created during display list construction by the builder's + * DisplayListClipState. + * The clip chain order is determined by the active scrolled root order. + * For every DisplayItemClipChain object |clipChain|, the following holds: + * !clipChain->mParent || + * ActiveScrolledRoot::IsAncestor(clipChain->mParent->mASR, clipChain->mASR). + * The clip chain can skip over active scrolled roots. That just means that + * there is no clip that moves with the skipped ASR in this chain. + */ +struct DisplayItemClipChain { + /** + * Get the display item clip in this chain that moves with aASR, or nullptr + * if no such clip exists. aClipChain can be null. + */ + static const DisplayItemClip* ClipForASR( + const DisplayItemClipChain* aClipChain, const ActiveScrolledRoot* aASR); + + static bool Equal(const DisplayItemClipChain* aClip1, + const DisplayItemClipChain* aClip2); + /** + * Hash function that returns the same value for any two clips A and B + * where Equal(A, B) is true. + */ + static uint32_t Hash(const DisplayItemClipChain* aClip); + + static nsCString ToString(const DisplayItemClipChain* aClipChain); + + bool HasRoundedCorners() const; + + void AddRef() { mRefCount++; } + void Release() { + MOZ_ASSERT(mRefCount > 0); + mRefCount--; + } + + DisplayItemClipChain(const DisplayItemClip& aClip, + const ActiveScrolledRoot* aASR, + const DisplayItemClipChain* aParent, + DisplayItemClipChain* aNextClipChainToDestroy) + : mClip(aClip), + mASR(aASR), + mParent(aParent), + mNextClipChainToDestroy(aNextClipChainToDestroy) +#ifdef DEBUG + , + mOnStack(true) +#endif + { + } + + DisplayItemClipChain() + : mASR(nullptr), + mNextClipChainToDestroy(nullptr) +#ifdef DEBUG + , + mOnStack(true) +#endif + { + } + + DisplayItemClip mClip; + const ActiveScrolledRoot* mASR; + RefPtr<const DisplayItemClipChain> mParent; + uint32_t mRefCount = 0; + DisplayItemClipChain* mNextClipChainToDestroy; +#ifdef DEBUG + bool mOnStack; +#endif +}; + +struct DisplayItemClipChainHasher { + typedef const DisplayItemClipChain* Key; + + std::size_t operator()(const Key& aKey) const { + return DisplayItemClipChain::Hash(aKey); + } +}; + +struct DisplayItemClipChainEqualer { + typedef const DisplayItemClipChain* Key; + + bool operator()(const Key& lhs, const Key& rhs) const { + return DisplayItemClipChain::Equal(lhs, rhs); + } +}; + +} // namespace mozilla + +#endif /* DISPLAYITEMCLIPCHAIN_H_ */ diff --git a/layout/painting/DisplayListClipState.cpp b/layout/painting/DisplayListClipState.cpp new file mode 100644 index 0000000000..8a87e2906b --- /dev/null +++ b/layout/painting/DisplayListClipState.cpp @@ -0,0 +1,149 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "DisplayListClipState.h" + +#include "nsDisplayList.h" + +namespace mozilla { + +const DisplayItemClipChain* DisplayListClipState::GetCurrentCombinedClipChain( + nsDisplayListBuilder* aBuilder) { + if (mCurrentCombinedClipChainIsValid) { + return mCurrentCombinedClipChain; + } + if (!mClipChainContentDescendants && !mClipChainContainingBlockDescendants) { + mCurrentCombinedClipChain = nullptr; + mCurrentCombinedClipChainIsValid = true; + return nullptr; + } + + mCurrentCombinedClipChain = aBuilder->CreateClipChainIntersection( + mCurrentCombinedClipChain, mClipChainContentDescendants, + mClipChainContainingBlockDescendants); + mCurrentCombinedClipChainIsValid = true; + return mCurrentCombinedClipChain; +} + +static void ApplyClip(nsDisplayListBuilder* aBuilder, + const DisplayItemClipChain*& aClipToModify, + const ActiveScrolledRoot* aASR, + DisplayItemClipChain& aClipChainOnStack) { + aClipChainOnStack.mASR = aASR; + if (aClipToModify && aClipToModify->mASR == aASR) { + // Intersect with aClipToModify and replace the clip chain item. + aClipChainOnStack.mClip.IntersectWith(aClipToModify->mClip); + aClipChainOnStack.mParent = aClipToModify->mParent; + aClipToModify = &aClipChainOnStack; + } else if (!aClipToModify || + ActiveScrolledRoot::IsAncestor(aClipToModify->mASR, aASR)) { + // Add a new clip chain item at the bottom. + aClipChainOnStack.mParent = aClipToModify; + aClipToModify = &aClipChainOnStack; + } else { + // We need to insert / intersect a DisplayItemClipChain in the middle of the + // aClipToModify chain. This is a very rare case. + // Find the common ancestor and have the builder create the + // DisplayItemClipChain intersection. This will create new + // DisplayItemClipChain objects for all descendants of ancestorSC and we + // will not hold on to a pointer to aClipChainOnStack. + const DisplayItemClipChain* ancestorSC = aClipToModify; + while (ancestorSC && + ActiveScrolledRoot::IsAncestor(aASR, ancestorSC->mASR)) { + ancestorSC = ancestorSC->mParent; + } + ancestorSC = aBuilder->CopyWholeChain(ancestorSC); + aClipChainOnStack.mParent = nullptr; + aClipToModify = aBuilder->CreateClipChainIntersection( + ancestorSC, aClipToModify, &aClipChainOnStack); + } +} + +void DisplayListClipState::ClipContainingBlockDescendants( + nsDisplayListBuilder* aBuilder, const nsRect& aRect, const nscoord* aRadii, + DisplayItemClipChain& aClipChainOnStack) { + if (aRadii) { + aClipChainOnStack.mClip.SetTo(aRect, aRadii); + } else { + aClipChainOnStack.mClip.SetTo(aRect); + } + const ActiveScrolledRoot* asr = aBuilder->CurrentActiveScrolledRoot(); + ApplyClip(aBuilder, mClipChainContainingBlockDescendants, asr, + aClipChainOnStack); + InvalidateCurrentCombinedClipChain(asr); +} + +void DisplayListClipState::ClipContentDescendants( + nsDisplayListBuilder* aBuilder, const nsRect& aRect, const nscoord* aRadii, + DisplayItemClipChain& aClipChainOnStack) { + if (aRadii) { + aClipChainOnStack.mClip.SetTo(aRect, aRadii); + } else { + aClipChainOnStack.mClip.SetTo(aRect); + } + const ActiveScrolledRoot* asr = aBuilder->CurrentActiveScrolledRoot(); + ApplyClip(aBuilder, mClipChainContentDescendants, asr, aClipChainOnStack); + InvalidateCurrentCombinedClipChain(asr); +} + +void DisplayListClipState::ClipContentDescendants( + nsDisplayListBuilder* aBuilder, const nsRect& aRect, + const nsRect& aRoundedRect, const nscoord* aRadii, + DisplayItemClipChain& aClipChainOnStack) { + if (aRadii) { + aClipChainOnStack.mClip.SetTo(aRect, aRoundedRect, aRadii); + } else { + nsRect intersect = aRect.Intersect(aRoundedRect); + aClipChainOnStack.mClip.SetTo(intersect); + } + const ActiveScrolledRoot* asr = aBuilder->CurrentActiveScrolledRoot(); + ApplyClip(aBuilder, mClipChainContentDescendants, asr, aClipChainOnStack); + InvalidateCurrentCombinedClipChain(asr); +} + +void DisplayListClipState::InvalidateCurrentCombinedClipChain( + const ActiveScrolledRoot* aInvalidateUpTo) { + mClippedToDisplayPort = false; + mCurrentCombinedClipChainIsValid = false; + while (mCurrentCombinedClipChain && + ActiveScrolledRoot::IsAncestor(aInvalidateUpTo, + mCurrentCombinedClipChain->mASR)) { + mCurrentCombinedClipChain = mCurrentCombinedClipChain->mParent; + } +} + +void DisplayListClipState::ClipContainingBlockDescendantsToContentBox( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + DisplayItemClipChain& aClipChainOnStack, uint32_t aFlags) { + nscoord radii[8]; + bool hasBorderRadius = aFrame->GetContentBoxBorderRadii(radii); + if (!hasBorderRadius && + (aFlags & ASSUME_DRAWING_RESTRICTED_TO_CONTENT_RECT)) { + return; + } + + nsRect clipRect = aFrame->GetContentRectRelativeToSelf() + + aBuilder->ToReferenceFrame(aFrame); + // If we have a border-radius, we have to clip our content to that + // radius. + ClipContainingBlockDescendants( + aBuilder, clipRect, hasBorderRadius ? radii : nullptr, aClipChainOnStack); +} + +DisplayListClipState::AutoSaveRestore::AutoSaveRestore( + nsDisplayListBuilder* aBuilder) + : mBuilder(aBuilder), + mState(aBuilder->ClipState()), + mSavedState(aBuilder->ClipState()) +#ifdef DEBUG + , + mClipUsed(false), + mRestored(false) +#endif +{ +} + +} // namespace mozilla diff --git a/layout/painting/DisplayListClipState.h b/layout/painting/DisplayListClipState.h new file mode 100644 index 0000000000..f38a6610e7 --- /dev/null +++ b/layout/painting/DisplayListClipState.h @@ -0,0 +1,300 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DISPLAYLISTCLIPSTATE_H_ +#define DISPLAYLISTCLIPSTATE_H_ + +#include "DisplayItemClip.h" +#include "DisplayItemClipChain.h" + +#include "mozilla/DebugOnly.h" + +class nsIFrame; +class nsIScrollableFrame; +class nsDisplayListBuilder; + +namespace mozilla { + +/** + * All clip coordinates are in appunits relative to the reference frame + * for the display item we're building. + */ +class DisplayListClipState { + public: + DisplayListClipState() + : mClipChainContentDescendants(nullptr), + mClipChainContainingBlockDescendants(nullptr), + mCurrentCombinedClipChain(nullptr), + mCurrentCombinedClipChainIsValid(false), + mClippedToDisplayPort(false) {} + + void SetClippedToDisplayPort() { mClippedToDisplayPort = true; } + bool IsClippedToDisplayPort() const { return mClippedToDisplayPort; } + + /** + * Returns intersection of mClipChainContainingBlockDescendants and + * mClipChainContentDescendants, allocated on aBuilder's arena. + */ + const DisplayItemClipChain* GetCurrentCombinedClipChain( + nsDisplayListBuilder* aBuilder); + + const DisplayItemClipChain* GetClipChainForContainingBlockDescendants() + const { + return mClipChainContainingBlockDescendants; + } + const DisplayItemClipChain* GetClipChainForContentDescendants() const { + return mClipChainContentDescendants; + } + + const ActiveScrolledRoot* GetContentClipASR() const { + return mClipChainContentDescendants ? mClipChainContentDescendants->mASR + : nullptr; + } + + class AutoSaveRestore; + + class AutoClipContainingBlockDescendantsToContentBox; + + class AutoClipMultiple; + + enum { ASSUME_DRAWING_RESTRICTED_TO_CONTENT_RECT = 0x01 }; + + private: + void Clear() { + mClipChainContentDescendants = nullptr; + mClipChainContainingBlockDescendants = nullptr; + mCurrentCombinedClipChain = nullptr; + mCurrentCombinedClipChainIsValid = false; + mClippedToDisplayPort = false; + } + + void SetClipChainForContainingBlockDescendants( + const DisplayItemClipChain* aClipChain) { + mClipChainContainingBlockDescendants = aClipChain; + InvalidateCurrentCombinedClipChain(aClipChain ? aClipChain->mASR : nullptr); + } + + /** + * Intersects the given clip rect (with optional aRadii) with the current + * mClipContainingBlockDescendants and sets mClipContainingBlockDescendants to + * the result, stored in aClipOnStack. + */ + void ClipContainingBlockDescendants(nsDisplayListBuilder* aBuilder, + const nsRect& aRect, + const nscoord* aRadii, + DisplayItemClipChain& aClipChainOnStack); + + void ClipContentDescendants(nsDisplayListBuilder* aBuilder, + const nsRect& aRect, const nscoord* aRadii, + DisplayItemClipChain& aClipChainOnStack); + void ClipContentDescendants(nsDisplayListBuilder* aBuilder, + const nsRect& aRect, const nsRect& aRoundedRect, + const nscoord* aRadii, + DisplayItemClipChain& aClipChainOnStack); + + void InvalidateCurrentCombinedClipChain( + const ActiveScrolledRoot* aInvalidateUpTo); + + /** + * Clips containing-block descendants to the frame's content-box, + * taking border-radius into account. + * If aFlags contains ASSUME_DRAWING_RESTRICTED_TO_CONTENT_RECT then + * we assume display items will not draw outside the content rect, so + * clipping is only required if there is a border-radius. This is an + * optimization to reduce the amount of clipping required. + */ + void ClipContainingBlockDescendantsToContentBox( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + DisplayItemClipChain& aClipChainOnStack, uint32_t aFlags); + + /** + * All content descendants (i.e. following placeholder frames to their + * out-of-flows if necessary) should be clipped by + * mClipChainContentDescendants. Null if no clipping applies. + */ + const DisplayItemClipChain* mClipChainContentDescendants; + /** + * All containing-block descendants (i.e. frame descendants), including + * display items for the current frame, should be clipped by + * mClipChainContainingBlockDescendants. + * Null if no clipping applies. + */ + const DisplayItemClipChain* mClipChainContainingBlockDescendants; + /** + * The intersection of mClipChainContentDescendants and + * mClipChainContainingBlockDescendants. + * Allocated in the nsDisplayListBuilder arena. Null if none has been + * allocated or both mClipChainContentDescendants and + * mClipChainContainingBlockDescendants are null. + */ + const DisplayItemClipChain* mCurrentCombinedClipChain; + bool mCurrentCombinedClipChainIsValid; + /** + * A flag that is used by sticky positioned items to know if the clip applied + * to them is just the displayport clip or if there is additional clipping. + */ + bool mClippedToDisplayPort; +}; + +/** + * A class to automatically save and restore the current clip state. Also + * offers methods for modifying the clip state. Only one modification is allowed + * to be in scope at a time using one of these objects; multiple modifications + * require nested objects. The interface is written this way to prevent + * dangling pointers to DisplayItemClips. + */ +class DisplayListClipState::AutoSaveRestore { + public: + explicit AutoSaveRestore(nsDisplayListBuilder* aBuilder); + void Restore() { + mState = mSavedState; +#ifdef DEBUG + mRestored = true; +#endif + } + ~AutoSaveRestore() { Restore(); } + + void Clear() { + NS_ASSERTION(!mRestored, "Already restored!"); + mState.Clear(); +#ifdef DEBUG + mClipUsed = false; +#endif + } + + void SetClipChainForContainingBlockDescendants( + const DisplayItemClipChain* aClipChain) { + mState.SetClipChainForContainingBlockDescendants(aClipChain); + } + + /** + * Intersects the given clip rect (with optional aRadii) with the current + * mClipContainingBlockDescendants and sets mClipContainingBlockDescendants to + * the result, stored in aClipOnStack. + */ + void ClipContainingBlockDescendants(const nsRect& aRect, + const nscoord* aRadii = nullptr) { + NS_ASSERTION(!mRestored, "Already restored!"); + NS_ASSERTION(!mClipUsed, "mClip already used"); +#ifdef DEBUG + mClipUsed = true; +#endif + mState.ClipContainingBlockDescendants(mBuilder, aRect, aRadii, mClipChain); + } + + void ClipContentDescendants(const nsRect& aRect, + const nscoord* aRadii = nullptr) { + NS_ASSERTION(!mRestored, "Already restored!"); + NS_ASSERTION(!mClipUsed, "mClip already used"); +#ifdef DEBUG + mClipUsed = true; +#endif + mState.ClipContentDescendants(mBuilder, aRect, aRadii, mClipChain); + } + + void ClipContentDescendants(const nsRect& aRect, const nsRect& aRoundedRect, + const nscoord* aRadii = nullptr) { + NS_ASSERTION(!mRestored, "Already restored!"); + NS_ASSERTION(!mClipUsed, "mClip already used"); +#ifdef DEBUG + mClipUsed = true; +#endif + mState.ClipContentDescendants(mBuilder, aRect, aRoundedRect, aRadii, + mClipChain); + } + + /** + * Clips containing-block descendants to the frame's content-box, + * taking border-radius into account. + * If aFlags contains ASSUME_DRAWING_RESTRICTED_TO_CONTENT_RECT then + * we assume display items will not draw outside the content rect, so + * clipping is only required if there is a border-radius. This is an + * optimization to reduce the amount of clipping required. + */ + void ClipContainingBlockDescendantsToContentBox( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, uint32_t aFlags = 0) { + NS_ASSERTION(!mRestored, "Already restored!"); + NS_ASSERTION(!mClipUsed, "mClip already used"); +#ifdef DEBUG + mClipUsed = true; +#endif + mState.ClipContainingBlockDescendantsToContentBox(aBuilder, aFrame, + mClipChain, aFlags); + } + + void SetClippedToDisplayPort() { mState.SetClippedToDisplayPort(); } + bool IsClippedToDisplayPort() const { + return mState.IsClippedToDisplayPort(); + } + + protected: + nsDisplayListBuilder* mBuilder; + DisplayListClipState& mState; + DisplayListClipState mSavedState; + DisplayItemClipChain mClipChain; +#ifdef DEBUG + bool mClipUsed; + bool mRestored; +#endif +}; + +class DisplayListClipState::AutoClipContainingBlockDescendantsToContentBox + : public AutoSaveRestore { + public: + AutoClipContainingBlockDescendantsToContentBox(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame, + uint32_t aFlags = 0) + : AutoSaveRestore(aBuilder) { +#ifdef DEBUG + mClipUsed = true; +#endif + mState.ClipContainingBlockDescendantsToContentBox(aBuilder, aFrame, + mClipChain, aFlags); + } +}; + +/** + * Do not use this outside of nsIFrame::BuildDisplayListForChild, use + * multiple AutoSaveRestores instead. We provide this class just to ensure + * BuildDisplayListForChild is as efficient as possible. + */ +class DisplayListClipState::AutoClipMultiple : public AutoSaveRestore { + public: + explicit AutoClipMultiple(nsDisplayListBuilder* aBuilder) + : AutoSaveRestore(aBuilder) +#ifdef DEBUG + , + mExtraClipUsed(false) +#endif + { + } + + /** + * Intersects the given clip rect (with optional aRadii) with the current + * mClipContainingBlockDescendants and sets mClipContainingBlockDescendants to + * the result, stored in aClipOnStack. + */ + void ClipContainingBlockDescendantsExtra(const nsRect& aRect, + const nscoord* aRadii) { + NS_ASSERTION(!mRestored, "Already restored!"); + NS_ASSERTION(!mExtraClipUsed, "mExtraClip already used"); +#ifdef DEBUG + mExtraClipUsed = true; +#endif + mState.ClipContainingBlockDescendants(mBuilder, aRect, aRadii, + mExtraClipChain); + } + + protected: + DisplayItemClipChain mExtraClipChain; +#ifdef DEBUG + bool mExtraClipUsed; +#endif +}; + +} // namespace mozilla + +#endif /* DISPLAYLISTCLIPSTATE_H_ */ diff --git a/layout/painting/DottedCornerFinder.cpp b/layout/painting/DottedCornerFinder.cpp new file mode 100644 index 0000000000..49a09ebaf6 --- /dev/null +++ b/layout/painting/DottedCornerFinder.cpp @@ -0,0 +1,538 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "DottedCornerFinder.h" + +#include <utility> + +#include "BorderCache.h" +#include "BorderConsts.h" + +namespace mozilla { + +using namespace gfx; + +static inline Float Square(Float x) { return x * x; } + +static Point PointRotateCCW90(const Point& aP) { return Point(aP.y, -aP.x); } + +struct BestOverlap { + Float overlap; + size_t count; + + BestOverlap() : overlap(0.0f), count(0) {} + + BestOverlap(Float aOverlap, size_t aCount) + : overlap(aOverlap), count(aCount) {} +}; + +static const size_t DottedCornerCacheSize = 256; +nsDataHashtable<FourFloatsHashKey, BestOverlap> DottedCornerCache; + +DottedCornerFinder::DottedCornerFinder(const Bezier& aOuterBezier, + const Bezier& aInnerBezier, + Corner aCorner, Float aBorderRadiusX, + Float aBorderRadiusY, const Point& aC0, + Float aR0, const Point& aCn, Float aRn, + const Size& aCornerDim) + : mOuterBezier(aOuterBezier), + mInnerBezier(aInnerBezier), + mCorner(aCorner), + mNormalSign((aCorner == C_TL || aCorner == C_BR) ? -1.0f : 1.0f), + mC0(aC0), + mCn(aCn), + mR0(aR0), + mRn(aRn), + mMaxR(std::max(aR0, aRn)), + mCenterCurveOrigin(mC0.x, mCn.y), + mCenterCurveR(0.0), + mInnerCurveOrigin(mInnerBezier.mPoints[0].x, mInnerBezier.mPoints[3].y), + mBestOverlap(0.0f), + mHasZeroBorderWidth(false), + mHasMore(true), + mMaxCount(aCornerDim.width + aCornerDim.height), + mType(OTHER), + mI(0), + mCount(0) { + NS_ASSERTION(mR0 > 0.0f || mRn > 0.0f, + "At least one side should have non-zero radius."); + + mInnerWidth = fabs(mInnerBezier.mPoints[0].x - mInnerBezier.mPoints[3].x); + mInnerHeight = fabs(mInnerBezier.mPoints[0].y - mInnerBezier.mPoints[3].y); + + DetermineType(aBorderRadiusX, aBorderRadiusY); + + Reset(); +} + +static bool IsSingleCurve(Float aMinR, Float aMaxR, Float aMinBorderRadius, + Float aMaxBorderRadius) { + return aMinR > 0.0f && aMinBorderRadius > aMaxR * 4.0f && + aMinBorderRadius / aMaxBorderRadius > 0.5f; +} + +void DottedCornerFinder::DetermineType(Float aBorderRadiusX, + Float aBorderRadiusY) { + // Calculate parameters for the center curve before swap. + Float centerCurveWidth = fabs(mC0.x - mCn.x); + Float centerCurveHeight = fabs(mC0.y - mCn.y); + Point cornerPoint(mCn.x, mC0.y); + + bool swapped = false; + if (mR0 < mRn) { + // Always draw from wider side to thinner side. + std::swap(mC0, mCn); + std::swap(mR0, mRn); + std::swap(mInnerBezier.mPoints[0], mInnerBezier.mPoints[3]); + std::swap(mInnerBezier.mPoints[1], mInnerBezier.mPoints[2]); + std::swap(mOuterBezier.mPoints[0], mOuterBezier.mPoints[3]); + std::swap(mOuterBezier.mPoints[1], mOuterBezier.mPoints[2]); + mNormalSign = -mNormalSign; + swapped = true; + } + + // See the comment at mType declaration for each condition. + + Float minR = std::min(mR0, mRn); + Float minBorderRadius = std::min(aBorderRadiusX, aBorderRadiusY); + Float maxBorderRadius = std::max(aBorderRadiusX, aBorderRadiusY); + if (IsSingleCurve(minR, mMaxR, minBorderRadius, maxBorderRadius)) { + if (mR0 == mRn) { + Float borderLength; + if (minBorderRadius == maxBorderRadius) { + mType = PERFECT; + borderLength = M_PI * centerCurveHeight / 2.0f; + + mCenterCurveR = centerCurveWidth; + } else { + mType = SINGLE_CURVE_AND_RADIUS; + borderLength = + GetQuarterEllipticArcLength(centerCurveWidth, centerCurveHeight); + } + + Float diameter = mR0 * 2.0f; + size_t count = round(borderLength / diameter); + if (count % 2) { + count++; + } + mCount = count / 2 - 1; + if (mCount > 0) { + mBestOverlap = 1.0f - borderLength / (diameter * count); + } + } else { + mType = SINGLE_CURVE; + } + } + + if (mType == SINGLE_CURVE_AND_RADIUS || mType == SINGLE_CURVE) { + Size cornerSize(centerCurveWidth, centerCurveHeight); + GetBezierPointsForCorner(&mCenterBezier, mCorner, cornerPoint, cornerSize); + if (swapped) { + std::swap(mCenterBezier.mPoints[0], mCenterBezier.mPoints[3]); + std::swap(mCenterBezier.mPoints[1], mCenterBezier.mPoints[2]); + } + } + + if (minR == 0.0f) { + mHasZeroBorderWidth = true; + } + + if ((mType == SINGLE_CURVE || mType == OTHER) && !mHasZeroBorderWidth) { + FindBestOverlap(minR, minBorderRadius, maxBorderRadius); + } +} + +bool DottedCornerFinder::HasMore(void) const { + if (mHasZeroBorderWidth) { + return mI < mMaxCount && mHasMore; + } + + return mI < mCount; +} + +DottedCornerFinder::Result DottedCornerFinder::Next(void) { + mI++; + + if (mType == PERFECT) { + Float phi = mI * 4.0f * mR0 * (1 - mBestOverlap) / mCenterCurveR; + if (mCorner == C_TL) { + phi = -M_PI / 2.0f - phi; + } else if (mCorner == C_TR) { + phi = -M_PI / 2.0f + phi; + } else if (mCorner == C_BR) { + phi = M_PI / 2.0f - phi; + } else { + phi = M_PI / 2.0f + phi; + } + + Point C(mCenterCurveOrigin.x + mCenterCurveR * cos(phi), + mCenterCurveOrigin.y + mCenterCurveR * sin(phi)); + return DottedCornerFinder::Result(C, mR0); + } + + // Find unfilled and filled circles. + (void)FindNext(mBestOverlap); + if (mHasMore) { + (void)FindNext(mBestOverlap); + } + + return Result(mLastC, mLastR); +} + +void DottedCornerFinder::Reset(void) { + mLastC = mC0; + mLastR = mR0; + mLastT = 0.0f; + mHasMore = true; +} + +void DottedCornerFinder::FindPointAndRadius(Point& C, Float& r, + const Point& innerTangent, + const Point& normal, Float t) { + // Find radius for the given tangent point on the inner curve such that the + // circle is also tangent to the outer curve. + + NS_ASSERTION(mType == OTHER, "Wrong mType"); + + Float lower = 0.0f; + Float upper = mMaxR; + const Float DIST_MARGIN = 0.1f; + for (size_t i = 0; i < MAX_LOOP; i++) { + r = (upper + lower) / 2.0f; + C = innerTangent + normal * r; + + Point Near = FindBezierNearestPoint(mOuterBezier, C, t); + Float distSquare = (C - Near).LengthSquare(); + + if (distSquare > Square(r + DIST_MARGIN)) { + lower = r; + } else if (distSquare < Square(r - DIST_MARGIN)) { + upper = r; + } else { + break; + } + } +} + +Float DottedCornerFinder::FindNext(Float overlap) { + Float lower = mLastT; + Float upper = 1.0f; + Float t; + + Point C = mLastC; + Float r = 0.0f; + + Float factor = (1.0f - overlap); + + Float circlesDist = 0.0f; + Float expectedDist = 0.0f; + + const Float DIST_MARGIN = 0.1f; + if (mType == SINGLE_CURVE_AND_RADIUS) { + r = mR0; + + expectedDist = (r + mLastR) * factor; + + // Find C_i on the center curve. + for (size_t i = 0; i < MAX_LOOP; i++) { + t = (upper + lower) / 2.0f; + C = GetBezierPoint(mCenterBezier, t); + + // Check overlap along arc. + circlesDist = GetBezierLength(mCenterBezier, mLastT, t); + if (circlesDist < expectedDist - DIST_MARGIN) { + lower = t; + } else if (circlesDist > expectedDist + DIST_MARGIN) { + upper = t; + } else { + break; + } + } + } else if (mType == SINGLE_CURVE) { + // Find C_i on the center curve, and calculate r_i. + for (size_t i = 0; i < MAX_LOOP; i++) { + t = (upper + lower) / 2.0f; + C = GetBezierPoint(mCenterBezier, t); + + Point Diff = GetBezierDifferential(mCenterBezier, t); + Float DiffLength = Diff.Length(); + if (DiffLength == 0.0f) { + // Basically this shouldn't happen. + // If differential is 0, we cannot calculate tangent circle, + // skip this point. + t = (t + upper) / 2.0f; + continue; + } + + Point normal = PointRotateCCW90(Diff / DiffLength) * (-mNormalSign); + r = CalculateDistanceToEllipticArc(C, normal, mInnerCurveOrigin, + mInnerWidth, mInnerHeight); + + // Check overlap along arc. + circlesDist = GetBezierLength(mCenterBezier, mLastT, t); + expectedDist = (r + mLastR) * factor; + if (circlesDist < expectedDist - DIST_MARGIN) { + lower = t; + } else if (circlesDist > expectedDist + DIST_MARGIN) { + upper = t; + } else { + break; + } + } + } else { + Float distSquareMax = Square(mMaxR * 3.0f); + Float circlesDistSquare = 0.0f; + + // Find C_i and r_i. + for (size_t i = 0; i < MAX_LOOP; i++) { + t = (upper + lower) / 2.0f; + Point innerTangent = GetBezierPoint(mInnerBezier, t); + if ((innerTangent - mLastC).LengthSquare() > distSquareMax) { + // It's clear that this tangent point is too far, skip it. + upper = t; + continue; + } + + Point Diff = GetBezierDifferential(mInnerBezier, t); + Float DiffLength = Diff.Length(); + if (DiffLength == 0.0f) { + // Basically this shouldn't happen. + // If differential is 0, we cannot calculate tangent circle, + // skip this point. + t = (t + upper) / 2.0f; + continue; + } + + Point normal = PointRotateCCW90(Diff / DiffLength) * mNormalSign; + FindPointAndRadius(C, r, innerTangent, normal, t); + + // Check overlap with direct distance. + circlesDistSquare = (C - mLastC).LengthSquare(); + expectedDist = (r + mLastR) * factor; + if (circlesDistSquare < Square(expectedDist - DIST_MARGIN)) { + lower = t; + } else if (circlesDistSquare > Square(expectedDist + DIST_MARGIN)) { + upper = t; + } else { + break; + } + } + + circlesDist = sqrt(circlesDistSquare); + } + + if (mHasZeroBorderWidth) { + // When calculating circle around r=0, it may result in wrong radius that + // is bigger than previous circle. Detect it and stop calculating. + const Float R_MARGIN = 0.1f; + if (mLastR < R_MARGIN && r > mLastR) { + mHasMore = false; + mLastR = 0.0f; + return 0.0f; + } + } + + mLastT = t; + mLastC = C; + mLastR = r; + + if (mHasZeroBorderWidth) { + const Float T_MARGIN = 0.001f; + if (mLastT >= 1.0f - T_MARGIN || + (mLastC - mCn).LengthSquare() < Square(mLastR)) { + mHasMore = false; + } + } + + if (expectedDist == 0.0f) { + return 0.0f; + } + + return 1.0f - circlesDist * factor / expectedDist; +} + +void DottedCornerFinder::FindBestOverlap(Float aMinR, Float aMinBorderRadius, + Float aMaxBorderRadius) { + // If overlap is not calculateable, find it with binary search, + // such that there exists i that C_i == C_n with the given overlap. + + FourFloats key(aMinR, mMaxR, aMinBorderRadius, aMaxBorderRadius); + BestOverlap best; + if (DottedCornerCache.Get(key, &best)) { + mCount = best.count; + mBestOverlap = best.overlap; + return; + } + + Float lower = 0.0f; + Float upper = 0.5f; + // Start from lower bound to find the minimum number of circles. + Float overlap = 0.0f; + mBestOverlap = overlap; + size_t targetCount = 0; + + const Float OVERLAP_MARGIN = 0.1f; + for (size_t j = 0; j < MAX_LOOP; j++) { + Reset(); + + size_t count; + Float actualOverlap; + if (!GetCountAndLastOverlap(overlap, &count, &actualOverlap)) { + if (j == 0) { + mCount = mMaxCount; + break; + } + } + + if (j == 0) { + if (count < 3 || (count == 3 && actualOverlap > 0.5f)) { + // |count == 3 && actualOverlap > 0.5f| means there could be + // a circle but it is too near from both ends. + // + // if actualOverlap == 0.0 + // 1 2 3 + // +-------+-------+-------+-------+ + // | ##### | ***** | ##### | ##### | + // |#######|*******|#######|#######| + // |###+###|***+***|###+###|###+###| + // |# C_0 #|* C_1 *|# C_2 #|# C_n #| + // | ##### | ***** | ##### | ##### | + // +-------+-------+-------+-------+ + // | + // V + // +-------+---+-------+---+-------+ + // | ##### | | ##### | | ##### | + // |#######| |#######| |#######| + // |###+###| |###+###| |###+###| Find the best overlap to place + // |# C_0 #| |# C_1 #| |# C_n #| C_1 at the middle of them + // | ##### | | ##### | | ##### | + // +-------+---+-------+---|-------+ + // + // if actualOverlap == 0.5 + // 1 2 3 + // +-------+-------+-------+---+ + // | ##### | ***** | ##### |## | + // |#######|*******|##### C_n #| + // |###+###|***+***|###+###+###| + // |# C_0 #|* C_1 *|# C_2 #|###| + // | ##### | ***** | ##### |## | + // +-------+-------+-------+---+ + // | + // V + // +-------+-+-------+-+-------+ + // | ##### | | ##### | | ##### | + // |#######| |#######| |#######| + // |###+###| |###+###| |###+###| Even if we place C_1 at the middle + // |# C_0 #| |# C_1 #| |# C_n #| of them, it's too near from them + // | ##### | | ##### | | ##### | + // +-------+-+-------+-|-------+ + // | + // V + // +-------+-----------+-------+ + // | ##### | | ##### | + // |#######| |#######| + // |###+###| |###+###| Do not draw any circle + // |# C_0 #| |# C_n #| + // | ##### | | ##### | + // +-------+-----------+-------+ + mCount = 0; + break; + } + + // targetCount should be 2n, as we're searching C_1 to C_n. + // + // targetCount = 4 + // mCount = 1 + // 1 2 3 4 + // +-------+-------+-------+-------+-------+ + // | ##### | ***** | ##### | ***** | ##### | + // |#######|*******|#######|*******|#######| + // |###+###|***+***|###+###|***+***|###+###| + // |# C_0 #|* C_1 *|# C_2 #|* C_3 *|# C_n #| + // | ##### | ***** | ##### | ***** | ##### | + // +-------+-------+-------+-------+-------+ + // 1 + // + // targetCount = 6 + // mCount = 2 + // 1 2 3 4 5 6 + // +-------+-------+-------+-------+-------+-------+-------+ + // | ##### | ***** | ##### | ***** | ##### | ***** | ##### | + // |#######|*******|#######|*******|#######|*******|#######| + // |###+###|***+***|###+###|***+***|###+###|***+***|###+###| + // |# C_0 #|* C_1 *|# C_2 #|* C_3 *|# C_4 #|* C_5 *|# C_n #| + // | ##### | ***** | ##### | ***** | ##### | ***** | ##### | + // +-------+-------+-------+-------+-------+-------+-------+ + // 1 2 + if (count % 2) { + targetCount = count + 1; + } else { + targetCount = count; + } + + mCount = targetCount / 2 - 1; + } + + if (count == targetCount) { + mBestOverlap = overlap; + + if (fabs(actualOverlap - overlap) < OVERLAP_MARGIN) { + break; + } + + // We started from upper bound, no need to update range when j == 0. + if (j > 0) { + if (actualOverlap > overlap) { + lower = overlap; + } else { + upper = overlap; + } + } + } else { + // |j == 0 && count != targetCount| means that |targetCount = count + 1|, + // and we started from upper bound, no need to update range when j == 0. + if (j > 0) { + if (count > targetCount) { + upper = overlap; + } else { + lower = overlap; + } + } + } + + overlap = (upper + lower) / 2.0f; + } + + if (DottedCornerCache.Count() > DottedCornerCacheSize) { + DottedCornerCache.Clear(); + } + DottedCornerCache.Put(key, BestOverlap(mBestOverlap, mCount)); +} + +bool DottedCornerFinder::GetCountAndLastOverlap(Float aOverlap, size_t* aCount, + Float* aActualOverlap) { + // Return the number of circles and the last circles' overlap for the + // given overlap. + + Reset(); + + const Float T_MARGIN = 0.001f; + const Float DIST_MARGIN = 0.1f; + const Float DIST_MARGIN_SQUARE = Square(DIST_MARGIN); + for (size_t i = 0; i < mMaxCount; i++) { + Float actualOverlap = FindNext(aOverlap); + if (mLastT >= 1.0f - T_MARGIN || + (mLastC - mCn).LengthSquare() < DIST_MARGIN_SQUARE) { + *aCount = i + 1; + *aActualOverlap = actualOverlap; + return true; + } + } + + return false; +} + +} // namespace mozilla diff --git a/layout/painting/DottedCornerFinder.h b/layout/painting/DottedCornerFinder.h new file mode 100644 index 0000000000..fd3a9e5d04 --- /dev/null +++ b/layout/painting/DottedCornerFinder.h @@ -0,0 +1,432 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_DottedCornerFinder_h_ +#define mozilla_DottedCornerFinder_h_ + +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/BezierUtils.h" +#include "gfxRect.h" + +namespace mozilla { + +// Calculate C_i and r_i for each filled/unfilled circles in dotted corner. +// Returns circle with C_{2j} and r_{2j} where 0 < 2j < n. +// +// ____-----------+ +// __---- ***** ###| +// __---- ********* ####| +// __--- ##### ***********#####| +// _-- ######### *****+*****#####+ C_0 +// _- ########### *** C_1****#####| +// / #####+##### ********* ####| +// / . ### C_2 ### ***** ###| +// | ######### ____-------+ +// | . #####____----- +// | __---- +// | . / +// | / +// | ***** | +// | ******* | +// |*********| +// |****+****| +// | C_{n-1} | +// | ******* | +// | ***** | +// | ##### | +// | ####### | +// |#########| +// +----+----+ +// C_n + +class DottedCornerFinder { + typedef mozilla::gfx::Bezier Bezier; + typedef mozilla::gfx::Float Float; + typedef mozilla::gfx::Point Point; + typedef mozilla::gfx::Size Size; + + public: + struct Result { + // Center point of dot and its radius. + Point C; + Float r; + + Result(const Point& aC, Float aR) : C(aC), r(aR) { MOZ_ASSERT(aR >= 0); } + }; + + // aBorderRadiusX + // aCornerDim.width + // |<----------------->| + // | | v + // --+-------------___---+-- + // ^ | __-- | | + // | | _- | | aR0 + // | | / aC0 +-- + // | | / | ^ + // | | | | + // aBorderRadiusY | | | __--+ + // aCornerDim.height | || _- + // | || / + // | | / + // | | | + // | | | + // | | | + // | | | + // v | aCn | + // --+----+----+ + // | | + // |<-->| + // aRn + // + // aCornerDim and (aBorderRadiusX, aBorderRadiusY) can be different when + // aBorderRadiusX is smaller than aRn*2 or + // aBorderRadiusY is smaller than aR0*2. + // + // aCornerDim.width + // |<----------------->| + // | | + // | aBorderRadiusX | + // |<--------->| | + // | | | + // -------------------+-------__--+-------+-- + // ^ ^ | _- | ^ + // | | | / | | + // | | | / | | + // | aBorderRadiusY | | | | | aR0 + // | | || | | + // | | | | | + // aCornerDim.height | v | | v + // | --+ aC0 +-- + // | | | + // | | | + // | | | + // | | | + // | | | + // v | aCn | + // -------------------+---------+---------+ + // | | + // |<------->| + // aRn + DottedCornerFinder(const Bezier& aOuterBezier, const Bezier& aInnerBezier, + mozilla::Corner aCorner, Float aBorderRadiusX, + Float aBorderRadiusY, const Point& aC0, Float aR0, + const Point& aCn, Float aRn, const Size& aCornerDim); + + bool HasMore(void) const; + Result Next(void); + + private: + static const size_t MAX_LOOP = 32; + + // Bezier control points for the outer curve, the inner curve, and a curve + // that center points of circles are on (center curve). + // + // ___---+ outer curve + // __-- | + // _- | + // / __---+ center curve + // / __-- | + // | / | + // | / __--+ inner curve + // | | _- + // | | / + // | | / + // | | | + // | | | + // | | | + // | | | + // | | | + // +----+----+ + Bezier mOuterBezier; + Bezier mInnerBezier; + Bezier mCenterBezier; + + mozilla::Corner mCorner; + + // Sign of the normal vector used in radius calculation, flipped depends on + // corner and start and end radii. + Float mNormalSign; + + // Center points and raii for start and end circles, mR0 >= mRn. + // mMaxR = max(mR0, mRn) + // + // v + // ___---+------ + // __-- #|# | mRn + // _- ##|## | + // / ##+## --- + // / mCn ^ + // | #|# + // | __--+ + // | _- + // | / + // | / + // | | + // | | + // | ##### | + // | ####### | + // |#########| + // +----+----+ + // |## mC0 ##| + // | ####### | + // | ##### | + // | | + // |<-->| + // + // mR0 + // + Point mC0; + Point mCn; + Float mR0; + Float mRn; + Float mMaxR; + + // Parameters for the center curve with perfect circle and the inner curve. + // The center curve doesn't necessarily share the origin with others. + // + // ___---+ + // __-- | + // _- | + // / __-+ | + // / __-- | + // | / | + // | / __--+-- + // | | _- | ^ + // | | / | | + // | | / | | + // | | | | | + // | | | | | mInnerHeight + // | | | | | + // | + | | | + // | | | v + // +---------+---------+ + // | | mInnerCurveOrigin + // |<------->| + // mInnerWidth + // + // ___---+ + // __-- + // _- + // / __-+ + // / __-- | + // | / | + // | / __--+ + // | | _- | + // | | / | + // | | / | + // | | | | + // | | | | + // | | | | + // | +--- | ------+ + // | | | | mCenterCurveOrigin + // + | + | + // | | + // | | + // | | + // | | + // |<---------->| + // mCenterCurveR + // + Point mCenterCurveOrigin; + Float mCenterCurveR; + Point mInnerCurveOrigin; + Float mInnerWidth; + Float mInnerHeight; + + Point mLastC; + Float mLastR; + Float mLastT; + + // Overlap between two circles. + // It uses arc length on PERFECT, SINGLE_CURVE_AND_RADIUS, and SINGLE_CURVE, + // and direct distance on OTHER. + Float mBestOverlap; + + // If one of border-widths is 0, do not calculate overlap, and draw circles + // until it reaches the other side or exceeds mMaxCount. + bool mHasZeroBorderWidth; + bool mHasMore; + + // The maximum number of filled/unfilled circles. + size_t mMaxCount; + + enum { + // radius.width + // |<----------------->| + // | | + // --+-------------___---+---- + // ^ | __-- #|# ^ + // | | _- ##|## | + // | | / ##+## | top-width + // | | / ##|## | + // | | | #|# v + // | | | __--+---- + // radius.height | || _- + // | || / + // | | / + // | | | + // | | | + // | | ##### | + // | | ####### | + // v |#########| + // --+----+----+ + // |#########| + // | ####### | + // | ##### | + // | | + // |<------->| + // left-width + + // * top-width == left-width + // * radius.width == radius.height + // * top-width < radius.width * 2 + // + // All circles has same radii and are on single perfect circle's arc. + // Overlap is known. + // + // Split the perfect circle's arc into 2n segments, each segment's length is + // top-width * (1 - overlap). Place each circle's center point C_i on each + // end of the segment, each circle's radius r_i is top-width / 2 + // + // ##### + // ####### + // perfect ######### + // circle's ___---+#### + // arc ##### __-- ## C_0 ## + // | #####_- ###|### + // | ####+#### ##|## + // | ##/C_i ## | + // | |###### | + // | | ##### | + // +->| | + // | | + // ##|## | + // ###|### | + // ####|#### | + // ####+-------------------+ + // ## C_n ## + // ####### + // ##### + PERFECT, + + // * top-width == left-width + // * 0.5 < radius.width / radius.height < 2.0 + // * top-width < min(radius.width, radius.height) * 2 + // + // All circles has same radii and are on single elliptic arc. + // Overlap is known. + // + // Split the elliptic arc into 2n segments, each segment's length is + // top-width * (1 - overlap). Place each circle's center point C_i on each + // end of the segment, each circle's radius r_i is top-width / 2 + // + // ##### + // ####### + // ##### ######### + // ####### ____----+#### + // elliptic ######__--- ## C_0 ## + // arc ##__+-### ###|### + // | / # C_i # ##|## + // +--> / ##### | + // | | + // ###|# | + // ###|### | + // ####|#### | + // ####+------------------------+ + // ## C_n ## + // ####### + // ##### + SINGLE_CURVE_AND_RADIUS, + + // * top-width != left-width + // * 0 < min(top-width, left-width) + // * 0.5 < radius.width / radius.height < 2.0 + // * max(top-width, left-width) < min(radius.width, radius.height) * 2 + // + // All circles are on single elliptic arc. + // Overlap is unknown. + // + // Place each circle's center point C_i on elliptic arc, each circle's + // radius r_i is the distance between the center point and the inner curve. + // The arc segment's length between C_i and C_{i-1} is + // (r_i + r_{i-1}) * (1 - overlap). + // + // outer curve + // / + // / + // / / center curve + // / ####### / + // /## /# + // +# / # + // /# / # + // / # C_i / # + // / # + # / + // / # / \ # / inner curve + // # / \ #/ + // # / r_i \+ + // #/ ##/ + // / ####### / + // / + SINGLE_CURVE, + + // Other cases. + // Circles are not on single elliptic arc. + // Overlap are unknown. + // + // Place tangent point innerTangent on the inner curve and find circle's + // center point C_i and radius r_i where the circle is also tangent to the + // outer curve. + // Distance between C_i and C_{i-1} is (r_i + r_{i-1}) * (1 - overlap). + // + // outer curve + // / + // / + // / + // / ####### + // /## ## + // +# # + // /# \ # + // / # \ # + // / # + # / + // / # C_i \ # / inner curve + // # \ #/ + // # r_i \+ + // ## ##/ innerTangent + // ####### / + // / + OTHER + } mType; + + size_t mI; + size_t mCount; + + // Determine mType from parameters. + void DetermineType(Float aBorderRadiusX, Float aBorderRadiusY); + + // Reset calculation. + void Reset(void); + + // Find radius for the given tangent point on the inner curve such that the + // circle is also tangent to the outer curve. + void FindPointAndRadius(Point& C, Float& r, const Point& innerTangent, + const Point& normal, Float t); + + // Find next dot. + Float FindNext(Float overlap); + + // Find mBestOverlap for parameters. + void FindBestOverlap(Float aMinR, Float aMinBorderRadius, + Float aMaxBorderRadius); + + // Fill corner with dots with given overlap, and return the number of dots + // and last two dots's overlap. + bool GetCountAndLastOverlap(Float aOverlap, size_t* aCount, + Float* aActualOverlap); +}; + +} // namespace mozilla + +#endif /* mozilla_DottedCornerFinder_h_ */ diff --git a/layout/painting/FrameLayerBuilder.cpp b/layout/painting/FrameLayerBuilder.cpp new file mode 100644 index 0000000000..1a5142d723 --- /dev/null +++ b/layout/painting/FrameLayerBuilder.cpp @@ -0,0 +1,7576 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "FrameLayerBuilder.h" + +#include <algorithm> +#include <deque> +#include <functional> +#include <utility> + +#include "ActiveLayerTracker.h" +#include "BasicLayers.h" +#include "GeckoProfiler.h" +#include "ImageContainer.h" +#include "ImageLayers.h" +#include "LayerTreeInvalidation.h" +#include "LayerUserData.h" +#include "Layers.h" +#include "MaskLayerImageCache.h" +#include "MatrixStack.h" +#include "TransformClipNode.h" +#include "UnitTransforms.h" +#include "Units.h" +#include "gfx2DGlue.h" +#include "gfxContext.h" +#include "gfxEnv.h" +#include "gfxUtils.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/DisplayPortUtils.h" +#include "mozilla/EffectCompositor.h" +#include "mozilla/LayerAnimationInfo.h" +#include "mozilla/LayerTimelineMarker.h" +#include "mozilla/LookAndFeel.h" +#include "mozilla/Maybe.h" +#include "mozilla/PerfStats.h" +#include "mozilla/PresShell.h" +#include "mozilla/ReverseIterator.h" +#include "mozilla/StaticPrefs_gfx.h" +#include "mozilla/StaticPrefs_layers.h" +#include "mozilla/StaticPrefs_layout.h" +#include "mozilla/SVGIntegrationUtils.h" +#include "mozilla/Unused.h" +#include "mozilla/dom/EffectsInfo.h" +#include "mozilla/dom/ProfileTimelineMarkerBinding.h" +#include "mozilla/dom/RemoteBrowser.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/Matrix.h" +#include "mozilla/gfx/Tools.h" +#include "mozilla/layers/ShadowLayers.h" +#include "mozilla/layers/TextureClient.h" +#include "mozilla/layers/TextureWrapperImage.h" +#include "mozilla/layers/WebRenderUserData.h" +#include "nsDisplayList.h" +#include "nsDocShell.h" +#include "nsIScrollableFrame.h" +#include "nsImageFrame.h" +#include "nsLayoutUtils.h" +#include "nsPresContext.h" +#include "nsPrintfCString.h" +#include "nsSubDocumentFrame.h" +#include "nsTransitionManager.h" + +using namespace mozilla::layers; +using namespace mozilla::gfx; +using mozilla::UniquePtr; +using mozilla::WrapUnique; + +// PaintedLayerData::mAssignedDisplayItems is a std::vector, which is +// non-memmovable +MOZ_DECLARE_RELOCATE_USING_MOVE_CONSTRUCTOR(mozilla::PaintedLayerData); + +namespace mozilla { + +class PaintedDisplayItemLayerUserData; + +static nsTHashtable<nsPtrHashKey<DisplayItemData>>* sAliveDisplayItemDatas; + +/** + * The address of gPaintedDisplayItemLayerUserData is used as the user + * data key for PaintedLayers created by FrameLayerBuilder. + * It identifies PaintedLayers used to draw non-layer content, which are + * therefore eligible for recycling. We want display items to be able to + * create their own dedicated PaintedLayers in BuildLayer, if necessary, + * and we wouldn't want to accidentally recycle those. + * The user data is a PaintedDisplayItemLayerUserData. + */ +uint8_t gPaintedDisplayItemLayerUserData; +/** + * The address of gColorLayerUserData is used as the user + * data key for ColorLayers created by FrameLayerBuilder. + * The user data is null. + */ +uint8_t gColorLayerUserData; +/** + * The address of gImageLayerUserData is used as the user + * data key for ImageLayers created by FrameLayerBuilder. + * The user data is null. + */ +uint8_t gImageLayerUserData; +/** + * The address of gLayerManagerUserData is used as the user + * data key for retained LayerManagers managed by FrameLayerBuilder. + * The user data is a LayerManagerData. + */ +uint8_t gLayerManagerUserData; +/** + * The address of gMaskLayerUserData is used as the user + * data key for mask layers managed by FrameLayerBuilder. + * The user data is a MaskLayerUserData. + */ +uint8_t gMaskLayerUserData; +/** + * The address of gCSSMaskLayerUserData is used as the user + * data key for mask layers of css masking managed by FrameLayerBuilder. + * The user data is a CSSMaskLayerUserData. + */ +uint8_t gCSSMaskLayerUserData; + +// a global cache of image containers used for mask layers +static MaskLayerImageCache* gMaskLayerImageCache = nullptr; + +static inline MaskLayerImageCache* GetMaskLayerImageCache() { + if (!gMaskLayerImageCache) { + gMaskLayerImageCache = new MaskLayerImageCache(); + } + + return gMaskLayerImageCache; +} + +struct InactiveLayerData { + RefPtr<layers::BasicLayerManager> mLayerManager; + RefPtr<layers::Layer> mLayer; + UniquePtr<layers::LayerProperties> mProps; + + ~InactiveLayerData(); +}; + +struct AssignedDisplayItem { + AssignedDisplayItem(nsPaintedDisplayItem* aItem, LayerState aLayerState, + DisplayItemData* aData, const nsRect& aContentRect, + DisplayItemEntryType aType, const bool aHasOpacity, + const RefPtr<TransformClipNode>& aTransform, + const bool aIsMerged); + AssignedDisplayItem(AssignedDisplayItem&& aRhs) = default; + + bool HasOpacity() const { return mHasOpacity; } + + bool HasTransform() const { return mTransform; } + + nsPaintedDisplayItem* mItem; + DisplayItemData* mDisplayItemData; + + /** + * If the display item is being rendered as an inactive + * layer, then this stores the layer manager being + * used for the inactive transaction. + */ + UniquePtr<InactiveLayerData> mInactiveLayerData; + RefPtr<TransformClipNode> mTransform; + + nsRect mContentRect; + LayerState mLayerState; + DisplayItemEntryType mType; + + bool mReused; + bool mMerged; + bool mHasOpacity; + bool mHasPaintRect; +}; + +struct DisplayItemEntry { + DisplayItemEntry(nsDisplayItem* aItem, DisplayItemEntryType aType) + : mItem(aItem), mType(aType) {} + + nsDisplayItem* mItem; + DisplayItemEntryType mType; +}; + +/** + * Returns true if the given |aType| is an effect start marker. + */ +static bool IsEffectStartMarker(DisplayItemEntryType aType) { + return aType == DisplayItemEntryType::PushOpacity || + aType == DisplayItemEntryType::PushOpacityWithBg || + aType == DisplayItemEntryType::PushTransform; +} + +/** + * Returns true if the given |aType| is an effect end marker. + */ +static bool IsEffectEndMarker(DisplayItemEntryType aType) { + return aType == DisplayItemEntryType::PopOpacity || + aType == DisplayItemEntryType::PopTransform; +} + +enum class MarkerType { StartMarker, EndMarker }; + +/** + * Returns true if the given nsDisplayOpacity |aItem| has had opacity applied + * to its children and can be flattened away. + */ +static bool IsOpacityAppliedToChildren(nsDisplayItem* aItem) { + MOZ_ASSERT(aItem->GetType() == DisplayItemType::TYPE_OPACITY); + return static_cast<nsDisplayOpacity*>(aItem)->OpacityAppliedToChildren(); +} + +/** + * Returns true if the given display item type supports flattening with markers. + */ +static bool SupportsFlatteningWithMarkers(const DisplayItemType& aType) { + return aType == DisplayItemType::TYPE_OPACITY || + aType == DisplayItemType::TYPE_TRANSFORM; +} + +/** + * Adds the effect marker to |aMarkers| based on the type of |aItem| and whether + * |markerType| is a start or end marker. + */ +template <MarkerType markerType> +static bool AddMarkerIfNeeded(nsDisplayItem* aItem, + std::deque<DisplayItemEntry>& aMarkers) { + const DisplayItemType type = aItem->GetType(); + if (!SupportsFlatteningWithMarkers(type)) { + return false; + } + + DisplayItemEntryType marker; + +// Just a fancy way to avoid writing two separate functions to select between +// PUSH and POP markers. This is done during compile time based on |markerType|. +#define GET_MARKER(start_marker, end_marker) \ + std::conditional< \ + markerType == MarkerType::StartMarker, \ + std::integral_constant<DisplayItemEntryType, start_marker>, \ + std::integral_constant<DisplayItemEntryType, end_marker>>::type::value; + + switch (type) { + case DisplayItemType::TYPE_OPACITY: + if (IsOpacityAppliedToChildren(aItem)) { + // TODO(miko): I am not a fan of this. The more correct solution would + // be to return an enum from nsDisplayItem::ShouldFlattenAway(), so that + // we could distinguish between different flattening methods and avoid + // entering this function when markers are not needed. + return false; + } + + marker = GET_MARKER(DisplayItemEntryType::PushOpacity, + DisplayItemEntryType::PopOpacity); + break; + case DisplayItemType::TYPE_TRANSFORM: + marker = GET_MARKER(DisplayItemEntryType::PushTransform, + DisplayItemEntryType::PopTransform); + break; + default: + MOZ_ASSERT_UNREACHABLE("Invalid display item type!"); + break; + } + + aMarkers.emplace_back(aItem, marker); + return true; +} + +DisplayItemData::DisplayItemData(LayerManagerData* aParent, uint32_t aKey, + Layer* aLayer, nsIFrame* aFrame) + + : mRefCnt(0), + mParent(aParent), + mLayer(aLayer), + mDisplayItemKey(aKey), + mItem(nullptr), + mUsed(true), + mIsInvalid(false), + mReusedItem(false) { + MOZ_COUNT_CTOR(DisplayItemData); + + if (!sAliveDisplayItemDatas) { + sAliveDisplayItemDatas = new nsTHashtable<nsPtrHashKey<DisplayItemData>>(); + } + MOZ_RELEASE_ASSERT(!sAliveDisplayItemDatas->Contains(this)); + sAliveDisplayItemDatas->PutEntry(this); + + MOZ_RELEASE_ASSERT(mLayer); + if (aFrame) { + AddFrame(aFrame); + } +} + +void DisplayItemData::Destroy() { + // Get the pres context. + RefPtr<nsPresContext> presContext = mFrameList[0]->PresContext(); + + // Call our destructor. + this->~DisplayItemData(); + + // Don't let the memory be freed, since it will be recycled + // instead. Don't call the global operator delete. + presContext->PresShell()->FreeByObjectID(eArenaObjectID_DisplayItemData, + this); +} + +void DisplayItemData::AddFrame(nsIFrame* aFrame) { + MOZ_RELEASE_ASSERT(mLayer); + MOZ_RELEASE_ASSERT(!mFrameList.Contains(aFrame)); + mFrameList.AppendElement(aFrame); + + SmallPointerArray<DisplayItemData>& array = aFrame->DisplayItemData(); + array.AppendElement(this); +} + +void DisplayItemData::RemoveFrame(nsIFrame* aFrame) { + MOZ_RELEASE_ASSERT(mLayer); + bool result = mFrameList.RemoveElement(aFrame); + MOZ_RELEASE_ASSERT(result, "Can't remove a frame that wasn't added!"); + + SmallPointerArray<DisplayItemData>& array = aFrame->DisplayItemData(); + array.RemoveElement(this); +} + +void DisplayItemData::EndUpdate() { + MOZ_RELEASE_ASSERT(mLayer); + mIsInvalid = false; + mUsed = false; + mReusedItem = false; + mOldTransform = nullptr; +} + +void DisplayItemData::EndUpdate(UniquePtr<nsDisplayItemGeometry>&& aGeometry) { + MOZ_RELEASE_ASSERT(mLayer); + MOZ_ASSERT(mItem); + MOZ_ASSERT(mGeometry || aGeometry); + + if (aGeometry) { + mGeometry = std::move(aGeometry); + } + mClip = mItem->GetClip(); + mChangedFrameInvalidations.SetEmpty(); + + EndUpdate(); +} + +void DisplayItemData::BeginUpdate(Layer* aLayer, LayerState aState, + bool aFirstUpdate, + nsPaintedDisplayItem* aItem /* = nullptr */) { + bool isReused = false; + bool isMerged = false; + + if (aItem) { + isReused = !aFirstUpdate ? aItem->IsReused() : false; + + const nsDisplayWrapList* wraplist = aItem->AsDisplayWrapList(); + isMerged = wraplist && wraplist->HasMergedFrames(); + } + + BeginUpdate(aLayer, aState, aItem, isReused, isMerged); +} + +void DisplayItemData::BeginUpdate(Layer* aLayer, LayerState aState, + nsPaintedDisplayItem* aItem, bool aIsReused, + bool aIsMerged) { + MOZ_RELEASE_ASSERT(mLayer); + MOZ_RELEASE_ASSERT(aLayer); + mLayer = aLayer; + mOptLayer = nullptr; + mInactiveManager = nullptr; + mLayerState = aState; + mUsed = true; + + if (aLayer->AsPaintedLayer()) { + if (aItem != mItem) { + aItem->SetDisplayItemData(this, aLayer->Manager()); + } else { + MOZ_ASSERT(aItem->GetDisplayItemData() == this); + } + mReusedItem = aIsReused; + } + + if (!aItem) { + return; + } + + if (!aIsMerged && mFrameList.Length() == 1) { + MOZ_ASSERT(mFrameList[0] == aItem->Frame()); + return; + } + + // We avoid adding or removing element unnecessarily + // since we have to modify userdata each time + CopyableAutoTArray<nsIFrame*, 4> copy(mFrameList); + if (!copy.RemoveElement(aItem->Frame())) { + AddFrame(aItem->Frame()); + mChangedFrameInvalidations.Or(mChangedFrameInvalidations, + aItem->Frame()->InkOverflowRect()); + } + + if (aIsMerged) { + MOZ_ASSERT(aItem->AsDisplayWrapList()); + + for (nsIFrame* frame : aItem->AsDisplayWrapList()->GetMergedFrames()) { + if (!copy.RemoveElement(frame)) { + AddFrame(frame); + mChangedFrameInvalidations.Or(mChangedFrameInvalidations, + frame->InkOverflowRect()); + } + } + } + + for (nsIFrame* frame : copy) { + RemoveFrame(frame); + mChangedFrameInvalidations.Or(mChangedFrameInvalidations, + frame->InkOverflowRect()); + } +} + +static const nsIFrame* sDestroyedFrame = nullptr; +DisplayItemData::~DisplayItemData() { + MOZ_COUNT_DTOR(DisplayItemData); + + if (mItem) { + MOZ_ASSERT(mItem->GetDisplayItemData() == this); + mItem->SetDisplayItemData(nullptr, nullptr); + } + + for (uint32_t i = 0; i < mFrameList.Length(); i++) { + nsIFrame* frame = mFrameList[i]; + if (frame == sDestroyedFrame) { + continue; + } + + SmallPointerArray<DisplayItemData>& array = frame->DisplayItemData(); + array.RemoveElement(this); + } + + MOZ_RELEASE_ASSERT(sAliveDisplayItemDatas); + nsPtrHashKey<mozilla::DisplayItemData>* entry = + sAliveDisplayItemDatas->GetEntry(this); + MOZ_RELEASE_ASSERT(entry); + + sAliveDisplayItemDatas->RemoveEntry(entry); + + if (sAliveDisplayItemDatas->Count() == 0) { + delete sAliveDisplayItemDatas; + sAliveDisplayItemDatas = nullptr; + } +} + +void DisplayItemData::NotifyRemoved() { + if (mDisplayItemKey > static_cast<uint8_t>(DisplayItemType::TYPE_MAX)) { + // This is sort of a hack. The display item key has higher bits set, which + // means that it is not the only display item for the frame. + // This branch skips separator transforms. + return; + } + + const DisplayItemType type = GetDisplayItemTypeFromKey(mDisplayItemKey); + + if (type == DisplayItemType::TYPE_REMOTE) { + // TYPE_REMOTE doesn't support merging, so access it directly + MOZ_ASSERT(mFrameList.Length() == 1); + if (mFrameList.Length() != 1) { + return; + } + + // This is a remote browser that is going away, notify it that it is now + // hidden + nsIFrame* frame = mFrameList[0]; + nsSubDocumentFrame* subdoc = static_cast<nsSubDocumentFrame*>(frame); + nsFrameLoader* frameLoader = subdoc->FrameLoader(); + if (frameLoader && frameLoader->GetRemoteBrowser()) { + frameLoader->GetRemoteBrowser()->UpdateEffects( + mozilla::dom::EffectsInfo::FullyHidden()); + } + } + + if (type != DisplayItemType::TYPE_TRANSFORM && + type != DisplayItemType::TYPE_OPACITY && + type != DisplayItemType::TYPE_BACKGROUND_COLOR) { + return; + } + + for (nsIFrame* frame : mFrameList) { + EffectCompositor::ClearIsRunningOnCompositor(frame, type); + } +} + +const nsRegion& DisplayItemData::GetChangedFrameInvalidations() { + return mChangedFrameInvalidations; +} + +DisplayItemData* DisplayItemData::AssertDisplayItemData( + DisplayItemData* aData) { + MOZ_RELEASE_ASSERT(aData); + MOZ_RELEASE_ASSERT(sAliveDisplayItemDatas && + sAliveDisplayItemDatas->Contains(aData)); + MOZ_RELEASE_ASSERT(aData->mLayer); + return aData; +} + +void* DisplayItemData::operator new(size_t sz, nsPresContext* aPresContext) { + // Check the recycle list first. + return aPresContext->PresShell()->AllocateByObjectID( + eArenaObjectID_DisplayItemData, sz); +} + +/** + * This is the userdata we associate with a layer manager. + */ +class LayerManagerData : public LayerUserData { + public: + explicit LayerManagerData(LayerManager* aManager) + : mLayerManager(aManager), +#ifdef DEBUG_DISPLAY_ITEM_DATA + mParent(nullptr), +#endif + mInvalidateAllLayers(false) { + MOZ_COUNT_CTOR(LayerManagerData); + } + ~LayerManagerData() override { MOZ_COUNT_DTOR(LayerManagerData); } + +#ifdef DEBUG_DISPLAY_ITEM_DATA + void Dump(const char* aPrefix = "") { + printf_stderr("%sLayerManagerData %p\n", aPrefix, this); + + for (auto& data : mDisplayItems) { + nsAutoCString prefix; + prefix += aPrefix; + prefix += " "; + + const char* layerState; + switch (data->mLayerState) { + case LayerState::LAYER_NONE: + layerState = "LAYER_NONE"; + break; + case LayerState::LAYER_INACTIVE: + layerState = "LAYER_INACTIVE"; + break; + case LayerState::LAYER_ACTIVE: + layerState = "LAYER_ACTIVE"; + break; + case LayerState::LAYER_ACTIVE_FORCE: + layerState = "LAYER_ACTIVE_FORCE"; + break; + case LayerState::LAYER_ACTIVE_EMPTY: + layerState = "LAYER_ACTIVE_EMPTY"; + break; + case LayerState::LAYER_SVG_EFFECTS: + layerState = "LAYER_SVG_EFFECTS"; + break; + } + uint32_t mask = (1 << TYPE_BITS) - 1; + + nsAutoCString str; + str += prefix; + str += nsPrintfCString("Frame %p ", data->mFrameList[0]); + str += nsDisplayItem::DisplayItemTypeName( + static_cast<nsDisplayItem::Type>(data->mDisplayItemKey & mask)); + if ((data->mDisplayItemKey >> TYPE_BITS)) { + str += nsPrintfCString("(%i)", data->mDisplayItemKey >> TYPE_BITS); + } + str += nsPrintfCString(", %s, Layer %p", layerState, data->mLayer.get()); + if (data->mOptLayer) { + str += nsPrintfCString(", OptLayer %p", data->mOptLayer.get()); + } + if (data->mInactiveManager) { + str += nsPrintfCString(", InactiveLayerManager %p", + data->mInactiveManager.get()); + } + str += "\n"; + + printf_stderr("%s", str.get()); + + if (data->mInactiveManager) { + prefix += " "; + printf_stderr("%sDumping inactive layer info:\n", prefix.get()); + LayerManagerData* lmd = static_cast<LayerManagerData*>( + data->mInactiveManager->GetUserData(&gLayerManagerUserData)); + lmd->Dump(prefix.get()); + } + } + } +#endif + + /** + * Tracks which frames have layers associated with them. + */ + LayerManager* mLayerManager; +#ifdef DEBUG_DISPLAY_ITEM_DATA + LayerManagerData* mParent; +#endif + std::vector<RefPtr<DisplayItemData>> mDisplayItems; + bool mInvalidateAllLayers; +}; + +/* static */ +void FrameLayerBuilder::DestroyDisplayItemDataFor(nsIFrame* aFrame) { + RemoveFrameFromLayerManager(aFrame, aFrame->DisplayItemData()); + aFrame->DisplayItemData().Clear(); + + // Destroying a WebRenderUserDataTable can cause destruction of other objects + // which can remove frame properties in their destructor. If we delete a frame + // property it runs the destructor of the stored object in the middle of + // updating the frame property table, so if the destruction of that object + // causes another update to the frame property table it would leave the frame + // property table in an inconsistent state. So we remove it from the table and + // then destroy it. (bug 1530657) + WebRenderUserDataTable* userDataTable = + aFrame->TakeProperty(WebRenderUserDataProperty::Key()); + if (userDataTable) { + for (auto iter = userDataTable->Iter(); !iter.Done(); iter.Next()) { + iter.UserData()->RemoveFromTable(); + } + delete userDataTable; + } +} + +/** + * We keep a stack of these to represent the PaintedLayers that are + * currently available to have display items added to. + * We use a stack here because as much as possible we want to + * assign display items to existing PaintedLayers, and to the lowest + * PaintedLayer in z-order. This reduces the number of layers and + * makes it more likely a display item will be rendered to an opaque + * layer, giving us the best chance of getting subpixel AA. + */ +class PaintedLayerData { + public: + PaintedLayerData() + : mAnimatedGeometryRoot(nullptr), + mASR(nullptr), + mClipChain(nullptr), + mReferenceFrame(nullptr), + mLayer(nullptr), + mSolidColor(NS_RGBA(0, 0, 0, 0)), + mIsSolidColorInVisibleRegion(false), + mNeedComponentAlpha(false), + mForceTransparentSurface(false), + mHideAllLayersBelow(false), + mOpaqueForAnimatedGeometryRootParent(false), + mBackfaceHidden(false), + mDTCRequiresTargetConfirmation(false), + mImage(nullptr), + mItemClip(nullptr), + mNewChildLayersIndex(-1) +#ifdef DEBUG + , + mTransformLevel(0) +#endif + { + } + + PaintedLayerData(PaintedLayerData&& aRhs) = default; + + ~PaintedLayerData() { MOZ_ASSERT(mTransformLevel == 0); } + +#ifdef MOZ_DUMP_PAINTING + /** + * Keep track of important decisions for debugging. + */ + nsCString mLog; + +# define FLB_LOG_PAINTED_LAYER_DECISION(pld, ...) \ + if (StaticPrefs::layers_dump_decision()) { \ + pld->mLog.AppendPrintf("\t\t\t\t"); \ + pld->mLog.AppendPrintf(__VA_ARGS__); \ + } +#else +# define FLB_LOG_PAINTED_LAYER_DECISION(...) +#endif + + /** + * Disables component alpha for |aItem| if the component alpha bounds are not + * contained in |mOpaqueRegion|. Alternatively if possible, sets + * |mNeedComponentAlpha| to true for this PaintedLayerData. + */ + bool SetupComponentAlpha(ContainerState* aState, nsPaintedDisplayItem* aItem, + const nsIntRect& aVisibleRect, + const TransformClipNode* aTransform); + + /** + * Record that an item has been added to the PaintedLayer, so we + * need to update our regions. + * @param aVisibleRect the area of the item that's visible + */ + void Accumulate(ContainerState* aState, nsPaintedDisplayItem* aItem, + const nsIntRect& aVisibleRect, const nsRect& aContentRect, + const DisplayItemClip& aClip, LayerState aLayerState, + nsDisplayList* aList, DisplayItemEntryType aType, + nsTArray<size_t>& aOpacityIndices, + const RefPtr<TransformClipNode>& aTransform); + + UniquePtr<InactiveLayerData> CreateInactiveLayerData( + ContainerState* aState, nsPaintedDisplayItem* aItem, + DisplayItemData* aData); + + /** + * Updates the status of |mTransform| and |aOpacityIndices|, based on |aType|. + */ + void UpdateEffectStatus(DisplayItemEntryType aType, + nsTArray<size_t>& aOpacityIndices); + + AnimatedGeometryRoot* GetAnimatedGeometryRoot() { + return mAnimatedGeometryRoot; + } + + /** + * A region including the horizontal pan, vertical pan, and no action regions. + */ + nsRegion CombinedTouchActionRegion(); + + /** + * Add the given hit test info to the hit regions for this PaintedLayer. + */ + void AccumulateHitTestItem(ContainerState* aState, nsDisplayItem* aItem, + const DisplayItemClip& aClip, + TransformClipNode* aTransform); + + void HitRegionsUpdated(); + + /** + * If this represents only a nsDisplayImage, and the image type supports being + * optimized to an ImageLayer, returns true. + */ + bool CanOptimizeToImageLayer(nsDisplayListBuilder* aBuilder); + + /** + * If this represents only a nsDisplayImage, and the image type supports being + * optimized to an ImageLayer, returns an ImageContainer for the underlying + * image if one is available. + */ + already_AddRefed<ImageContainer> GetContainerForImageLayer( + nsDisplayListBuilder* aBuilder); + + bool VisibleAboveRegionIntersects(const nsIntRegion& aRegion) const { + return !mVisibleAboveRegion.Intersect(aRegion).IsEmpty(); + } + bool VisibleRegionIntersects(const nsIntRegion& aRegion) const { + return !mVisibleRegion.Intersect(aRegion).IsEmpty(); + } + + /** + * The owning ContainerState that created this PaintedLayerData. + */ + ContainerState* mState; + + /** + * The region of visible content in the layer, relative to the + * container layer (which is at the snapped top-left of the display + * list reference frame). + */ + nsIntRegion mVisibleRegion; + /** + * The region of visible content in the layer that is opaque. + * Same coordinate system as mVisibleRegion. + */ + nsIntRegion mOpaqueRegion; + /** + * The definitely-hit region for this PaintedLayer. + */ + nsRegion mHitRegion; + /** + * The maybe-hit region for this PaintedLayer. + */ + nsRegion mMaybeHitRegion; + /** + * The dispatch-to-content hit region for this PaintedLayer. + */ + nsRegion mDispatchToContentHitRegion; + /** + * The region for this PaintedLayer that is sensitive to events + * but disallows panning and zooming. This is an approximation + * and any deviation from the true region will be part of the + * mDispatchToContentHitRegion. + */ + nsRegion mNoActionRegion; + /** + * The region for this PaintedLayer that is sensitive to events and + * allows horizontal panning but not zooming. This is an approximation + * and any deviation from the true region will be part of the + * mDispatchToContentHitRegion. + */ + nsRegion mHorizontalPanRegion; + /** + * The region for this PaintedLayer that is sensitive to events and + * allows vertical panning but not zooming. This is an approximation + * and any deviation from the true region will be part of the + * mDispatchToContentHitRegion. + */ + nsRegion mVerticalPanRegion; + + bool mCollapsedTouchActions = false; + /** + * Scaled versions of the bounds of mHitRegion and mMaybeHitRegion. + * We store these because FindPaintedLayerFor() needs to consume them + * in this form, and it's a hot code path so we don't want to scale + * them inside that function. + */ + nsIntRect mScaledHitRegionBounds; + nsIntRect mScaledMaybeHitRegionBounds; + /** + * The "active scrolled root" for all content in the layer. Must + * be non-null; all content in a PaintedLayer must have the same + * active scrolled root. + */ + AnimatedGeometryRoot* mAnimatedGeometryRoot; + const ActiveScrolledRoot* mASR; + /** + * The chain of clips that should apply to this layer. + */ + const DisplayItemClipChain* mClipChain; + /** + * The offset between mAnimatedGeometryRoot and the reference frame. + */ + nsPoint mAnimatedGeometryRootOffset; + /** + * If non-null, the frame from which we'll extract "fixed positioning" + * metadata for this layer. This can be a position:fixed frame or a viewport + * frame; the latter case is used for background-attachment:fixed content. + */ + const nsIFrame* mReferenceFrame; + PaintedLayer* mLayer; + /** + * If mIsSolidColorInVisibleRegion is true, this is the color of the visible + * region. + */ + nscolor mSolidColor; + /** + * True if every pixel in mVisibleRegion will have color mSolidColor. + */ + bool mIsSolidColorInVisibleRegion; + /** + * True if there is any text visible in the layer that's over + * transparent pixels in the layer. + */ + bool mNeedComponentAlpha; + /** + * Set if the layer should be treated as transparent, even if its entire + * area is covered by opaque display items. For example, this needs to + * be set if something is going to "punch holes" in the layer by clearing + * part of its surface. + */ + bool mForceTransparentSurface; + /** + * Set if all layers below this PaintedLayer should be hidden. + */ + bool mHideAllLayersBelow; + /** + * Set if the opaque region for this layer can be applied to the parent + * animated geometry root of this layer's animated geometry root. + * We set this when a PaintedLayer's animated geometry root is a scrollframe + * and the PaintedLayer completely fills the displayport of the scrollframe. + */ + bool mOpaqueForAnimatedGeometryRootParent; + /** + * Set if the backface of this region is hidden to the user. + * Content that backface is hidden should not be draw on the layer + * with visible backface. + */ + bool mBackfaceHidden; + /** + * Set to true if events targeting the dispatch-to-content region + * require target confirmation. + * See CompositorHitTestFlags::eRequiresTargetConfirmation and + * EventRegions::mDTCRequiresTargetConfirmation. + */ + bool mDTCRequiresTargetConfirmation; + /** + * Stores the pointer to the nsDisplayImage if we want to + * convert this to an ImageLayer. + */ + nsDisplayImageContainer* mImage; + /** + * Stores the clip that we need to apply to the image or, if there is no + * image, a clip for SOME item in the layer. There is no guarantee which + * item's clip will be stored here and mItemClip should not be used to clip + * the whole layer - only some part of the clip should be used, as determined + * by PaintedDisplayItemLayerUserData::GetCommonClipCount() - which may even + * be no part at all. + */ + const DisplayItemClip* mItemClip; + /** + * Index of this layer in mNewChildLayers. + */ + int32_t mNewChildLayersIndex; + /** + * The region of visible content above the layer and below the + * next PaintedLayerData currently in the stack, if any. + * This is a conservative approximation: it contains the true region. + */ + nsIntRegion mVisibleAboveRegion; + /** + * All the display items that have been assigned to this painted layer. + * These items get added by Accumulate(). + */ + std::vector<AssignedDisplayItem> mAssignedDisplayItems; + +#ifdef DEBUG + /** + * Tracks the level of transform to ensure balanced PUSH/POP markers. + */ + int mTransformLevel; +#endif +}; + +struct NewLayerEntry { + NewLayerEntry() + : mAnimatedGeometryRoot(nullptr), + mASR(nullptr), + mClipChain(nullptr), + mScrollMetadataASR(nullptr), + mLayerContentsVisibleRect(0, 0, -1, -1), + mLayerState(LayerState::LAYER_INACTIVE), + mHideAllLayersBelow(false), + mOpaqueForAnimatedGeometryRootParent(false), + mUntransformedVisibleRegion(false), + mIsFixedToRootScrollFrame(false) {} + // mLayer is null if the previous entry is for a PaintedLayer that hasn't + // been optimized to some other form (yet). + RefPtr<Layer> mLayer; + AnimatedGeometryRoot* mAnimatedGeometryRoot; + const ActiveScrolledRoot* mASR; + const DisplayItemClipChain* mClipChain; + const ActiveScrolledRoot* mScrollMetadataASR; + // If non-null, this ScrollMetadata is set to the be the first ScrollMetadata + // on the layer. + UniquePtr<ScrollMetadata> mBaseScrollMetadata; + // The following are only used for retained layers (for occlusion + // culling of those layers). These regions are all relative to the + // container reference frame. + nsIntRegion mVisibleRegion; + nsIntRegion mOpaqueRegion; + // This rect is in the layer's own coordinate space. The computed visible + // region for the layer cannot extend beyond this rect. + nsIntRect mLayerContentsVisibleRect; + LayerState mLayerState; + bool mHideAllLayersBelow; + // When mOpaqueForAnimatedGeometryRootParent is true, the opaque region of + // this layer is opaque in the same position even subject to the animation of + // geometry of mAnimatedGeometryRoot. For example when mAnimatedGeometryRoot + // is a scrolled frame and the scrolled content is opaque everywhere in the + // displayport, we can set this flag. + // When this flag is set, we can treat this opaque region as covering + // content whose animated geometry root is the animated geometry root for + // mAnimatedGeometryRoot->GetParent(). + bool mOpaqueForAnimatedGeometryRootParent; + + // mVisibleRegion is relative to the associated frame before + // transform. + bool mUntransformedVisibleRegion; + bool mIsFixedToRootScrollFrame; +}; + +class PaintedLayerDataTree; + +/** + * This is tree node type for PaintedLayerDataTree. + * Each node corresponds to a different animated geometry root, and contains + * a stack of PaintedLayerDatas, in bottom-to-top order. + * There is at most one node per animated geometry root. The ancestor and + * descendant relations in PaintedLayerDataTree tree mirror those in the frame + * tree. + * Each node can have clip that describes the potential extents that items in + * this node can cover. If mHasClip is false, it means that the node's contents + * can move anywhere. + * Testing against the clip instead of the node's actual contents has the + * advantage that the node's contents can move or animate without affecting + * content in other nodes. So we don't need to re-layerize during animations + * (sync or async), and during async animations everything is guaranteed to + * look correct. + * The contents of a node's PaintedLayerData stack all share the node's + * animated geometry root. The child nodes are on top of the PaintedLayerData + * stack, in z-order, and the clip rects of the child nodes are allowed to + * intersect with the visible region or visible above region of their parent + * node's PaintedLayerDatas. + */ +class PaintedLayerDataNode { + public: + PaintedLayerDataNode(PaintedLayerDataTree& aTree, + PaintedLayerDataNode* aParent, + AnimatedGeometryRoot* aAnimatedGeometryRoot); + ~PaintedLayerDataNode(); + + AnimatedGeometryRoot* GetAnimatedGeometryRoot() const { + return mAnimatedGeometryRoot; + } + + /** + * Whether this node's contents can potentially intersect aRect. + * aRect is in our tree's ContainerState's coordinate space. + */ + bool Intersects(const nsIntRect& aRect) const { + return !mHasClip || mClipRect.Intersects(aRect); + } + + /** + * Create a PaintedLayerDataNode for aAnimatedGeometryRoot, add it to our + * children, and return it. + */ + PaintedLayerDataNode* AddChildNodeFor( + AnimatedGeometryRoot* aAnimatedGeometryRoot); + + /** + * Find a PaintedLayerData in our mPaintedLayerDataStack that aItem can be + * added to. Creates a new PaintedLayerData by calling + * aNewPaintedLayerCallback if necessary. + */ + template <typename NewPaintedLayerCallbackType> + PaintedLayerData* FindPaintedLayerFor( + const nsIntRect& aVisibleRect, bool aBackfaceHidden, + const ActiveScrolledRoot* aASR, const DisplayItemClipChain* aClipChain, + NewPaintedLayerCallbackType aNewPaintedLayerCallback); + + /** + * Find an opaque background color for aRegion. Pulls a color from the parent + * geometry root if appropriate, but only if that color is present underneath + * the whole clip of this node, so that this node's contents can animate or + * move (possibly async) without having to change the background color. + * @param aUnderIndex Searching will start in mPaintedLayerDataStack right + * below aUnderIndex. + */ + enum { ABOVE_TOP = -1 }; + nscolor FindOpaqueBackgroundColor(const nsIntRegion& aRegion, + int32_t aUnderIndex = ABOVE_TOP) const; + /** + * Same as FindOpaqueBackgroundColor, but only returns a color if absolutely + * nothing is in between, so that it can be used for a layer that can move + * anywhere inside our clip. + */ + nscolor FindOpaqueBackgroundColorCoveringEverything() const; + + /** + * Adds aRect to this node's top PaintedLayerData's mVisibleAboveRegion, + * or mVisibleAboveBackgroundRegion if mPaintedLayerDataStack is empty. + */ + void AddToVisibleAboveRegion(const nsIntRect& aRect); + /** + * Call this if all of our existing content can potentially be covered, so + * nothing can merge with it and all new content needs to create new items + * on top. This will finish all of our children and pop our whole + * mPaintedLayerDataStack. + */ + void SetAllDrawingAbove(); + + /** + * Finish this node: Finish all children, finish our PaintedLayer contents, + * and (if requested) adjust our parent's visible above region to include + * our clip. + */ + void Finish(bool aParentNeedsAccurateVisibleAboveRegion); + + /** + * Finish any children that intersect aRect. + */ + void FinishChildrenIntersecting(const nsIntRect& aRect); + + /** + * Finish all children. + */ + void FinishAllChildren() { FinishAllChildren(true); } + + protected: + /** + * Finish all items in mPaintedLayerDataStack and clear the stack. + */ + void PopAllPaintedLayerData(); + /** + * Finish all of our child nodes, but don't touch mPaintedLayerDataStack. + */ + void FinishAllChildren(bool aThisNodeNeedsAccurateVisibleAboveRegion); + /** + * Pass off opaque background color searching to our parent node, if we have + * one. + */ + nscolor FindOpaqueBackgroundColorInParentNode() const; + + PaintedLayerDataTree& mTree; + PaintedLayerDataNode* mParent; + AnimatedGeometryRoot* mAnimatedGeometryRoot; + + /** + * Our contents: a PaintedLayerData stack and our child nodes. + */ + AutoTArray<PaintedLayerData, 3> mPaintedLayerDataStack; + + /** + * UniquePtr is used here in the sense of "unique ownership", i.e. there is + * only one owner. Not in the sense of "this is the only pointer to the + * node": There are two other, non-owning, pointers to our child nodes: The + * node's respective children point to their parent node with their mParent + * pointer, and the tree keeps a map of animated geometry root to node in its + * mNodes member. These outside pointers are the reason that mChildren isn't + * just an nsTArray<PaintedLayerDataNode> (since the pointers would become + * invalid whenever the array expands its capacity). + */ + nsTArray<UniquePtr<PaintedLayerDataNode>> mChildren; + + /** + * The region that's covered between our "background" and the bottom of + * mPaintedLayerDataStack. This is used to indicate whether we can pull + * a background color from our parent node. If mVisibleAboveBackgroundRegion + * should be considered infinite, mAllDrawingAboveBackground will be true and + * the value of mVisibleAboveBackgroundRegion will be meaningless. + */ + nsIntRegion mVisibleAboveBackgroundRegion; + + /** + * Our clip, if we have any. If not, that means we can move anywhere, and + * mHasClip will be false and mClipRect will be meaningless. + */ + nsIntRect mClipRect; + bool mHasClip; + + /** + * Whether mVisibleAboveBackgroundRegion should be considered infinite. + */ + bool mAllDrawingAboveBackground; +}; + +class ContainerState; + +/** + * A tree of PaintedLayerDataNodes. At any point in time, the tree only + * contains nodes for animated geometry roots that new items can potentially + * merge into. Any time content is added on top that overlaps existing things + * in such a way that we no longer want to merge new items with some existing + * content, that existing content gets "finished". + * The public-facing methods of this class are FindPaintedLayerFor, + * AddingOwnLayer, and Finish. The other public methods are for + * PaintedLayerDataNode. + * The tree calls out to its containing ContainerState for some things. + * All coordinates / rects in the tree or the tree nodes are in the + * ContainerState's coordinate space, i.e. relative to the reference frame and + * in layer pixels. + * The clip rects of sibling nodes never overlap. This is ensured by finishing + * existing nodes before adding new ones, if this property were to be violated. + * The root tree node doesn't get finished until the ContainerState is + * finished. + * The tree's root node is always the root reference frame of the builder. We + * don't stop at the container state's mContainerAnimatedGeometryRoot because + * some of our contents can have animated geometry roots that are not + * descendants of the container's animated geometry root. Every animated + * geometry root we encounter for our contents needs to have a defined place in + * the tree. + */ +class PaintedLayerDataTree { + public: + PaintedLayerDataTree(ContainerState& aContainerState, + nscolor& aBackgroundColor) + : mContainerState(aContainerState), + mContainerUniformBackgroundColor(aBackgroundColor), + mForInactiveLayer(false) {} + + ~PaintedLayerDataTree() { + MOZ_ASSERT(!mRoot); + MOZ_ASSERT(mNodes.Count() == 0); + } + + void InitializeForInactiveLayer(AnimatedGeometryRoot* aAnimatedGeometryRoot); + + /** + * Notify our contents that some non-PaintedLayer content has been added. + * *aRect needs to be a rectangle that doesn't move with respect to + * aAnimatedGeometryRoot and that contains the added item. + * If aRect is null, the extents will be considered infinite. + * If aOutUniformBackgroundColor is non-null, it will be set to an opaque + * color that can be pulled into the background of the added content, or + * transparent if that is not possible. + */ + void AddingOwnLayer(AnimatedGeometryRoot* aAnimatedGeometryRoot, + const nsIntRect* aRect, + nscolor* aOutUniformBackgroundColor); + + /** + * Find a PaintedLayerData for aItem. This can either be an existing + * PaintedLayerData from inside a node in our tree, or a new one that gets + * created by a call out to aNewPaintedLayerCallback. + */ + template <typename NewPaintedLayerCallbackType> + PaintedLayerData* FindPaintedLayerFor( + AnimatedGeometryRoot* aAnimatedGeometryRoot, + const ActiveScrolledRoot* aASR, const DisplayItemClipChain* aClipChain, + const nsIntRect& aVisibleRect, const bool aBackfaceHidden, + NewPaintedLayerCallbackType aNewPaintedLayerCallback); + + /** + * Finish everything. + */ + void Finish(); + + /** + * Get the parent animated geometry root of aAnimatedGeometryRoot. + * That's either aAnimatedGeometryRoot's animated geometry root, or, if + * that's aAnimatedGeometryRoot itself, then it's the animated geometry + * root for aAnimatedGeometryRoot's cross-doc parent frame. + */ + AnimatedGeometryRoot* GetParentAnimatedGeometryRoot( + AnimatedGeometryRoot* aAnimatedGeometryRoot); + + /** + * Whether aAnimatedGeometryRoot has an intrinsic clip that doesn't move with + * respect to aAnimatedGeometryRoot's parent animated geometry root. + * If aAnimatedGeometryRoot is a scroll frame, this will be the scroll frame's + * scroll port, otherwise there is no clip. + * This method doesn't have much to do with PaintedLayerDataTree, but this is + * where we have easy access to a display list builder, which we use to get + * the clip rect result into the right coordinate space. + */ + bool IsClippedWithRespectToParentAnimatedGeometryRoot( + AnimatedGeometryRoot* aAnimatedGeometryRoot, nsIntRect* aOutClip); + + /** + * Called by PaintedLayerDataNode when it is finished, so that we can drop + * our pointers to it. + */ + void NodeWasFinished(AnimatedGeometryRoot* aAnimatedGeometryRoot); + + nsDisplayListBuilder* Builder() const; + ContainerState& ContState() const { return mContainerState; } + nscolor UniformBackgroundColor() const { + return mContainerUniformBackgroundColor; + } + + protected: + /** + * Finish all nodes that potentially intersect *aRect, where *aRect is a rect + * that doesn't move with respect to aAnimatedGeometryRoot. + * If aRect is null, *aRect will be considered infinite. + */ + void FinishPotentiallyIntersectingNodes( + AnimatedGeometryRoot* aAnimatedGeometryRoot, const nsIntRect* aRect); + + /** + * Make sure that there is a node for aAnimatedGeometryRoot and all of its + * ancestor geometry roots. Return the node for aAnimatedGeometryRoot. + */ + PaintedLayerDataNode* EnsureNodeFor( + AnimatedGeometryRoot* aAnimatedGeometryRoot); + + /** + * Find an existing node in the tree for an ancestor of aAnimatedGeometryRoot. + * *aOutAncestorChild will be set to the last ancestor that was encountered + * in the search up from aAnimatedGeometryRoot; it will be a child animated + * geometry root of the result, if neither are null. + */ + PaintedLayerDataNode* FindNodeForAncestorAnimatedGeometryRoot( + AnimatedGeometryRoot* aAnimatedGeometryRoot, + AnimatedGeometryRoot** aOutAncestorChild); + + ContainerState& mContainerState; + Maybe<PaintedLayerDataNode> mRoot; + + /** + * The uniform opaque color from behind this container layer, or + * NS_RGBA(0,0,0,0) if the background behind this container layer is not + * uniform and opaque. This color can be pulled into PaintedLayers that are + * directly above the background. + */ + nscolor mContainerUniformBackgroundColor; + + /** + * A hash map for quick access the node belonging to a particular animated + * geometry root. + */ + nsDataHashtable<nsPtrHashKey<AnimatedGeometryRoot>, PaintedLayerDataNode*> + mNodes; + + bool mForInactiveLayer; +}; + +/** + * This is a helper object used to build up the layer children for + * a ContainerLayer. + */ +class ContainerState { + public: + ContainerState(nsDisplayListBuilder* aBuilder, LayerManager* aManager, + FrameLayerBuilder* aLayerBuilder, nsIFrame* aContainerFrame, + nsDisplayItem* aContainerItem, const nsRect& aContainerBounds, + ContainerLayer* aContainerLayer, + const ContainerLayerParameters& aParameters, + nscolor aBackgroundColor, + const ActiveScrolledRoot* aContainerASR, + const ActiveScrolledRoot* aContainerScrollMetadataASR, + const ActiveScrolledRoot* aContainerCompositorASR) + : mBuilder(aBuilder), + mManager(aManager), + mLayerBuilder(aLayerBuilder), + mContainerFrame(aContainerFrame), + mContainerLayer(aContainerLayer), + mContainerBounds(aContainerBounds), + mContainerASR(aContainerASR), + mContainerScrollMetadataASR(aContainerScrollMetadataASR), + mContainerCompositorASR(aContainerCompositorASR), + mParameters(aParameters), + mPaintedLayerDataTree(*this, aBackgroundColor), + mLastDisplayPortAGR(nullptr), + mContainerItem(aContainerItem) { + nsPresContext* presContext = aContainerFrame->PresContext(); + mAppUnitsPerDevPixel = presContext->AppUnitsPerDevPixel(); + mContainerReferenceFrame = const_cast<nsIFrame*>( + aContainerItem ? aContainerItem->ReferenceFrameForChildren() + : mBuilder->FindReferenceFrameFor(mContainerFrame)); + bool isAtRoot = !aContainerItem || + (aContainerItem->Frame() == mBuilder->RootReferenceFrame()); + MOZ_ASSERT(!isAtRoot || + mContainerReferenceFrame == mBuilder->RootReferenceFrame()); + mContainerAnimatedGeometryRoot = + isAtRoot ? aBuilder->GetRootAnimatedGeometryRoot() + : aContainerItem->GetAnimatedGeometryRoot(); + MOZ_ASSERT( + !mBuilder->IsPaintingToWindow() || + nsLayoutUtils::IsAncestorFrameCrossDoc( + mBuilder->RootReferenceFrame(), *mContainerAnimatedGeometryRoot)); + // When AllowResidualTranslation is false, display items will be drawn + // scaled with a translation by integer pixels, so we know how the snapping + // will work. + mSnappingEnabled = aManager->IsSnappingEffectiveTransforms() && + !mParameters.AllowResidualTranslation(); + CollectOldLayers(); + } + + /** + * This is the method that actually walks a display list and builds + * the child layers. + */ + void ProcessDisplayItems(nsDisplayList* aList); + /** + * This finalizes all the open PaintedLayers by popping every element off + * mPaintedLayerDataStack, then sets the children of the container layer + * to be all the layers in mNewChildLayers in that order and removes any + * layers as children of the container that aren't in mNewChildLayers. + * @param aTextContentFlags if any child layer has CONTENT_COMPONENT_ALPHA, + * set *aTextContentFlags to CONTENT_COMPONENT_ALPHA + */ + void Finish(uint32_t* aTextContentFlags, + const nsIntRect& aContainerPixelBounds, + nsDisplayList* aChildItems); + + nscoord GetAppUnitsPerDevPixel() { return mAppUnitsPerDevPixel; } + + nsIntRect ScaleToNearestPixels(const nsRect& aRect) const { + return aRect.ScaleToNearestPixels(mParameters.mXScale, mParameters.mYScale, + mAppUnitsPerDevPixel); + } + nsIntRect ScaleToOutsidePixels(const nsRect& aRect, + bool aSnap = false) const { + if (aRect.IsEmpty()) { + return nsIntRect(); + } + if (aSnap && mSnappingEnabled) { + return ScaleToNearestPixels(aRect); + } + return aRect.ScaleToOutsidePixels(mParameters.mXScale, mParameters.mYScale, + mAppUnitsPerDevPixel); + } + nsIntRect ScaleToInsidePixels(const nsRect& aRect, bool aSnap = false) const { + if (aSnap && mSnappingEnabled) { + return ScaleToNearestPixels(aRect); + } + return aRect.ScaleToInsidePixels(mParameters.mXScale, mParameters.mYScale, + mAppUnitsPerDevPixel); + } + nsIntRegion ScaleRegionToNearestPixels(const nsRegion& aRegion) const { + return aRegion.ScaleToNearestPixels( + mParameters.mXScale, mParameters.mYScale, mAppUnitsPerDevPixel); + } + nsIntRegion ScaleRegionToInsidePixels(const nsRegion& aRegion, + bool aSnap = false) const { + if (aSnap && mSnappingEnabled) { + return ScaleRegionToNearestPixels(aRegion); + } + return aRegion.ScaleToInsidePixels(mParameters.mXScale, mParameters.mYScale, + mAppUnitsPerDevPixel); + } + + nsIntRegion ScaleRegionToOutsidePixels(const nsRegion& aRegion, + bool aSnap = false) const { + if (aRegion.IsEmpty()) { + return nsIntRegion(); + } + if (aSnap && mSnappingEnabled) { + return ScaleRegionToNearestPixels(aRegion); + } + return aRegion.ScaleToOutsidePixels( + mParameters.mXScale, mParameters.mYScale, mAppUnitsPerDevPixel); + } + + nsIFrame* GetContainerFrame() const { return mContainerFrame; } + nsDisplayListBuilder* Builder() const { return mBuilder; } + FrameLayerBuilder* LayerBuilder() const { return mLayerBuilder; } + + /** + * Check if we are currently inside an inactive layer. + */ + bool IsInInactiveLayer() const { + return mLayerBuilder->GetContainingPaintedLayerData(); + } + + /** + * Sets aOuterVisibleRegion as aLayer's visible region. + * @param aOuterVisibleRegion + * is in the coordinate space of the container reference frame. + * @param aLayerContentsVisibleRect, if non-null, is in the layer's own + * coordinate system. + * @param aOuterUntransformed is true if the given aOuterVisibleRegion + * is already untransformed with the matrix of the layer. + */ + void SetOuterVisibleRegionForLayer( + Layer* aLayer, const nsIntRegion& aOuterVisibleRegion, + const nsIntRect* aLayerContentsVisibleRect = nullptr, + bool aOuterUntransformed = false) const; + + /** + * Try to determine whether the PaintedLayer aData has a single opaque color + * covering aRect. If successful, return that color, otherwise return + * NS_RGBA(0,0,0,0). + * If aRect turns out not to intersect any content in the layer, + * *aOutIntersectsLayer will be set to false. + */ + nscolor FindOpaqueBackgroundColorInLayer(const PaintedLayerData* aData, + const nsIntRect& aRect, + bool* aOutIntersectsLayer) const; + + /** + * Indicate that we are done adding items to the PaintedLayer represented by + * aData. Make sure that a real PaintedLayer exists for it, and set the final + * visible region and opaque-content. + */ + template <typename FindOpaqueBackgroundColorCallbackType> + void FinishPaintedLayerData( + PaintedLayerData& aData, + FindOpaqueBackgroundColorCallbackType aFindOpaqueBackgroundColor); + + protected: + friend class PaintedLayerData; + friend class FLBDisplayListIterator; + + LayerManager::PaintedLayerCreationHint GetLayerCreationHint( + AnimatedGeometryRoot* aAnimatedGeometryRoot); + + /** + * Creates a new PaintedLayer and sets up the transform on the PaintedLayer + * to account for scrolling. + */ + already_AddRefed<PaintedLayer> CreatePaintedLayer(PaintedLayerData* aData); + + /** + * Find a PaintedLayer for recycling, recycle it and prepare it for use, or + * return null if no suitable layer was found. + */ + already_AddRefed<PaintedLayer> AttemptToRecyclePaintedLayer( + AnimatedGeometryRoot* aAnimatedGeometryRoot, nsDisplayItem* aItem, + const nsPoint& aTopLeft, const nsIFrame* aReferenceFrame); + /** + * Recycle aLayer and do any necessary invalidation. + */ + PaintedDisplayItemLayerUserData* RecyclePaintedLayer( + PaintedLayer* aLayer, AnimatedGeometryRoot* aAnimatedGeometryRoot, + bool& didResetScrollPositionForLayerPixelAlignment); + + /** + * Perform the last step of CreatePaintedLayer / AttemptToRecyclePaintedLayer: + * Initialize aData, set up the layer's transform for scrolling, and + * invalidate the layer for layer pixel alignment changes if necessary. + */ + void PreparePaintedLayerForUse( + PaintedLayer* aLayer, PaintedDisplayItemLayerUserData* aData, + AnimatedGeometryRoot* aAnimatedGeometryRoot, + const nsIFrame* aReferenceFrame, const nsPoint& aTopLeft, + bool aDidResetScrollPositionForLayerPixelAlignment); + + /** + * Attempt to prepare an ImageLayer based upon the provided PaintedLayerData. + * Returns nullptr on failure. + */ + already_AddRefed<Layer> PrepareImageLayer(PaintedLayerData* aData); + + /** + * Attempt to prepare a ColorLayer based upon the provided PaintedLayerData. + * Returns nullptr on failure. + */ + already_AddRefed<Layer> PrepareColorLayer(PaintedLayerData* aData); + + /** + * Grab the next recyclable ColorLayer, or create one if there are no + * more recyclable ColorLayers. + */ + already_AddRefed<ColorLayer> CreateOrRecycleColorLayer( + PaintedLayer* aPainted); + /** + * Grab the next recyclable ImageLayer, or create one if there are no + * more recyclable ImageLayers. + */ + already_AddRefed<ImageLayer> CreateOrRecycleImageLayer( + PaintedLayer* aPainted); + /** + * Grab a recyclable ImageLayer for use as a mask layer for aLayer (that is a + * mask layer which has been used for aLayer before), or create one if such + * a layer doesn't exist. + * + * Since mask layers can exist either on the layer directly, or as a side- + * attachment to FrameMetrics (for ancestor scrollframe clips), we key the + * recycle operation on both the originating layer and the mask layer's + * index in the layer, if any. + */ + struct MaskLayerKey; + template <typename UserData> + already_AddRefed<ImageLayer> CreateOrRecycleMaskImageLayerFor( + const MaskLayerKey& aKey, UserData* (*aGetUserData)(Layer* aLayer), + void (*aSetDefaultUserData)(Layer* aLayer)); + /** + * Grabs all PaintedLayers and ColorLayers from the ContainerLayer and makes + * them available for recycling. + */ + void CollectOldLayers(); + /** + * If aItem used to belong to a PaintedLayer, invalidates the area of + * aItem in that layer. If aNewLayer is a PaintedLayer, invalidates the area + * of aItem in that layer. + */ + void InvalidateForLayerChange(nsDisplayItem* aItem, PaintedLayer* aNewLayer, + DisplayItemData* aData); + /** + * Returns true if aItem's opaque area (in aOpaque) covers the entire + * scrollable area of its presshell. + */ + bool ItemCoversScrollableArea(nsDisplayItem* aItem, const nsRegion& aOpaque); + + /** + * Set ScrollMetadata and scroll-induced clipping on aEntry's layer. + */ + void SetupScrollingMetadata(NewLayerEntry* aEntry); + + /** + * Applies occlusion culling. + * For each layer in mNewChildLayers, remove from its visible region the + * opaque regions of the layers at higher z-index, but only if they have + * the same animated geometry root and fixed-pos frame ancestor. + * The opaque region for the child layers that share the same animated + * geometry root as the container frame is returned in + * *aOpaqueRegionForContainer. + * + * Also sets scroll metadata on the layers. + */ + void PostprocessRetainedLayers(nsIntRegion* aOpaqueRegionForContainer); + + /** + * Computes the snapped opaque area of aItem. Sets aList's opaque flag + * if it covers the entire list bounds. Sets *aHideAllLayersBelow to true + * this item covers the entire viewport so that all layers below are + * permanently invisible. + */ + nsIntRegion ComputeOpaqueRect(nsDisplayItem* aItem, + AnimatedGeometryRoot* aAnimatedGeometryRoot, + const ActiveScrolledRoot* aASR, + const DisplayItemClip& aClip, + nsDisplayList* aList, bool* aHideAllLayersBelow, + bool* aOpaqueForAnimatedGeometryRootParent); + + /** + * Fills a PaintedLayerData object that is initialized for a layer that the + * current item will be assigned to. Also creates mNewChildLayers entries. + * @param aData The PaintedLayerData that will be filled. + * @param aVisibleRect The visible rect of the item. + * @param aAnimatedGeometryRoot The item's animated geometry root. + * @param aASR The active scrolled root that moves this + * PaintedLayer. + * @param aClipChain The clip chain that the compositor needs to + * apply to this layer. + * @param aScrollMetadataASR The leaf ASR for which scroll metadata needs + * to be set on the layer, because either the layer itself or its scrolled + * clip need to move with that ASR. + * @param aTopLeft The offset between aAnimatedGeometryRoot and + * the reference frame. + * @param aReferenceFrame The reference frame for the item. + * @param aBackfaceHidden The backface visibility for the item frame. + */ + void NewPaintedLayerData( + PaintedLayerData* aData, AnimatedGeometryRoot* aAnimatedGeometryRoot, + const ActiveScrolledRoot* aASR, const DisplayItemClipChain* aClipChain, + const ActiveScrolledRoot* aScrollMetadataASR, const nsPoint& aTopLeft, + const nsIFrame* aReferenceFrame, const bool aBackfaceHidden); + + /* Build a mask layer to represent the clipping region. Will return null if + * there is no clipping specified or a mask layer cannot be built. + * Builds an ImageLayer for the appropriate backend; the mask is relative to + * aLayer's visible region. + * aLayer is the layer to be clipped. + * relative to the container reference frame + * aRoundedRectClipCount is used when building mask layers for PaintedLayers, + */ + void SetupMaskLayer(Layer* aLayer, const DisplayItemClip& aClip); + + /** + * If |aClip| has rounded corners, create a mask layer for them, and + * add it to |aLayer|'s ancestor mask layers, returning an index into + * the array of ancestor mask layers. Returns an empty Maybe if + * |aClip| does not have rounded corners, or if no mask layer could + * be created. + */ + Maybe<size_t> SetupMaskLayerForScrolledClip(Layer* aLayer, + const DisplayItemClip& aClip); + + /** + * Create/find a mask layer with suitable size for aMaskItem to paint + * css-positioned-masking onto. + */ + void SetupMaskLayerForCSSMask(Layer* aLayer, + nsDisplayMasksAndClipPaths* aMaskItem); + + already_AddRefed<Layer> CreateMaskLayer( + Layer* aLayer, const DisplayItemClip& aClip, + const Maybe<size_t>& aForAncestorMaskLayer); + + /** + * Get the display port for an AGR. + * The result would be cached for later reusing. + */ + nsRect GetDisplayPortForAnimatedGeometryRoot( + AnimatedGeometryRoot* aAnimatedGeometryRoot); + + nsDisplayListBuilder* mBuilder; + LayerManager* mManager; + FrameLayerBuilder* mLayerBuilder; + nsIFrame* mContainerFrame; + nsIFrame* mContainerReferenceFrame; + AnimatedGeometryRoot* mContainerAnimatedGeometryRoot; + ContainerLayer* mContainerLayer; + nsRect mContainerBounds; + + // Due to the way we store scroll annotations in the layer tree, we need to + // keep track of three (possibly different) ASRs here. + // mContainerASR is the ASR of the container display item that this + // ContainerState was created for. + // mContainerScrollMetadataASR is the ASR of the leafmost scroll metadata + // that's in effect on mContainerLayer. + // mContainerCompositorASR is the ASR that mContainerLayer moves with on + // the compositor / APZ side, taking into account both the scroll meta data + // and the fixed position annotation on itself and its ancestors. + const ActiveScrolledRoot* mContainerASR; + const ActiveScrolledRoot* mContainerScrollMetadataASR; + const ActiveScrolledRoot* mContainerCompositorASR; +#ifdef DEBUG + nsRect mAccumulatedChildBounds; +#endif + ContainerLayerParameters mParameters; + /** + * The region of PaintedLayers that should be invalidated every time + * we recycle one. + */ + nsIntRegion mInvalidPaintedContent; + PaintedLayerDataTree mPaintedLayerDataTree; + /** + * We collect the list of children in here. During ProcessDisplayItems, + * the layers in this array either have mContainerLayer as their parent, + * or no parent. + * PaintedLayers have two entries in this array: the second one is used only + * if the PaintedLayer is optimized away to a ColorLayer or ImageLayer. It's + * essential that this array is only appended to, since PaintedLayerData + * records the index of its PaintedLayer in this array. + */ + typedef AutoTArray<NewLayerEntry, 1> AutoLayersArray; + AutoLayersArray mNewChildLayers; + nsTHashtable<nsRefPtrHashKey<PaintedLayer>> + mPaintedLayersAvailableForRecycling; + nscoord mAppUnitsPerDevPixel; + bool mSnappingEnabled; + + struct MaskLayerKey { + MaskLayerKey() : mLayer(nullptr) {} + MaskLayerKey(Layer* aLayer, const Maybe<size_t>& aAncestorIndex) + : mLayer(aLayer), mAncestorIndex(aAncestorIndex) {} + + PLDHashNumber Hash() const { + // Hash the layer and add the layer index to the hash. + return (NS_PTR_TO_UINT32(mLayer) >> 2) + + (mAncestorIndex ? (*mAncestorIndex + 1) : 0); + } + bool operator==(const MaskLayerKey& aOther) const { + return mLayer == aOther.mLayer && mAncestorIndex == aOther.mAncestorIndex; + } + + Layer* mLayer; + Maybe<size_t> mAncestorIndex; + }; + + nsDataHashtable<nsGenericHashKey<MaskLayerKey>, RefPtr<ImageLayer>> + mRecycledMaskImageLayers; + // Keep display port of AGR to avoid wasting time on doing the same + // thing repeatly. + AnimatedGeometryRoot* mLastDisplayPortAGR; + nsRect mLastDisplayPortRect; + + nsDisplayItem* mContainerItem; + + // Cache ScrollMetadata so it doesn't need recomputed if the ASR and clip are + // unchanged. If mASR == nullptr then mMetadata is not valid. + struct CachedScrollMetadata { + const ActiveScrolledRoot* mASR; + const DisplayItemClip* mClip; + Maybe<ScrollMetadata> mMetadata; + + CachedScrollMetadata() : mASR(nullptr), mClip(nullptr) {} + }; + CachedScrollMetadata mCachedScrollMetadata; +}; + +class FLBDisplayListIterator : public FlattenedDisplayListIterator { + public: + FLBDisplayListIterator(nsDisplayListBuilder* aBuilder, nsDisplayList* aList, + ContainerState* aState) + : FlattenedDisplayListIterator(aBuilder, aList, false), mState(aState) { + MOZ_ASSERT(mState); + + if (mState->mContainerItem) { + // Add container item hit test information for processing, if needed. + AddHitTestMarkerIfNeeded(mState->mContainerItem); + } + + ResolveFlattening(); + } + + DisplayItemEntry GetNextEntry() { + if (!mMarkers.empty()) { + DisplayItemEntry entry = mMarkers.front(); + mMarkers.pop_front(); + return entry; + } + + return DisplayItemEntry{GetNextItem(), DisplayItemEntryType::Item}; + } + + bool HasNext() const override { + return FlattenedDisplayListIterator::HasNext() || !mMarkers.empty(); + } + + private: + void AddHitTestMarkerIfNeeded(nsDisplayItem* aItem) { + if (aItem->HasHitTestInfo()) { + mMarkers.emplace_back(aItem, DisplayItemEntryType::HitTestInfo); + } + } + + bool ShouldFlattenNextItem() override { + if (!FlattenedDisplayListIterator::ShouldFlattenNextItem()) { + return false; + } + + nsDisplayItem* next = PeekNext(); + const DisplayItemType type = next->GetType(); + + if (type == DisplayItemType::TYPE_SVG_WRAPPER) { + // We mark SetContainsSVG for the CONTENT_FRAME_TIME_WITH_SVG metric + if (RefPtr<LayerManager> lm = mState->mBuilder->GetWidgetLayerManager()) { + lm->SetContainsSVG(true); + } + } + + if (!SupportsFlatteningWithMarkers(type)) { + return true; + } + + if (type == DisplayItemType::TYPE_OPACITY && + IsOpacityAppliedToChildren(next)) { + // This is the previous opacity flattening path, where the opacity has + // been applied to children. + return true; + } + + if (mState->IsInInactiveLayer() || !ItemWantsInactiveLayer(next)) { + // Do not flatten nested inactive display items, or display items that + // want an active layer. + return false; + } + + // If we reach here, we will emit an effect start marker for + // nsDisplayTransform or nsDisplayOpacity. + MOZ_ASSERT(type == DisplayItemType::TYPE_TRANSFORM || + !IsOpacityAppliedToChildren(next)); + return true; + } + + void EnterChildList(nsDisplayItem* aContainerItem) override { + mFlattenedLists.AppendElement(aContainerItem); + AddMarkerIfNeeded<MarkerType::StartMarker>(aContainerItem, mMarkers); + AddHitTestMarkerIfNeeded(aContainerItem); + } + + void ExitChildList() override { + MOZ_ASSERT(!mFlattenedLists.IsEmpty()); + nsDisplayItem* aContainerItem = mFlattenedLists.PopLastElement(); + AddMarkerIfNeeded<MarkerType::EndMarker>(aContainerItem, mMarkers); + } + + bool ItemWantsInactiveLayer(nsDisplayItem* aItem) { + const LayerState layerState = aItem->GetLayerState( + mState->mBuilder, mState->mManager, mState->mParameters); + + return layerState == LayerState::LAYER_INACTIVE; + } + + std::deque<DisplayItemEntry> mMarkers; + AutoTArray<nsDisplayItem*, 16> mFlattenedLists; + ContainerState* mState; +}; + +class PaintedDisplayItemLayerUserData : public LayerUserData { + public: + PaintedDisplayItemLayerUserData() + : mForcedBackgroundColor(NS_RGBA(0, 0, 0, 0)), + mXScale(1.f), + mYScale(1.f), + mAppUnitsPerDevPixel(0), + mTranslation(0, 0), + mAnimatedGeometryRootPosition(0, 0), + mLastItemCount(0), + mContainerLayerFrame(nullptr), + mDisabledAlpha(false) {} + + NS_INLINE_DECL_REFCOUNTING(PaintedDisplayItemLayerUserData); + + /** + * A color that should be painted over the bounds of the layer's visible + * region before any other content is painted. + */ + nscolor mForcedBackgroundColor; + + /** + * The resolution scale used. + */ + float mXScale, mYScale; + + /** + * The appunits per dev pixel for the items in this layer. + */ + nscoord mAppUnitsPerDevPixel; + + /** + * The offset from the PaintedLayer's 0,0 to the + * reference frame. This isn't necessarily the same as the transform + * set on the PaintedLayer since we might also be applying an extra + * offset specified by the parent ContainerLayer/ + */ + nsIntPoint mTranslation; + + /** + * We try to make 0,0 of the PaintedLayer be the top-left of the + * border-box of the "active scrolled root" frame (i.e. the nearest ancestor + * frame for the display items that is being actively scrolled). But + * we force the PaintedLayer transform to be an integer translation, and we + * may have a resolution scale, so we have to snap the PaintedLayer transform, + * so 0,0 may not be exactly the top-left of the active scrolled root. Here we + * store the coordinates in PaintedLayer space of the top-left of the + * active scrolled root. + */ + gfxPoint mAnimatedGeometryRootPosition; + + nsIntRegion mRegionToInvalidate; + + // The offset between the active scrolled root of this layer + // and the root of the container for the previous and current + // paints respectively. + nsPoint mLastAnimatedGeometryRootOrigin; + nsPoint mAnimatedGeometryRootOrigin; + + RefPtr<ColorLayer> mColorLayer; + RefPtr<ImageLayer> mImageLayer; + + // The region for which display item visibility for this layer has already + // been calculated. Used to reduce the number of calls to + // RecomputeVisibilityForItems if it is known in advance that a larger + // region will be painted during a transaction than in a single call to + // DrawPaintedLayer, for example when progressive paint is enabled. + nsIntRegion mVisibilityComputedRegion; + + // The area for which we called RecomputeVisibilityForItems on the + // previous paint. + nsRect mPreviousRecomputeVisibilityRect; + + // The number of items assigned to this layer on the previous paint. + size_t mLastItemCount; + + // The translation set on this PaintedLayer during the previous paint. This + // is needed when invalidating based on a display item's geometry information + // from the previous paint. + Maybe<nsIntPoint> mLastPaintOffset; + + // Temporary state only valid during the FrameLayerBuilder's lifetime. + // FLB's mPaintedLayerItems is responsible for cleaning these up when + // we finish painting to avoid dangling pointers. + std::vector<AssignedDisplayItem> mItems; + nsIFrame* mContainerLayerFrame; + + /** + * This is set when the painted layer has no component alpha. + */ + bool mDisabledAlpha; + + protected: + ~PaintedDisplayItemLayerUserData() override = default; +}; + +FrameLayerBuilder::FrameLayerBuilder() + : mRetainingManager(nullptr), + mDisplayListBuilder(nullptr), + mContainingPaintedLayer(nullptr), + mInactiveLayerClip(nullptr), + mInvalidateAllLayers(false), + mInLayerTreeCompressionMode(false), + mIsInactiveLayerManager(false) { + MOZ_COUNT_CTOR(FrameLayerBuilder); +} + +FrameLayerBuilder::~FrameLayerBuilder() { + GetMaskLayerImageCache()->Sweep(); + for (PaintedDisplayItemLayerUserData* userData : mPaintedLayerItems) { + userData->mLastPaintOffset = Some(userData->mTranslation); + userData->mItems.clear(); + userData->mContainerLayerFrame = nullptr; + } + MOZ_COUNT_DTOR(FrameLayerBuilder); +} + +void FrameLayerBuilder::AddPaintedLayerItemsEntry( + PaintedDisplayItemLayerUserData* aData) { + mPaintedLayerItems.AppendElement(aData); +} + +/* + * User data for layers which will be used as masks. + */ +struct MaskLayerUserData : public LayerUserData { + MaskLayerUserData() + : mScaleX(-1.0f), mScaleY(-1.0f), mAppUnitsPerDevPixel(-1) {} + MaskLayerUserData(const DisplayItemClip& aClip, int32_t aAppUnitsPerDevPixel, + const ContainerLayerParameters& aParams) + : mScaleX(aParams.mXScale), + mScaleY(aParams.mYScale), + mOffset(aParams.mOffset), + mAppUnitsPerDevPixel(aAppUnitsPerDevPixel) { + aClip.AppendRoundedRects(&mRoundedClipRects); + } + + void operator=(MaskLayerUserData&& aOther) { + mScaleX = aOther.mScaleX; + mScaleY = aOther.mScaleY; + mOffset = aOther.mOffset; + mAppUnitsPerDevPixel = aOther.mAppUnitsPerDevPixel; + mRoundedClipRects = std::move(aOther.mRoundedClipRects); + } + + bool operator==(const MaskLayerUserData& aOther) const { + return mRoundedClipRects == aOther.mRoundedClipRects && + mScaleX == aOther.mScaleX && mScaleY == aOther.mScaleY && + mOffset == aOther.mOffset && + mAppUnitsPerDevPixel == aOther.mAppUnitsPerDevPixel; + } + + // Keeps a MaskLayerImageKey alive by managing its mLayerCount member-var + MaskLayerImageCache::MaskLayerImageKeyRef mImageKey; + // properties of the mask layer; the mask layer may be re-used if these + // remain unchanged. + nsTArray<DisplayItemClip::RoundedRect> mRoundedClipRects; + // scale from the masked layer which is applied to the mask + float mScaleX, mScaleY; + // The ContainerLayerParameters offset which is applied to the mask's + // transform. + nsIntPoint mOffset; + int32_t mAppUnitsPerDevPixel; +}; + +/* + * User data for layers which will be used as masks for css positioned mask. + */ +struct CSSMaskLayerUserData : public LayerUserData { + CSSMaskLayerUserData() : mMaskStyle(nsStyleImageLayers::LayerType::Mask) {} + + CSSMaskLayerUserData(nsIFrame* aFrame, const nsIntRect& aMaskBounds, + const nsPoint& aMaskLayerOffset) + : mMaskBounds(aMaskBounds), + mMaskStyle(aFrame->StyleSVGReset()->mMask), + mMaskLayerOffset(aMaskLayerOffset) {} + + void operator=(CSSMaskLayerUserData&& aOther) { + mMaskBounds = aOther.mMaskBounds; + mMaskStyle = std::move(aOther.mMaskStyle); + mMaskLayerOffset = aOther.mMaskLayerOffset; + } + + bool operator==(const CSSMaskLayerUserData& aOther) const { + if (!mMaskBounds.IsEqualInterior(aOther.mMaskBounds)) { + return false; + } + + // Make sure we draw the same portion of the mask onto mask layer. + if (mMaskLayerOffset != aOther.mMaskLayerOffset) { + return false; + } + + return mMaskStyle == aOther.mMaskStyle; + } + + private: + nsIntRect mMaskBounds; + nsStyleImageLayers mMaskStyle; + nsPoint mMaskLayerOffset; // The offset from the origin of mask bounds to + // the origin of mask layer. +}; + +/* + * A helper object to create a draw target for painting mask and create a + * image container to hold the drawing result. The caller can then bind this + * image container with a image mask layer via ImageLayer::SetContainer. + */ +class MaskImageData { + public: + MaskImageData(const gfx::IntSize& aSize, LayerManager* aLayerManager) + : mTextureClientLocked(false), + mSize(aSize), + mLayerManager(aLayerManager) { + MOZ_ASSERT(!mSize.IsEmpty()); + MOZ_ASSERT(mLayerManager); + } + + ~MaskImageData() { + if (mTextureClientLocked) { + MOZ_ASSERT(mTextureClient); + // Clear DrawTarget before Unlock. + mDrawTarget = nullptr; + mTextureClient->Unlock(); + } + } + + gfx::DrawTarget* CreateDrawTarget() { + if (mDrawTarget) { + return mDrawTarget; + } + + if (mLayerManager->GetBackendType() == LayersBackend::LAYERS_BASIC) { + mDrawTarget = mLayerManager->CreateOptimalMaskDrawTarget(mSize); + return mDrawTarget; + } + + MOZ_ASSERT(mLayerManager->GetBackendType() == + LayersBackend::LAYERS_CLIENT || + mLayerManager->GetBackendType() == LayersBackend::LAYERS_WR); + + KnowsCompositor* knowsCompositor = mLayerManager->AsKnowsCompositor(); + if (!knowsCompositor) { + return nullptr; + } + mTextureClient = TextureClient::CreateForDrawing( + knowsCompositor, SurfaceFormat::A8, mSize, BackendSelector::Content, + TextureFlags::DISALLOW_BIGIMAGE, + TextureAllocationFlags::ALLOC_CLEAR_BUFFER); + if (!mTextureClient) { + return nullptr; + } + + mTextureClientLocked = mTextureClient->Lock(OpenMode::OPEN_READ_WRITE); + if (!mTextureClientLocked) { + return nullptr; + } + + mDrawTarget = mTextureClient->BorrowDrawTarget(); + return mDrawTarget; + } + + already_AddRefed<ImageContainer> CreateImageAndImageContainer() { + RefPtr<ImageContainer> container = LayerManager::CreateImageContainer(); + RefPtr<Image> image = CreateImage(); + + if (!image) { + return nullptr; + } + container->SetCurrentImageInTransaction(image); + + return container.forget(); + } + + private: + already_AddRefed<Image> CreateImage() { + if (mLayerManager->GetBackendType() == LayersBackend::LAYERS_BASIC && + mDrawTarget) { + RefPtr<SourceSurface> surface = mDrawTarget->Snapshot(); + RefPtr<SourceSurfaceImage> image = new SourceSurfaceImage(mSize, surface); + // Disallow BIGIMAGE (splitting into multiple textures) for mask + // layer images + image->SetTextureFlags(TextureFlags::DISALLOW_BIGIMAGE); + return image.forget(); + } + + if ((mLayerManager->GetBackendType() == LayersBackend::LAYERS_CLIENT || + mLayerManager->GetBackendType() == LayersBackend::LAYERS_WR) && + mTextureClient && mDrawTarget) { + RefPtr<TextureWrapperImage> image = new TextureWrapperImage( + mTextureClient, gfx::IntRect(gfx::IntPoint(0, 0), mSize)); + return image.forget(); + } + + return nullptr; + } + + bool mTextureClientLocked; + gfx::IntSize mSize; + LayerManager* mLayerManager; + RefPtr<gfx::DrawTarget> mDrawTarget; + RefPtr<TextureClient> mTextureClient; +}; + +/* static */ +void FrameLayerBuilder::Shutdown() { + if (gMaskLayerImageCache) { + delete gMaskLayerImageCache; + gMaskLayerImageCache = nullptr; + } +} + +void FrameLayerBuilder::Init(nsDisplayListBuilder* aBuilder, + LayerManager* aManager, + PaintedLayerData* aLayerData, + bool aIsInactiveLayerManager, + const DisplayItemClip* aInactiveLayerClip) { + mDisplayListBuilder = aBuilder; + mRootPresContext = + aBuilder->RootReferenceFrame()->PresContext()->GetRootPresContext(); + mContainingPaintedLayer = aLayerData; + mIsInactiveLayerManager = aIsInactiveLayerManager; + mInactiveLayerClip = aInactiveLayerClip; + aManager->SetUserData(&gLayerManagerLayerBuilder, this); +} + +void FrameLayerBuilder::FlashPaint(gfxContext* aContext) { + float r = float(rand()) / float(RAND_MAX); + float g = float(rand()) / float(RAND_MAX); + float b = float(rand()) / float(RAND_MAX); + aContext->SetColor(sRGBColor(r, g, b, 0.4f)); + aContext->Paint(); +} + +DisplayItemData* FrameLayerBuilder::GetDisplayItemData(nsIFrame* aFrame, + uint32_t aKey) { + const SmallPointerArray<DisplayItemData>& array = aFrame->DisplayItemData(); + for (uint32_t i = 0; i < array.Length(); i++) { + DisplayItemData* item = + DisplayItemData::AssertDisplayItemData(array.ElementAt(i)); + if (item->mDisplayItemKey == aKey && item->FirstFrame() == aFrame && + item->mLayer->Manager() == mRetainingManager) { + return item; + } + } + return nullptr; +} + +#ifdef MOZ_DUMP_PAINTING +static nsACString& AppendToString(nsACString& s, const nsIntRect& r, + const char* pfx = "", const char* sfx = "") { + s += pfx; + s += nsPrintfCString("(x=%d, y=%d, w=%d, h=%d)", r.x, r.y, r.width, r.height); + return s += sfx; +} + +static nsACString& AppendToString(nsACString& s, const nsIntRegion& r, + const char* pfx = "", const char* sfx = "") { + s += pfx; + + s += "< "; + for (auto iter = r.RectIter(); !iter.Done(); iter.Next()) { + AppendToString(s, iter.Get()) += "; "; + } + s += ">"; + + return s += sfx; +} +#endif // MOZ_DUMP_PAINTING + +/** + * Invalidate aRegion in aLayer. aLayer is in the coordinate system + * *after* aTranslation has been applied, so we need to + * apply the inverse of that transform before calling InvalidateRegion. + */ +static void InvalidatePostTransformRegion(PaintedLayer* aLayer, + const nsIntRegion& aRegion, + const nsIntPoint& aTranslation) { + // Convert the region from the coordinates of the container layer + // (relative to the snapped top-left of the display list reference frame) + // to the PaintedLayer's own coordinates + nsIntRegion rgn = aRegion; + + rgn.MoveBy(-aTranslation); + aLayer->InvalidateRegion(rgn); +#ifdef MOZ_DUMP_PAINTING + if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) { + nsAutoCString str; + AppendToString(str, rgn); + printf_stderr("Invalidating layer %p: %s\n", aLayer, str.get()); + } +#endif +} + +static PaintedDisplayItemLayerUserData* GetPaintedDisplayItemLayerUserData( + Layer* aLayer) { + return static_cast<PaintedDisplayItemLayerUserData*>( + aLayer->GetUserData(&gPaintedDisplayItemLayerUserData)); +} + +static nsIntPoint GetTranslationForPaintedLayer(PaintedLayer* aLayer) { + PaintedDisplayItemLayerUserData* layerData = + GetPaintedDisplayItemLayerUserData(aLayer); + NS_ASSERTION(layerData, "Must be a tracked painted layer!"); + + return layerData->mTranslation; +} + +/** + * Get the translation transform that was in aLayer when we last painted. It's + * either the transform saved by ~FrameLayerBuilder(), or else the transform + * that's currently in the layer (which must be an integer translation). + */ +static nsIntPoint GetLastPaintOffset(PaintedLayer* aLayer) { + auto* layerData = GetPaintedDisplayItemLayerUserData(aLayer); + MOZ_ASSERT(layerData); + return layerData->mLastPaintOffset.valueOr(layerData->mTranslation); +} + +static void InvalidatePreTransformRect(PaintedLayer* aLayer, + const nsRect& aRect, + const DisplayItemClip& aClip, + const nsIntPoint& aTranslation, + TransformClipNode* aTransform) { + auto* data = GetPaintedDisplayItemLayerUserData(aLayer); + + nsRect rect = aClip.ApplyNonRoundedIntersection(aRect); + + if (aTransform) { + rect = aTransform->TransformRect(rect, data->mAppUnitsPerDevPixel); + } + + nsIntRect pixelRect = rect.ScaleToOutsidePixels(data->mXScale, data->mYScale, + data->mAppUnitsPerDevPixel); + + InvalidatePostTransformRegion(aLayer, pixelRect, aTranslation); +} + +/** + * Some frames can have multiple, nested, retaining layer managers + * associated with them (normal manager, inactive managers, SVG effects). + * In these cases we store the 'outermost' LayerManager data property + * on the frame since we can walk down the chain from there. + * + * If one of these frames has just been destroyed, we will free the inner + * layer manager when removing the entry from mFramesWithLayers. Destroying + * the layer manager destroys the LayerManagerData and calls into + * the DisplayItemData destructor. If the inner layer manager had any + * items with the same frame, then we attempt to retrieve properties + * from the deleted frame. + * + * Cache the destroyed frame pointer here so we can avoid crashing in this case. + */ + +/* static */ +void FrameLayerBuilder::RemoveFrameFromLayerManager( + const nsIFrame* aFrame, SmallPointerArray<DisplayItemData>& aArray) { + MOZ_RELEASE_ASSERT(!sDestroyedFrame); + sDestroyedFrame = aFrame; + + // Hold a reference to all the items so that they don't get + // deleted from under us. + nsTArray<RefPtr<DisplayItemData>> arrayCopy; + for (DisplayItemData* data : aArray) { + arrayCopy.AppendElement(data); + } + +#ifdef DEBUG_DISPLAY_ITEM_DATA + if (aArray->Length()) { + LayerManagerData* rootData = aArray->ElementAt(0)->mParent; + while (rootData->mParent) { + rootData = rootData->mParent; + } + printf_stderr("Removing frame %p - dumping display data\n", aFrame); + rootData->Dump(); + } +#endif + + for (DisplayItemData* data : aArray) { + PaintedLayer* t = data->mLayer ? data->mLayer->AsPaintedLayer() : nullptr; + if (t) { + auto* paintedData = GetPaintedDisplayItemLayerUserData(t); + if (paintedData && data->mGeometry) { + const int32_t appUnitsPerDevPixel = paintedData->mAppUnitsPerDevPixel; + nsRegion rgn = data->mGeometry->ComputeInvalidationRegion(); + nsIntRegion pixelRgn = rgn.ToOutsidePixels(appUnitsPerDevPixel); + + if (data->mTransform) { + pixelRgn = data->mTransform->TransformRegion(pixelRgn); + } + + pixelRgn = + pixelRgn.ScaleRoundOut(paintedData->mXScale, paintedData->mYScale); + + pixelRgn.MoveBy(-GetTranslationForPaintedLayer(t)); + + paintedData->mRegionToInvalidate.Or(paintedData->mRegionToInvalidate, + pixelRgn); + paintedData->mRegionToInvalidate.SimplifyOutward(8); + } + } + + auto it = std::find(data->mParent->mDisplayItems.begin(), + data->mParent->mDisplayItems.end(), data); + MOZ_ASSERT(it != data->mParent->mDisplayItems.end()); + std::iter_swap(it, data->mParent->mDisplayItems.end() - 1); + data->mParent->mDisplayItems.pop_back(); + } + + if (aFrame->IsSubDocumentFrame()) { + const nsSubDocumentFrame* subdoc = + static_cast<const nsSubDocumentFrame*>(aFrame); + nsFrameLoader* frameLoader = subdoc->FrameLoader(); + if (frameLoader && frameLoader->GetRemoteBrowser()) { + // This is a remote browser that is going away, notify it that it is now + // hidden + frameLoader->GetRemoteBrowser()->UpdateEffects( + mozilla::dom::EffectsInfo::FullyHidden()); + } + } + + arrayCopy.Clear(); + sDestroyedFrame = nullptr; +} + +void FrameLayerBuilder::DidBeginRetainedLayerTransaction( + LayerManager* aManager) { + mRetainingManager = aManager; + LayerManagerData* data = static_cast<LayerManagerData*>( + aManager->GetUserData(&gLayerManagerUserData)); + if (data) { + mInvalidateAllLayers = data->mInvalidateAllLayers; + } else { + data = new LayerManagerData(aManager); + aManager->SetUserData(&gLayerManagerUserData, data); + } +} + +void FrameLayerBuilder::DidEndTransaction() { + GetMaskLayerImageCache()->Sweep(); +} + +void FrameLayerBuilder::WillEndTransaction() { + if (!mRetainingManager) { + return; + } + + // We need to save the data we'll need to support retaining. + LayerManagerData* data = static_cast<LayerManagerData*>( + mRetainingManager->GetUserData(&gLayerManagerUserData)); + NS_ASSERTION(data, "Must have data!"); + + // Update all the frames that used to have layers. + auto iter = data->mDisplayItems.begin(); + while (iter != data->mDisplayItems.end()) { + DisplayItemData* did = iter->get(); + if (!did->mUsed) { + // This item was visible, but isn't anymore. + PaintedLayer* t = did->mLayer->AsPaintedLayer(); + if (t && did->mGeometry) { +#ifdef MOZ_DUMP_PAINTING + if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) { + printf_stderr( + "Invalidating unused display item (%i) belonging to " + "frame %p from layer %p\n", + did->mDisplayItemKey, did->mFrameList[0], t); + } +#endif + InvalidatePreTransformRect( + t, did->mGeometry->ComputeInvalidationRegion(), did->mClip, + GetLastPaintOffset(t), did->mTransform); + } + + did->NotifyRemoved(); + + // Remove this item. Swapping it with the last element first is + // quicker than erasing from the middle. + if (iter != data->mDisplayItems.end() - 1) { + std::iter_swap(iter, data->mDisplayItems.end() - 1); + data->mDisplayItems.pop_back(); + } else { + data->mDisplayItems.pop_back(); + break; + } + + // Don't increment iter because we still need to process the item which + // was moved. + + } else { + ComputeGeometryChangeForItem(did); + iter++; + } + } + + data->mInvalidateAllLayers = false; +} + +/* static */ +DisplayItemData* FrameLayerBuilder::GetDisplayItemDataForManager( + nsPaintedDisplayItem* aItem, LayerManager* aManager) { + for (DisplayItemData* did : aItem->Frame()->DisplayItemData()) { + DisplayItemData* data = DisplayItemData::AssertDisplayItemData(did); + if (data->mDisplayItemKey == aItem->GetPerFrameKey() && + data->mLayer->Manager() == aManager) { + return data; + } + } + + return nullptr; +} + +bool FrameLayerBuilder::HasRetainedDataFor(nsIFrame* aFrame, + uint32_t aDisplayItemKey) { + const SmallPointerArray<DisplayItemData>& array = aFrame->DisplayItemData(); + for (uint32_t i = 0; i < array.Length(); i++) { + if (DisplayItemData::AssertDisplayItemData(array.ElementAt(i)) + ->mDisplayItemKey == aDisplayItemKey) { + return true; + } + } + if (RefPtr<WebRenderUserData> data = + GetWebRenderUserData<WebRenderFallbackData>(aFrame, + aDisplayItemKey)) { + return true; + } + return false; +} + +DisplayItemData* FrameLayerBuilder::GetOldLayerForFrame( + nsIFrame* aFrame, uint32_t aDisplayItemKey, + DisplayItemData* aOldData, /* = nullptr */ + LayerManager* aOldLayerManager /* = nullptr */) { + // If we need to build a new layer tree, then just refuse to recycle + // anything. + if (!mRetainingManager || mInvalidateAllLayers) { + return nullptr; + } + + MOZ_ASSERT(!aOldData || aOldLayerManager, + "You must provide aOldLayerManager to check aOldData's validity."); + MOZ_ASSERT_IF(aOldData, aOldLayerManager == aOldData->mLayer->Manager()); + + DisplayItemData* data = aOldData; + if (!data || aOldLayerManager != mRetainingManager) { + data = GetDisplayItemData(aFrame, aDisplayItemKey); + } + + MOZ_ASSERT(data == GetDisplayItemData(aFrame, aDisplayItemKey)); + + return data; +} + +Layer* FrameLayerBuilder::GetOldLayerFor(nsDisplayItem* aItem, + nsDisplayItemGeometry** aOldGeometry, + DisplayItemClip** aOldClip) { + uint32_t key = aItem->GetPerFrameKey(); + nsIFrame* frame = aItem->Frame(); + + DisplayItemData* oldData = GetOldLayerForFrame(frame, key); + if (oldData) { + if (aOldGeometry) { + *aOldGeometry = oldData->mGeometry.get(); + } + if (aOldClip) { + *aOldClip = &oldData->mClip; + } + return oldData->mLayer; + } + + return nullptr; +} + +/* static */ +DisplayItemData* FrameLayerBuilder::GetOldDataFor(nsDisplayItem* aItem) { + const SmallPointerArray<DisplayItemData>& array = + aItem->Frame()->DisplayItemData(); + + for (uint32_t i = 0; i < array.Length(); i++) { + DisplayItemData* data = + DisplayItemData::AssertDisplayItemData(array.ElementAt(i)); + + if (data->mDisplayItemKey == aItem->GetPerFrameKey()) { + return data; + } + } + return nullptr; +} + +// Reset state that should not persist when a layer is recycled. +static void ResetLayerStateForRecycling(Layer* aLayer) { + // Currently, this clears the mask layer and ancestor mask layers. + // Other cleanup may be added here. + aLayer->SetMaskLayer(nullptr); + aLayer->SetAncestorMaskLayers({}); +} + +already_AddRefed<ColorLayer> ContainerState::CreateOrRecycleColorLayer( + PaintedLayer* aPainted) { + auto* data = GetPaintedDisplayItemLayerUserData(aPainted); + RefPtr<ColorLayer> layer = data->mColorLayer; + if (layer) { + ResetLayerStateForRecycling(layer); + layer->ClearExtraDumpInfo(); + } else { + // Create a new layer + layer = mManager->CreateColorLayer(); + if (!layer) { + return nullptr; + } + // Mark this layer as being used for painting display items + data->mColorLayer = layer; + layer->SetUserData(&gColorLayerUserData, nullptr); + + // Remove other layer types we might have stored for this PaintedLayer + data->mImageLayer = nullptr; + } + return layer.forget(); +} + +already_AddRefed<ImageLayer> ContainerState::CreateOrRecycleImageLayer( + PaintedLayer* aPainted) { + auto* data = GetPaintedDisplayItemLayerUserData(aPainted); + RefPtr<ImageLayer> layer = data->mImageLayer; + if (layer) { + ResetLayerStateForRecycling(layer); + layer->ClearExtraDumpInfo(); + } else { + // Create a new layer + layer = mManager->CreateImageLayer(); + if (!layer) { + return nullptr; + } + // Mark this layer as being used for painting display items + data->mImageLayer = layer; + layer->SetUserData(&gImageLayerUserData, nullptr); + + // Remove other layer types we might have stored for this PaintedLayer + data->mColorLayer = nullptr; + } + return layer.forget(); +} + +template <typename UserData> +already_AddRefed<ImageLayer> ContainerState::CreateOrRecycleMaskImageLayerFor( + const MaskLayerKey& aKey, UserData* (*aGetUserData)(Layer* aLayer), + void (*aSetDefaultUserData)(Layer* aLayer)) { + RefPtr<ImageLayer> result = mRecycledMaskImageLayers.Get(aKey); + + if (result && aGetUserData(result.get())) { + mRecycledMaskImageLayers.Remove(aKey); + aKey.mLayer->ClearExtraDumpInfo(); + // XXX if we use clip on mask layers, null it out here + } else { + // Create a new layer + result = mManager->CreateImageLayer(); + if (!result) { + return nullptr; + } + aSetDefaultUserData(result); + } + + return result.forget(); +} + +static const double SUBPIXEL_OFFSET_EPSILON = 0.02; + +/** + * This normally computes NSToIntRoundUp(aValue). However, if that would + * give a residual near 0.5 while aOldResidual is near -0.5, or + * it would give a residual near -0.5 while aOldResidual is near 0.5, then + * instead we return the integer in the other direction so that the residual + * is close to aOldResidual. + */ +static int32_t RoundToMatchResidual(double aValue, double aOldResidual) { + int32_t v = NSToIntRoundUp(aValue); + double residual = aValue - v; + if (aOldResidual < 0) { + if (residual > 0 && + fabs(residual - 1.0 - aOldResidual) < SUBPIXEL_OFFSET_EPSILON) { + // Round up instead + return int32_t(ceil(aValue)); + } + } else if (aOldResidual > 0) { + if (residual < 0 && + fabs(residual + 1.0 - aOldResidual) < SUBPIXEL_OFFSET_EPSILON) { + // Round down instead + return int32_t(floor(aValue)); + } + } + return v; +} + +static void ResetScrollPositionForLayerPixelAlignment( + AnimatedGeometryRoot* aAnimatedGeometryRoot) { + nsIScrollableFrame* sf = + nsLayoutUtils::GetScrollableFrameFor(*aAnimatedGeometryRoot); + if (sf) { + sf->ResetScrollPositionForLayerPixelAlignment(); + } +} + +static void InvalidateEntirePaintedLayer( + PaintedLayer* aLayer, AnimatedGeometryRoot* aAnimatedGeometryRoot, + const char* aReason) { +#ifdef MOZ_DUMP_PAINTING + if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) { + printf_stderr("Invalidating entire layer %p: %s\n", aLayer, aReason); + } +#endif + aLayer->InvalidateWholeLayer(); + aLayer->SetInvalidRectToVisibleRegion(); + ResetScrollPositionForLayerPixelAlignment(aAnimatedGeometryRoot); +} + +LayerManager::PaintedLayerCreationHint ContainerState::GetLayerCreationHint( + AnimatedGeometryRoot* aAnimatedGeometryRoot) { + // Check whether the layer will be scrollable. This is used as a hint to + // influence whether tiled layers are used or not. + + // Check creation hint inherited from our parent. + if (mParameters.mLayerCreationHint == LayerManager::SCROLLABLE) { + return LayerManager::SCROLLABLE; + } + + // Check whether there's any active scroll frame on the animated geometry + // root chain. + for (AnimatedGeometryRoot* agr = aAnimatedGeometryRoot; + agr && agr != mContainerAnimatedGeometryRoot; agr = agr->mParentAGR) { + nsIFrame* fParent = nsLayoutUtils::GetCrossDocParentFrame(*agr); + if (!fParent) { + break; + } + nsIScrollableFrame* scrollable = do_QueryFrame(fParent); + if (scrollable) { + return LayerManager::SCROLLABLE; + } + } + return LayerManager::NONE; +} + +already_AddRefed<PaintedLayer> ContainerState::AttemptToRecyclePaintedLayer( + AnimatedGeometryRoot* aAnimatedGeometryRoot, nsDisplayItem* aItem, + const nsPoint& aTopLeft, const nsIFrame* aReferenceFrame) { + Layer* oldLayer = mLayerBuilder->GetOldLayerFor(aItem); + if (!oldLayer || !oldLayer->AsPaintedLayer()) { + return nullptr; + } + + if (!mPaintedLayersAvailableForRecycling.EnsureRemoved( + oldLayer->AsPaintedLayer())) { + // Not found. + return nullptr; + } + + // Try to recycle the layer. + RefPtr<PaintedLayer> layer = oldLayer->AsPaintedLayer(); + + // Check if the layer hint has changed and whether or not the layer should + // be recreated because of it. + if (!layer->IsOptimizedFor(GetLayerCreationHint(aAnimatedGeometryRoot))) { + return nullptr; + } + + bool didResetScrollPositionForLayerPixelAlignment = false; + PaintedDisplayItemLayerUserData* data = + RecyclePaintedLayer(layer, aAnimatedGeometryRoot, + didResetScrollPositionForLayerPixelAlignment); + PreparePaintedLayerForUse(layer, data, aAnimatedGeometryRoot, aReferenceFrame, + aTopLeft, + didResetScrollPositionForLayerPixelAlignment); + + return layer.forget(); +} + +static void ReleaseLayerUserData(void* aData) { + PaintedDisplayItemLayerUserData* userData = + static_cast<PaintedDisplayItemLayerUserData*>(aData); + userData->Release(); +} + +already_AddRefed<PaintedLayer> ContainerState::CreatePaintedLayer( + PaintedLayerData* aData) { + LayerManager::PaintedLayerCreationHint creationHint = + GetLayerCreationHint(aData->mAnimatedGeometryRoot); + + // Create a new painted layer + RefPtr<PaintedLayer> layer = + mManager->CreatePaintedLayerWithHint(creationHint); + if (!layer) { + return nullptr; + } + + // Mark this layer as being used for painting display items + RefPtr<PaintedDisplayItemLayerUserData> userData = + new PaintedDisplayItemLayerUserData(); + userData->mDisabledAlpha = + mParameters.mDisableSubpixelAntialiasingInDescendants; + userData.get()->AddRef(); + layer->SetUserData(&gPaintedDisplayItemLayerUserData, userData, + ReleaseLayerUserData); + ResetScrollPositionForLayerPixelAlignment(aData->mAnimatedGeometryRoot); + + PreparePaintedLayerForUse(layer, userData, aData->mAnimatedGeometryRoot, + aData->mReferenceFrame, + aData->mAnimatedGeometryRootOffset, true); + + return layer.forget(); +} + +PaintedDisplayItemLayerUserData* ContainerState::RecyclePaintedLayer( + PaintedLayer* aLayer, AnimatedGeometryRoot* aAnimatedGeometryRoot, + bool& didResetScrollPositionForLayerPixelAlignment) { + // Clear clip rect and mask layer so we don't accidentally stay clipped. + // We will reapply any necessary clipping. + ResetLayerStateForRecycling(aLayer); + aLayer->ClearExtraDumpInfo(); + + auto* data = GetPaintedDisplayItemLayerUserData(aLayer); + NS_ASSERTION(data, "Recycled PaintedLayers must have user data"); + + // This gets called on recycled PaintedLayers that are going to be in the + // final layer tree, so it's a convenient time to invalidate the + // content that changed where we don't know what PaintedLayer it belonged + // to, or if we need to invalidate the entire layer, we can do that. + // This needs to be done before we update the PaintedLayer to its new + // transform. See nsGfxScrollFrame::InvalidateInternal, where + // we ensure that mInvalidPaintedContent is updated according to the + // scroll position as of the most recent paint. + if (!FuzzyEqual(data->mXScale, mParameters.mXScale, 0.00001f) || + !FuzzyEqual(data->mYScale, mParameters.mYScale, 0.00001f) || + data->mAppUnitsPerDevPixel != mAppUnitsPerDevPixel) { +#ifdef MOZ_DUMP_PAINTING + if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) { + printf_stderr("Recycled layer %p changed scale\n", aLayer); + } +#endif + InvalidateEntirePaintedLayer(aLayer, aAnimatedGeometryRoot, + "recycled layer changed state"); + didResetScrollPositionForLayerPixelAlignment = true; + } + if (!data->mRegionToInvalidate.IsEmpty()) { +#ifdef MOZ_DUMP_PAINTING + if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) { + printf_stderr("Invalidating deleted frame content from layer %p\n", + aLayer); + } +#endif + aLayer->InvalidateRegion(data->mRegionToInvalidate); +#ifdef MOZ_DUMP_PAINTING + if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) { + nsAutoCString str; + AppendToString(str, data->mRegionToInvalidate); + printf_stderr("Invalidating layer %p: %s\n", aLayer, str.get()); + } +#endif + data->mRegionToInvalidate.SetEmpty(); + } + return data; +} + +void ContainerState::PreparePaintedLayerForUse( + PaintedLayer* aLayer, PaintedDisplayItemLayerUserData* aData, + AnimatedGeometryRoot* aAnimatedGeometryRoot, + const nsIFrame* aReferenceFrame, const nsPoint& aTopLeft, + bool didResetScrollPositionForLayerPixelAlignment) { + aData->mXScale = mParameters.mXScale; + aData->mYScale = mParameters.mYScale; + aData->mLastAnimatedGeometryRootOrigin = aData->mAnimatedGeometryRootOrigin; + aData->mAnimatedGeometryRootOrigin = aTopLeft; + aData->mAppUnitsPerDevPixel = mAppUnitsPerDevPixel; + aLayer->SetAllowResidualTranslation(mParameters.AllowResidualTranslation()); + + // Set up transform so that 0,0 in the PaintedLayer corresponds to the + // (pixel-snapped) top-left of the aAnimatedGeometryRoot. + nsPoint offset = + (*aAnimatedGeometryRoot)->GetOffsetToCrossDoc(aReferenceFrame); + nscoord appUnitsPerDevPixel = + (*aAnimatedGeometryRoot)->PresContext()->AppUnitsPerDevPixel(); + gfxPoint scaledOffset( + NSAppUnitsToDoublePixels(offset.x, appUnitsPerDevPixel) * + mParameters.mXScale, + NSAppUnitsToDoublePixels(offset.y, appUnitsPerDevPixel) * + mParameters.mYScale); + // We call RoundToMatchResidual here so that the residual after rounding + // is close to aData->mAnimatedGeometryRootPosition if possible. + nsIntPoint pixOffset( + RoundToMatchResidual(scaledOffset.x, + aData->mAnimatedGeometryRootPosition.x), + RoundToMatchResidual(scaledOffset.y, + aData->mAnimatedGeometryRootPosition.y)); + aData->mTranslation = pixOffset; + pixOffset += mParameters.mOffset; + Matrix matrix = Matrix::Translation(pixOffset.x, pixOffset.y); + aLayer->SetBaseTransform(Matrix4x4::From2D(matrix)); + + aData->mVisibilityComputedRegion.SetEmpty(); + + // Calculate exact position of the top-left of the active scrolled root. + // This might not be 0,0 due to the snapping in ScaleToNearestPixels. + gfxPoint animatedGeometryRootTopLeft = + scaledOffset - ThebesPoint(matrix.GetTranslation()) + mParameters.mOffset; + const bool disableAlpha = + mParameters.mDisableSubpixelAntialiasingInDescendants; + if (aData->mDisabledAlpha != disableAlpha) { + aData->mAnimatedGeometryRootPosition = animatedGeometryRootTopLeft; + InvalidateEntirePaintedLayer(aLayer, aAnimatedGeometryRoot, + "change of subpixel-AA"); + aData->mDisabledAlpha = disableAlpha; + return; + } + + // FIXME: Temporary workaround for bug 681192 and bug 724786. +#ifndef MOZ_WIDGET_ANDROID + // If it has changed, then we need to invalidate the entire layer since the + // pixels in the layer buffer have the content at a (subpixel) offset + // from what we need. + if (!animatedGeometryRootTopLeft.WithinEpsilonOf( + aData->mAnimatedGeometryRootPosition, SUBPIXEL_OFFSET_EPSILON)) { + aData->mAnimatedGeometryRootPosition = animatedGeometryRootTopLeft; + InvalidateEntirePaintedLayer(aLayer, aAnimatedGeometryRoot, + "subpixel offset"); + } else if (didResetScrollPositionForLayerPixelAlignment) { + aData->mAnimatedGeometryRootPosition = animatedGeometryRootTopLeft; + } +#else + Unused << didResetScrollPositionForLayerPixelAlignment; +#endif +} + +#if defined(DEBUG) || defined(MOZ_DUMP_PAINTING) +/** + * Returns the appunits per dev pixel for the item's frame + */ +static int32_t AppUnitsPerDevPixel(nsDisplayItem* aItem) { + // The underlying frame for zoom items is the root frame of the subdocument. + // But zoom display items report their bounds etc using the parent document's + // APD because zoom items act as a conversion layer between the two different + // APDs. + if (aItem->GetType() == DisplayItemType::TYPE_ZOOM) { + return static_cast<nsDisplayZoom*>(aItem)->GetParentAppUnitsPerDevPixel(); + } + return aItem->Frame()->PresContext()->AppUnitsPerDevPixel(); +} +#endif + +/** + * Set the visible region for aLayer. + * aOuterVisibleRegion is the visible region relative to the parent layer. + * aLayerContentsVisibleRect, if non-null, is a rectangle in the layer's + * own coordinate system to which the layer's visible region is restricted. + * Consumes *aOuterVisibleRegion. + */ +static void SetOuterVisibleRegion( + Layer* aLayer, nsIntRegion* aOuterVisibleRegion, + const nsIntRect* aLayerContentsVisibleRect = nullptr, + bool aOuterUntransformed = false) { + Matrix4x4 transform = aLayer->GetTransform(); + Matrix transform2D; + if (aOuterUntransformed) { + if (aLayerContentsVisibleRect) { + aOuterVisibleRegion->And(*aOuterVisibleRegion, + *aLayerContentsVisibleRect); + } + } else if (transform.Is2D(&transform2D) && + !transform2D.HasNonIntegerTranslation()) { + aOuterVisibleRegion->MoveBy(-int(transform2D._31), -int(transform2D._32)); + if (aLayerContentsVisibleRect) { + aOuterVisibleRegion->And(*aOuterVisibleRegion, + *aLayerContentsVisibleRect); + } + } else { + nsIntRect outerRect = aOuterVisibleRegion->GetBounds(); + // if 'transform' is not invertible, then nothing will be displayed + // for the layer, so it doesn't really matter what we do here + Rect outerVisible(outerRect.x, outerRect.y, outerRect.width, + outerRect.height); + transform.Invert(); + + Rect layerContentsVisible = Rect::MaxIntRect(); + + if (aLayerContentsVisibleRect) { + NS_ASSERTION(aLayerContentsVisibleRect->width >= 0 && + aLayerContentsVisibleRect->height >= 0, + "Bad layer contents rectangle"); + // restrict to aLayerContentsVisibleRect before call GfxRectToIntRect, + // in case layerVisible is extremely large (as it can be when + // projecting through the inverse of a 3D transform) + layerContentsVisible = Rect( + aLayerContentsVisibleRect->x, aLayerContentsVisibleRect->y, + aLayerContentsVisibleRect->width, aLayerContentsVisibleRect->height); + } + + Rect layerVisible = + transform.ProjectRectBounds(outerVisible, layerContentsVisible); + + layerVisible.RoundOut(); + + IntRect intRect; + if (!layerVisible.ToIntRect(&intRect)) { + intRect = IntRect::MaxIntRect(); + } + + *aOuterVisibleRegion = intRect; + } + + aLayer->SetVisibleRegion( + LayerIntRegion::FromUnknownRegion(*aOuterVisibleRegion)); +} + +void ContainerState::SetOuterVisibleRegionForLayer( + Layer* aLayer, const nsIntRegion& aOuterVisibleRegion, + const nsIntRect* aLayerContentsVisibleRect, + bool aOuterUntransformed) const { + nsIntRegion visRegion = aOuterVisibleRegion; + if (!aOuterUntransformed) { + visRegion.MoveBy(mParameters.mOffset); + } + SetOuterVisibleRegion(aLayer, &visRegion, aLayerContentsVisibleRect, + aOuterUntransformed); +} + +nscolor ContainerState::FindOpaqueBackgroundColorInLayer( + const PaintedLayerData* aData, const nsIntRect& aRect, + bool* aOutIntersectsLayer) const { + *aOutIntersectsLayer = true; + + // Scan the candidate's display items. + nsIntRect deviceRect = aRect; + nsRect appUnitRect = ToAppUnits(deviceRect, mAppUnitsPerDevPixel); + appUnitRect.ScaleInverseRoundOut(mParameters.mXScale, mParameters.mYScale); + + for (auto& assignedItem : Reversed(aData->mAssignedDisplayItems)) { + if (assignedItem.HasOpacity() || assignedItem.HasTransform()) { + // We cannot easily calculate the opaque background color for items inside + // a flattened effect. + continue; + } + + if (IsEffectEndMarker(assignedItem.mType)) { + // An optimization: the underlying display item for effect markers is the + // same for both start and end markers. Skip the effect end markers. + continue; + } + + nsDisplayItem* item = assignedItem.mItem; + bool snap; + nsRect bounds = item->GetBounds(mBuilder, &snap); + if (snap && mSnappingEnabled) { + nsIntRect snappedBounds = ScaleToNearestPixels(bounds); + if (!snappedBounds.Intersects(deviceRect)) continue; + + if (!snappedBounds.Contains(deviceRect)) return NS_RGBA(0, 0, 0, 0); + + } else { + // The layer's visible rect is already (close enough to) pixel + // aligned, so no need to round out and in here. + if (!bounds.Intersects(appUnitRect)) continue; + + if (!bounds.Contains(appUnitRect)) return NS_RGBA(0, 0, 0, 0); + } + + if (item->IsInvisibleInRect(appUnitRect)) { + continue; + } + + if (item->GetClip().IsRectAffectedByClip(deviceRect, mParameters.mXScale, + mParameters.mYScale, + mAppUnitsPerDevPixel)) { + return NS_RGBA(0, 0, 0, 0); + } + + MOZ_ASSERT(!assignedItem.HasOpacity() && !assignedItem.HasTransform()); + Maybe<nscolor> color = item->IsUniform(mBuilder); + + if (color && NS_GET_A(*color) == 255) { + return *color; + } + + return NS_RGBA(0, 0, 0, 0); + } + + *aOutIntersectsLayer = false; + return NS_RGBA(0, 0, 0, 0); +} + +nscolor PaintedLayerDataNode::FindOpaqueBackgroundColor( + const nsIntRegion& aTargetVisibleRegion, int32_t aUnderIndex) const { + if (aUnderIndex == ABOVE_TOP) { + aUnderIndex = mPaintedLayerDataStack.Length(); + } + for (int32_t i = aUnderIndex - 1; i >= 0; --i) { + const PaintedLayerData* candidate = &mPaintedLayerDataStack[i]; + if (candidate->VisibleAboveRegionIntersects(aTargetVisibleRegion)) { + // Some non-PaintedLayer content between target and candidate; this is + // hopeless + return NS_RGBA(0, 0, 0, 0); + } + + if (!candidate->VisibleRegionIntersects(aTargetVisibleRegion)) { + // The layer doesn't intersect our target, ignore it and move on + continue; + } + + bool intersectsLayer = true; + nsIntRect rect = aTargetVisibleRegion.GetBounds(); + nscolor color = mTree.ContState().FindOpaqueBackgroundColorInLayer( + candidate, rect, &intersectsLayer); + if (!intersectsLayer) { + continue; + } + return color; + } + if (mAllDrawingAboveBackground || + !mVisibleAboveBackgroundRegion.Intersect(aTargetVisibleRegion) + .IsEmpty()) { + // Some non-PaintedLayer content is between this node's background and + // target. + return NS_RGBA(0, 0, 0, 0); + } + return FindOpaqueBackgroundColorInParentNode(); +} + +nscolor PaintedLayerDataNode::FindOpaqueBackgroundColorCoveringEverything() + const { + if (!mPaintedLayerDataStack.IsEmpty() || mAllDrawingAboveBackground || + !mVisibleAboveBackgroundRegion.IsEmpty()) { + return NS_RGBA(0, 0, 0, 0); + } + return FindOpaqueBackgroundColorInParentNode(); +} + +nscolor PaintedLayerDataNode::FindOpaqueBackgroundColorInParentNode() const { + if (mParent) { + if (mHasClip) { + // Check whether our parent node has uniform content behind our whole + // clip. + // There's one tricky case here: If our parent node is also a scrollable, + // and is currently scrolled in such a way that this inner one is + // clipped by it, then it's not really clear how we should determine + // whether we have a uniform background in the parent: There might be + // non-uniform content in the parts that our scroll port covers in the + // parent and that are currently outside the parent's clip. + // For now, we'll fail to pull a background color in that case. + return mParent->FindOpaqueBackgroundColor(mClipRect); + } + return mParent->FindOpaqueBackgroundColorCoveringEverything(); + } + // We are the root. + return mTree.UniformBackgroundColor(); +} + +bool PaintedLayerData::CanOptimizeToImageLayer(nsDisplayListBuilder* aBuilder) { + if (!mImage) { + return false; + } + + return mImage->CanOptimizeToImageLayer(mLayer->Manager(), aBuilder); +} + +already_AddRefed<ImageContainer> PaintedLayerData::GetContainerForImageLayer( + nsDisplayListBuilder* aBuilder) { + if (!mImage) { + return nullptr; + } + + return mImage->GetContainer(mLayer->Manager(), aBuilder); +} + +PaintedLayerDataNode::PaintedLayerDataNode( + PaintedLayerDataTree& aTree, PaintedLayerDataNode* aParent, + AnimatedGeometryRoot* aAnimatedGeometryRoot) + : mTree(aTree), + mParent(aParent), + mAnimatedGeometryRoot(aAnimatedGeometryRoot), + mAllDrawingAboveBackground(false) { + MOZ_ASSERT(nsLayoutUtils::IsAncestorFrameCrossDoc( + mTree.Builder()->RootReferenceFrame(), *mAnimatedGeometryRoot)); + mHasClip = mTree.IsClippedWithRespectToParentAnimatedGeometryRoot( + mAnimatedGeometryRoot, &mClipRect); +} + +PaintedLayerDataNode::~PaintedLayerDataNode() { + MOZ_ASSERT(mPaintedLayerDataStack.IsEmpty()); + MOZ_ASSERT(mChildren.IsEmpty()); +} + +PaintedLayerDataNode* PaintedLayerDataNode::AddChildNodeFor( + AnimatedGeometryRoot* aAnimatedGeometryRoot) { + MOZ_ASSERT(aAnimatedGeometryRoot->mParentAGR == mAnimatedGeometryRoot); + UniquePtr<PaintedLayerDataNode> child = + MakeUnique<PaintedLayerDataNode>(mTree, this, aAnimatedGeometryRoot); + mChildren.AppendElement(std::move(child)); + return mChildren.LastElement().get(); +} + +template <typename NewPaintedLayerCallbackType> +PaintedLayerData* PaintedLayerDataNode::FindPaintedLayerFor( + const nsIntRect& aVisibleRect, const bool aBackfaceHidden, + const ActiveScrolledRoot* aASR, const DisplayItemClipChain* aClipChain, + NewPaintedLayerCallbackType aNewPaintedLayerCallback) { + if (!mPaintedLayerDataStack.IsEmpty()) { + PaintedLayerData* lowestUsableLayer = nullptr; + for (auto& data : Reversed(mPaintedLayerDataStack)) { + if (data.mVisibleAboveRegion.Intersects(aVisibleRect)) { + break; + } + if (data.mBackfaceHidden == aBackfaceHidden && data.mASR == aASR && + data.mClipChain == aClipChain) { + lowestUsableLayer = &data; + } + // Also check whether the event-regions intersect the visible rect, + // unless we're in an inactive layer, in which case the event-regions + // will be hoisted out into their own layer. + // For performance reasons, we check the intersection with the bounds + // of the event-regions. + if (!mTree.ContState().IsInInactiveLayer() && + (data.mScaledHitRegionBounds.Intersects(aVisibleRect) || + data.mScaledMaybeHitRegionBounds.Intersects(aVisibleRect))) { + break; + } + // If the visible region intersects with the current layer then we + // can't possibly use any of the layers below it, so stop the search + // now. + // + // If we're trying to minimize painted layer size and we don't + // intersect the current visible region, then make sure we don't + // use this painted layer. + if (data.mVisibleRegion.Intersects(aVisibleRect)) { + break; + } + + if (StaticPrefs::layout_smaller_painted_layers()) { + lowestUsableLayer = nullptr; + } + } + if (lowestUsableLayer) { + return lowestUsableLayer; + } + } + PaintedLayerData* data = mPaintedLayerDataStack.AppendElement(); + aNewPaintedLayerCallback(data); + + return data; +} + +void PaintedLayerDataNode::FinishChildrenIntersecting(const nsIntRect& aRect) { + for (int32_t i = mChildren.Length() - 1; i >= 0; i--) { + if (mChildren[i]->Intersects(aRect)) { + mChildren[i]->Finish(true); + mChildren.RemoveElementAt(i); + } + } +} + +void PaintedLayerDataNode::FinishAllChildren( + bool aThisNodeNeedsAccurateVisibleAboveRegion) { + for (int32_t i = mChildren.Length() - 1; i >= 0; i--) { + mChildren[i]->Finish(aThisNodeNeedsAccurateVisibleAboveRegion); + } + mChildren.Clear(); +} + +void PaintedLayerDataNode::Finish(bool aParentNeedsAccurateVisibleAboveRegion) { + // Skip "visible above region" maintenance, because this node is going away. + FinishAllChildren(false); + + PopAllPaintedLayerData(); + + if (mParent && aParentNeedsAccurateVisibleAboveRegion) { + if (mHasClip) { + mParent->AddToVisibleAboveRegion(mClipRect); + } else { + mParent->SetAllDrawingAbove(); + } + } + mTree.NodeWasFinished(mAnimatedGeometryRoot); +} + +void PaintedLayerDataNode::AddToVisibleAboveRegion(const nsIntRect& aRect) { + nsIntRegion& visibleAboveRegion = + mPaintedLayerDataStack.IsEmpty() + ? mVisibleAboveBackgroundRegion + : mPaintedLayerDataStack.LastElement().mVisibleAboveRegion; + visibleAboveRegion.Or(visibleAboveRegion, aRect); + visibleAboveRegion.SimplifyOutward(8); +} + +void PaintedLayerDataNode::SetAllDrawingAbove() { + PopAllPaintedLayerData(); + mAllDrawingAboveBackground = true; + mVisibleAboveBackgroundRegion.SetEmpty(); +} + +void PaintedLayerDataNode::PopAllPaintedLayerData() { + for (int32_t index = mPaintedLayerDataStack.Length() - 1; index >= 0; + index--) { + PaintedLayerData& data = mPaintedLayerDataStack[index]; + mTree.ContState().FinishPaintedLayerData(data, [this, &data, index]() { + return this->FindOpaqueBackgroundColor(data.mVisibleRegion, index); + }); + } + mPaintedLayerDataStack.Clear(); +} + +void PaintedLayerDataTree::InitializeForInactiveLayer( + AnimatedGeometryRoot* aAnimatedGeometryRoot) { + mForInactiveLayer = true; + mRoot.emplace(*this, nullptr, aAnimatedGeometryRoot); +} + +nsDisplayListBuilder* PaintedLayerDataTree::Builder() const { + return mContainerState.Builder(); +} + +void PaintedLayerDataTree::Finish() { + if (mRoot) { + mRoot->Finish(false); + } + MOZ_ASSERT(mNodes.Count() == 0); + mRoot.reset(); +} + +void PaintedLayerDataTree::NodeWasFinished( + AnimatedGeometryRoot* aAnimatedGeometryRoot) { + mNodes.Remove(aAnimatedGeometryRoot); +} + +void PaintedLayerDataTree::AddingOwnLayer( + AnimatedGeometryRoot* aAnimatedGeometryRoot, const nsIntRect* aRect, + nscolor* aOutUniformBackgroundColor) { + PaintedLayerDataNode* node = nullptr; + if (mForInactiveLayer) { + node = mRoot.ptr(); + } else { + FinishPotentiallyIntersectingNodes(aAnimatedGeometryRoot, aRect); + node = EnsureNodeFor(aAnimatedGeometryRoot); + } + if (aRect) { + if (aOutUniformBackgroundColor) { + *aOutUniformBackgroundColor = node->FindOpaqueBackgroundColor(*aRect); + } + node->AddToVisibleAboveRegion(*aRect); + } else { + if (aOutUniformBackgroundColor) { + *aOutUniformBackgroundColor = + node->FindOpaqueBackgroundColorCoveringEverything(); + } + node->SetAllDrawingAbove(); + } +} + +template <typename NewPaintedLayerCallbackType> +PaintedLayerData* PaintedLayerDataTree::FindPaintedLayerFor( + AnimatedGeometryRoot* aAnimatedGeometryRoot, const ActiveScrolledRoot* aASR, + const DisplayItemClipChain* aClipChain, const nsIntRect& aVisibleRect, + const bool aBackfaceHidden, + NewPaintedLayerCallbackType aNewPaintedLayerCallback) { + const nsIntRect* bounds = &aVisibleRect; + PaintedLayerDataNode* node = nullptr; + if (mForInactiveLayer) { + node = mRoot.ptr(); + } else { + FinishPotentiallyIntersectingNodes(aAnimatedGeometryRoot, bounds); + node = EnsureNodeFor(aAnimatedGeometryRoot); + } + + PaintedLayerData* data = + node->FindPaintedLayerFor(aVisibleRect, aBackfaceHidden, aASR, aClipChain, + aNewPaintedLayerCallback); + return data; +} + +void PaintedLayerDataTree::FinishPotentiallyIntersectingNodes( + AnimatedGeometryRoot* aAnimatedGeometryRoot, const nsIntRect* aRect) { + AnimatedGeometryRoot* ancestorThatIsChildOfCommonAncestor = nullptr; + PaintedLayerDataNode* ancestorNode = FindNodeForAncestorAnimatedGeometryRoot( + aAnimatedGeometryRoot, &ancestorThatIsChildOfCommonAncestor); + if (!ancestorNode) { + // None of our ancestors are in the tree. This should only happen if this + // is the very first item we're looking at. + MOZ_ASSERT(!mRoot); + return; + } + + if (ancestorNode->GetAnimatedGeometryRoot() == aAnimatedGeometryRoot) { + // aAnimatedGeometryRoot already has a node in the tree. + // This is the common case. + MOZ_ASSERT(!ancestorThatIsChildOfCommonAncestor); + if (aRect) { + ancestorNode->FinishChildrenIntersecting(*aRect); + } else { + ancestorNode->FinishAllChildren(); + } + return; + } + + // We have found an existing ancestor, but it's a proper ancestor of our + // animated geometry root. + // ancestorThatIsChildOfCommonAncestor is the last animated geometry root + // encountered on the way up from aAnimatedGeometryRoot to ancestorNode. + MOZ_ASSERT(ancestorThatIsChildOfCommonAncestor); + MOZ_ASSERT(nsLayoutUtils::IsAncestorFrameCrossDoc( + *ancestorThatIsChildOfCommonAncestor, *aAnimatedGeometryRoot)); + MOZ_ASSERT(ancestorThatIsChildOfCommonAncestor->mParentAGR == + ancestorNode->GetAnimatedGeometryRoot()); + + // ancestorThatIsChildOfCommonAncestor is not in the tree yet! + MOZ_ASSERT(!mNodes.Get(ancestorThatIsChildOfCommonAncestor)); + + // We're about to add a node for ancestorThatIsChildOfCommonAncestor, so we + // finish all intersecting siblings. + nsIntRect clip; + if (IsClippedWithRespectToParentAnimatedGeometryRoot( + ancestorThatIsChildOfCommonAncestor, &clip)) { + ancestorNode->FinishChildrenIntersecting(clip); + } else { + ancestorNode->FinishAllChildren(); + } +} + +PaintedLayerDataNode* PaintedLayerDataTree::EnsureNodeFor( + AnimatedGeometryRoot* aAnimatedGeometryRoot) { + MOZ_ASSERT(aAnimatedGeometryRoot); + PaintedLayerDataNode* node = mNodes.Get(aAnimatedGeometryRoot); + if (node) { + return node; + } + + AnimatedGeometryRoot* parentAnimatedGeometryRoot = + aAnimatedGeometryRoot->mParentAGR; + if (!parentAnimatedGeometryRoot) { + MOZ_ASSERT(!mRoot); + MOZ_ASSERT(*aAnimatedGeometryRoot == Builder()->RootReferenceFrame()); + mRoot.emplace(*this, nullptr, aAnimatedGeometryRoot); + node = mRoot.ptr(); + } else { + PaintedLayerDataNode* parentNode = + EnsureNodeFor(parentAnimatedGeometryRoot); + MOZ_ASSERT(parentNode); + node = parentNode->AddChildNodeFor(aAnimatedGeometryRoot); + } + MOZ_ASSERT(node); + mNodes.Put(aAnimatedGeometryRoot, node); + return node; +} + +bool PaintedLayerDataTree::IsClippedWithRespectToParentAnimatedGeometryRoot( + AnimatedGeometryRoot* aAnimatedGeometryRoot, nsIntRect* aOutClip) { + if (mForInactiveLayer) { + return false; + } + nsIScrollableFrame* scrollableFrame = + nsLayoutUtils::GetScrollableFrameFor(*aAnimatedGeometryRoot); + if (!scrollableFrame) { + return false; + } + nsIFrame* scrollFrame = do_QueryFrame(scrollableFrame); + nsRect scrollPort = scrollableFrame->GetScrollPortRect() + + Builder()->ToReferenceFrame(scrollFrame); + *aOutClip = mContainerState.ScaleToNearestPixels(scrollPort); + return true; +} + +PaintedLayerDataNode* +PaintedLayerDataTree::FindNodeForAncestorAnimatedGeometryRoot( + AnimatedGeometryRoot* aAnimatedGeometryRoot, + AnimatedGeometryRoot** aOutAncestorChild) { + if (!aAnimatedGeometryRoot) { + return nullptr; + } + PaintedLayerDataNode* node = mNodes.Get(aAnimatedGeometryRoot); + if (node) { + return node; + } + *aOutAncestorChild = aAnimatedGeometryRoot; + return FindNodeForAncestorAnimatedGeometryRoot( + aAnimatedGeometryRoot->mParentAGR, aOutAncestorChild); +} + +static bool CanOptimizeAwayPaintedLayer(PaintedLayerData* aData, + FrameLayerBuilder* aLayerBuilder) { + if (!aLayerBuilder->IsBuildingRetainedLayers()) { + return false; + } + + // If there's no painted layer with valid content in it that we can reuse, + // always create a color or image layer (and potentially throw away an + // existing completely invalid painted layer). + if (aData->mLayer->GetValidRegion().IsEmpty()) { + return true; + } + + // There is an existing painted layer we can reuse. Throwing it away can make + // compositing cheaper (see bug 946952), but it might cause us to re-allocate + // the painted layer frequently due to an animation. So we only discard it if + // we're in tree compression mode, which is triggered at a low frequency. + return aLayerBuilder->CheckInLayerTreeCompressionMode(); +} + +#ifdef DEBUG +static int32_t FindIndexOfLayerIn(nsTArray<NewLayerEntry>& aArray, + Layer* aLayer) { + for (uint32_t i = 0; i < aArray.Length(); ++i) { + if (aArray[i].mLayer == aLayer) { + return i; + } + } + return -1; +} +#endif + +already_AddRefed<Layer> ContainerState::PrepareImageLayer( + PaintedLayerData* aData) { + RefPtr<ImageContainer> imageContainer = + aData->GetContainerForImageLayer(mBuilder); + if (!imageContainer) { + return nullptr; + } + + RefPtr<ImageLayer> imageLayer = CreateOrRecycleImageLayer(aData->mLayer); + imageLayer->SetContainer(imageContainer); + aData->mImage->ConfigureLayer(imageLayer, mParameters); + imageLayer->SetPostScale(mParameters.mXScale, mParameters.mYScale); + + if (aData->mItemClip->HasClip()) { + ParentLayerIntRect clip = ViewAs<ParentLayerPixel>( + ScaleToNearestPixels(aData->mItemClip->GetClipRect())); + clip.MoveBy(ViewAs<ParentLayerPixel>(mParameters.mOffset)); + imageLayer->SetClipRect(Some(clip)); + } else { + imageLayer->SetClipRect(Nothing()); + } + + FLB_LOG_PAINTED_LAYER_DECISION(aData, " Selected image layer=%p\n", + imageLayer.get()); + + return imageLayer.forget(); +} + +already_AddRefed<Layer> ContainerState::PrepareColorLayer( + PaintedLayerData* aData) { + RefPtr<ColorLayer> colorLayer = CreateOrRecycleColorLayer(aData->mLayer); + colorLayer->SetColor(ToDeviceColor(aData->mSolidColor)); + + // Copy transform + colorLayer->SetBaseTransform(aData->mLayer->GetBaseTransform()); + colorLayer->SetPostScale(aData->mLayer->GetPostXScale(), + aData->mLayer->GetPostYScale()); + + nsIntRect visibleRect = aData->mVisibleRegion.GetBounds(); + visibleRect.MoveBy(-GetTranslationForPaintedLayer(aData->mLayer)); + colorLayer->SetBounds(visibleRect); + colorLayer->SetClipRect(Nothing()); + + FLB_LOG_PAINTED_LAYER_DECISION(aData, " Selected color layer=%p\n", + colorLayer.get()); + + return colorLayer.forget(); +} + +static void SetBackfaceHiddenForLayer(bool aBackfaceHidden, Layer* aLayer) { + if (aBackfaceHidden) { + aLayer->SetContentFlags(aLayer->GetContentFlags() | + Layer::CONTENT_BACKFACE_HIDDEN); + } else { + aLayer->SetContentFlags(aLayer->GetContentFlags() & + ~Layer::CONTENT_BACKFACE_HIDDEN); + } +} + +template <typename FindOpaqueBackgroundColorCallbackType> +void ContainerState::FinishPaintedLayerData( + PaintedLayerData& aData, + FindOpaqueBackgroundColorCallbackType aFindOpaqueBackgroundColor) { + PaintedLayerData* data = &aData; + + if (!data->mLayer) { + // No layer was recycled, so we create a new one. + RefPtr<PaintedLayer> paintedLayer = CreatePaintedLayer(data); + data->mLayer = paintedLayer; + + NS_ASSERTION(FindIndexOfLayerIn(mNewChildLayers, paintedLayer) < 0, + "Layer already in list???"); + mNewChildLayers[data->mNewChildLayersIndex].mLayer = + std::move(paintedLayer); + } + + auto* userData = GetPaintedDisplayItemLayerUserData(data->mLayer); + NS_ASSERTION(userData, "where did our user data go?"); + userData->mLastItemCount = data->mAssignedDisplayItems.size(); + + NewLayerEntry* newLayerEntry = &mNewChildLayers[data->mNewChildLayersIndex]; + + RefPtr<Layer> layer; + bool canOptimizeToImageLayer = data->CanOptimizeToImageLayer(mBuilder); + + FLB_LOG_PAINTED_LAYER_DECISION(data, "Selecting layer for pld=%p\n", data); + FLB_LOG_PAINTED_LAYER_DECISION( + data, " Solid=%i, hasImage=%c, canOptimizeAwayPaintedLayer=%i\n", + data->mIsSolidColorInVisibleRegion, canOptimizeToImageLayer ? 'y' : 'n', + CanOptimizeAwayPaintedLayer(data, mLayerBuilder)); + + if ((data->mIsSolidColorInVisibleRegion || canOptimizeToImageLayer) && + CanOptimizeAwayPaintedLayer(data, mLayerBuilder)) { + NS_ASSERTION( + !(data->mIsSolidColorInVisibleRegion && canOptimizeToImageLayer), + "Can't be a solid color as well as an image!"); + + layer = canOptimizeToImageLayer ? PrepareImageLayer(data) + : PrepareColorLayer(data); + + if (layer) { + NS_ASSERTION(FindIndexOfLayerIn(mNewChildLayers, layer) < 0, + "Layer already in list???"); + NS_ASSERTION(newLayerEntry->mLayer == data->mLayer, + "Painted layer at wrong index"); + // Store optimized layer in reserved slot + NewLayerEntry* paintedLayerEntry = newLayerEntry; + newLayerEntry = &mNewChildLayers[data->mNewChildLayersIndex + 1]; + NS_ASSERTION(!newLayerEntry->mLayer, "Slot already occupied?"); + newLayerEntry->mLayer = layer; + newLayerEntry->mAnimatedGeometryRoot = data->mAnimatedGeometryRoot; + newLayerEntry->mASR = paintedLayerEntry->mASR; + newLayerEntry->mClipChain = paintedLayerEntry->mClipChain; + newLayerEntry->mScrollMetadataASR = paintedLayerEntry->mScrollMetadataASR; + + // Hide the PaintedLayer. We leave it in the layer tree so that we + // can find and recycle it later. + ParentLayerIntRect emptyRect; + data->mLayer->SetClipRect(Some(emptyRect)); + data->mLayer->SetVisibleRegion(LayerIntRegion()); + data->mLayer->InvalidateWholeLayer(); + data->mLayer->SetEventRegions(EventRegions()); + } + } + + if (!layer) { + // We couldn't optimize to an image layer or a color layer above. + layer = data->mLayer; + layer->SetClipRect(Nothing()); + FLB_LOG_PAINTED_LAYER_DECISION(data, " Selected painted layer=%p\n", + layer.get()); + } + + for (auto& item : data->mAssignedDisplayItems) { + MOZ_ASSERT(item.mItem->GetType() != + DisplayItemType::TYPE_COMPOSITOR_HITTEST_INFO); + + if (IsEffectEndMarker(item.mType)) { + // Do not invalidate for end markers. + continue; + } + + InvalidateForLayerChange(item.mItem, data->mLayer, item.mDisplayItemData); + mLayerBuilder->AddPaintedDisplayItem(data, item, layer); + item.mDisplayItemData = nullptr; + } + + if (mLayerBuilder->IsBuildingRetainedLayers()) { + newLayerEntry->mVisibleRegion = data->mVisibleRegion; + newLayerEntry->mOpaqueRegion = data->mOpaqueRegion; + newLayerEntry->mHideAllLayersBelow = data->mHideAllLayersBelow; + newLayerEntry->mOpaqueForAnimatedGeometryRootParent = + data->mOpaqueForAnimatedGeometryRootParent; + } else { + SetOuterVisibleRegionForLayer(layer, data->mVisibleRegion); + } + +#ifdef MOZ_DUMP_PAINTING + if (!data->mLog.IsEmpty()) { + PaintedLayerData* containingPld = + mLayerBuilder->GetContainingPaintedLayerData(); + if (containingPld && containingPld->mLayer) { + containingPld->mLayer->AddExtraDumpInfo(nsCString(data->mLog)); + } else { + layer->AddExtraDumpInfo(nsCString(data->mLog)); + } + } +#endif + + mLayerBuilder->AddPaintedLayerItemsEntry(userData); + + nsIntRegion transparentRegion; + transparentRegion.Sub(data->mVisibleRegion, data->mOpaqueRegion); + bool isOpaque = transparentRegion.IsEmpty(); + // For translucent PaintedLayers, try to find an opaque background + // color that covers the entire area beneath it so we can pull that + // color into this layer to make it opaque. + if (layer == data->mLayer) { + nscolor backgroundColor = NS_RGBA(0, 0, 0, 0); + if (!isOpaque) { + backgroundColor = aFindOpaqueBackgroundColor(); + if (NS_GET_A(backgroundColor) == 255) { + isOpaque = true; + } + } + + // Store the background color + if (userData->mForcedBackgroundColor != backgroundColor) { + // Invalidate the entire target PaintedLayer since we're changing + // the background color +#ifdef MOZ_DUMP_PAINTING + if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) { + printf_stderr( + "Forced background color has changed from #%08X to #%08X " + "on layer %p\n", + userData->mForcedBackgroundColor, backgroundColor, data->mLayer); + nsAutoCString str; + AppendToString(str, data->mLayer->GetValidRegion()); + printf_stderr("Invalidating layer %p: %s\n", data->mLayer, str.get()); + } +#endif + data->mLayer->InvalidateWholeLayer(); + } + userData->mForcedBackgroundColor = backgroundColor; + } else { + // mask layer for image and color layers + SetupMaskLayer(layer, *data->mItemClip); + } + + uint32_t flags = 0; + nsIWidget* widget = mContainerReferenceFrame->PresContext()->GetRootWidget(); + // See bug 941095. Not quite ready to disable this. + bool hidpi = false && widget && widget->GetDefaultScale().scale >= 2; + if (hidpi) { + flags |= Layer::CONTENT_DISABLE_SUBPIXEL_AA; + } + if (isOpaque && !data->mForceTransparentSurface) { + flags |= Layer::CONTENT_OPAQUE; + } else if (data->mNeedComponentAlpha && !hidpi) { + flags |= Layer::CONTENT_COMPONENT_ALPHA; + } + layer->SetContentFlags(flags); + + userData->mItems = std::move(data->mAssignedDisplayItems); + userData->mContainerLayerFrame = GetContainerFrame(); + + PaintedLayerData* containingPaintedLayerData = + mLayerBuilder->GetContainingPaintedLayerData(); + // If we're building layers for an inactive layer, the event regions are + // clipped to the inactive layer's clip prior to being combined into the + // event regions of the containing PLD. + // For the dispatch-to-content and maybe-hit regions, rounded corners on + // the clip are ignored, since these are approximate regions. For the + // remaining regions, rounded corners in the clip cause the region to + // be combined into the corresponding "imprecise" region of the + // containing's PLD (e.g. the maybe-hit region instead of the hit region). + const DisplayItemClip* inactiveLayerClip = + mLayerBuilder->GetInactiveLayerClip(); + if (containingPaintedLayerData) { + if (!data->mDispatchToContentHitRegion.GetBounds().IsEmpty()) { + nsRect rect = nsLayoutUtils::TransformFrameRectToAncestor( + mContainerReferenceFrame, + data->mDispatchToContentHitRegion.GetBounds(), + containingPaintedLayerData->mReferenceFrame); + if (inactiveLayerClip) { + rect = inactiveLayerClip->ApplyNonRoundedIntersection(rect); + } + containingPaintedLayerData->mDispatchToContentHitRegion.Or( + containingPaintedLayerData->mDispatchToContentHitRegion, rect); + containingPaintedLayerData->mDispatchToContentHitRegion.SimplifyOutward( + 8); + if (data->mDTCRequiresTargetConfirmation) { + containingPaintedLayerData->mDTCRequiresTargetConfirmation = true; + } + } + if (!data->mMaybeHitRegion.GetBounds().IsEmpty()) { + nsRect rect = nsLayoutUtils::TransformFrameRectToAncestor( + mContainerReferenceFrame, data->mMaybeHitRegion.GetBounds(), + containingPaintedLayerData->mReferenceFrame); + if (inactiveLayerClip) { + rect = inactiveLayerClip->ApplyNonRoundedIntersection(rect); + } + containingPaintedLayerData->mMaybeHitRegion.Or( + containingPaintedLayerData->mMaybeHitRegion, rect); + containingPaintedLayerData->mMaybeHitRegion.SimplifyOutward(8); + } + Maybe<Matrix4x4Flagged> matrixCache; + nsLayoutUtils::TransformToAncestorAndCombineRegions( + data->mHitRegion, mContainerReferenceFrame, + containingPaintedLayerData->mReferenceFrame, + &containingPaintedLayerData->mHitRegion, + &containingPaintedLayerData->mMaybeHitRegion, &matrixCache, + inactiveLayerClip); + // See the comment in nsDisplayList::AddFrame, where the touch action + // regions are handled. The same thing applies here. + bool alreadyHadRegions = + !containingPaintedLayerData->mNoActionRegion.IsEmpty() || + !containingPaintedLayerData->mHorizontalPanRegion.IsEmpty() || + !containingPaintedLayerData->mVerticalPanRegion.IsEmpty(); + nsLayoutUtils::TransformToAncestorAndCombineRegions( + data->mNoActionRegion, mContainerReferenceFrame, + containingPaintedLayerData->mReferenceFrame, + &containingPaintedLayerData->mNoActionRegion, + &containingPaintedLayerData->mDispatchToContentHitRegion, &matrixCache, + inactiveLayerClip); + nsLayoutUtils::TransformToAncestorAndCombineRegions( + data->mHorizontalPanRegion, mContainerReferenceFrame, + containingPaintedLayerData->mReferenceFrame, + &containingPaintedLayerData->mHorizontalPanRegion, + &containingPaintedLayerData->mDispatchToContentHitRegion, &matrixCache, + inactiveLayerClip); + nsLayoutUtils::TransformToAncestorAndCombineRegions( + data->mVerticalPanRegion, mContainerReferenceFrame, + containingPaintedLayerData->mReferenceFrame, + &containingPaintedLayerData->mVerticalPanRegion, + &containingPaintedLayerData->mDispatchToContentHitRegion, &matrixCache, + inactiveLayerClip); + if (alreadyHadRegions) { + containingPaintedLayerData->mDispatchToContentHitRegion.OrWith( + containingPaintedLayerData->CombinedTouchActionRegion()); + } + containingPaintedLayerData->HitRegionsUpdated(); + } else { + EventRegions regions( + ScaleRegionToOutsidePixels(data->mHitRegion), + ScaleRegionToOutsidePixels(data->mMaybeHitRegion), + ScaleRegionToOutsidePixels(data->mDispatchToContentHitRegion), + ScaleRegionToOutsidePixels(data->mNoActionRegion), + ScaleRegionToOutsidePixels(data->mHorizontalPanRegion), + ScaleRegionToOutsidePixels(data->mVerticalPanRegion), + data->mDTCRequiresTargetConfirmation); + + Matrix mat = layer->GetTransform().As2D(); + mat.Invert(); + regions.ApplyTranslationAndScale(mat._31, mat._32, mat._11, mat._22); + + layer->SetEventRegions(regions); + } + + SetBackfaceHiddenForLayer(data->mBackfaceHidden, data->mLayer); + if (layer != data->mLayer) { + SetBackfaceHiddenForLayer(data->mBackfaceHidden, layer); + } +} + +static bool IsItemAreaInWindowOpaqueRegion( + nsDisplayListBuilder* aBuilder, nsDisplayItem* aItem, + const nsRect& aComponentAlphaBounds) { + if (!aItem->Frame()->PresContext()->IsChrome()) { + // Assume that Web content is always in the window opaque region. + return true; + } + if (aItem->ReferenceFrame() != aBuilder->RootReferenceFrame()) { + // aItem is probably in some transformed subtree. + // We're not going to bother figuring out where this landed, we're just + // going to assume it might have landed over a transparent part of + // the window. + return false; + } + return aBuilder->GetWindowOpaqueRegion().Contains(aComponentAlphaBounds); +} + +void PaintedLayerData::UpdateEffectStatus(DisplayItemEntryType aType, + nsTArray<size_t>& aOpacityIndices) { + switch (aType) { + case DisplayItemEntryType::PushOpacity: + // The index of the new assigned display item in |mAssignedDisplayItems| + // array will be the current length of the array. + aOpacityIndices.AppendElement(mAssignedDisplayItems.size()); + break; + case DisplayItemEntryType::PopOpacity: + MOZ_ASSERT(!aOpacityIndices.IsEmpty()); + aOpacityIndices.RemoveLastElement(); + break; +#ifdef DEBUG + case DisplayItemEntryType::PopTransform: + MOZ_ASSERT(mTransformLevel >= 0); + mTransformLevel--; + break; + case DisplayItemEntryType::PushTransform: + mTransformLevel++; + break; +#endif + default: + break; + } +} + +bool PaintedLayerData::SetupComponentAlpha( + ContainerState* aState, nsPaintedDisplayItem* aItem, + const nsIntRect& aVisibleRect, const TransformClipNode* aTransform) { + nsRect componentAlphaBounds = + aItem->GetComponentAlphaBounds(aState->mBuilder); + + if (componentAlphaBounds.IsEmpty()) { + // The item does not require component alpha, nothing do do here. + return false; + } + + if (aTransform) { + componentAlphaBounds = aTransform->TransformRect( + componentAlphaBounds, aState->mAppUnitsPerDevPixel); + } + + const nsIntRect pixelBounds = + aState->ScaleToOutsidePixels(componentAlphaBounds, false); + + const nsIntRect visibleRect = pixelBounds.Intersect(aVisibleRect); + + if (!mOpaqueRegion.Contains(visibleRect)) { + nsRect buildingRect = aItem->GetBuildingRect(); + + if (aTransform) { + buildingRect = + aTransform->TransformRect(buildingRect, aState->mAppUnitsPerDevPixel); + } + + const nsRect tightBounds = componentAlphaBounds.Intersect(buildingRect); + + if (IsItemAreaInWindowOpaqueRegion(aState->mBuilder, aItem, tightBounds)) { + mNeedComponentAlpha = true; + } else { + // There is no opaque background below the item, disable component alpha. + aItem->DisableComponentAlpha(); + return false; + } + } + + return true; +} + +UniquePtr<InactiveLayerData> PaintedLayerData::CreateInactiveLayerData( + ContainerState* aState, nsPaintedDisplayItem* aItem, + DisplayItemData* aData) { + RefPtr<BasicLayerManager> tempManager; + if (aData) { + tempManager = aData->InactiveManager(); + } + if (!tempManager) { + tempManager = new BasicLayerManager(BasicLayerManager::BLM_INACTIVE); + } + UniquePtr<InactiveLayerData> data = MakeUnique<InactiveLayerData>(); + data->mLayerManager = tempManager; + + FrameLayerBuilder* layerBuilder = new FrameLayerBuilder(); + // Ownership of layerBuilder is passed to tempManager. + layerBuilder->Init(aState->Builder(), tempManager, this, true, + &aItem->GetClip()); + + tempManager->BeginTransaction(); + if (aState->LayerBuilder()->GetRetainingLayerManager()) { + layerBuilder->DidBeginRetainedLayerTransaction(tempManager); + } + + data->mProps = LayerProperties::CloneFrom(tempManager->GetRoot()); + data->mLayer = aItem->BuildLayer(aState->Builder(), tempManager, + ContainerLayerParameters()); + return data; +} + +void PaintedLayerData::Accumulate( + ContainerState* aState, nsPaintedDisplayItem* aItem, + const nsIntRect& aVisibleRect, const nsRect& aContentRect, + const DisplayItemClip& aClip, LayerState aLayerState, nsDisplayList* aList, + DisplayItemEntryType aType, nsTArray<size_t>& aOpacityIndices, + const RefPtr<TransformClipNode>& aTransform) { + // If aItem is nullptr, the cast to nsPaintedDisplayItem failed. + MOZ_ASSERT(aItem, "Can only accumulate display items that are painted!"); + + FLB_LOG_PAINTED_LAYER_DECISION( + this, "Accumulating dp=%s(%p), f=%p against pld=%p\n", aItem->Name(), + aItem, aItem->Frame(), this); + + const bool hasOpacity = aOpacityIndices.Length() > 0; + UpdateEffectStatus(aType, aOpacityIndices); + + const DisplayItemClip* oldClip = mItemClip; + mItemClip = &aClip; + + const bool isMerged = aItem->AsDisplayWrapList() && + aItem->AsDisplayWrapList()->HasMergedFrames(); + + if (IsEffectEndMarker(aType)) { + mAssignedDisplayItems.emplace_back(aItem, aLayerState, nullptr, + aContentRect, aType, hasOpacity, + aTransform, isMerged); + return; + } + + bool clipMatches = + (oldClip == mItemClip) || (oldClip && *oldClip == *mItemClip); + + DisplayItemData* currentData = + isMerged ? nullptr : aItem->GetDisplayItemData(); + + DisplayItemData* oldData = aState->mLayerBuilder->GetOldLayerForFrame( + aItem->Frame(), aItem->GetPerFrameKey(), currentData, + aItem->GetDisplayItemDataLayerManager()); + + mAssignedDisplayItems.emplace_back(aItem, aLayerState, oldData, aContentRect, + aType, hasOpacity, aTransform, isMerged); + + if (aLayerState != LayerState::LAYER_NONE) { + FLB_LOG_PAINTED_LAYER_DECISION(this, "Creating nested FLB for item %p\n", + aItem); + mAssignedDisplayItems.back().mInactiveLayerData = + CreateInactiveLayerData(aState, aItem, oldData); + } + + if (aState->mBuilder->NeedToForceTransparentSurfaceForItem(aItem)) { + mForceTransparentSurface = true; + } + + if (aState->mParameters.mDisableSubpixelAntialiasingInDescendants) { + // Disable component alpha. + // Note that the transform (if any) on the PaintedLayer is always an integer + // translation so we don't have to factor that in here. + aItem->DisableComponentAlpha(); + } else { + const bool needsComponentAlpha = + SetupComponentAlpha(aState, aItem, aVisibleRect, aTransform); + + if (needsComponentAlpha) { + // This display item needs background copy when pushing opacity group. + for (size_t i : aOpacityIndices) { + AssignedDisplayItem& item = mAssignedDisplayItems[i]; + MOZ_ASSERT(item.mType == DisplayItemEntryType::PushOpacity || + item.mType == DisplayItemEntryType::PushOpacityWithBg); + item.mType = DisplayItemEntryType::PushOpacityWithBg; + } + } + } + + if (aTransform && aType == DisplayItemEntryType::Item) { + // Bounds transformed with axis-aligned transforms could be included in the + // opaque region calculations. For simplicity, this is currently not done. + return; + } + + if (!mIsSolidColorInVisibleRegion && mOpaqueRegion.Contains(aVisibleRect) && + mVisibleRegion.Contains(aVisibleRect) && !mImage) { + // A very common case! Most pages have a PaintedLayer with the page + // background (opaque) visible and most or all of the page content over the + // top of that background. + // The rest of this method won't do anything. mVisibleRegion and + // mOpaqueRegion don't need updating. mVisibleRegion contains aVisibleRect + // already, mOpaqueRegion contains aVisibleRect and therefore whatever the + // opaque region of the item is. mVisibleRegion must contain mOpaqueRegion + // and therefore aVisibleRect. + return; + } + + nsIntRegion opaquePixels; + + // Active opacity means no opaque pixels. + if (!hasOpacity) { + opaquePixels = aState->ComputeOpaqueRect( + aItem, mAnimatedGeometryRoot, mASR, aClip, aList, &mHideAllLayersBelow, + &mOpaqueForAnimatedGeometryRootParent); + opaquePixels.AndWith(aVisibleRect); + } + + /* Mark as available for conversion to image layer if this is a nsDisplayImage + * and it's the only thing visible in this layer. + */ + if (nsIntRegion(aVisibleRect).Contains(mVisibleRegion) && + opaquePixels.Contains(mVisibleRegion) && + aItem->SupportsOptimizingToImage()) { + mImage = static_cast<nsDisplayImageContainer*>(aItem); + FLB_LOG_PAINTED_LAYER_DECISION( + this, " Tracking image: nsDisplayImageContainer covers the layer\n"); + } else if (mImage) { + FLB_LOG_PAINTED_LAYER_DECISION(this, " No longer tracking image\n"); + mImage = nullptr; + } + + bool isFirstVisibleItem = mVisibleRegion.IsEmpty(); + + Maybe<nscolor> uniformColor; + if (!hasOpacity) { + uniformColor = aItem->IsUniform(aState->mBuilder); + } + + // Some display items have to exist (so they can set forceTransparentSurface + // below) but don't draw anything. They'll return true for isUniform but + // a color with opacity 0. + if (!uniformColor || NS_GET_A(*uniformColor) > 0) { + // Make sure that the visible area is covered by uniform pixels. In + // particular this excludes cases where the edges of the item are not + // pixel-aligned (thus the item will not be truly uniform). + if (uniformColor) { + bool snap; + nsRect bounds = aItem->GetBounds(aState->mBuilder, &snap); + if (!aState->ScaleToInsidePixels(bounds, snap).Contains(aVisibleRect)) { + uniformColor = Nothing(); + FLB_LOG_PAINTED_LAYER_DECISION( + this, " Display item does not cover the visible rect\n"); + } + } + if (uniformColor) { + if (isFirstVisibleItem) { + // This color is all we have + mSolidColor = *uniformColor; + mIsSolidColorInVisibleRegion = true; + } else if (mIsSolidColorInVisibleRegion && + mVisibleRegion.IsEqual(nsIntRegion(aVisibleRect)) && + clipMatches) { + // we can just blend the colors together + mSolidColor = NS_ComposeColors(mSolidColor, *uniformColor); + } else { + FLB_LOG_PAINTED_LAYER_DECISION( + this, " Layer not a solid color: Can't blend colors togethers\n"); + mIsSolidColorInVisibleRegion = false; + } + } else { + FLB_LOG_PAINTED_LAYER_DECISION(this, + " Layer is not a solid color: Display " + "item is not uniform over the visible " + "bound\n"); + mIsSolidColorInVisibleRegion = false; + } + + mVisibleRegion.Or(mVisibleRegion, aVisibleRect); + mVisibleRegion.SimplifyOutward(4); + } + + if (!opaquePixels.IsEmpty()) { + for (auto iter = opaquePixels.RectIter(); !iter.Done(); iter.Next()) { + // We don't use SimplifyInward here since it's not defined exactly + // what it will discard. For our purposes the most important case + // is a large opaque background at the bottom of z-order (e.g., + // a canvas background), so we need to make sure that the first rect + // we see doesn't get discarded. + nsIntRegion tmp; + tmp.Or(mOpaqueRegion, iter.Get()); + // Opaque display items in chrome documents whose window is partially + // transparent are always added to the opaque region. This helps ensure + // that we get as much subpixel-AA as possible in the chrome. + if (tmp.GetNumRects() <= 4 || aItem->Frame()->PresContext()->IsChrome()) { + mOpaqueRegion = std::move(tmp); + } + } + } +} + +nsRegion PaintedLayerData::CombinedTouchActionRegion() { + nsRegion result; + result.Or(mHorizontalPanRegion, mVerticalPanRegion); + result.OrWith(mNoActionRegion); + return result; +} + +void PaintedLayerData::AccumulateHitTestItem(ContainerState* aState, + nsDisplayItem* aItem, + const DisplayItemClip& aClip, + TransformClipNode* aTransform) { + auto* item = static_cast<nsDisplayHitTestInfoBase*>(aItem); + const HitTestInfo& info = item->GetHitTestInfo(); + + nsRect area = info.mArea; + const CompositorHitTestInfo& flags = info.mFlags; + + FLB_LOG_PAINTED_LAYER_DECISION( + this, + "Accumulating hit test info %p against pld=%p, " + "area: [%d, %d, %d, %d], flags: 0x%x]\n", + item, this, area.x, area.y, area.width, area.height, flags.serialize()); + + area = aClip.ApplyNonRoundedIntersection(area); + + if (aTransform) { + area = aTransform->TransformRect(area, aState->mAppUnitsPerDevPixel); + } + + if (area.IsEmpty()) { + FLB_LOG_PAINTED_LAYER_DECISION( + this, "Discarded empty hit test info %p for pld=%p\n", item, this); + return; + } + + bool hasRoundedCorners = aClip.GetRoundedRectCount() > 0; + + // use the NS_FRAME_SIMPLE_EVENT_REGIONS to avoid calling the slightly + // expensive HasNonZeroCorner function if we know from a previous run that + // the frame has zero corners. + nsIFrame* frame = item->Frame(); + bool simpleRegions = frame->HasAnyStateBits(NS_FRAME_SIMPLE_EVENT_REGIONS); + if (!simpleRegions) { + if (nsLayoutUtils::HasNonZeroCorner(frame->StyleBorder()->mBorderRadius)) { + hasRoundedCorners = true; + } else { + frame->AddStateBits(NS_FRAME_SIMPLE_EVENT_REGIONS); + } + } + + if (hasRoundedCorners || frame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) { + mMaybeHitRegion.OrWith(area); + } else { + mHitRegion.OrWith(area); + } + + const auto dtcFlags = flags & CompositorHitTestDispatchToContent; + if (!dtcFlags.isEmpty()) { + mDispatchToContentHitRegion.OrWith(area); + + if (flags.contains(CompositorHitTestFlags::eRequiresTargetConfirmation)) { + mDTCRequiresTargetConfirmation = true; + } + } + + const auto touchFlags = flags & CompositorHitTestTouchActionMask; + if (!touchFlags.isEmpty()) { + // If there are multiple touch-action areas, there are multiple elements + // with touch-action properties. We don't know what the relationship is + // between those elements in terms of DOM ancestry, and so we don't know how + // to combine the regions properly. Instead, we just add all the areas to + // the dispatch-to-content region, so that the APZ knows to check with the + // main thread. See bug 1286957. + if (mCollapsedTouchActions) { + mDispatchToContentHitRegion.OrWith(area); + } else if (touchFlags == CompositorHitTestTouchActionMask) { + // everything was disabled, so touch-action:none + mNoActionRegion.OrWith(area); + } else { + // The event regions code does not store enough information to actually + // represent all the different states. Prior to the introduction of + // CompositorHitTestInfo here in bug 1389149, the following two cases + // were effectively getting collapsed: + // (1) touch-action: auto + // (2) touch-action: manipulation + // In both of these cases, none of {mNoActionRegion, mHorizontalPanRegion, + // mVerticalPanRegion} were modified, and so the fact that case (2) should + // have prevented double-tap-zooming was getting lost. + // With CompositorHitTestInfo we can now represent that case correctly, + // but only if we use CompositorHitTestInfo all the way to the compositor + // (i.e. in the WebRender-enabled case). In the non-WebRender case where + // we still use the event regions, we must collapse these two cases back + // together. Or add another region to the event regions to fix this + // properly. + if (touchFlags != + CompositorHitTestFlags::eTouchActionDoubleTapZoomDisabled) { + if (!flags.contains(CompositorHitTestFlags::eTouchActionPanXDisabled)) { + // pan-x is allowed + mHorizontalPanRegion.OrWith(area); + } + if (!flags.contains(CompositorHitTestFlags::eTouchActionPanYDisabled)) { + // pan-y is allowed + mVerticalPanRegion.OrWith(area); + } + } else { + // the touch-action: manipulation case described above. To preserve the + // existing behaviour, don't touch either mHorizontalPanRegion or + // mVerticalPanRegion + } + } + } + + if (!mCollapsedTouchActions) { + // If there are multiple touch-action areas, there are multiple elements + // with touch-action properties. We don't know what the relationship is + // between those elements in terms of DOM ancestry, and so we don't know how + // to combine the regions properly. Instead, we just add all the areas to + // the dispatch-to-content region, so that the APZ knows to check with the + // main thread. See bug 1286957. + const int alreadyHadRegions = mNoActionRegion.GetNumRects() + + mHorizontalPanRegion.GetNumRects() + + mVerticalPanRegion.GetNumRects(); + + if (alreadyHadRegions > 1) { + mDispatchToContentHitRegion.OrWith(CombinedTouchActionRegion()); + mNoActionRegion.SetEmpty(); + mHorizontalPanRegion.SetEmpty(); + mVerticalPanRegion.SetEmpty(); + mCollapsedTouchActions = true; + } + } + + // Avoid quadratic performance as a result of the region growing to include + // and arbitrarily large number of rects, which can happen on some pages. + mMaybeHitRegion.SimplifyOutward(8); + mDispatchToContentHitRegion.SimplifyOutward(8); + + HitRegionsUpdated(); +} + +void PaintedLayerData::HitRegionsUpdated() { + // Calculate scaled versions of the bounds of mHitRegion and mMaybeHitRegion + // for quick access in FindPaintedLayerFor(). + mScaledHitRegionBounds = mState->ScaleToOutsidePixels(mHitRegion.GetBounds()); + mScaledMaybeHitRegionBounds = + mState->ScaleToOutsidePixels(mMaybeHitRegion.GetBounds()); +} + +void ContainerState::NewPaintedLayerData( + PaintedLayerData* aData, AnimatedGeometryRoot* aAnimatedGeometryRoot, + const ActiveScrolledRoot* aASR, const DisplayItemClipChain* aClipChain, + const ActiveScrolledRoot* aScrollMetadataASR, const nsPoint& aTopLeft, + const nsIFrame* aReferenceFrame, const bool aBackfaceHidden) { + aData->mState = this; + aData->mAnimatedGeometryRoot = aAnimatedGeometryRoot; + aData->mASR = aASR; + aData->mClipChain = aClipChain; + aData->mAnimatedGeometryRootOffset = aTopLeft; + aData->mReferenceFrame = aReferenceFrame; + aData->mBackfaceHidden = aBackfaceHidden; + + aData->mNewChildLayersIndex = mNewChildLayers.Length(); + NewLayerEntry* newLayerEntry = mNewChildLayers.AppendElement(); + newLayerEntry->mAnimatedGeometryRoot = aAnimatedGeometryRoot; + newLayerEntry->mASR = aASR; + newLayerEntry->mScrollMetadataASR = aScrollMetadataASR; + newLayerEntry->mClipChain = aClipChain; + // newLayerEntry->mOpaqueRegion is filled in later from + // paintedLayerData->mOpaqueRegion, if necessary. + + // Allocate another entry for this layer's optimization to + // ColorLayer/ImageLayer + mNewChildLayers.AppendElement(); +} + +#ifdef MOZ_DUMP_PAINTING +static void DumpPaintedImage(nsDisplayItem* aItem, SourceSurface* aSurface) { + nsCString string(aItem->Name()); + string.Append('-'); + string.AppendInt((uint64_t)aItem); + fprintf_stderr(gfxUtils::sDumpPaintFile, "<script>array[\"%s\"]=\"", + string.BeginReading()); + gfxUtils::DumpAsDataURI(aSurface, gfxUtils::sDumpPaintFile); + fprintf_stderr(gfxUtils::sDumpPaintFile, "\";</script>\n"); +} +#endif + +static void PaintInactiveLayer(nsDisplayListBuilder* aBuilder, + LayerManager* aManager, nsDisplayItem* aItem, + gfxContext* aContext, gfxContext* aCtx) { + // This item has an inactive layer. Render it to a PaintedLayer + // using a temporary BasicLayerManager. + BasicLayerManager* basic = static_cast<BasicLayerManager*>(aManager); + RefPtr<gfxContext> context = aContext; +#ifdef MOZ_DUMP_PAINTING + int32_t appUnitsPerDevPixel = AppUnitsPerDevPixel(aItem); + nsIntRect itemVisibleRect = + aItem->GetPaintRect().ToOutsidePixels(appUnitsPerDevPixel); + + RefPtr<DrawTarget> tempDT; + if (gfxEnv::DumpPaint()) { + tempDT = gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget( + itemVisibleRect.Size(), SurfaceFormat::B8G8R8A8); + if (tempDT) { + context = gfxContext::CreateOrNull(tempDT); + if (!context) { + // Leave this as crash, it's in the debugging code, we want to know + gfxDevCrash(LogReason::InvalidContext) + << "PaintInactive context problem " << gfx::hexa(tempDT); + return; + } + context->SetMatrix( + Matrix::Translation(-itemVisibleRect.x, -itemVisibleRect.y)); + } + } +#endif + basic->BeginTransaction(); + basic->SetTarget(context); + + if (aItem->GetType() == DisplayItemType::TYPE_MASK) { + static_cast<nsDisplayMasksAndClipPaths*>(aItem)->PaintAsLayer(aBuilder, + aCtx, basic); + if (basic->InTransaction()) { + basic->AbortTransaction(); + } + } else if (aItem->GetType() == DisplayItemType::TYPE_FILTER) { + static_cast<nsDisplayFilters*>(aItem)->PaintAsLayer(aBuilder, aCtx, basic); + if (basic->InTransaction()) { + basic->AbortTransaction(); + } + } else { + basic->EndTransaction(FrameLayerBuilder::DrawPaintedLayer, aBuilder); + } + FrameLayerBuilder* builder = static_cast<FrameLayerBuilder*>( + basic->GetUserData(&gLayerManagerLayerBuilder)); + if (builder) { + builder->DidEndTransaction(); + } + + basic->SetTarget(nullptr); + +#ifdef MOZ_DUMP_PAINTING + if (gfxEnv::DumpPaint() && tempDT) { + RefPtr<SourceSurface> surface = tempDT->Snapshot(); + DumpPaintedImage(aItem, surface); + + DrawTarget* drawTarget = aContext->GetDrawTarget(); + Rect rect(itemVisibleRect.x, itemVisibleRect.y, itemVisibleRect.width, + itemVisibleRect.height); + drawTarget->DrawSurface(surface, rect, Rect(Point(0, 0), rect.Size())); + + aItem->SetPainted(); + } +#endif +} + +nsRect ContainerState::GetDisplayPortForAnimatedGeometryRoot( + AnimatedGeometryRoot* aAnimatedGeometryRoot) { + if (mLastDisplayPortAGR == aAnimatedGeometryRoot) { + return mLastDisplayPortRect; + } + + mLastDisplayPortAGR = aAnimatedGeometryRoot; + + nsIScrollableFrame* sf = + nsLayoutUtils::GetScrollableFrameFor(*aAnimatedGeometryRoot); + if (sf == nullptr || + nsLayoutUtils::UsesAsyncScrolling(*aAnimatedGeometryRoot)) { + mLastDisplayPortRect = nsRect(); + return mLastDisplayPortRect; + } + + bool usingDisplayport = DisplayPortUtils::GetDisplayPort( + (*aAnimatedGeometryRoot)->GetContent(), &mLastDisplayPortRect, + DisplayPortOptions().With(DisplayportRelativeTo::ScrollFrame)); + if (!usingDisplayport) { + // No async scrolling, so all that matters is that the layer contents + // cover the scrollport. + mLastDisplayPortRect = sf->GetScrollPortRect(); + } + nsIFrame* scrollFrame = do_QueryFrame(sf); + mLastDisplayPortRect += + scrollFrame->GetOffsetToCrossDoc(mContainerReferenceFrame); + return mLastDisplayPortRect; +} + +nsIntRegion ContainerState::ComputeOpaqueRect( + nsDisplayItem* aItem, AnimatedGeometryRoot* aAnimatedGeometryRoot, + const ActiveScrolledRoot* aASR, const DisplayItemClip& aClip, + nsDisplayList* aList, bool* aHideAllLayersBelow, + bool* aOpaqueForAnimatedGeometryRootParent) { + bool snapOpaque; + nsRegion opaque = aItem->GetOpaqueRegion(mBuilder, &snapOpaque); + MOZ_ASSERT(!opaque.IsComplex()); + if (opaque.IsEmpty()) { + return nsIntRegion(); + } + + nsIntRegion opaquePixels; + nsRegion opaqueClipped; + for (auto iter = opaque.RectIter(); !iter.Done(); iter.Next()) { + opaqueClipped.Or(opaqueClipped, + aClip.ApproximateIntersectInward(iter.Get())); + } + if (aAnimatedGeometryRoot == mContainerAnimatedGeometryRoot && + aASR == mContainerASR && opaqueClipped.Contains(mContainerBounds)) { + *aHideAllLayersBelow = true; + aList->SetIsOpaque(); + } + // Add opaque areas to the "exclude glass" region. Only do this when our + // container layer is going to be the rootmost layer, otherwise transforms + // etc will mess us up (and opaque contributions from other containers are + // not needed). + if (!nsLayoutUtils::GetCrossDocParentFrame(mContainerFrame)) { + mBuilder->AddWindowOpaqueRegion(aItem->Frame(), opaqueClipped.GetBounds()); + } + opaquePixels = ScaleRegionToInsidePixels(opaqueClipped, snapOpaque); + + if (IsInInactiveLayer()) { + return opaquePixels; + } + + const nsRect& displayport = + GetDisplayPortForAnimatedGeometryRoot(aAnimatedGeometryRoot); + if (!displayport.IsEmpty() && + opaquePixels.Contains(ScaleRegionToNearestPixels(displayport))) { + *aOpaqueForAnimatedGeometryRootParent = true; + } + return opaquePixels; +} + +Maybe<size_t> ContainerState::SetupMaskLayerForScrolledClip( + Layer* aLayer, const DisplayItemClip& aClip) { + if (aClip.GetRoundedRectCount() > 0) { + Maybe<size_t> maskLayerIndex = Some(aLayer->GetAncestorMaskLayerCount()); + if (RefPtr<Layer> maskLayer = + CreateMaskLayer(aLayer, aClip, maskLayerIndex)) { + aLayer->AddAncestorMaskLayer(maskLayer); + return maskLayerIndex; + } + // Fall through to |return Nothing()|. + } + return Nothing(); +} + +static const ActiveScrolledRoot* GetASRForPerspective( + const ActiveScrolledRoot* aASR, nsIFrame* aPerspectiveFrame) { + for (const ActiveScrolledRoot* asr = aASR; asr; asr = asr->mParent) { + nsIFrame* scrolledFrame = asr->mScrollableFrame->GetScrolledFrame(); + if (nsLayoutUtils::IsAncestorFrameCrossDoc(scrolledFrame, + aPerspectiveFrame)) { + return asr; + } + } + return nullptr; +} + +static CSSMaskLayerUserData* GetCSSMaskLayerUserData(Layer* aMaskLayer) { + if (!aMaskLayer) { + return nullptr; + } + + return static_cast<CSSMaskLayerUserData*>( + aMaskLayer->GetUserData(&gCSSMaskLayerUserData)); +} + +static void SetCSSMaskLayerUserData(Layer* aMaskLayer) { + MOZ_ASSERT(aMaskLayer); + + aMaskLayer->SetUserData(&gCSSMaskLayerUserData, new CSSMaskLayerUserData()); +} + +void ContainerState::SetupMaskLayerForCSSMask( + Layer* aLayer, nsDisplayMasksAndClipPaths* aMaskItem) { + RefPtr<ImageLayer> maskLayer = CreateOrRecycleMaskImageLayerFor( + MaskLayerKey(aLayer, Nothing()), GetCSSMaskLayerUserData, + SetCSSMaskLayerUserData); + CSSMaskLayerUserData* oldUserData = GetCSSMaskLayerUserData(maskLayer.get()); + MOZ_ASSERT(oldUserData); + + bool snap; + nsRect bounds = aMaskItem->GetBounds(mBuilder, &snap); + nsIntRect itemRect = ScaleToOutsidePixels(bounds, snap); + + // Setup mask layer offset. + // We do not repaint mask for mask position change, so update base transform + // each time is required. + Matrix4x4 matrix; + matrix.PreTranslate(itemRect.x, itemRect.y, 0); + matrix.PreTranslate(mParameters.mOffset.x, mParameters.mOffset.y, 0); + maskLayer->SetBaseTransform(matrix); + + nsPoint maskLayerOffset = aMaskItem->ToReferenceFrame() - bounds.TopLeft(); + + CSSMaskLayerUserData newUserData(aMaskItem->Frame(), itemRect, + maskLayerOffset); + nsRect dirtyRect; + if (!aMaskItem->IsInvalid(dirtyRect) && *oldUserData == newUserData) { + aLayer->SetMaskLayer(maskLayer); + return; + } + + int32_t maxSize = mManager->GetMaxTextureSize(); + IntSize surfaceSize(std::min(itemRect.width, maxSize), + std::min(itemRect.height, maxSize)); + + if (surfaceSize.IsEmpty()) { + // Return early if we know that the size of this mask surface is empty. + return; + } + + MaskImageData imageData(surfaceSize, mManager); + RefPtr<DrawTarget> dt = imageData.CreateDrawTarget(); + if (!dt || !dt->IsValid()) { + NS_WARNING("Could not create DrawTarget for mask layer."); + return; + } + + RefPtr<gfxContext> maskCtx = gfxContext::CreateOrNull(dt); + maskCtx->SetMatrix(Matrix::Translation(-itemRect.TopLeft())); + maskCtx->Multiply( + gfxMatrix::Scaling(mParameters.mXScale, mParameters.mYScale)); + + bool isPaintFinished = aMaskItem->PaintMask(mBuilder, maskCtx); + + RefPtr<ImageContainer> imgContainer = + imageData.CreateImageAndImageContainer(); + if (!imgContainer) { + return; + } + maskLayer->SetContainer(imgContainer); + + if (isPaintFinished) { + *oldUserData = std::move(newUserData); + } + aLayer->SetMaskLayer(maskLayer); +} + +static bool IsScrollThumbLayer(nsDisplayItem* aItem) { + return aItem->GetType() == DisplayItemType::TYPE_OWN_LAYER && + static_cast<nsDisplayOwnLayer*>(aItem)->IsScrollThumbLayer(); +} + +template <typename ClearFn, typename SelectFn> +static void ProcessDisplayItemMarker(DisplayItemEntryType aMarker, + ClearFn ClearLayerSelectionIfNeeded, + SelectFn SelectLayerIfNeeded) { + switch (aMarker) { + case DisplayItemEntryType::PushTransform: + case DisplayItemEntryType::PushOpacity: + SelectLayerIfNeeded(); + break; + case DisplayItemEntryType::PopTransform: + case DisplayItemEntryType::PopOpacity: + ClearLayerSelectionIfNeeded(); + break; + default: + break; + } +} +/* + * Iterate through the non-clip items in aList and its descendants. + * For each item we compute the effective clip rect. Each item is assigned + * to a layer. We invalidate the areas in PaintedLayers where an item + * has moved from one PaintedLayer to another. Also, + * aState->mInvalidPaintedContent is invalidated in every PaintedLayer. + * We set the clip rect for items that generated their own layer, and + * create a mask layer to do any rounded rect clipping. + * (PaintedLayers don't need a clip rect on the layer, we clip the items + * individually when we draw them.) + * We set the visible rect for all layers, although the actual setting + * of visible rects for some PaintedLayers is deferred until the calling + * of ContainerState::Finish. + */ +void ContainerState::ProcessDisplayItems(nsDisplayList* aList) { + AUTO_PROFILER_LABEL("ContainerState::ProcessDisplayItems", + GRAPHICS_LayerBuilding); + PerfStats::AutoMetricRecording<PerfStats::Metric::LayerBuilding> + autoRecording; + + nsPoint topLeft(0, 0); + + int32_t maxLayers = StaticPrefs::layers_max_active(); + int layerCount = 0; + + if (!mManager->IsWidgetLayerManager()) { + mPaintedLayerDataTree.InitializeForInactiveLayer( + mContainerAnimatedGeometryRoot); + } + + AnimatedGeometryRoot* lastAnimatedGeometryRoot = nullptr; + nsPoint lastTopLeft; + + // Tracks the PaintedLayerData that the item will be accumulated in, if it is + // non-null. + PaintedLayerData* selectedLayer = nullptr; + AutoTArray<size_t, 2> opacityIndices; + + // AGR and ASR for the container item that was flattened. + AnimatedGeometryRoot* containerAGR = nullptr; + const ActiveScrolledRoot* containerASR = nullptr; + nsIFrame* containerReferenceFrame = nullptr; + RefPtr<TransformClipNode> transformNode = nullptr; + + const auto InTransform = [&]() { return transformNode; }; + + const auto InOpacity = [&]() { + return selectedLayer && opacityIndices.Length() > 0; + }; + + FLBDisplayListIterator iter(mBuilder, aList, this); + while (iter.HasNext()) { + DisplayItemEntry e = iter.GetNextEntry(); + DisplayItemEntryType marker = e.mType; + nsDisplayItem* item = e.mItem; + MOZ_ASSERT(item); + DisplayItemType itemType = item->GetType(); + + if (itemType == DisplayItemType::TYPE_COMPOSITOR_HITTEST_INFO) { + // Override the marker for nsDisplayCompositorHitTestInfo items. + marker = DisplayItemEntryType::HitTestInfo; + } + + const bool inEffect = InTransform() || InOpacity(); + + NS_ASSERTION(mAppUnitsPerDevPixel == AppUnitsPerDevPixel(item), + "items in a container layer should all have the same app " + "units per dev pixel"); + + if (mBuilder->NeedToForceTransparentSurfaceForItem(item)) { + aList->SetNeedsTransparentSurface(); + } + + LayerState layerState = LayerState::LAYER_NONE; + if (marker == DisplayItemEntryType::Item) { + layerState = item->GetLayerState(mBuilder, mManager, mParameters); + + if (layerState == LayerState::LAYER_INACTIVE && + nsDisplayItem::ForceActiveLayers()) { + layerState = LayerState::LAYER_ACTIVE; + } + } + + AnimatedGeometryRoot* itemAGR = nullptr; + const ActiveScrolledRoot* itemASR = nullptr; + const DisplayItemClipChain* layerClipChain = nullptr; + const DisplayItemClipChain* itemClipChain = nullptr; + const DisplayItemClip* itemClipPtr = nullptr; + + bool snap = false; + nsRect itemContent; + + if (marker == DisplayItemEntryType::HitTestInfo) { + MOZ_ASSERT(item->IsHitTestItem()); + const auto& hitTestInfo = + static_cast<nsDisplayHitTestInfoBase*>(item)->GetHitTestInfo(); + + // Override the layer selection hints for items that have hit test + // information. This is needed because container items may have different + // clipping, AGR, or ASR than the child items in them. + itemAGR = hitTestInfo.mAGR; + itemASR = hitTestInfo.mASR; + itemClipChain = hitTestInfo.mClipChain; + itemClipPtr = hitTestInfo.mClip; + itemContent = hitTestInfo.mArea; + } else { + itemAGR = item->GetAnimatedGeometryRoot(); + itemASR = item->GetActiveScrolledRoot(); + itemClipChain = item->GetClipChain(); + itemClipPtr = &item->GetClip(); + itemContent = item->GetBounds(mBuilder, &snap); + } + + if (mManager->IsWidgetLayerManager() && !inEffect) { + if (itemClipChain && itemClipChain->mASR == itemASR && + itemType != DisplayItemType::TYPE_STICKY_POSITION) { + layerClipChain = itemClipChain->mParent; + } else { + layerClipChain = itemClipChain; + } + } else { + // Inside a flattened effect or inactive layer, use container AGR and ASR. + itemAGR = inEffect ? containerAGR : mContainerAnimatedGeometryRoot; + itemASR = inEffect ? containerASR : mContainerASR; + + if (marker == DisplayItemEntryType::HitTestInfo) { + // Items with hit test info are processed twice, once with ::HitTestInfo + // marker and then with ::Item marker. + // With ::HitTestInfo markers, fuse the clip chain of hit test struct, + // and with ::Item markers, fuse the clip chain of the actual item. + itemClipChain = mBuilder->FuseClipChainUpTo(itemClipChain, itemASR); + } else if (!IsEffectEndMarker(marker)) { + // No need to fuse clip chain for effect end markers, since it was + // already done for effect start markers. + item->FuseClipChainUpTo(mBuilder, itemASR); + itemClipChain = item->GetClipChain(); + } + + itemClipPtr = itemClipChain ? &itemClipChain->mClip : nullptr; + } + + const DisplayItemClip& itemClip = + itemClipPtr ? *itemClipPtr : DisplayItemClip::NoClip(); + + if (inEffect && marker == DisplayItemEntryType::HitTestInfo) { + // Fast-path for hit test items inside flattened inactive layers. + MOZ_ASSERT(selectedLayer); + selectedLayer->AccumulateHitTestItem(this, item, itemClip, transformNode); + continue; + } + + if (inEffect && marker == DisplayItemEntryType::Item) { + // Fast-path for items inside flattened inactive layers. This works + // because the layer state of the item cannot be active, otherwise the + // parent item would not have been flattened. + MOZ_ASSERT(selectedLayer); + selectedLayer->Accumulate(this, item->AsPaintedDisplayItem(), nsIntRect(), + nsRect(), itemClip, layerState, aList, marker, + opacityIndices, transformNode); + continue; + } + + // Items outside of flattened effects and non-item markers inside flattened + // effects are processed here. + MOZ_ASSERT(!inEffect || (marker != DisplayItemEntryType::Item)); + + if (itemAGR == lastAnimatedGeometryRoot) { + topLeft = lastTopLeft; + } else { + lastTopLeft = topLeft = + (*itemAGR)->GetOffsetToCrossDoc(mContainerReferenceFrame); + lastAnimatedGeometryRoot = itemAGR; + } + + const ActiveScrolledRoot* scrollMetadataASR = + layerClipChain + ? ActiveScrolledRoot::PickDescendant(itemASR, layerClipChain->mASR) + : itemASR; + + const bool prerenderedTransform = + itemType == DisplayItemType::TYPE_TRANSFORM && + static_cast<nsDisplayTransform*>(item)->MayBeAnimated(mBuilder); + + nsIntRect itemDrawRect = ScaleToOutsidePixels(itemContent, snap); + ParentLayerIntRect clipRect; + if (itemClip.HasClip()) { + const nsRect& itemClipRect = itemClip.GetClipRect(); + itemContent.IntersectRect(itemContent, itemClipRect); + clipRect = ViewAs<ParentLayerPixel>(ScaleToNearestPixels(itemClipRect)); + + if (!prerenderedTransform && !IsScrollThumbLayer(item)) { + itemDrawRect.IntersectRect(itemDrawRect, clipRect.ToUnknownRect()); + } + + clipRect.MoveBy(ViewAs<ParentLayerPixel>(mParameters.mOffset)); + } + + if (marker == DisplayItemEntryType::PopTransform) { + MOZ_ASSERT(transformNode); + transformNode = transformNode->Parent(); + } + + nsRect itemVisibleRectAu = itemContent; + if (transformNode) { + // If we are within transform, transform itemContent and itemDrawRect. + MOZ_ASSERT(transformNode); + + itemContent = + transformNode->TransformRect(itemContent, mAppUnitsPerDevPixel); + + itemDrawRect = transformNode->TransformRect(itemDrawRect); + } + +#ifdef DEBUG + nsRect bounds = itemContent; + + if (marker == DisplayItemEntryType::HitTestInfo || inEffect) { + bounds.SetEmpty(); + } + + if (!bounds.IsEmpty() && itemASR != mContainerASR) { + if (Maybe<nsRect> clip = + item->GetClipWithRespectToASR(mBuilder, mContainerASR)) { + bounds = clip.ref(); + } + } + + ((nsRect&)mAccumulatedChildBounds) + .UnionRect(mAccumulatedChildBounds, bounds); +#endif + + nsIntRect itemVisibleRect = itemDrawRect; + + // We intersect the building rect with the clipped item bounds to get a + // tighter visible rect. + if (!prerenderedTransform) { + nsRect itemBuildingRect = item->GetBuildingRect(); + + if (transformNode) { + itemBuildingRect = transformNode->TransformRect(itemBuildingRect, + mAppUnitsPerDevPixel); + } + + itemVisibleRect = itemVisibleRect.Intersect( + ScaleToOutsidePixels(itemBuildingRect, false)); + } + + const bool forceInactive = maxLayers != -1 && layerCount >= maxLayers; + + // Assign the item to a layer + bool treatInactiveItemAsActive = + (layerState == LayerState::LAYER_INACTIVE && + mLayerBuilder->GetContainingPaintedLayerData()); + if (layerState == LayerState::LAYER_ACTIVE_FORCE || + treatInactiveItemAsActive || + (!forceInactive && (layerState == LayerState::LAYER_ACTIVE_EMPTY || + layerState == LayerState::LAYER_ACTIVE))) { + layerCount++; + + // Currently we do not support flattening effects within nested inactive + // layer trees. + MOZ_ASSERT(selectedLayer == nullptr); + MOZ_ASSERT(marker == DisplayItemEntryType::Item); + + // LayerState::LAYER_ACTIVE_EMPTY means the layer is created just for its + // metadata. We should never see an empty layer with any visible content! + NS_ASSERTION( + layerState != LayerState::LAYER_ACTIVE_EMPTY || + itemVisibleRect.IsEmpty(), + "State is LayerState::LAYER_ACTIVE_EMPTY but visible rect is not."); + + // As long as the new layer isn't going to be a PaintedLayer, + // InvalidateForLayerChange doesn't need the new layer pointer. + // We also need to check the old data now, because BuildLayer + // can overwrite it. + DisplayItemData* oldData = mLayerBuilder->GetOldLayerForFrame( + item->Frame(), item->GetPerFrameKey()); + InvalidateForLayerChange(item, nullptr, oldData); + + // 3D-transformed layers don't necessarily draw in the order in which + // they're added to their parent container layer. + bool mayDrawOutOfOrder = itemType == DisplayItemType::TYPE_TRANSFORM && + (item->Combines3DTransformWithAncestors() || + item->Frame()->Extend3DContext()); + + // Let mPaintedLayerDataTree know about this item, so that + // FindPaintedLayerFor and FindOpaqueBackgroundColor are aware of this + // item, even though it's not in any PaintedLayerDataStack. + // Ideally we'd only need the "else" case here and have + // mPaintedLayerDataTree figure out the right clip from the animated + // geometry root that we give it, but it can't easily figure about + // overflow:hidden clips on ancestors just by looking at the frame. + // So we'll do a little hand holding and pass the clip instead of the + // visible rect for the two important cases. + nscolor uniformColor = NS_RGBA(0, 0, 0, 0); + nscolor* uniformColorPtr = + (mayDrawOutOfOrder || IsInInactiveLayer()) ? nullptr : &uniformColor; + nsIntRect clipRectUntyped; + nsIntRect* clipPtr = nullptr; + if (itemClip.HasClip()) { + clipRectUntyped = clipRect.ToUnknownRect(); + clipPtr = &clipRectUntyped; + } + + bool isStickyNotClippedToDisplayPort = + itemType == DisplayItemType::TYPE_STICKY_POSITION && + !static_cast<nsDisplayStickyPosition*>(item) + ->IsClippedToDisplayPort(); + bool hasScrolledClip = + layerClipChain && layerClipChain->mClip.HasClip() && + (!ActiveScrolledRoot::IsAncestor(layerClipChain->mASR, itemASR) || + isStickyNotClippedToDisplayPort); + + if (hasScrolledClip) { + // If the clip is scrolled, reserve just the area of the clip for + // layerization, so that elements outside the clip can still merge + // into the same layer. + const ActiveScrolledRoot* clipASR = layerClipChain->mASR; + AnimatedGeometryRoot* clipAGR = + mBuilder->AnimatedGeometryRootForASR(clipASR); + nsIntRect scrolledClipRect = + ScaleToNearestPixels(layerClipChain->mClip.GetClipRect()) + + mParameters.mOffset; + mPaintedLayerDataTree.AddingOwnLayer(clipAGR, &scrolledClipRect, + uniformColorPtr); + } else if (item->ShouldFixToViewport(mBuilder) && itemClip.HasClip() && + item->AnimatedGeometryRootForScrollMetadata() != itemAGR && + !nsLayoutUtils::UsesAsyncScrolling(item->Frame())) { + // This is basically the same as the case above, but for the non-APZ + // case. At the moment, when APZ is off, there is only the root ASR + // (because scroll frames without display ports don't create ASRs) and + // the whole clip chain is always just one fused clip. + // Bug 1336516 aims to change that and to remove this workaround. + AnimatedGeometryRoot* clipAGR = + item->AnimatedGeometryRootForScrollMetadata(); + nsIntRect scrolledClipRect = + ScaleToNearestPixels(itemClip.GetClipRect()) + mParameters.mOffset; + mPaintedLayerDataTree.AddingOwnLayer(clipAGR, &scrolledClipRect, + uniformColorPtr); + } else if (IsScrollThumbLayer(item) && mManager->IsWidgetLayerManager()) { + // For scrollbar thumbs, the clip we care about is the clip added by the + // slider frame. + mPaintedLayerDataTree.AddingOwnLayer(itemAGR->mParentAGR, clipPtr, + uniformColorPtr); + } else if (prerenderedTransform && mManager->IsWidgetLayerManager()) { + if (itemAGR->mParentAGR) { + mPaintedLayerDataTree.AddingOwnLayer(itemAGR->mParentAGR, clipPtr, + uniformColorPtr); + } else { + mPaintedLayerDataTree.AddingOwnLayer(itemAGR, nullptr, + uniformColorPtr); + } + } else { + // Using itemVisibleRect here isn't perfect. itemVisibleRect can be + // larger or smaller than the potential bounds of item's contents in + // itemAGR: It's too large if there's a clipped display + // port somewhere among item's contents (see bug 1147673), and it can + // be too small if the contents can move, because it only looks at the + // contents' current bounds and doesn't anticipate any animations. + // Time will tell whether this is good enough, or whether we need to do + // something more sophisticated here. + mPaintedLayerDataTree.AddingOwnLayer(itemAGR, &itemVisibleRect, + uniformColorPtr); + } + + ContainerLayerParameters params = mParameters; + params.mBackgroundColor = uniformColor; + params.mLayerCreationHint = GetLayerCreationHint(itemAGR); + if (!transformNode) { + params.mItemVisibleRect = &itemVisibleRectAu; + } else { + // We only use mItemVisibleRect for getting the visible rect for + // remote browsers (which should never have inactive transforms), so we + // avoid doing transforms on itemVisibleRectAu above and can't report + // an accurate bounds here. + params.mItemVisibleRect = nullptr; + } + params.mScrollMetadataASR = + ActiveScrolledRoot::IsAncestor(scrollMetadataASR, + mContainerScrollMetadataASR) + ? mContainerScrollMetadataASR + : scrollMetadataASR; + params.mCompositorASR = + params.mScrollMetadataASR != mContainerScrollMetadataASR + ? params.mScrollMetadataASR + : mContainerCompositorASR; + if (itemType == DisplayItemType::TYPE_FIXED_POSITION) { + params.mCompositorASR = itemASR; + } + + // Perspective items have a single child item, an nsDisplayTransform. + // If the perspective item is scrolled, but the perspective-inducing + // frame is outside the scroll frame (indicated by item->Frame() + // being outside that scroll frame), we have to take special care to + // make APZ scrolling work properly. APZ needs us to put the scroll + // frame's FrameMetrics on our child transform ContainerLayer instead. + // We make a similar adjustment for OwnLayer items built for frames + // with perspective transforms (e.g. when they have rounded corners). + // It's worth investigating whether this ASR adjustment can be done at + // display item creation time. + bool deferASRForPerspective = + itemType == DisplayItemType::TYPE_PERSPECTIVE || + (itemType == DisplayItemType::TYPE_OWN_LAYER && + item->Frame()->IsTransformed() && item->Frame()->HasPerspective()); + if (deferASRForPerspective) { + scrollMetadataASR = GetASRForPerspective( + scrollMetadataASR, + item->Frame()->GetContainingBlock(nsIFrame::SKIP_SCROLLED_FRAME)); + params.mScrollMetadataASR = scrollMetadataASR; + itemASR = scrollMetadataASR; + } + + // Just use its layer. + // Set layerContentsVisibleRect.width/height to -1 to indicate we + // currently don't know. If BuildContainerLayerFor gets called by + // item->BuildLayer, this will be set to a proper rect. + nsIntRect layerContentsVisibleRect(0, 0, -1, -1); + params.mLayerContentsVisibleRect = &layerContentsVisibleRect; + + // If this display item wants to build inactive layers but we are treating + // it as active because we are already inside an inactive layer tree, + // we need to make sure that the display item's clip is reflected in + // FrameLayerBuilder::mInactiveLayerClip (which is normally set in + // AddPaintedDisplayItem() when entering an inactive layer tree). + // We intersect the display item's clip into any existing inactive layer + // clip. + const DisplayItemClip* originalInactiveClip = nullptr; + DisplayItemClip combinedInactiveClip; + if (treatInactiveItemAsActive) { + originalInactiveClip = mLayerBuilder->GetInactiveLayerClip(); + if (originalInactiveClip) { + combinedInactiveClip = *originalInactiveClip; + } + DisplayItemClip nestedClip = item->GetClip(); + if (nestedClip.HasClip()) { + nsRect nestedClipRect = nestedClip.NonRoundedIntersection(); + + // Transform the nested clip to be relative to the same reference + // frame as the existing mInactiveLayerClip, so that we can intersect + // them below. + nestedClipRect = nsLayoutUtils::TransformFrameRectToAncestor( + item->ReferenceFrame(), nestedClipRect, + mLayerBuilder->GetContainingPaintedLayerData()->mReferenceFrame); + + nestedClip.SetTo(nestedClipRect); + combinedInactiveClip.IntersectWith(nestedClip); + mLayerBuilder->SetInactiveLayerClip(&combinedInactiveClip); + } + } + + RefPtr<Layer> ownLayer = + item->AsPaintedDisplayItem()->BuildLayer(mBuilder, mManager, params); + + // If above we combined a nested clip into mInactiveLayerClip, restore + // the original inactive layer clip here. + if (treatInactiveItemAsActive) { + mLayerBuilder->SetInactiveLayerClip(originalInactiveClip); + } + + if (!ownLayer) { + continue; + } + + NS_ASSERTION(!ownLayer->AsPaintedLayer(), + "Should never have created a dedicated Painted layer!"); + + SetBackfaceHiddenForLayer(item->BackfaceIsHidden(), ownLayer); + + nsRect invalid; + if (item->IsInvalid(invalid)) { + ownLayer->SetInvalidRectToVisibleRegion(); + } + + // If it's not a ContainerLayer, we need to apply the scale transform + // ourselves. + if (!ownLayer->AsContainerLayer()) { + ownLayer->SetPostScale(mParameters.mXScale, mParameters.mYScale); + } + + // Update that layer's clip and visible rects. + NS_ASSERTION(ownLayer->Manager() == mManager, "Wrong manager"); + NS_ASSERTION(!ownLayer->HasUserData(&gLayerManagerUserData), + "We shouldn't have a FrameLayerBuilder-managed layer here!"); + NS_ASSERTION(itemClip.HasClip() || itemClip.GetRoundedRectCount() == 0, + "If we have rounded rects, we must have a clip rect"); + + // It has its own layer. Update that layer's clip and visible rects. + ownLayer->SetClipRect(Nothing()); + ownLayer->SetScrolledClip(Nothing()); + ownLayer->SetAncestorMaskLayers({}); + if (itemClip.HasClip()) { + ownLayer->SetClipRect(Some(clipRect)); + + // rounded rectangle clipping using mask layers + // (must be done after visible rect is set on layer) + if (itemClip.GetRoundedRectCount() > 0) { + SetupMaskLayer(ownLayer, itemClip); + } + } + + if (hasScrolledClip) { + const DisplayItemClip& scrolledClip = layerClipChain->mClip; + LayerClip scrolledLayerClip; + scrolledLayerClip.SetClipRect(ViewAs<ParentLayerPixel>( + ScaleToNearestPixels(scrolledClip.GetClipRect()) + + mParameters.mOffset)); + if (scrolledClip.GetRoundedRectCount() > 0) { + scrolledLayerClip.SetMaskLayerIndex( + SetupMaskLayerForScrolledClip(ownLayer.get(), scrolledClip)); + } + ownLayer->SetScrolledClip(Some(scrolledLayerClip)); + } + + if (item->GetType() == DisplayItemType::TYPE_MASK) { + MOZ_ASSERT(itemClip.GetRoundedRectCount() == 0); + + nsDisplayMasksAndClipPaths* maskItem = + static_cast<nsDisplayMasksAndClipPaths*>(item); + SetupMaskLayerForCSSMask(ownLayer, maskItem); + + if (iter.PeekNext() && iter.PeekNext()->GetType() == + DisplayItemType::TYPE_SCROLL_INFO_LAYER) { + // Since we do build a layer for mask, there is no need for this + // scroll info layer anymore. + iter.GetNextItem(); + } + } + + // Convert the visible rect to a region and give the item + // a chance to try restrict it further. + nsIntRegion itemVisibleRegion = itemVisibleRect; + nsRegion tightBounds = item->GetTightBounds(mBuilder, &snap); + if (!tightBounds.IsEmpty()) { + itemVisibleRegion.AndWith( + ScaleRegionToOutsidePixels(tightBounds, snap)); + } + + ContainerLayer* oldContainer = ownLayer->GetParent(); + if (oldContainer && oldContainer != mContainerLayer) { + oldContainer->RemoveChild(ownLayer); + } + NS_ASSERTION(FindIndexOfLayerIn(mNewChildLayers, ownLayer) < 0, + "Layer already in list???"); + + // NewLayerEntry::mClipChain is used by SetupScrollingMetadata() to + // populate any scroll clips in the scroll metadata. Perspective layers + // have their ASR adjusted such that a scroll metadata that would normally + // go on the perspective layer goes on its transform layer child instead. + // However, the transform item's clip chain does not contain the + // corresponding scroll clip, so we use the perspective item's clip + // chain instead. + const DisplayItemClipChain* clipChainForScrollClips = layerClipChain; + if (itemType == DisplayItemType::TYPE_TRANSFORM && mContainerItem && + mContainerItem->GetType() == DisplayItemType::TYPE_PERSPECTIVE) { + clipChainForScrollClips = mContainerItem->GetClipChain(); + } + + NewLayerEntry* newLayerEntry = mNewChildLayers.AppendElement(); + newLayerEntry->mLayer = ownLayer; + newLayerEntry->mAnimatedGeometryRoot = itemAGR; + newLayerEntry->mASR = itemASR; + newLayerEntry->mScrollMetadataASR = scrollMetadataASR; + newLayerEntry->mClipChain = clipChainForScrollClips; + newLayerEntry->mLayerState = layerState; + if (itemType == DisplayItemType::TYPE_FIXED_POSITION) { + newLayerEntry->mIsFixedToRootScrollFrame = + item->Frame()->StyleDisplay()->mPosition == + StylePositionProperty::Fixed && + nsLayoutUtils::IsReallyFixedPos(item->Frame()); + } + + float contentXScale = 1.0f; + float contentYScale = 1.0f; + if (ContainerLayer* ownContainer = ownLayer->AsContainerLayer()) { + contentXScale = 1 / ownContainer->GetPreXScale(); + contentYScale = 1 / ownContainer->GetPreYScale(); + } + // nsDisplayTransform::BuildLayer must set layerContentsVisibleRect. + // We rely on this to ensure 3D transforms compute a reasonable + // layer visible region. + NS_ASSERTION(itemType != DisplayItemType::TYPE_TRANSFORM || + layerContentsVisibleRect.width >= 0, + "Transform items must set layerContentsVisibleRect!"); + if (mLayerBuilder->IsBuildingRetainedLayers()) { + newLayerEntry->mLayerContentsVisibleRect = layerContentsVisibleRect; + if (itemType == DisplayItemType::TYPE_PERSPECTIVE || + (itemType == DisplayItemType::TYPE_TRANSFORM && + (item->Combines3DTransformWithAncestors() || + item->Frame()->Extend3DContext() || + item->Frame()->HasPerspective()))) { + // Give untransformed visible region as outer visible region + // to avoid failure caused by singular transforms. + newLayerEntry->mUntransformedVisibleRegion = true; + newLayerEntry->mVisibleRegion = + item->GetBuildingRectForChildren().ScaleToOutsidePixels( + contentXScale, contentYScale, mAppUnitsPerDevPixel); + } else { + newLayerEntry->mVisibleRegion = itemVisibleRegion; + } + newLayerEntry->mOpaqueRegion = ComputeOpaqueRect( + item, itemAGR, itemASR, itemClip, aList, + &newLayerEntry->mHideAllLayersBelow, + &newLayerEntry->mOpaqueForAnimatedGeometryRootParent); + } else { + bool useChildrenVisible = itemType == DisplayItemType::TYPE_TRANSFORM && + (item->Frame()->IsPreserve3DLeaf() || + item->Frame()->HasPerspective()); + const nsIntRegion& visible = + useChildrenVisible + ? item->GetBuildingRectForChildren().ScaleToOutsidePixels( + contentXScale, contentYScale, mAppUnitsPerDevPixel) + : itemVisibleRegion; + + SetOuterVisibleRegionForLayer(ownLayer, visible, + layerContentsVisibleRect.width >= 0 + ? &layerContentsVisibleRect + : nullptr, + useChildrenVisible); + } + if (itemType == DisplayItemType::TYPE_SCROLL_INFO_LAYER) { + nsDisplayScrollInfoLayer* scrollItem = + static_cast<nsDisplayScrollInfoLayer*>(item); + newLayerEntry->mOpaqueForAnimatedGeometryRootParent = false; + newLayerEntry->mBaseScrollMetadata = scrollItem->ComputeScrollMetadata( + mBuilder, ownLayer->Manager(), mParameters); + } + + /** + * No need to allocate geometry for items that aren't + * part of a PaintedLayer. + */ + if (ownLayer->Manager() == mLayerBuilder->GetRetainingLayerManager()) { + oldData = mLayerBuilder->GetOldLayerForFrame(item->Frame(), + item->GetPerFrameKey()); + + mLayerBuilder->StoreDataForFrame(item->AsPaintedDisplayItem(), ownLayer, + layerState, oldData); + } + } else { + const bool backfaceHidden = item->In3DContextAndBackfaceIsHidden(); + + // When container item hit test info is processed, we need to use the same + // reference frame as the container children. + const nsIFrame* referenceFrame = item == mContainerItem + ? mContainerReferenceFrame + : item->ReferenceFrame(); + + MOZ_ASSERT(item != mContainerItem || + marker == DisplayItemEntryType::HitTestInfo); + + PaintedLayerData* paintedLayerData = selectedLayer; + + if (!paintedLayerData) { + paintedLayerData = mPaintedLayerDataTree.FindPaintedLayerFor( + itemAGR, itemASR, layerClipChain, itemVisibleRect, backfaceHidden, + [&](PaintedLayerData* aData) { + NewPaintedLayerData(aData, itemAGR, itemASR, layerClipChain, + scrollMetadataASR, topLeft, referenceFrame, + backfaceHidden); + }); + } + MOZ_ASSERT(paintedLayerData); + + if (marker == DisplayItemEntryType::HitTestInfo) { + MOZ_ASSERT(!transformNode); + paintedLayerData->AccumulateHitTestItem(this, item, itemClip, nullptr); + } else { + paintedLayerData->Accumulate( + this, item->AsPaintedDisplayItem(), itemVisibleRect, itemContent, + itemClip, layerState, aList, marker, opacityIndices, transformNode); + + if (!paintedLayerData->mLayer) { + // Try to recycle the old layer of this display item. + RefPtr<PaintedLayer> layer = AttemptToRecyclePaintedLayer( + itemAGR, item, topLeft, + inEffect ? containerReferenceFrame : referenceFrame); + if (layer) { + paintedLayerData->mLayer = layer; + + auto* userData = GetPaintedDisplayItemLayerUserData(layer); + paintedLayerData->mAssignedDisplayItems.reserve( + userData->mLastItemCount); + + NS_ASSERTION(FindIndexOfLayerIn(mNewChildLayers, layer) < 0, + "Layer already in list???"); + mNewChildLayers[paintedLayerData->mNewChildLayersIndex].mLayer = + std::move(layer); + } + } + } + + const auto ClearLayerSelectionIfNeeded = [&]() { + if (!InOpacity() && !InTransform()) { + selectedLayer = nullptr; + containerAGR = nullptr; + containerASR = nullptr; + containerReferenceFrame = nullptr; + } + }; + + const auto SelectLayerIfNeeded = [&]() { + if (!selectedLayer) { + selectedLayer = paintedLayerData; + containerAGR = itemAGR; + containerASR = itemASR; + containerReferenceFrame = const_cast<nsIFrame*>(referenceFrame); + } + }; + + if (marker == DisplayItemEntryType::PushTransform) { + nsDisplayTransform* transform = static_cast<nsDisplayTransform*>(item); + + const Matrix4x4Flagged& matrix = transform->GetTransformForRendering(); + + Maybe<gfx::IntRect> clip; + if (itemClip.HasClip()) { + const nsRect nonRoundedClip = itemClip.NonRoundedIntersection(); + clip.emplace(nonRoundedClip.ToNearestPixels(mAppUnitsPerDevPixel)); + } + + transformNode = new TransformClipNode(transformNode, matrix, clip); + } + + ProcessDisplayItemMarker(marker, ClearLayerSelectionIfNeeded, + SelectLayerIfNeeded); + } + + nsDisplayList* childItems = item->GetSameCoordinateSystemChildren(); + if (childItems && childItems->NeedsTransparentSurface()) { + aList->SetNeedsTransparentSurface(); + } + } + + MOZ_ASSERT(selectedLayer == nullptr); +} + +void ContainerState::InvalidateForLayerChange(nsDisplayItem* aItem, + PaintedLayer* aNewLayer, + DisplayItemData* aData) { + NS_ASSERTION(aItem->GetPerFrameKey(), + "Display items that render using Thebes must have a key"); + Layer* oldLayer = aData ? aData->mLayer.get() : nullptr; + if (aNewLayer != oldLayer && oldLayer) { + // The item has changed layers. + // Invalidate the old bounds in the old layer and new bounds in the new + // layer. + PaintedLayer* t = oldLayer->AsPaintedLayer(); + if (t && aData->mGeometry) { + // Note that whenever the layer's scale changes, we invalidate the whole + // thing, so it doesn't matter whether we are using the old scale at last + // paint or a new scale here +#ifdef MOZ_DUMP_PAINTING + if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) { + printf_stderr("Display item type %s(%p) changed layers %p to %p!\n", + aItem->Name(), aItem->Frame(), t, aNewLayer); + } +#endif + InvalidatePreTransformRect( + t, aData->mGeometry->ComputeInvalidationRegion(), aData->mClip, + GetLastPaintOffset(t), aData->mTransform); + } + // Clear the old geometry so that invalidation thinks the item has been + // added this paint. + aData->mGeometry = nullptr; + } +} + +static nsRect GetInvalidationRect(nsDisplayItemGeometry* aGeometry, + const DisplayItemClip& aClip, + TransformClipNode* aTransform, + const int32_t aA2D) { + const nsRect& rect = aGeometry->ComputeInvalidationRegion(); + const nsRect clipped = aClip.ApplyNonRoundedIntersection(rect); + + if (aTransform) { + return aTransform->TransformRect(clipped, aA2D); + } + + return clipped; +} + +void FrameLayerBuilder::ComputeGeometryChangeForItem(DisplayItemData* aData) { + nsDisplayItem* item = aData->mItem; + PaintedLayer* paintedLayer = aData->mLayer->AsPaintedLayer(); + // If aData->mOptLayer is presence, means this item has been optimized to the + // separate layer. Thus, skip geometry change calculation. + if (aData->mOptLayer || !item || !paintedLayer) { + aData->EndUpdate(); + return; + } + + // If we're a reused display item, then we can't be invalid, so no need to + // do an in-depth comparison. If we haven't previously stored geometry + // for this item (if it was an active layer), then we can't skip this + // yet. + UniquePtr<nsDisplayItemGeometry> geometry; + if (aData->mReusedItem && aData->mGeometry) { + aData->EndUpdate(); + return; + } + + auto* layerData = GetPaintedDisplayItemLayerUserData(aData->mLayer); + nsPoint shift = layerData->mAnimatedGeometryRootOrigin - + layerData->mLastAnimatedGeometryRootOrigin; + + const DisplayItemClip& clip = item->GetClip(); + const int32_t appUnitsPerDevPixel = layerData->mAppUnitsPerDevPixel; + + // If the frame is marked as invalidated, and didn't specify a rect to + // invalidate then we want to invalidate both the old and new bounds, + // otherwise we only want to invalidate the changed areas. If we do get an + // invalid rect, then we want to add this on top of the change areas. + nsRect invalid; + nsIntRegion invalidPixels; + + if (!aData->mGeometry) { + // This item is being added for the first time, invalidate its entire area. + geometry = WrapUnique(item->AllocateGeometry(mDisplayListBuilder)); + + const nsRect bounds = GetInvalidationRect( + geometry.get(), clip, aData->mTransform, appUnitsPerDevPixel); + + invalidPixels = bounds.ScaleToOutsidePixels( + layerData->mXScale, layerData->mYScale, appUnitsPerDevPixel); +#ifdef MOZ_DUMP_PAINTING + if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) { + printf_stderr("Display item type %s(%p) added to layer %p!\n", + item->Name(), item->Frame(), aData->mLayer.get()); + } +#endif + } else if (aData->mIsInvalid || + (item->IsInvalid(invalid) && invalid.IsEmpty())) { + // Layout marked item/frame as needing repainting (without an explicit + // rect), invalidate the entire old and new areas. + geometry = WrapUnique(item->AllocateGeometry(mDisplayListBuilder)); + + nsRect oldArea = + GetInvalidationRect(aData->mGeometry.get(), aData->mClip, + aData->mOldTransform, appUnitsPerDevPixel); + oldArea.MoveBy(shift); + + nsRect newArea = GetInvalidationRect( + geometry.get(), clip, aData->mTransform, appUnitsPerDevPixel); + + nsRegion combined; + combined.Or(oldArea, newArea); + invalidPixels = combined.ScaleToOutsidePixels( + layerData->mXScale, layerData->mYScale, appUnitsPerDevPixel); +#ifdef MOZ_DUMP_PAINTING + if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) { + printf_stderr( + "Display item type %s(%p) (in layer %p) belongs to an " + "invalidated frame!\n", + item->Name(), item->Frame(), aData->mLayer.get()); + } +#endif + } else { + // Let the display item check for geometry changes and decide what needs to + // be repainted. + const nsRegion& changedFrameInvalidations = + aData->GetChangedFrameInvalidations(); + + if (aData->mTransform) { + // If this display item is inside a flattened transform the offset is + // already included in the root transform, so there is no need to shift. + shift = nsPoint(); + } + + aData->mGeometry->MoveBy(shift); + + nsRegion combined; + item->ComputeInvalidationRegion(mDisplayListBuilder, aData->mGeometry.get(), + &combined); + + // Only allocate a new geometry object if something actually changed, + // otherwise the existing one should be fine. We always reallocate for + // inactive layers, since these types don't implement + // ComputeInvalidateRegion (and rely on the ComputeDifferences call in + // AddPaintedDisplayItem instead). + if (!combined.IsEmpty() || + aData->mLayerState == LayerState::LAYER_INACTIVE || + item->NeedsGeometryUpdates()) { + geometry = WrapUnique(item->AllocateGeometry(mDisplayListBuilder)); + } + + aData->mClip.AddOffsetAndComputeDifference( + shift, aData->mGeometry->ComputeInvalidationRegion(), clip, + geometry ? geometry->ComputeInvalidationRegion() + : aData->mGeometry->ComputeInvalidationRegion(), + &combined); + + // Add in any rect that the frame specified + combined.Or(combined, invalid); + combined.Or(combined, changedFrameInvalidations); + + // Restrict invalidation to the clipped region + nsRegion clipRegion; + if (clip.ComputeRegionInClips(&aData->mClip, shift, &clipRegion)) { + combined.And(combined, clipRegion); + } + + invalidPixels = combined.ToOutsidePixels(appUnitsPerDevPixel); + + if (aData->mTransform) { + invalidPixels = aData->mTransform->TransformRegion(invalidPixels); + } + + invalidPixels.ScaleRoundOut(layerData->mXScale, layerData->mYScale); + +#ifdef MOZ_DUMP_PAINTING + if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) { + if (!combined.IsEmpty()) { + printf_stderr( + "Display item type %s(%p) (in layer %p) changed geometry!\n", + item->Name(), item->Frame(), aData->mLayer.get()); + } + } +#endif + } + + if (!invalidPixels.IsEmpty()) { + InvalidatePostTransformRegion(paintedLayer, invalidPixels, + layerData->mTranslation); + } + + aData->EndUpdate(std::move(geometry)); +} + +void FrameLayerBuilder::AddPaintedDisplayItem(PaintedLayerData* aLayerData, + AssignedDisplayItem& aItem, + Layer* aLayer) { + PaintedLayer* layer = aLayerData->mLayer; + auto* paintedData = GetPaintedDisplayItemLayerUserData(layer); + + if (layer->Manager() == mRetainingManager) { + DisplayItemData* data = aItem.mDisplayItemData; + if (data && !data->mUsed) { + data->BeginUpdate(layer, aItem.mLayerState, aItem.mItem, aItem.mReused, + aItem.mMerged); + } else { + if (data && data->mUsed) { + // If the DID has already been used (by a previously merged frame, + // which is not merged this paint) we must create a new DID for the + // item. + aItem.mItem->SetDisplayItemData(nullptr, nullptr); + } + data = StoreDataForFrame(aItem.mItem, layer, aItem.mLayerState, nullptr); + } + data->mInactiveManager = aItem.mInactiveLayerData + ? aItem.mInactiveLayerData->mLayerManager + : nullptr; + // We optimized this PaintedLayer into a ColorLayer/ImageLayer. Store the + // optimized layer here. + if (aLayer != layer) { + data->mOptLayer = aLayer; + } + + data->mOldTransform = data->mTransform; + data->mTransform = aItem.mTransform; + } + + if (aItem.mInactiveLayerData) { + RefPtr<BasicLayerManager> tempManager = + aItem.mInactiveLayerData->mLayerManager; + FrameLayerBuilder* layerBuilder = tempManager->GetLayerBuilder(); + Layer* tmpLayer = aItem.mInactiveLayerData->mLayer; + + // We have no easy way of detecting if this transaction will ever actually + // get finished. For now, I've just silenced the warning with nested + // transactions in BasicLayers.cpp + if (!tmpLayer) { + tempManager->EndTransaction(nullptr, nullptr); + tempManager->SetUserData(&gLayerManagerLayerBuilder, nullptr); + aItem.mItem = nullptr; + return; + } + + bool snap; + nsRect visibleRect = aItem.mItem->GetBuildingRect().Intersect( + aItem.mItem->GetBounds(mDisplayListBuilder, &snap)); + nsIntRegion rgn = + visibleRect.ToOutsidePixels(paintedData->mAppUnitsPerDevPixel); + + // Convert the visible rect to a region and give the item + // a chance to try restrict it further. + nsRegion tightBounds = + aItem.mItem->GetTightBounds(mDisplayListBuilder, &snap); + if (!tightBounds.IsEmpty()) { + rgn.AndWith( + tightBounds.ToOutsidePixels(paintedData->mAppUnitsPerDevPixel)); + } + SetOuterVisibleRegion(tmpLayer, &rgn); + + DisplayItemData* data = nullptr; + // If BuildLayer didn't call BuildContainerLayerFor, then our new layer + // won't have been stored in layerBuilder. Manually add it now. + if (mRetainingManager) { +#ifdef DEBUG_DISPLAY_ITEM_DATA + LayerManagerData* parentLmd = static_cast<LayerManagerData*>( + layer->Manager()->GetUserData(&gLayerManagerUserData)); + LayerManagerData* lmd = static_cast<LayerManagerData*>( + tempManager->GetUserData(&gLayerManagerUserData)); + lmd->mParent = parentLmd; +#endif + data = + layerBuilder->GetDisplayItemDataForManager(aItem.mItem, tempManager); + data = layerBuilder->StoreDataForFrame(aItem.mItem, tmpLayer, + LayerState::LAYER_ACTIVE, data); + data->mOldTransform = data->mTransform; + data->mTransform = aItem.mTransform; + } + + tempManager->SetRoot(tmpLayer); + layerBuilder->WillEndTransaction(); + tempManager->AbortTransaction(); + + if (gfxUtils::DumpDisplayList() || gfxEnv::DumpPaint()) { + fprintf_stderr( + gfxUtils::sDumpPaintFile, + "Basic layer tree for painting contents of display item %s(%p):\n", + aItem.mItem->Name(), aItem.mItem->Frame()); + std::stringstream stream; + tempManager->Dump(stream, "", gfxEnv::DumpPaintToFile()); + fprint_stderr(gfxUtils::sDumpPaintFile, + stream); // not a typo, fprint_stderr declared in nsDebug.h + } + + nsIntPoint offset = + GetLastPaintOffset(layer) - GetTranslationForPaintedLayer(layer); + aItem.mInactiveLayerData->mProps->MoveBy(-offset); + // Effective transforms are needed by ComputeDifferences(). + tmpLayer->ComputeEffectiveTransforms(Matrix4x4()); + nsIntRegion invalid; + if (!aItem.mInactiveLayerData->mProps->ComputeDifferences(tmpLayer, invalid, + nullptr)) { + nsRect visible = aItem.mItem->Frame()->InkOverflowRect(); + invalid = visible.ToOutsidePixels(paintedData->mAppUnitsPerDevPixel); + } + if (aItem.mLayerState == LayerState::LAYER_SVG_EFFECTS) { + invalid = SVGIntegrationUtils::AdjustInvalidAreaForSVGEffects( + aItem.mItem->Frame(), aItem.mItem->ToReferenceFrame(), invalid); + } + if (!invalid.IsEmpty()) { +#ifdef MOZ_DUMP_PAINTING + if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) { + printf_stderr( + "Inactive LayerManager(%p) for display item %s(%p) has " + "an invalid region - invalidating layer %p\n", + tempManager.get(), aItem.mItem->Name(), aItem.mItem->Frame(), + layer); + } +#endif + + if (data && data->mTransform) { + invalid = data->mTransform->TransformRegion(invalid); + } + + invalid.ScaleRoundOut(paintedData->mXScale, paintedData->mYScale); + + InvalidatePostTransformRegion(layer, invalid, + GetTranslationForPaintedLayer(layer)); + } + } +} + +DisplayItemData* FrameLayerBuilder::StoreDataForFrame( + nsPaintedDisplayItem* aItem, Layer* aLayer, LayerState aState, + DisplayItemData* aData) { + MOZ_ASSERT(aItem); + + if (aData) { + if (!aData->mUsed) { + aData->BeginUpdate(aLayer, aState, false, aItem); + } + return aData; + } + + LayerManagerData* lmd = static_cast<LayerManagerData*>( + mRetainingManager->GetUserData(&gLayerManagerUserData)); + + RefPtr<DisplayItemData> data = new (aItem->Frame()->PresContext()) + DisplayItemData(lmd, aItem->GetPerFrameKey(), aLayer); + + data->BeginUpdate(aLayer, aState, true, aItem); + + lmd->mDisplayItems.push_back(data); + return data; +} + +void FrameLayerBuilder::StoreDataForFrame(nsIFrame* aFrame, + uint32_t aDisplayItemKey, + Layer* aLayer, LayerState aState) { + DisplayItemData* oldData = GetDisplayItemData(aFrame, aDisplayItemKey); + if (oldData && oldData->mFrameList.Length() == 1) { + oldData->BeginUpdate(aLayer, aState, false); + return; + } + + LayerManagerData* lmd = static_cast<LayerManagerData*>( + mRetainingManager->GetUserData(&gLayerManagerUserData)); + + RefPtr<DisplayItemData> data = new (aFrame->PresContext()) + DisplayItemData(lmd, aDisplayItemKey, aLayer, aFrame); + + data->BeginUpdate(aLayer, aState, true); + + lmd->mDisplayItems.push_back(data); +} + +AssignedDisplayItem::AssignedDisplayItem( + nsPaintedDisplayItem* aItem, LayerState aLayerState, DisplayItemData* aData, + const nsRect& aContentRect, DisplayItemEntryType aType, + const bool aHasOpacity, const RefPtr<TransformClipNode>& aTransform, + const bool aIsMerged) + : mItem(aItem), + mDisplayItemData(aData), + mTransform(aTransform), + mContentRect(aContentRect), + mLayerState(aLayerState), + mType(aType), + mReused(aItem->IsReused()), + mMerged(aIsMerged), + mHasOpacity(aHasOpacity), + mHasPaintRect(aItem->HasPaintRect()) {} + +InactiveLayerData::~InactiveLayerData() { + if (mLayerManager) { + mLayerManager->SetUserData(&gLayerManagerLayerBuilder, nullptr); + } +} + +bool FrameLayerBuilder::CheckInLayerTreeCompressionMode() { + if (mInLayerTreeCompressionMode) { + return true; + } + + // If we wanted to be in layer tree compression mode, but weren't, then + // scheduled a delayed repaint where we will be. + mRootPresContext->PresShell()->GetRootFrame()->SchedulePaint( + nsIFrame::PAINT_DELAYED_COMPRESS, false); + + return false; +} + +void ContainerState::CollectOldLayers() { + for (Layer* layer = mContainerLayer->GetFirstChild(); layer; + layer = layer->GetNextSibling()) { + NS_ASSERTION(!layer->HasUserData(&gMaskLayerUserData), + "Mask layers should not be part of the layer tree."); + if (layer->HasUserData(&gPaintedDisplayItemLayerUserData)) { + NS_ASSERTION(layer->AsPaintedLayer(), "Wrong layer type"); + mPaintedLayersAvailableForRecycling.PutEntry( + static_cast<PaintedLayer*>(layer)); + } + + if (Layer* maskLayer = layer->GetMaskLayer()) { + NS_ASSERTION(maskLayer->GetType() == Layer::TYPE_IMAGE, + "Could not recycle mask layer, unsupported layer type."); + mRecycledMaskImageLayers.Put(MaskLayerKey(layer, Nothing()), + static_cast<ImageLayer*>(maskLayer)); + } + for (size_t i = 0; i < layer->GetAncestorMaskLayerCount(); i++) { + Layer* maskLayer = layer->GetAncestorMaskLayerAt(i); + + NS_ASSERTION(maskLayer->GetType() == Layer::TYPE_IMAGE, + "Could not recycle mask layer, unsupported layer type."); + mRecycledMaskImageLayers.Put(MaskLayerKey(layer, Some(i)), + static_cast<ImageLayer*>(maskLayer)); + } + } +} + +struct OpaqueRegionEntry { + AnimatedGeometryRoot* mAnimatedGeometryRoot; + const ActiveScrolledRoot* mASR; + nsIntRegion mOpaqueRegion; +}; + +static OpaqueRegionEntry* FindOpaqueRegionEntry( + nsTArray<OpaqueRegionEntry>& aEntries, + AnimatedGeometryRoot* aAnimatedGeometryRoot, + const ActiveScrolledRoot* aASR) { + for (uint32_t i = 0; i < aEntries.Length(); ++i) { + OpaqueRegionEntry* d = &aEntries[i]; + if (d->mAnimatedGeometryRoot == aAnimatedGeometryRoot && d->mASR == aASR) { + return d; + } + } + return nullptr; +} + +static const ActiveScrolledRoot* FindDirectChildASR( + const ActiveScrolledRoot* aParent, const ActiveScrolledRoot* aDescendant) { + MOZ_ASSERT(aDescendant, "can't start at the root when looking for a child"); + MOZ_ASSERT(ActiveScrolledRoot::IsAncestor(aParent, aDescendant)); + const ActiveScrolledRoot* directChild = aDescendant; + while (directChild->mParent != aParent) { + directChild = directChild->mParent; + MOZ_RELEASE_ASSERT(directChild, "this must not be null"); + } + return directChild; +} + +static void FixUpFixedPositionLayer( + Layer* aLayer, const ActiveScrolledRoot* aTargetASR, + const ActiveScrolledRoot* aLeafScrollMetadataASR, + const ActiveScrolledRoot* aContainerScrollMetadataASR, + const ActiveScrolledRoot* aContainerCompositorASR, + bool aIsFixedToRootScrollFrame) { + if (!aLayer->GetIsFixedPosition()) { + return; + } + + // Analyze ASRs to figure out if we need to fix up fixedness annotations on + // the layer. Fixed annotations are required in multiple cases: + // - Sometimes we set scroll metadata on a layer for a scroll frame that we + // don't want the layer to be moved by. (We have to do this if there is a + // scrolled clip that is moved by that scroll frame.) So we set the fixed + // annotation so that the compositor knows that it should ignore that + // scroll metadata when determining the layer's position. + // - Sometimes there is a scroll meta data on aLayer's parent layer for a + // scroll frame that we don't want aLayer to be moved by. The most common + // way for this to happen is with containerful root scrolling, where the + // scroll metadata for the root scroll frame is on a container layer that + // wraps the whole document's contents. + // - Sometimes it's just needed for hit testing, i.e. figuring out what + // scroll frame should be scrolled by events over the layer. + // A fixed layer needs to be annotated with the scroll ID of the scroll frame + // that it is *fixed with respect to*, i.e. the outermost scroll frame which + // does not move the layer. nsDisplayFixedPosition only ever annotates layers + // with the scroll ID of the presshell's root scroll frame, which is + // sometimes the wrong thing to do, so we correct it here. Specifically, + // it's the wrong thing to do if the fixed frame's containing block is a + // transformed frame - in that case, the fixed frame needs to scroll along + // with the transformed frame instead of being fixed with respect to the rsf. + // (It would be nice to compute the annotation only in one place and get it + // right, instead of fixing it up after the fact like this, but this will + // need to do for now.) + // compositorASR is the ASR that the layer would move with on the compositor + // if there were no fixed annotation on it. + const ActiveScrolledRoot* compositorASR = + aLeafScrollMetadataASR == aContainerScrollMetadataASR + ? aContainerCompositorASR + : aLeafScrollMetadataASR; + + // The goal of the annotation is to have the layer move with aTargetASR. + if (compositorASR && aTargetASR != compositorASR) { + // Mark this layer as fixed with respect to the child scroll frame of + // aTargetASR. + aLayer->SetFixedPositionData( + FindDirectChildASR(aTargetASR, compositorASR)->GetViewId(), + aLayer->GetFixedPositionAnchor(), aLayer->GetFixedPositionSides()); + } else { + // Remove the fixed annotation from the layer, unless this layers is fixed + // to the document's root scroll frame - in that case, the annotation is + // needed for hit testing, because fixed layers in iframes should scroll + // the iframe, even though their position is not affected by scrolling in + // the iframe. (The APZ hit testing code has a special case for this.) + // nsDisplayFixedPosition has annotated this layer with the document's + // root scroll frame's scroll id. + aLayer->SetIsFixedPosition(aIsFixedToRootScrollFrame); + } +} + +void ContainerState::SetupScrollingMetadata(NewLayerEntry* aEntry) { + if (!mBuilder->IsPaintingToWindow()) { + // async scrolling not possible, and async scrolling info not computed + // for this paint. + return; + } + + const ActiveScrolledRoot* startASR = aEntry->mScrollMetadataASR; + const ActiveScrolledRoot* stopASR = mContainerScrollMetadataASR; + if (!ActiveScrolledRoot::IsAncestor(stopASR, startASR)) { + if (ActiveScrolledRoot::IsAncestor(startASR, stopASR)) { + // startASR and stopASR are in the same branch of the ASR tree, but + // startASR is closer to the root. Just start at stopASR so that the loop + // below doesn't actually do anything. + startASR = stopASR; + } else { + // startASR and stopASR are in different branches of the + // ASR tree. Find a common ancestor and make that the stopASR. + // This can happen when there's a scrollable frame inside a fixed layer + // which has a scrolled clip. As far as scroll metadata is concerned, + // the scroll frame's scroll metadata will be a child of the scroll ID + // that scrolls the clip on the fixed layer. But as far as ASRs are + // concerned, those two ASRs are siblings, parented to the ASR of the + // fixed layer. + do { + stopASR = stopASR->mParent; + } while (!ActiveScrolledRoot::IsAncestor(stopASR, startASR)); + } + } + + FixUpFixedPositionLayer(aEntry->mLayer, aEntry->mASR, startASR, + mContainerScrollMetadataASR, mContainerCompositorASR, + aEntry->mIsFixedToRootScrollFrame); + + AutoTArray<ScrollMetadata, 2> metricsArray; + if (aEntry->mBaseScrollMetadata) { + metricsArray.AppendElement(*aEntry->mBaseScrollMetadata); + + // The base FrameMetrics was not computed by the nsIScrollableframe, so it + // should not have a mask layer. + MOZ_ASSERT(!aEntry->mBaseScrollMetadata->HasMaskLayer()); + } + + // Any extra mask layers we need to attach to ScrollMetadatas. + // The list may already contain an entry added for the layer's scrolled clip + // so add to it rather than overwriting it (we clear the list when recycling + // a layer). + nsTArray<RefPtr<Layer>> maskLayers( + aEntry->mLayer->GetAllAncestorMaskLayers().Clone()); + + // Iterate over the ASR chain and create the corresponding scroll metadatas. + // This loop is slightly tricky because the scrollframe-to-clip relationship + // is reversed between DisplayItemClipChain and ScrollMetadata: + // - DisplayItemClipChain associates the clip with the scroll frame that + // this clip is *moved by*, i.e. the clip is moving inside the scroll + // frame. + // - ScrollMetaData associates the scroll frame with the clip that's + // *just outside* the scroll frame, i.e. not moved by the scroll frame + // itself. + // This discrepancy means that the leaf clip item of the clip chain is never + // applied to any scroll meta data. Instead, it was applied earlier as the + // layer's clip (or fused with the painted layer contents), or it was applied + // as a ScrolledClip on the layer. + const DisplayItemClipChain* clipChain = aEntry->mClipChain; + + for (const ActiveScrolledRoot* asr = startASR; asr != stopASR; + asr = asr->mParent) { + if (!asr) { + MOZ_ASSERT_UNREACHABLE("Should have encountered stopASR on the way up."); + break; + } + if (clipChain && clipChain->mASR == asr) { + clipChain = clipChain->mParent; + } + + nsIScrollableFrame* scrollFrame = asr->mScrollableFrame; + const DisplayItemClip* clip = (clipChain && clipChain->mASR == asr->mParent) + ? &clipChain->mClip + : nullptr; + + scrollFrame->ClipLayerToDisplayPort(aEntry->mLayer, clip, mParameters); + + Maybe<ScrollMetadata> metadata; + if (mCachedScrollMetadata.mASR == asr && + mCachedScrollMetadata.mClip == clip) { + metadata = mCachedScrollMetadata.mMetadata; + } else { + metadata = scrollFrame->ComputeScrollMetadata(aEntry->mLayer->Manager(), + mContainerReferenceFrame, + Some(mParameters), clip); + mBuilder->AddScrollFrameToNotify(scrollFrame); + mCachedScrollMetadata.mASR = asr; + mCachedScrollMetadata.mClip = clip; + mCachedScrollMetadata.mMetadata = metadata; + } + + if (!metadata) { + continue; + } + + if (clip && clip->HasClip() && clip->GetRoundedRectCount() > 0) { + // The clip in between this scrollframe and its ancestor scrollframe + // requires a mask layer. Since this mask layer should not move with + // the APZC associated with this FrameMetrics, we attach the mask + // layer as an additional, separate clip. + Maybe<size_t> nextIndex = Some(maskLayers.Length()); + RefPtr<Layer> maskLayer = + CreateMaskLayer(aEntry->mLayer, *clip, nextIndex); + if (maskLayer) { + MOZ_ASSERT(metadata->HasScrollClip()); + metadata->ScrollClip().SetMaskLayerIndex(nextIndex); + maskLayers.AppendElement(maskLayer); + } + } + + metricsArray.AppendElement(*metadata); + } + + // Watch out for FrameMetrics copies in profiles + aEntry->mLayer->SetScrollMetadata(metricsArray); + aEntry->mLayer->SetAncestorMaskLayers(maskLayers); +} + +static inline Maybe<ParentLayerIntRect> GetStationaryClipInContainer( + Layer* aLayer) { + if (size_t metricsCount = aLayer->GetScrollMetadataCount()) { + return aLayer->GetScrollMetadata(metricsCount - 1).GetClipRect(); + } + return aLayer->GetClipRect(); +} + +void ContainerState::PostprocessRetainedLayers( + nsIntRegion* aOpaqueRegionForContainer) { + AutoTArray<OpaqueRegionEntry, 4> opaqueRegions; + bool hideAll = false; + int32_t opaqueRegionForContainer = -1; + + for (int32_t i = mNewChildLayers.Length() - 1; i >= 0; --i) { + NewLayerEntry* e = &mNewChildLayers.ElementAt(i); + if (!e->mLayer) { + continue; + } + + OpaqueRegionEntry* data = + FindOpaqueRegionEntry(opaqueRegions, e->mAnimatedGeometryRoot, e->mASR); + + SetupScrollingMetadata(e); + + if (hideAll) { + e->mVisibleRegion.SetEmpty(); + } else if (!e->mLayer->IsScrollbarContainer()) { + Maybe<ParentLayerIntRect> clipRect = + GetStationaryClipInContainer(e->mLayer); + if (clipRect && opaqueRegionForContainer >= 0 && + opaqueRegions[opaqueRegionForContainer].mOpaqueRegion.Contains( + clipRect->ToUnknownRect())) { + e->mVisibleRegion.SetEmpty(); + } else if (data) { + e->mVisibleRegion.Sub(e->mVisibleRegion, data->mOpaqueRegion); + } + } + + SetOuterVisibleRegionForLayer(e->mLayer, e->mVisibleRegion, + e->mLayerContentsVisibleRect.width >= 0 + ? &e->mLayerContentsVisibleRect + : nullptr, + e->mUntransformedVisibleRegion); + + if (!e->mOpaqueRegion.IsEmpty()) { + AnimatedGeometryRoot* animatedGeometryRootToCover = + e->mAnimatedGeometryRoot; + const ActiveScrolledRoot* asrToCover = e->mASR; + if (e->mOpaqueForAnimatedGeometryRootParent && + e->mAnimatedGeometryRoot->mParentAGR == + mContainerAnimatedGeometryRoot) { + animatedGeometryRootToCover = mContainerAnimatedGeometryRoot; + asrToCover = mContainerASR; + data = FindOpaqueRegionEntry(opaqueRegions, animatedGeometryRootToCover, + asrToCover); + } + + if (!data) { + if (animatedGeometryRootToCover == mContainerAnimatedGeometryRoot && + asrToCover == mContainerASR) { + NS_ASSERTION(opaqueRegionForContainer == -1, "Already found it?"); + opaqueRegionForContainer = opaqueRegions.Length(); + } + data = opaqueRegions.AppendElement(); + data->mAnimatedGeometryRoot = animatedGeometryRootToCover; + data->mASR = asrToCover; + } + + nsIntRegion clippedOpaque = e->mOpaqueRegion; + Maybe<ParentLayerIntRect> clipRect = e->mLayer->GetCombinedClipRect(); + if (clipRect) { + clippedOpaque.AndWith(clipRect->ToUnknownRect()); + } + if (e->mLayer->GetScrolledClip()) { + // The clip can move asynchronously, so we can't rely on opaque parts + // staying visible. + clippedOpaque.SetEmpty(); + } else if (e->mHideAllLayersBelow) { + hideAll = true; + } + data->mOpaqueRegion.Or(data->mOpaqueRegion, clippedOpaque); + } + + if (e->mLayer->GetType() == Layer::TYPE_READBACK) { + // ReadbackLayers need to accurately read what's behind them. So, + // we don't want to do any occlusion culling of layers behind them. + // Theoretically we could just punch out the ReadbackLayer's rectangle + // from all mOpaqueRegions, but that's probably not worth doing. + opaqueRegions.Clear(); + opaqueRegionForContainer = -1; + } + } + + if (opaqueRegionForContainer >= 0) { + aOpaqueRegionForContainer->Or( + *aOpaqueRegionForContainer, + opaqueRegions[opaqueRegionForContainer].mOpaqueRegion); + } +} + +void ContainerState::Finish(uint32_t* aTextContentFlags, + const nsIntRect& aContainerPixelBounds, + nsDisplayList* aChildItems) { + mPaintedLayerDataTree.Finish(); + + NS_ASSERTION(mContainerBounds.IsEqualInterior(mAccumulatedChildBounds), + "Bounds computation mismatch"); + + if (mLayerBuilder->IsBuildingRetainedLayers()) { + nsIntRegion containerOpaqueRegion; + PostprocessRetainedLayers(&containerOpaqueRegion); + if (containerOpaqueRegion.Contains(aContainerPixelBounds)) { + aChildItems->SetIsOpaque(); + } + } + + uint32_t textContentFlags = 0; + + // Make sure that current/existing layers are added to the parent and are + // in the correct order. + Layer* layer = nullptr; + Layer* prevChild = nullptr; + for (uint32_t i = 0; i < mNewChildLayers.Length(); ++i, prevChild = layer) { + if (!mNewChildLayers[i].mLayer) { + continue; + } + + layer = mNewChildLayers[i].mLayer; + + if (!layer->GetVisibleRegion().IsEmpty()) { + textContentFlags |= layer->GetContentFlags() & + (Layer::CONTENT_COMPONENT_ALPHA | + Layer::CONTENT_COMPONENT_ALPHA_DESCENDANT | + Layer::CONTENT_DISABLE_FLATTENING); + } + + if (!layer->GetParent()) { + // This is not currently a child of the container, so just add it + // now. + mContainerLayer->InsertAfter(layer, prevChild); + } else { + NS_ASSERTION(layer->GetParent() == mContainerLayer, + "Layer shouldn't be the child of some other container"); + if (layer->GetPrevSibling() != prevChild) { + mContainerLayer->RepositionChild(layer, prevChild); + } + } + } + + // Remove old layers that have become unused. + if (!layer) { + layer = mContainerLayer->GetFirstChild(); + } else { + layer = layer->GetNextSibling(); + } + while (layer) { + Layer* layerToRemove = layer; + layer = layer->GetNextSibling(); + mContainerLayer->RemoveChild(layerToRemove); + } + + *aTextContentFlags = textContentFlags; +} + +static void RestrictScaleToMaxLayerSize(Size& aScale, + const nsRect& aVisibleRect, + nsIFrame* aContainerFrame, + Layer* aContainerLayer) { + if (!aContainerLayer->Manager()->IsWidgetLayerManager()) { + return; + } + + nsIntRect pixelSize = aVisibleRect.ScaleToOutsidePixels( + aScale.width, aScale.height, + aContainerFrame->PresContext()->AppUnitsPerDevPixel()); + + int32_t maxLayerSize = aContainerLayer->GetMaxLayerSize(); + + if (pixelSize.width > maxLayerSize) { + float scale = (float)pixelSize.width / maxLayerSize; + scale = gfxUtils::ClampToScaleFactor(scale); + aScale.width /= scale; + } + if (pixelSize.height > maxLayerSize) { + float scale = (float)pixelSize.height / maxLayerSize; + scale = gfxUtils::ClampToScaleFactor(scale); + aScale.height /= scale; + } +} + +static nsSize ComputeDesiredDisplaySizeForAnimation(nsIFrame* aContainerFrame) { + // Use the size of the nearest widget as the maximum size. This + // is important since it might be a popup that is bigger than the + // pres context's size. + nsPresContext* presContext = aContainerFrame->PresContext(); + nsIWidget* widget = aContainerFrame->GetNearestWidget(); + if (widget) { + return LayoutDevicePixel::ToAppUnits(widget->GetClientSize(), + presContext->AppUnitsPerDevPixel()); + } + + return presContext->GetVisibleArea().Size(); +} + +/* static */ +Size FrameLayerBuilder::ChooseScale(nsIFrame* aContainerFrame, + nsDisplayItem* aContainerItem, + const nsRect& aVisibleRect, float aXScale, + float aYScale, const Matrix& aTransform2d, + bool aCanDraw2D) { + Size scale; + // XXX Should we do something for 3D transforms? + if (aCanDraw2D && !aContainerFrame->Combines3DTransformWithAncestors() && + !aContainerFrame->HasPerspective()) { + // If the container's transform is animated off main thread, fix a suitable + // scale size for animation + if (aContainerItem && + aContainerItem->GetType() == DisplayItemType::TYPE_TRANSFORM && + // FIXME: What we need is only transform, rotate, and scale, not + // translate, so it's be better to use a property set, instead of + // display item type here. + EffectCompositor::HasAnimationsForCompositor( + aContainerFrame, DisplayItemType::TYPE_TRANSFORM)) { + nsSize displaySize = + ComputeDesiredDisplaySizeForAnimation(aContainerFrame); + // compute scale using the animation on the container, taking ancestors in + // to account + nsSize scaledVisibleSize = nsSize(aVisibleRect.Width() * aXScale, + aVisibleRect.Height() * aYScale); + scale = nsLayoutUtils::ComputeSuitableScaleForAnimation( + aContainerFrame, scaledVisibleSize, displaySize); + // multiply by the scale inherited from ancestors--we use a uniform + // scale factor to prevent blurring when the layer is rotated. + float incomingScale = std::max(aXScale, aYScale); + scale.width *= incomingScale; + scale.height *= incomingScale; + } else { + // Scale factors are normalized to a power of 2 to reduce the number of + // resolution changes + scale = aTransform2d.ScaleFactors(); + // For frames with a changing scale transform round scale factors up to + // nearest power-of-2 boundary so that we don't keep having to redraw + // the content as it scales up and down. Rounding up to nearest + // power-of-2 boundary ensures we never scale up, only down --- avoiding + // jaggies. It also ensures we never scale down by more than a factor of + // 2, avoiding bad downscaling quality. + Matrix frameTransform; + if (ActiveLayerTracker::IsScaleSubjectToAnimation(aContainerFrame)) { + scale.width = gfxUtils::ClampToScaleFactor(scale.width); + scale.height = gfxUtils::ClampToScaleFactor(scale.height); + + // Limit animated scale factors to not grow excessively beyond the + // display size. + nsSize maxScale(4, 4); + if (!aVisibleRect.IsEmpty()) { + nsSize displaySize = + ComputeDesiredDisplaySizeForAnimation(aContainerFrame); + maxScale = Max(maxScale, displaySize / aVisibleRect.Size()); + } + if (scale.width > maxScale.width) { + scale.width = gfxUtils::ClampToScaleFactor(maxScale.width, true); + } + if (scale.height > maxScale.height) { + scale.height = gfxUtils::ClampToScaleFactor(maxScale.height, true); + } + } else { + // XXX Do we need to move nearly-integer values to integers here? + } + } + // If the scale factors are too small, just use 1.0. The content is being + // scaled out of sight anyway. + if (fabs(scale.width) < 1e-8 || fabs(scale.height) < 1e-8) { + scale = Size(1.0, 1.0); + } + } else { + scale = Size(1.0, 1.0); + } + + // Prevent the scale from getting too large, to avoid excessive memory + // allocation. Usually memory allocation is limited by the visible region, + // which should be restricted to the display port. But at very large scales + // the visible region itself can become excessive due to rounding errors. + // Clamping the scale here prevents that. + scale = + Size(std::min(scale.width, 32768.0f), std::min(scale.height, 32768.0f)); + + return scale; +} + +static bool ChooseScaleAndSetTransform( + FrameLayerBuilder* aLayerBuilder, nsDisplayListBuilder* aDisplayListBuilder, + nsIFrame* aContainerFrame, nsDisplayItem* aContainerItem, + const nsRect& aVisibleRect, const Matrix4x4* aTransform, + const ContainerLayerParameters& aIncomingScale, ContainerLayer* aLayer, + ContainerLayerParameters& aOutgoingScale) { + nsIntPoint offset; + + Matrix4x4 transform = + Matrix4x4::Scaling(aIncomingScale.mXScale, aIncomingScale.mYScale, 1.0); + if (aTransform) { + // aTransform is applied first, then the scale is applied to the result + transform = (*aTransform) * transform; + // Set any matrix entries close to integers to be those exact integers. + // This protects against floating-point inaccuracies causing problems + // in the checks below. + // We use the fixed epsilon version here because we don't want the nudging + // to depend on the scroll position. + transform.NudgeToIntegersFixedEpsilon(); + } + Matrix transform2d; + if (aContainerFrame && aLayerBuilder->GetContainingPaintedLayerData() && + (!aTransform || + (aTransform->Is2D(&transform2d) && !transform2d.HasNonTranslation()))) { + // When we have an inactive ContainerLayer, translate the container by the + // offset to the reference frame (and offset all child layers by the + // reverse) so that the coordinate space of the child layers isn't affected + // by scrolling. This gets confusing for complicated transform (since we'd + // have to compute the scale factors for the matrix), so we don't bother. + // Any frames that are building an nsDisplayTransform for a css transform + // would have 0,0 as their offset to the reference frame, so this doesn't + // matter. + nsPoint appUnitOffset = + aDisplayListBuilder->ToReferenceFrame(aContainerFrame); + nscoord appUnitsPerDevPixel = + aContainerFrame->PresContext()->AppUnitsPerDevPixel(); + offset = nsIntPoint(NS_lround(NSAppUnitsToDoublePixels( + appUnitOffset.x, appUnitsPerDevPixel) * + aIncomingScale.mXScale), + NS_lround(NSAppUnitsToDoublePixels( + appUnitOffset.y, appUnitsPerDevPixel) * + aIncomingScale.mYScale)); + } + transform.PostTranslate(offset.x + aIncomingScale.mOffset.x, + offset.y + aIncomingScale.mOffset.y, 0); + + if (transform.IsSingular()) { + return false; + } + + bool canDraw2D = transform.CanDraw2D(&transform2d); + Size scale = FrameLayerBuilder::ChooseScale( + aContainerFrame, aContainerItem, aVisibleRect, aIncomingScale.mXScale, + aIncomingScale.mYScale, transform2d, canDraw2D); + + // If this is a transform container layer, then pre-rendering might + // mean we try render a layer bigger than the max texture size. If we have + // tiling, that's not a problem, since we'll automatically choose a tiled + // layer for layers of that size. If not, we need to apply clamping to + // prevent this. + if (aTransform && !StaticPrefs::layers_enable_tiles_AtStartup()) { + RestrictScaleToMaxLayerSize(scale, aVisibleRect, aContainerFrame, aLayer); + } + + // Store the inverse of our resolution-scale on the layer + aLayer->SetBaseTransform(transform); + aLayer->SetPreScale(1.0f / scale.width, 1.0f / scale.height); + aLayer->SetInheritedScale(aIncomingScale.mXScale, aIncomingScale.mYScale); + + aOutgoingScale = ContainerLayerParameters(scale.width, scale.height, -offset, + aIncomingScale); + if (aTransform) { + aOutgoingScale.mInTransformedSubtree = true; + if (ActiveLayerTracker::IsTransformAnimated(aDisplayListBuilder, + aContainerFrame)) { + aOutgoingScale.mInActiveTransformedSubtree = true; + } + } + if ((aLayerBuilder->IsBuildingRetainedLayers() && + (!canDraw2D || transform2d.HasNonIntegerTranslation())) || + aContainerFrame->Extend3DContext() || + aContainerFrame->Combines3DTransformWithAncestors() || + // For async transform animation, the value would be changed at + // any time, integer translation is not always true. + aContainerFrame->HasAnimationOfTransform()) { + aOutgoingScale.mDisableSubpixelAntialiasingInDescendants = true; + } + return true; +} + +already_AddRefed<ContainerLayer> FrameLayerBuilder::BuildContainerLayerFor( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + nsIFrame* aContainerFrame, nsDisplayItem* aContainerItem, + nsDisplayList* aChildren, const ContainerLayerParameters& aParameters, + const Matrix4x4* aTransform, uint32_t aFlags) { + uint32_t containerDisplayItemKey = + aContainerItem ? aContainerItem->GetPerFrameKey() : 0; + NS_ASSERTION(aContainerFrame, + "Container display items here should have a frame"); + NS_ASSERTION(!aContainerItem || aContainerItem->Frame() == aContainerFrame, + "Container display item must match given frame"); + + if (!aParameters.mXScale || !aParameters.mYScale) { + return nullptr; + } + + RefPtr<ContainerLayer> containerLayer; + if (aManager == mRetainingManager) { + // Using GetOldLayerFor will search merged frames, as well as the underlying + // frame. The underlying frame can change when a page scrolls, so this + // avoids layer recreation in the situation that a new underlying frame is + // picked for a layer. + Layer* oldLayer = nullptr; + if (aContainerItem) { + oldLayer = GetOldLayerFor(aContainerItem); + } else { + DisplayItemData* data = + GetOldLayerForFrame(aContainerFrame, containerDisplayItemKey); + if (data) { + oldLayer = data->mLayer; + } + } + + if (oldLayer) { + NS_ASSERTION(oldLayer->Manager() == aManager, "Wrong manager"); + if (oldLayer->HasUserData(&gPaintedDisplayItemLayerUserData)) { + // The old layer for this item is actually our PaintedLayer + // because we rendered its layer into that PaintedLayer. So we + // don't actually have a retained container layer. + } else { + NS_ASSERTION(oldLayer->GetType() == Layer::TYPE_CONTAINER, + "Wrong layer type"); + containerLayer = static_cast<ContainerLayer*>(oldLayer); + ResetLayerStateForRecycling(containerLayer); + } + } + } + if (!containerLayer) { + // No suitable existing layer was found. + containerLayer = aManager->CreateContainerLayer(); + if (!containerLayer) return nullptr; + } + + if (aContainerItem && + aContainerItem->GetType() == DisplayItemType::TYPE_SCROLL_INFO_LAYER) { + // Empty layers only have metadata and should never have display items. We + // early exit because later, invalidation will walk up the frame tree to + // determine which painted layer gets invalidated. Since an empty layer + // should never have anything to paint, it should never be invalidated. + NS_ASSERTION(aChildren->IsEmpty(), "Should have no children"); + return containerLayer.forget(); + } + + const ActiveScrolledRoot* containerASR = + aContainerItem ? aContainerItem->GetActiveScrolledRoot() : nullptr; + const ActiveScrolledRoot* containerScrollMetadataASR = + aParameters.mScrollMetadataASR; + const ActiveScrolledRoot* containerCompositorASR = aParameters.mCompositorASR; + + ContainerLayerParameters scaleParameters; + nsRect bounds = + aChildren->GetClippedBoundsWithRespectToASR(aBuilder, containerASR); + nsRect childrenVisible = + aContainerItem ? aContainerItem->GetBuildingRectForChildren() + : aContainerFrame->InkOverflowRectRelativeToSelf(); + if (!ChooseScaleAndSetTransform( + this, aBuilder, aContainerFrame, aContainerItem, + bounds.Intersect(childrenVisible), aTransform, aParameters, + containerLayer, scaleParameters)) { + return nullptr; + } + + if (mRetainingManager) { + if (aContainerItem) { + nsPaintedDisplayItem* item = aContainerItem->AsPaintedDisplayItem(); + MOZ_ASSERT(item, "Only painted display items should build layers"); + + DisplayItemData* data = + GetDisplayItemDataForManager(item, mRetainingManager); + StoreDataForFrame(item, containerLayer, LayerState::LAYER_ACTIVE, data); + } else { + StoreDataForFrame(aContainerFrame, containerDisplayItemKey, + containerLayer, LayerState::LAYER_ACTIVE); + } + } + + nsIntRect pixBounds; + nscoord appUnitsPerDevPixel; + + nscolor backgroundColor = NS_RGBA(0, 0, 0, 0); + if (aFlags & CONTAINER_ALLOW_PULL_BACKGROUND_COLOR) { + backgroundColor = aParameters.mBackgroundColor; + } + + uint32_t flags; + ContainerState state(aBuilder, aManager, aManager->GetLayerBuilder(), + aContainerFrame, aContainerItem, bounds, containerLayer, + scaleParameters, backgroundColor, containerASR, + containerScrollMetadataASR, containerCompositorASR); + + state.ProcessDisplayItems(aChildren); + + // Set CONTENT_COMPONENT_ALPHA if any of our children have it. + // This is suboptimal ... a child could have text that's over transparent + // pixels in its own layer, but over opaque parts of previous siblings. + pixBounds = state.ScaleToOutsidePixels(bounds, false); + appUnitsPerDevPixel = state.GetAppUnitsPerDevPixel(); + state.Finish(&flags, pixBounds, aChildren); + + // CONTENT_COMPONENT_ALPHA is propogated up to the nearest CONTENT_OPAQUE + // ancestor so that BasicLayerManager knows when to copy the background into + // pushed groups. Accelerated layers managers can't necessarily do this (only + // when the visible region is a simple rect), so we propogate + // CONTENT_COMPONENT_ALPHA_DESCENDANT all the way to the root. + if (flags & Layer::CONTENT_COMPONENT_ALPHA) { + flags |= Layer::CONTENT_COMPONENT_ALPHA_DESCENDANT; + } + + // Make sure that rounding the visible region out didn't add any area + // we won't paint + if (aChildren->IsOpaque() && !aChildren->NeedsTransparentSurface()) { + bounds.ScaleRoundIn(scaleParameters.mXScale, scaleParameters.mYScale); + if (bounds.Contains(ToAppUnits(pixBounds, appUnitsPerDevPixel))) { + // Clear CONTENT_COMPONENT_ALPHA and add CONTENT_OPAQUE instead. + flags &= ~Layer::CONTENT_COMPONENT_ALPHA; + flags |= Layer::CONTENT_OPAQUE; + } + } + if (nsLayoutUtils::ShouldSnapToGrid(aContainerFrame)) { + flags |= Layer::CONTENT_SNAP_TO_GRID; + } + containerLayer->SetContentFlags(flags); + // If aContainerItem is non-null some BuildContainerLayer further up the + // call stack is responsible for setting containerLayer's visible region. + if (!aContainerItem) { + containerLayer->SetVisibleRegion( + LayerIntRegion::FromUnknownRegion(pixBounds)); + } + if (aParameters.mLayerContentsVisibleRect) { + *aParameters.mLayerContentsVisibleRect = + pixBounds + scaleParameters.mOffset; + } + + nsPresContext::ClearNotifySubDocInvalidationData(containerLayer); + + return containerLayer.forget(); +} + +Layer* FrameLayerBuilder::GetLeafLayerFor(nsDisplayListBuilder* aBuilder, + nsDisplayItem* aItem) { + Layer* layer = GetOldLayerFor(aItem); + if (!layer) { + return nullptr; + } + if (layer->HasUserData(&gPaintedDisplayItemLayerUserData)) { + // This layer was created to render Thebes-rendered content for this + // display item. The display item should not use it for its own + // layer rendering. + return nullptr; + } + ResetLayerStateForRecycling(layer); + return layer; +} + +/* static */ +void FrameLayerBuilder::InvalidateAllLayers(LayerManager* aManager) { + LayerManagerData* data = static_cast<LayerManagerData*>( + aManager->GetUserData(&gLayerManagerUserData)); + if (data) { + data->mInvalidateAllLayers = true; + } +} + +/* static */ +void FrameLayerBuilder::InvalidateAllLayersForFrame(nsIFrame* aFrame) { + const SmallPointerArray<DisplayItemData>& array = aFrame->DisplayItemData(); + + for (uint32_t i = 0; i < array.Length(); i++) { + DisplayItemData::AssertDisplayItemData(array.ElementAt(i)) + ->mParent->mInvalidateAllLayers = true; + } +} + +/* static */ +Layer* FrameLayerBuilder::GetDedicatedLayer(nsIFrame* aFrame, + DisplayItemType aDisplayItemKey) { + // TODO: This isn't completely correct, since a frame could exist as a layer + // in the normal widget manager, and as a different layer (or no layer) + // in the secondary manager + + const SmallPointerArray<DisplayItemData>& array = aFrame->DisplayItemData(); + ; + + for (uint32_t i = 0; i < array.Length(); i++) { + DisplayItemData* element = + DisplayItemData::AssertDisplayItemData(array.ElementAt(i)); + if (!element->mParent->mLayerManager->IsWidgetLayerManager()) { + continue; + } + if (GetDisplayItemTypeFromKey(element->mDisplayItemKey) == + aDisplayItemKey) { + if (element->mOptLayer) { + return element->mOptLayer; + } + + Layer* layer = element->mLayer; + if (!layer->HasUserData(&gColorLayerUserData) && + !layer->HasUserData(&gImageLayerUserData) && + !layer->HasUserData(&gPaintedDisplayItemLayerUserData)) { + return layer; + } + } + } + return nullptr; +} + +/* static */ +void FrameLayerBuilder::EnumerateGenerationForDedicatedLayers( + const nsIFrame* aFrame, AnimationGenerationCallback aCallback) { + std::bitset<static_cast<uint32_t>(DisplayItemType::TYPE_MAX)> notFoundTypes; + for (auto displayItemType : LayerAnimationInfo::sDisplayItemTypes) { + notFoundTypes.set(static_cast<uint32_t>(displayItemType)); + } + + for (auto displayItemType : LayerAnimationInfo::sDisplayItemTypes) { + // For transform animations, the animation is on the primary frame but + // |aFrame| is the style frame. + const nsIFrame* frameToQuery = + displayItemType == DisplayItemType::TYPE_TRANSFORM + ? nsLayoutUtils::GetPrimaryFrameFromStyleFrame(aFrame) + : aFrame; + const nsIFrame::DisplayItemDataArray& displayItemDataArray = + frameToQuery->DisplayItemData(); + + for (uint32_t i = 0; i < displayItemDataArray.Length(); i++) { + DisplayItemData* element = DisplayItemData::AssertDisplayItemData( + displayItemDataArray.ElementAt(i)); + if (!element->mParent->mLayerManager->IsWidgetLayerManager()) { + continue; + } + + if (GetDisplayItemTypeFromKey(element->mDisplayItemKey) != + displayItemType) { + continue; + } + + notFoundTypes.reset(static_cast<uint32_t>(displayItemType)); + + Maybe<uint64_t> generation; + if (element->mOptLayer) { + generation = element->mOptLayer->GetAnimationGeneration(); + } else if (!element->mLayer->HasUserData(&gColorLayerUserData) && + !element->mLayer->HasUserData(&gImageLayerUserData) && + !element->mLayer->HasUserData( + &gPaintedDisplayItemLayerUserData)) { + generation = element->mLayer->GetAnimationGeneration(); + } + + if (!aCallback(generation, displayItemType)) { + return; + } + + break; + } + } + + // Bail out if we have already enumerated all possible layers for the given + // display item types. + if (notFoundTypes.none()) { + return; + } + + // If there are any display item types that the nsIFrame doesn't have, we need + // to call the callback function for them respectively. + for (auto displayItemType : LayerAnimationInfo::sDisplayItemTypes) { + if (notFoundTypes[static_cast<uint32_t>(displayItemType)] && + !aCallback(Nothing(), displayItemType)) { + return; + } + } +} + +gfxSize FrameLayerBuilder::GetPaintedLayerScaleForFrame(nsIFrame* aFrame) { + MOZ_ASSERT(aFrame, "need a frame"); + + nsPresContext* presCtx = aFrame->PresContext()->GetRootPresContext(); + + if (!presCtx) { + presCtx = aFrame->PresContext(); + MOZ_ASSERT(presCtx); + } + + nsIFrame* root = presCtx->PresShell()->GetRootFrame(); + + MOZ_ASSERT(root); + + float resolution = presCtx->PresShell()->GetResolution(); + + Matrix4x4Flagged transform = Matrix4x4::Scaling(resolution, resolution, 1.0); + if (aFrame != root) { + // aTransform is applied first, then the scale is applied to the result + transform = nsLayoutUtils::GetTransformToAncestor(RelativeTo{aFrame}, + RelativeTo{root}) * + transform; + } + + Matrix transform2d; + if (transform.CanDraw2D(&transform2d)) { + return ThebesMatrix(transform2d).ScaleFactors(); + } + + return gfxSize(1.0, 1.0); +} + +#ifdef MOZ_DUMP_PAINTING +static void DebugPaintItem(DrawTarget& aDrawTarget, nsPresContext* aPresContext, + nsPaintedDisplayItem* aItem, + nsDisplayListBuilder* aBuilder) { + bool snap; + Rect bounds = NSRectToRect(aItem->GetBounds(aBuilder, &snap), + aPresContext->AppUnitsPerDevPixel()); + + const IntSize size = IntSize::Truncate(bounds.width, bounds.height); + if (size.IsEmpty()) { + return; + } + + RefPtr<DrawTarget> tempDT = + aDrawTarget.CreateSimilarDrawTarget(size, SurfaceFormat::B8G8R8A8); + RefPtr<gfxContext> context = gfxContext::CreateOrNull(tempDT); + if (!context) { + // Leave this as crash, it's in the debugging code, we want to know + gfxDevCrash(LogReason::InvalidContext) + << "DebugPaintItem context problem " << gfx::hexa(tempDT); + return; + } + context->SetMatrix(Matrix::Translation(-bounds.x, -bounds.y)); + + aItem->Paint(aBuilder, context); + RefPtr<SourceSurface> surface = tempDT->Snapshot(); + DumpPaintedImage(aItem, surface); + + aDrawTarget.DrawSurface(surface, bounds, Rect(Point(0, 0), bounds.Size())); + + aItem->SetPainted(); +} +#endif + +/* static */ +void FrameLayerBuilder::RecomputeVisibilityForItems( + std::vector<AssignedDisplayItem>& aItems, nsDisplayListBuilder* aBuilder, + const nsIntRegion& aRegionToDraw, nsRect& aPreviousRectToDraw, + const nsIntPoint& aOffset, int32_t aAppUnitsPerDevPixel, float aXScale, + float aYScale) { + // Update visible regions. We perform visibility analysis to take account + // of occlusion culling. + nsRegion visible = aRegionToDraw.ToAppUnits(aAppUnitsPerDevPixel); + visible.MoveBy(NSIntPixelsToAppUnits(aOffset.x, aAppUnitsPerDevPixel), + NSIntPixelsToAppUnits(aOffset.y, aAppUnitsPerDevPixel)); + visible.ScaleInverseRoundOut(aXScale, aYScale); + + // We're going to read from previousRectToDraw for every iteration, let's do + // that on the stack, and just update the heap allocated one now. By the end + // of this function {visible} will have been modified by occlusion culling. + nsRect previousRectToDraw = aPreviousRectToDraw; + aPreviousRectToDraw = visible.GetBounds(); + + for (uint32_t i = aItems.size(); i > 0; --i) { + AssignedDisplayItem* cdi = &aItems[i - 1]; + if (!cdi->mItem) { + continue; + } + + if (cdi->mHasPaintRect && + !cdi->mContentRect.Intersects(visible.GetBounds()) && + !cdi->mContentRect.Intersects(previousRectToDraw)) { + continue; + } + + if (IsEffectEndMarker(cdi->mType) || cdi->HasOpacity() || + cdi->HasTransform()) { + // The visibility calculations are skipped when the item is an effect end + // marker, or when the display item is within a flattened effect group. + // This is because RecomputeVisibility has already been called for the + // group item, and all the children. + continue; + } + + const DisplayItemClip& clip = cdi->mItem->GetClip(); + + NS_ASSERTION(AppUnitsPerDevPixel(cdi->mItem) == aAppUnitsPerDevPixel, + "a painted layer should contain items only at the same zoom"); + + MOZ_ASSERT(clip.HasClip() || clip.GetRoundedRectCount() == 0, + "If we have rounded rects, we must have a clip rect"); + + if (!clip.IsRectAffectedByClip(visible.GetBounds())) { + cdi->mItem->RecomputeVisibility(aBuilder, &visible); + continue; + } + + // Do a little dance to account for the fact that we're clipping + // to cdi->mClipRect + nsRegion clipped; + clipped.And(visible, clip.NonRoundedIntersection()); + nsRegion finalClipped = clipped; + cdi->mItem->RecomputeVisibility(aBuilder, &finalClipped); + // If we have rounded clip rects, don't subtract from the visible + // region since we aren't displaying everything inside the rect. + if (clip.GetRoundedRectCount() == 0) { + nsRegion removed; + removed.Sub(clipped, finalClipped); + nsRegion newVisible; + newVisible.Sub(visible, removed); + // Don't let the visible region get too complex. + if (newVisible.GetNumRects() <= 15) { + visible = std::move(newVisible); + } + } + } +} + +/** + * Tracks and caches the item clip. + */ +struct ItemClipTracker { + explicit ItemClipTracker(gfxContext* aContext, + const int32_t aAppUnitsPerDevPixel) + : mContext(aContext), + mHasClip(false), + mAppUnitsPerDevPixel(aAppUnitsPerDevPixel) {} + + /** + * Returns true if a clip is set. + */ + bool HasClip() const { return mHasClip; } + + /** + * Returns true if the given |aClip| is set. + */ + bool HasClip(const DisplayItemClip* aClip) const { + MOZ_ASSERT(aClip && aClip->HasClip()); + return mHasClip && mCurrentClip == *aClip; + } + + /** + * Removes the clip, if there is one. + */ + void Restore() { + if (mCurrentClip.HasClip()) { + mCurrentClip = DisplayItemClip::NoClip(); + } + + if (!HasClip()) { + return; + } + + mContext->Restore(); + mHasClip = false; + }; + + /** + * Sets the clip to |aClip|, if it is not set already. + */ + void ChangeClipIfNeeded(const DisplayItemClip* aClip) { + MOZ_ASSERT(aClip && aClip->HasClip()); + + if (HasClip(aClip)) { + // Reuse the old clip. + return; + } + + // Remove the previous clip and save the current state. + Restore(); + mContext->Save(); + + // Apply the new clip. + mHasClip = true; + mCurrentClip = *aClip; + mCurrentClip.ApplyTo(mContext, mAppUnitsPerDevPixel); + mContext->NewPath(); + } + + private: + gfxContext* mContext; + bool mHasClip; + const int32_t mAppUnitsPerDevPixel; + + DisplayItemClip mCurrentClip; +}; + +/** + * Tracks clips managed by |PushClip()| and |PopClip()|. + * If allowed by the caller, the top clip may be reused when a new clip that + * matches the previous one is pushed to the stack. + */ +struct ClipStack { + explicit ClipStack(gfxContext* aContext, const int32_t aAppUnitsPerDevPixel) + : mContext(aContext), + mAppUnitsPerDevPixel(aAppUnitsPerDevPixel), + mDeferredPopClip(false) {} + + ~ClipStack() { + MOZ_ASSERT(!mDeferredPopClip); + MOZ_ASSERT(!HasClips()); + } + + /** + * Returns true if there are clips set. + */ + bool HasClips() const { return mClips.Length() > 0; } + + /** + * Returns the clip at the top of the stack. + */ + const DisplayItemClip& TopClip() const { + MOZ_ASSERT(HasClips()); + return mClips.LastElement(); + } + + /** + * Returns true if the top clip matches the given |aClip|. + */ + bool TopClipMatches(const DisplayItemClip& aClip) { + return HasClips() && TopClip() == aClip; + } + + /** + * Pops the current top clip. If |aDeferPopClip| is true, the top clip will + * not be popped before the next call to |PopClip(false)|. + * This allows the previously set clip to be reused during the next + * |PushClip()| call, if the new clip is identical with the top clip. + */ + void PopClip(bool aDeferPopClip) { + MOZ_ASSERT(HasClips()); + + if (aDeferPopClip) { + // Do not allow reusing clip with nested effects. + MOZ_ASSERT(!mDeferredPopClip); + mDeferredPopClip = true; + return; + } + + if (TopClip().HasClip()) { + mContext->Restore(); + } + + mClips.RemoveLastElement(); + mDeferredPopClip = false; + } + + /** + * Pops the clip, if a call to |PopClip()| has been deferred. + */ + void PopDeferredClip() { + if (mDeferredPopClip) { + PopClip(false); + } + } + + /** + * Pushes the given |aClip| to the stack. + */ + void PushClip(const DisplayItemClip& aClip) { + if (mDeferredPopClip && TopClipMatches(aClip)) { + // Reuse this clip. Defer the decision to reuse it again until the next + // call to PopClip(). + mDeferredPopClip = false; + return; + } + + PopDeferredClip(); + + mClips.AppendElement(aClip); + + // Save the current state and apply new clip, if needed. + if (aClip.HasClip()) { + mContext->Save(); + aClip.ApplyTo(mContext, mAppUnitsPerDevPixel); + mContext->NewPath(); + } + } + + private: + gfxContext* mContext; + const int32_t mAppUnitsPerDevPixel; + AutoTArray<DisplayItemClip, 2> mClips; + bool mDeferredPopClip; +}; + +/** + * Returns a clip for the given |aItem|. If the clip can be simplified to not + * include rounded rects, |aOutClip| is used to store the simplified clip. + */ +static const DisplayItemClip* GetItemClip(const nsDisplayItem* aItem, + DisplayItemClip& aOutClip) { + const DisplayItemClip& clip = aItem->GetClip(); + + if (!clip.HasClip()) { + return nullptr; + } + + if (clip.GetRoundedRectCount() > 0 && + !clip.IsRectClippedByRoundedCorner(aItem->GetPaintRect())) { + aOutClip.SetTo(clip.GetClipRect()); + return &aOutClip; + } + + return &clip; +} + +/** + * Pushes a new opacity group for |aContext| based on |aItem|. + */ +static void PushOpacity(gfxContext* aContext, AssignedDisplayItem& aItem) { + MOZ_ASSERT(aItem.mType == DisplayItemEntryType::PushOpacity || + aItem.mType == DisplayItemEntryType::PushOpacityWithBg); + MOZ_ASSERT(aItem.mItem->GetType() == DisplayItemType::TYPE_OPACITY); + nsDisplayOpacity* item = static_cast<nsDisplayOpacity*>(aItem.mItem); + + const float opacity = item->GetOpacity(); + if (aItem.mType == DisplayItemEntryType::PushOpacityWithBg) { + aContext->PushGroupAndCopyBackground(gfxContentType::COLOR_ALPHA, opacity); + } else { + aContext->PushGroupForBlendBack(gfxContentType::COLOR_ALPHA, opacity); + } +} + +/** + * Pushes the transformation matrix of |aItem| into |aMatrixStack| and sets the + * accumulated transform as the current transformation matrix for |aContext|. + */ +static void PushTransform(gfxContext* aContext, AssignedDisplayItem& aItem, + nsDisplayListBuilder* aBuilder, + MatrixStack4x4& aMatrixStack, + const Matrix4x4Flagged& aBaseMatrix) { + MOZ_ASSERT(aItem.mType == DisplayItemEntryType::PushTransform); + MOZ_ASSERT(aItem.mItem->GetType() == DisplayItemType::TYPE_TRANSFORM); + + nsDisplayTransform* item = static_cast<nsDisplayTransform*>(aItem.mItem); + if (item->ShouldSkipTransform(aBuilder)) { + aMatrixStack.Push(Matrix4x4Flagged()); + } else { + aMatrixStack.Push(item->GetTransformForRendering()); + } + + gfx::Matrix4x4Flagged matrix = aMatrixStack.CurrentMatrix() * aBaseMatrix; + gfx::Matrix matrix2d; + DebugOnly<bool> ok = matrix.CanDraw2D(&matrix2d); + MOZ_ASSERT(ok); + + aContext->SetMatrix(matrix2d); +} + +static void UpdateEffectTracking(int& aOpacityLevel, int& aTransformLevel, + const DisplayItemEntryType aType) { + switch (aType) { + case DisplayItemEntryType::PushOpacity: + case DisplayItemEntryType::PushOpacityWithBg: + aOpacityLevel++; + break; + case DisplayItemEntryType::PopOpacity: + aOpacityLevel--; + break; + case DisplayItemEntryType::PushTransform: + aTransformLevel++; + break; + case DisplayItemEntryType::PopTransform: + aTransformLevel--; + break; + default: + break; + } + + MOZ_ASSERT(aOpacityLevel >= 0 && aTransformLevel >= 0); +} + +void FrameLayerBuilder::PaintItems(std::vector<AssignedDisplayItem>& aItems, + const nsIntRect& aRect, gfxContext* aContext, + nsDisplayListBuilder* aBuilder, + nsPresContext* aPresContext, + const nsIntPoint& aOffset, float aXScale, + float aYScale) { + DrawTarget& aDrawTarget = *aContext->GetDrawTarget(); + + int32_t appUnitsPerDevPixel = aPresContext->AppUnitsPerDevPixel(); + nsRect boundRect = ToAppUnits(aRect, appUnitsPerDevPixel); + boundRect.MoveBy(NSIntPixelsToAppUnits(aOffset.x, appUnitsPerDevPixel), + NSIntPixelsToAppUnits(aOffset.y, appUnitsPerDevPixel)); + boundRect.ScaleInverseRoundOut(aXScale, aYScale); + + if (boundRect.IsEmpty()) { + // Hack! This can happen if the conversion of |aRect| to scaled and offset + // app units overflowed. Ideally the conversion would detect this and handle + // such situations gracefully. For now, do nothing. + return; + } + +#ifdef DEBUG + // Tracks effect nesting level. These are used to track that every effect + // start marker has a corresponding end marker. + int opacityLevel = 0; + int transformLevel = 0; +#endif + + // Tracks effect nesting level for skipping items between effect markers, + // when the effect display item does not intersect with the invalidated area. + int emptyEffectLevel = 0; + + // Stores a simplified version of the item clip, if needed. + DisplayItemClip temporaryClip; + + // Two types of clips are used during PaintItems(): clips for items and clips + // for effects. Item clips are always the most recent clip set, and they are + // never nested. The previous item clip is reused, if the next item has the + // same clip. Item clips are removed when an effect starts or ends. + ItemClipTracker itemClipTracker(aContext, appUnitsPerDevPixel); + + // Since effects can be nested, the effect clips need to be nested as well. + // They are pushed for effect start marker, and popped for effect end marker. + // Effect clips are tracked by |effectClipStack|. If there are consecutive + // effects with the same clip, |effectClipStack| defers popping the clip for + // the first end marker, and tries to reuse the previously set clip, when + // processing the start marker for the next effect. + ClipStack effectClipStack(aContext, appUnitsPerDevPixel); + + MatrixStack4x4 matrixStack; + const Matrix4x4Flagged base = Matrix4x4::From2D(aContext->CurrentMatrix()); + + for (uint32_t i = 0; i < aItems.size(); ++i) { + AssignedDisplayItem& cdi = aItems[i]; + nsDisplayItem* item = cdi.mItem; + + const auto NextItemStartsEffect = [&]() { + const uint32_t next = i + 1; + return next < aItems.size() && IsEffectStartMarker(aItems[next].mType); + }; + + if (!item) { + MOZ_ASSERT(cdi.mType == DisplayItemEntryType::Item); + continue; + } + + nsRect visibleRect = item->GetPaintRect(); + + if (matrixStack.HasTransform()) { + MOZ_ASSERT(transformLevel > 0); + + if (IsEffectEndMarker(cdi.mType)) { + // Always process the effect end markers. + visibleRect = boundRect; + } else { + const Matrix4x4Flagged& matrix = matrixStack.CurrentMatrix(); + visibleRect = nsLayoutUtils::MatrixTransformRect(visibleRect, matrix, + appUnitsPerDevPixel); + } + } + + const nsRect paintRect = visibleRect.Intersect(boundRect); + + if (paintRect.IsEmpty() || emptyEffectLevel > 0) { + // In order for this branch to be hit, either this item has an empty paint + // rect and nothing would be drawn, or an effect marker before this + // item had an empty paint rect. In the latter case, the items are skipped + // until effect POP markers bring |emptyEffectLevel| back to 0. + UpdateEffectTracking(emptyEffectLevel, emptyEffectLevel, cdi.mType); + + // Sometimes the item that was going to reuse the previous clip is culled. + // Since |PushClip()| is never called for culled items, pop the clip now. + effectClipStack.PopDeferredClip(); + continue; + } + +#ifdef MOZ_DUMP_PAINTING + AUTO_PROFILER_LABEL_DYNAMIC_CSTR_NONSENSITIVE( + "FrameLayerBuilder::PaintItems", GRAPHICS_Rasterization, item->Name()); +#else + AUTO_PROFILER_LABEL("FrameLayerBuilder::PaintItems", + GRAPHICS_Rasterization); +#endif + + MOZ_ASSERT((opacityLevel == 0 && !cdi.HasOpacity()) || + (opacityLevel > 0 && cdi.HasOpacity()) || + (transformLevel == 0 && !cdi.HasTransform()) || + (transformLevel > 0 && cdi.HasTransform())); + + if (cdi.mType != DisplayItemEntryType::Item) { + // If we are processing an effect marker, remove the current item clip, if + // there is one. + itemClipTracker.Restore(); + } + + if (cdi.mType == DisplayItemEntryType::PushOpacity || + cdi.mType == DisplayItemEntryType::PushOpacityWithBg) { + // To avoid pushing large temporary surfaces, it is important to clip + // opacity group with both the paint rect and the actual opacity clip. + DisplayItemClip effectClip; + effectClip.SetTo(item->GetPaintRect()); + effectClip.IntersectWith(item->GetClip()); + effectClipStack.PushClip(effectClip); + PushOpacity(aContext, cdi); + } + + if (cdi.mType == DisplayItemEntryType::PopOpacity) { + MOZ_ASSERT(opacityLevel > 0); + aContext->PopGroupAndBlend(); + } + + if (cdi.mType == DisplayItemEntryType::PushTransform) { + effectClipStack.PushClip(item->GetClip()); + aContext->Save(); + PushTransform(aContext, cdi, aBuilder, matrixStack, base); + } + + if (cdi.mType == DisplayItemEntryType::PopTransform) { + MOZ_ASSERT(transformLevel > 0); + matrixStack.Pop(); + aContext->Restore(); + } + + if (IsEffectEndMarker(cdi.mType)) { + // Pop the clip for the effect. + MOZ_ASSERT(effectClipStack.HasClips()); + + // If the next item starts an effect, defer popping the current clip, and + // try to reuse it during the next call to |PushClip()|. Trying to reuse + // clips between nested effects would be difficult, for example due to + // possibly different coordinate system, so this optimization is limited + // to consecutive effects. + effectClipStack.PopClip(NextItemStartsEffect()); + } + + if (cdi.mType != DisplayItemEntryType::Item) { +#ifdef DEBUG + UpdateEffectTracking(opacityLevel, transformLevel, cdi.mType); +#endif + // Nothing more to do with effect markers. + continue; + } + + const bool paintAsLayer = cdi.mInactiveLayerData.get(); + nsPaintedDisplayItem* paintedItem = item->AsPaintedDisplayItem(); + MOZ_ASSERT(paintAsLayer || paintedItem, + "The display item does not support painting"); + + const DisplayItemClip* itemClip = GetItemClip(item, temporaryClip); + bool itemPaintsOwnClip = false; + + if (itemClip && !itemClipTracker.HasClip(itemClip)) { + // The clip has changed. Remove the previous clip. + itemClipTracker.Restore(); + + // Check if the item supports painting with clip. + itemPaintsOwnClip = + paintAsLayer ? false : paintedItem->CanPaintWithClip(*itemClip); + + if (!itemPaintsOwnClip) { + // Item does not support painting with clip, set the clip. + itemClipTracker.ChangeClipIfNeeded(itemClip); + } + } + + if (!itemClip) { + // Item does not need clipping, remove the clip if there is one. + itemClipTracker.Restore(); + } + + if (paintAsLayer) { + bool saved = aDrawTarget.GetPermitSubpixelAA(); + PaintInactiveLayer(aBuilder, cdi.mInactiveLayerData->mLayerManager, item, + aContext, aContext); + aDrawTarget.SetPermitSubpixelAA(saved); + continue; + } + + nsIFrame* frame = item->Frame(); + if (aBuilder->IsPaintingToWindow()) { + frame->AddStateBits(NS_FRAME_PAINTED_THEBES); + } + +#ifdef MOZ_DUMP_PAINTING + if (gfxEnv::DumpPaintItems()) { + DebugPaintItem(aDrawTarget, aPresContext, paintedItem, aBuilder); + continue; + } +#endif + + if (itemPaintsOwnClip) { + MOZ_ASSERT(itemClip); + paintedItem->PaintWithClip(aBuilder, aContext, *itemClip); + } else { + paintedItem->Paint(aBuilder, aContext); + } + } + + itemClipTracker.Restore(); + + MOZ_ASSERT(opacityLevel == 0); + MOZ_ASSERT(transformLevel == 0); + MOZ_ASSERT(emptyEffectLevel == 0); +} + +/** + * Returns true if it is preferred to draw the list of display + * items separately for each rect in the visible region rather + * than clipping to a complex region. + */ +static bool ShouldDrawRectsSeparately(DrawTarget* aDrawTarget, + DrawRegionClip aClip) { + if (!StaticPrefs::layout_paint_rects_separately_AtStartup() || + aClip == DrawRegionClip::NONE) { + return false; + } + + return !aDrawTarget->SupportsRegionClipping(); +} + +static void DrawForcedBackgroundColor(DrawTarget& aDrawTarget, + const IntRect& aBounds, + nscolor aBackgroundColor) { + if (NS_GET_A(aBackgroundColor) > 0) { + ColorPattern color(ToDeviceColor(aBackgroundColor)); + aDrawTarget.FillRect(Rect(aBounds), color); + } +} + +/* + * A note on residual transforms: + * + * In a transformed subtree we sometimes apply the PaintedLayer's + * "residual transform" when drawing content into the PaintedLayer. + * This is a translation by components in the range [-0.5,0.5) provided + * by the layer system; applying the residual transform followed by the + * transforms used by layer compositing ensures that the subpixel alignment + * of the content of the PaintedLayer exactly matches what it would be if + * we used cairo/Thebes to draw directly to the screen without going through + * retained layer buffers. + * + * The visible and valid regions of the PaintedLayer are computed without + * knowing the residual transform (because we don't know what the residual + * transform is going to be until we've built the layer tree!). So we have to + * consider whether content painted in the range [x, xmost) might be painted + * outside the visible region we computed for that content. The visible region + * would be [floor(x), ceil(xmost)). The content would be rendered at + * [x + r, xmost + r), where -0.5 <= r < 0.5. So some half-rendered pixels could + * indeed fall outside the computed visible region, which is not a big deal; + * similar issues already arise when we snap cliprects to nearest pixels. + * Note that if the rendering of the content is snapped to nearest pixels --- + * which it often is --- then the content is actually rendered at + * [snap(x + r), snap(xmost + r)). It turns out that floor(x) <= snap(x + r) + * and ceil(xmost) >= snap(xmost + r), so the rendering of snapped content + * always falls within the visible region we computed. + */ + +/* static */ +void FrameLayerBuilder::DrawPaintedLayer(PaintedLayer* aLayer, + gfxContext* aContext, + const nsIntRegion& aRegionToDraw, + const nsIntRegion& aDirtyRegion, + DrawRegionClip aClip, + const nsIntRegion& aRegionToInvalidate, + void* aCallbackData) { + DrawTarget& aDrawTarget = *aContext->GetDrawTarget(); + + AUTO_PROFILER_LABEL("FrameLayerBuilder::DrawPaintedLayer", + GRAPHICS_Rasterization); + + nsDisplayListBuilder* builder = + static_cast<nsDisplayListBuilder*>(aCallbackData); + + FrameLayerBuilder* layerBuilder = aLayer->Manager()->GetLayerBuilder(); + NS_ASSERTION(layerBuilder, "Unexpectedly null layer builder!"); + + auto* userData = GetPaintedDisplayItemLayerUserData(aLayer); + NS_ASSERTION(userData, "where did our user data go?"); + if (!userData->mContainerLayerFrame) { + return; + } + + bool shouldDrawRectsSeparately = + ShouldDrawRectsSeparately(&aDrawTarget, aClip); + + if (!shouldDrawRectsSeparately) { + if (aClip == DrawRegionClip::DRAW) { + gfxUtils::ClipToRegion(aContext, aRegionToDraw); + } + + DrawForcedBackgroundColor(aDrawTarget, aRegionToDraw.GetBounds(), + userData->mForcedBackgroundColor); + } + + // make the origin of the context coincide with the origin of the + // PaintedLayer + gfxContextMatrixAutoSaveRestore saveMatrix(aContext); + nsIntPoint offset = GetTranslationForPaintedLayer(aLayer); + nsPresContext* presContext = userData->mContainerLayerFrame->PresContext(); + + if (!userData->mVisibilityComputedRegion.Contains(aDirtyRegion) && + !layerBuilder->GetContainingPaintedLayerData()) { + // Recompute visibility of items in our PaintedLayer, if required. Note + // that this recomputes visibility for all descendants of our display + // items too, so there's no need to do this for the items in inactive + // PaintedLayers. If aDirtyRegion has not changed since the previous call + // then we can skip this. + int32_t appUnitsPerDevPixel = presContext->AppUnitsPerDevPixel(); + RecomputeVisibilityForItems(userData->mItems, builder, aDirtyRegion, + userData->mPreviousRecomputeVisibilityRect, + offset, appUnitsPerDevPixel, userData->mXScale, + userData->mYScale); + userData->mVisibilityComputedRegion = aDirtyRegion; + } + + if (shouldDrawRectsSeparately) { + for (auto iter = aRegionToDraw.RectIter(); !iter.Done(); iter.Next()) { + const nsIntRect& iterRect = iter.Get(); + gfxContextAutoSaveRestore save(aContext); + aContext->NewPath(); + aContext->Rectangle(ThebesRect(iterRect)); + aContext->Clip(); + + DrawForcedBackgroundColor(aDrawTarget, iterRect, + userData->mForcedBackgroundColor); + + // Apply the residual transform if it has been enabled, to ensure that + // snapping when we draw into aContext exactly matches the ideal + // transform. See above for why this is OK. + aContext->SetMatrixDouble( + aContext->CurrentMatrixDouble() + .PreTranslate(aLayer->GetResidualTranslation() - + gfxPoint(offset.x, offset.y)) + .PreScale(userData->mXScale, userData->mYScale)); + + layerBuilder->PaintItems(userData->mItems, iterRect, aContext, builder, + presContext, offset, userData->mXScale, + userData->mYScale); + if (StaticPrefs::gfx_logging_painted_pixel_count_enabled()) { + aLayer->Manager()->AddPaintedPixelCount(iterRect.Area()); + } + } + } else { + // Apply the residual transform if it has been enabled, to ensure that + // snapping when we draw into aContext exactly matches the ideal transform. + // See above for why this is OK. + aContext->SetMatrixDouble( + aContext->CurrentMatrixDouble() + .PreTranslate(aLayer->GetResidualTranslation() - + gfxPoint(offset.x, offset.y)) + .PreScale(userData->mXScale, userData->mYScale)); + + layerBuilder->PaintItems(userData->mItems, aRegionToDraw.GetBounds(), + aContext, builder, presContext, offset, + userData->mXScale, userData->mYScale); + if (StaticPrefs::gfx_logging_painted_pixel_count_enabled()) { + aLayer->Manager()->AddPaintedPixelCount(aRegionToDraw.GetBounds().Area()); + } + } + + bool isActiveLayerManager = !aLayer->Manager()->IsInactiveLayerManager(); + + if (presContext->GetPaintFlashing() && isActiveLayerManager) { + gfxContextAutoSaveRestore save(aContext); + if (shouldDrawRectsSeparately) { + if (aClip == DrawRegionClip::DRAW) { + gfxUtils::ClipToRegion(aContext, aRegionToDraw); + } + } + FlashPaint(aContext); + } + + if (presContext->GetDocShell() && isActiveLayerManager) { + nsDocShell* docShell = static_cast<nsDocShell*>(presContext->GetDocShell()); + RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get(); + + if (timelines && timelines->HasConsumer(docShell)) { + timelines->AddMarkerForDocShell( + docShell, MakeUnique<LayerTimelineMarker>(aRegionToDraw)); + } + } + + if (!aRegionToInvalidate.IsEmpty()) { + aLayer->AddInvalidRect(aRegionToInvalidate.GetBounds()); + } +} + +/* static */ +void FrameLayerBuilder::DumpRetainedLayerTree(LayerManager* aManager, + std::stringstream& aStream, + bool aDumpHtml) { + aManager->Dump(aStream, "", aDumpHtml); +} + +nsDisplayItemGeometry* FrameLayerBuilder::GetMostRecentGeometry( + nsDisplayItem* aItem) { + typedef SmallPointerArray<DisplayItemData> DataArray; + + // Retrieve the array of DisplayItemData associated with our frame. + DataArray& dataArray = aItem->Frame()->DisplayItemData(); + + // Find our display item data, if it exists, and return its geometry. + // We first check for ones with an inactive manager, since items that + // create inactive layers will create two DisplayItemData entries, + // and we want the outer one. + DisplayItemData* firstMatching = nullptr; + uint32_t itemPerFrameKey = aItem->GetPerFrameKey(); + for (DisplayItemData* data : dataArray) { + DisplayItemData::AssertDisplayItemData(data); + if (data->GetDisplayItemKey() == itemPerFrameKey) { + if (data->InactiveManager()) { + return data->GetGeometry(); + } + if (!firstMatching) { + firstMatching = data; + } + } + } + if (firstMatching) { + return firstMatching->GetGeometry(); + } + if (RefPtr<WebRenderFallbackData> data = + GetWebRenderUserData<WebRenderFallbackData>(aItem->Frame(), + itemPerFrameKey)) { + return data->GetGeometry(); + } + + return nullptr; +} + +static gfx::Rect CalculateBounds( + const nsTArray<DisplayItemClip::RoundedRect>& aRects, + int32_t aAppUnitsPerDevPixel) { + nsRect bounds = aRects[0].mRect; + for (uint32_t i = 1; i < aRects.Length(); ++i) { + bounds.UnionRect(bounds, aRects[i].mRect); + } + + return gfx::Rect(bounds.ToNearestPixels(aAppUnitsPerDevPixel)); +} + +void ContainerState::SetupMaskLayer(Layer* aLayer, + const DisplayItemClip& aClip) { + // don't build an unnecessary mask + if (aClip.GetRoundedRectCount() == 0) { + return; + } + + RefPtr<Layer> maskLayer = CreateMaskLayer(aLayer, aClip, Nothing()); + + if (!maskLayer) { + return; + } + + aLayer->SetMaskLayer(maskLayer); +} + +static MaskLayerUserData* GetMaskLayerUserData(Layer* aMaskLayer) { + if (!aMaskLayer) { + return nullptr; + } + + return static_cast<MaskLayerUserData*>( + aMaskLayer->GetUserData(&gMaskLayerUserData)); +} + +static void SetMaskLayerUserData(Layer* aMaskLayer) { + MOZ_ASSERT(aMaskLayer); + + aMaskLayer->SetUserData(&gMaskLayerUserData, new MaskLayerUserData()); +} + +already_AddRefed<Layer> ContainerState::CreateMaskLayer( + Layer* aLayer, const DisplayItemClip& aClip, + const Maybe<size_t>& aForAncestorMaskLayer) { + // aLayer will never be the container layer created by an + // nsDisplayMasksAndClipPaths because nsDisplayMasksAndClipPaths propagates + // the DisplayItemClip to its contents and is not clipped itself. + // This assertion will fail if that ever stops being the case. + MOZ_ASSERT(!aLayer->GetUserData(&gCSSMaskLayerUserData), + "A layer contains round clips should not have css-mask on it."); + + // check if we can re-use the mask layer + RefPtr<ImageLayer> maskLayer = CreateOrRecycleMaskImageLayerFor( + MaskLayerKey(aLayer, aForAncestorMaskLayer), GetMaskLayerUserData, + SetMaskLayerUserData); + MaskLayerUserData* userData = GetMaskLayerUserData(maskLayer.get()); + + int32_t A2D = mContainerFrame->PresContext()->AppUnitsPerDevPixel(); + MaskLayerUserData newData(aClip, A2D, mParameters); + if (*userData == newData) { + return maskLayer.forget(); + } + + gfx::Rect boundingRect = + CalculateBounds(newData.mRoundedClipRects, newData.mAppUnitsPerDevPixel); + boundingRect.Scale(mParameters.mXScale, mParameters.mYScale); + if (boundingRect.IsEmpty()) { + // Return early if we know that there is effectively no visible data. + return nullptr; + } + + uint32_t maxSize = mManager->GetMaxTextureSize(); + NS_ASSERTION(maxSize > 0, "Invalid max texture size"); +#ifdef MOZ_GFX_OPTIMIZE_MOBILE + // Make mask image width aligned to 4. See Bug 1245552. + gfx::Size surfaceSize( + std::min<gfx::Float>( + GetAlignedStride<4>(NSToIntCeil(boundingRect.Width()), 1), maxSize), + std::min<gfx::Float>(boundingRect.Height(), maxSize)); +#else + gfx::Size surfaceSize(std::min<gfx::Float>(boundingRect.Width(), maxSize), + std::min<gfx::Float>(boundingRect.Height(), maxSize)); +#endif + + // maskTransform is applied to the clip when it is painted into the mask (as a + // component of imageTransform), and its inverse used when the mask is used + // for masking. It is the transform from the masked layer's space to mask + // space + gfx::Matrix maskTransform = + Matrix::Scaling(surfaceSize.width / boundingRect.Width(), + surfaceSize.height / boundingRect.Height()); + if (surfaceSize.IsEmpty()) { + // Return early if we know that the size of this mask surface is empty. + return nullptr; + } + + gfx::Point p = boundingRect.TopLeft(); + maskTransform.PreTranslate(-p.x, -p.y); + // imageTransform is only used when the clip is painted to the mask + gfx::Matrix imageTransform = maskTransform; + imageTransform.PreScale(mParameters.mXScale, mParameters.mYScale); + + UniquePtr<MaskLayerImageCache::MaskLayerImageKey> newKey( + new MaskLayerImageCache::MaskLayerImageKey()); + + // copy and transform the rounded rects + for (uint32_t i = 0; i < newData.mRoundedClipRects.Length(); ++i) { + newKey->mRoundedClipRects.AppendElement( + MaskLayerImageCache::PixelRoundedRect(newData.mRoundedClipRects[i], + mContainerFrame->PresContext())); + newKey->mRoundedClipRects[i].ScaleAndTranslate(imageTransform); + } + newKey->mKnowsCompositor = mManager->AsKnowsCompositor(); + + const MaskLayerImageCache::MaskLayerImageKey* lookupKey = newKey.get(); + + // check to see if we can reuse a mask image + RefPtr<ImageContainer> container = + GetMaskLayerImageCache()->FindImageFor(&lookupKey); + + if (!container) { + IntSize surfaceSizeInt(NSToIntCeil(surfaceSize.width), + NSToIntCeil(surfaceSize.height)); + // no existing mask image, so build a new one + MaskImageData imageData(surfaceSizeInt, mManager); + RefPtr<DrawTarget> dt = imageData.CreateDrawTarget(); + + // fail if we can't get the right surface + if (!dt || !dt->IsValid()) { + NS_WARNING("Could not create DrawTarget for mask layer."); + return nullptr; + } + + RefPtr<gfxContext> context = gfxContext::CreateOrNull(dt); + MOZ_ASSERT(context); // already checked the draw target above + context->Multiply(ThebesMatrix(imageTransform)); + + // paint the clipping rects with alpha to create the mask + aClip.FillIntersectionOfRoundedRectClips( + context, DeviceColor::MaskOpaqueWhite(), newData.mAppUnitsPerDevPixel); + + // build the image and container + MOZ_ASSERT(aLayer->Manager() == mManager); + container = imageData.CreateImageAndImageContainer(); + NS_ASSERTION(container, "Could not create image container for mask layer."); + + if (!container) { + return nullptr; + } + + GetMaskLayerImageCache()->PutImage(newKey.release(), container); + } + + maskLayer->SetContainer(container); + + maskTransform.Invert(); + Matrix4x4 matrix = Matrix4x4::From2D(maskTransform); + matrix.PreTranslate(mParameters.mOffset.x, mParameters.mOffset.y, 0); + maskLayer->SetBaseTransform(matrix); + + // save the details of the clip in user data + *userData = std::move(newData); + userData->mImageKey.Reset(lookupKey); + + return maskLayer.forget(); +} + +} // namespace mozilla diff --git a/layout/painting/FrameLayerBuilder.h b/layout/painting/FrameLayerBuilder.h new file mode 100644 index 0000000000..8446ce634e --- /dev/null +++ b/layout/painting/FrameLayerBuilder.h @@ -0,0 +1,732 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef FRAMELAYERBUILDER_H_ +#define FRAMELAYERBUILDER_H_ + +#include <cstddef> // for size_t +#include <cstdint> // for uint32_t, UINT32_MAX, int32_t, uint64_t, uint8_t +#include <iosfwd> // for stringstream +#include <vector> // for vector +#include "DisplayItemClip.h" // for DisplayItemClip +#include "LayerState.h" // for LayerState +#include "LayerUserData.h" // for LayerUserData +#include "Units.h" // for LayerIntPoint, LayoutDeviceToLayerScale2D +#include "gfxPoint.h" // for gfxSize +#include "mozilla/AlreadyAddRefed.h" // for already_AddRefed +#include "mozilla/Assertions.h" // for AssertionConditionType, MOZ_ASSERT_HELPER2 +#include "mozilla/FunctionRef.h" // for FunctionRef +#include "mozilla/RefPtr.h" // for RefPtr +#include "mozilla/UniquePtr.h" // for UniquePtr +#include "mozilla/gfx/Matrix.h" // for Matrix, Matrix4x4 +#include "mozilla/gfx/Point.h" // for Size +#include "mozilla/layers/LayerManager.h" // for LayerManager, LayerManager::NONE, LayerManager::PaintedLayerCreationHint, LayerMetricsW... +#include "mozilla/layers/LayersTypes.h" // for DrawRegionClip, EventRegions +#include "nsColor.h" // for NS_RGBA, nscolor +#include "nsDebug.h" // for NS_WARNING +#include "nsDisplayItemTypes.h" // for DisplayItemType +#include "nsISupports.h" // for NS_INLINE_DECL_REFCOUNTING, NS_LOG_ADDREF, NS_LOG_RELEASE +#include "nsPoint.h" // for nsIntPoint +#include "nsRect.h" // for nsRect (ptr only), nsIntRect +#include "nsRegion.h" // for nsIntRegion, nsRegion +#include "nsTArray.h" // for AutoTArray, nsTArray_Impl +#include "nscore.h" // for nsrefcnt + +class gfxContext; +class nsDisplayItem; +class nsDisplayItemGeometry; +class nsDisplayList; +class nsDisplayListBuilder; +class nsDisplayMasksAndClipPaths; +class nsIFrame; +class nsPaintedDisplayItem; +class nsPresContext; +class nsRootPresContext; + +namespace mozilla { +struct ActiveScrolledRoot; +struct DisplayItemClipChain; +class TransformClipNode; +template <class T> +class Maybe; +template <typename T> +class SmallPointerArray; + +namespace layers { +class ContainerLayer; +class Layer; +class BasicLayerManager; +class PaintedLayer; +class ImageLayer; +struct LayerProperties; +} // namespace layers + +class FrameLayerBuilder; +class LayerManagerData; +class PaintedLayerData; +class ContainerState; +class PaintedDisplayItemLayerUserData; + +enum class DisplayItemEntryType : uint8_t { + Item, + PushOpacity, + PushOpacityWithBg, + PopOpacity, + PushTransform, + PopTransform, + HitTestInfo, +}; + +/** + * Retained data storage: + * + * Each layer manager (widget, and inactive) stores a LayerManagerData object + * that keeps a hash-set of DisplayItemData items that were drawn into it. + * Each frame also keeps a list of DisplayItemData pointers that were + * created for that frame. DisplayItemData objects manage these lists + * automatically. + * + * During layer construction we update the data in the LayerManagerData object, + * marking items that are modified. At the end we sweep the LayerManagerData + * hash-set and remove all items that haven't been modified. + */ + +/** + * Retained data for a display item. + */ +class DisplayItemData final { + public: + friend class FrameLayerBuilder; + friend class ContainerState; + + uint32_t GetDisplayItemKey() { return mDisplayItemKey; } + layers::Layer* GetLayer() const { return mLayer; } + nsDisplayItemGeometry* GetGeometry() const { return mGeometry.get(); } + const DisplayItemClip& GetClip() const { return mClip; } + void Invalidate() { mIsInvalid = true; } + void NotifyRemoved(); + void SetItem(nsPaintedDisplayItem* aItem) { mItem = aItem; } + nsPaintedDisplayItem* GetItem() const { return mItem; } + nsIFrame* FirstFrame() const { return mFrameList[0]; } + layers::BasicLayerManager* InactiveManager() const { + return mInactiveManager; + } + + bool HasMergedFrames() const { return mFrameList.Length() > 1; } + + static DisplayItemData* AssertDisplayItemData(DisplayItemData* aData); + + void* operator new(size_t sz, nsPresContext* aPresContext); + + nsrefcnt AddRef() { + if (mRefCnt == UINT32_MAX) { + NS_WARNING("refcount overflow, leaking object"); + return mRefCnt; + } + ++mRefCnt; + NS_LOG_ADDREF(this, mRefCnt, "DisplayItemData", sizeof(DisplayItemData)); + return mRefCnt; + } + + nsrefcnt Release() { + if (mRefCnt == UINT32_MAX) { + NS_WARNING("refcount overflow, leaking object"); + return mRefCnt; + } + --mRefCnt; + NS_LOG_RELEASE(this, mRefCnt, "DisplayItemData"); + if (mRefCnt == 0) { + Destroy(); + return 0; + } + return mRefCnt; + } + + RefPtr<TransformClipNode> mTransform; + RefPtr<TransformClipNode> mOldTransform; + + private: + DisplayItemData(LayerManagerData* aParent, uint32_t aKey, + layers::Layer* aLayer, nsIFrame* aFrame = nullptr); + + /** + * Removes any references to this object from frames + * in mFrameList. + */ + ~DisplayItemData(); + + void Destroy(); + + /** + * Associates this DisplayItemData with a frame, and adds it + * to the LayerManagerDataProperty list on the frame. + */ + void AddFrame(nsIFrame* aFrame); + void RemoveFrame(nsIFrame* aFrame); + const nsRegion& GetChangedFrameInvalidations(); + + /** + * Updates the contents of this item to a new set of data, instead of + * allocating a new object. Set the passed in parameters, and clears the opt + * layer and inactive manager. Parent, and display item key are assumed to be + * the same. + * + * EndUpdate must be called before the end of the transaction to complete the + * update. + */ + void BeginUpdate(layers::Layer* aLayer, LayerState aState, bool aFirstUpdate, + nsPaintedDisplayItem* aItem = nullptr); + void BeginUpdate(layers::Layer* aLayer, LayerState aState, + nsPaintedDisplayItem* aItem, bool aIsReused, bool aIsMerged); + + /** + * Completes the update of this, and removes any references to data that won't + * live longer than the transaction. + * + * Updates the geometry, frame list and clip. + * For items within a PaintedLayer, a geometry object must be specified to + * retain until the next transaction. + * + */ + void EndUpdate(mozilla::UniquePtr<nsDisplayItemGeometry>&& aGeometry); + void EndUpdate(); + + uint32_t mRefCnt; + LayerManagerData* mParent; + RefPtr<layers::Layer> mLayer; + RefPtr<layers::Layer> mOptLayer; + RefPtr<layers::BasicLayerManager> mInactiveManager; + AutoTArray<nsIFrame*, 1> mFrameList; + mozilla::UniquePtr<nsDisplayItemGeometry> mGeometry; + DisplayItemClip mClip; + uint32_t mDisplayItemKey; + LayerState mLayerState; + + /** + * Temporary stoarage of the display item being referenced, only valid between + * BeginUpdate and EndUpdate. + */ + nsPaintedDisplayItem* mItem; + nsRegion mChangedFrameInvalidations; + + /** + * Used to track if data currently stored in mFramesWithLayers (from an + * existing paint) has been updated in the current paint. + */ + bool mUsed; + bool mIsInvalid; + bool mReusedItem; +}; + +class RefCountedRegion { + private: + ~RefCountedRegion() = default; + + public: + NS_INLINE_DECL_REFCOUNTING(RefCountedRegion) + + RefCountedRegion() : mIsInfinite(false) {} + nsRegion mRegion; + bool mIsInfinite; +}; + +struct AssignedDisplayItem; + +struct ContainerLayerParameters { + ContainerLayerParameters() + : mXScale(1), + mYScale(1), + mLayerContentsVisibleRect(nullptr), + mBackgroundColor(NS_RGBA(0, 0, 0, 0)), + mScrollMetadataASR(nullptr), + mCompositorASR(nullptr), + mInTransformedSubtree(false), + mInActiveTransformedSubtree(false), + mDisableSubpixelAntialiasingInDescendants(false), + mLayerCreationHint(layers::LayerManager::NONE) {} + ContainerLayerParameters(float aXScale, float aYScale) + : mXScale(aXScale), + mYScale(aYScale), + mLayerContentsVisibleRect(nullptr), + mItemVisibleRect(nullptr), + mBackgroundColor(NS_RGBA(0, 0, 0, 0)), + mScrollMetadataASR(nullptr), + mCompositorASR(nullptr), + mInTransformedSubtree(false), + mInActiveTransformedSubtree(false), + mDisableSubpixelAntialiasingInDescendants(false), + mLayerCreationHint(layers::LayerManager::NONE) {} + ContainerLayerParameters(float aXScale, float aYScale, + const nsIntPoint& aOffset, + const ContainerLayerParameters& aParent) + : mXScale(aXScale), + mYScale(aYScale), + mLayerContentsVisibleRect(nullptr), + mItemVisibleRect(nullptr), + mOffset(aOffset), + mBackgroundColor(aParent.mBackgroundColor), + mScrollMetadataASR(aParent.mScrollMetadataASR), + mCompositorASR(aParent.mCompositorASR), + mInTransformedSubtree(aParent.mInTransformedSubtree), + mInActiveTransformedSubtree(aParent.mInActiveTransformedSubtree), + mDisableSubpixelAntialiasingInDescendants( + aParent.mDisableSubpixelAntialiasingInDescendants), + mLayerCreationHint(aParent.mLayerCreationHint) {} + + float mXScale, mYScale; + + LayoutDeviceToLayerScale2D Scale() const { + return LayoutDeviceToLayerScale2D(mXScale, mYScale); + } + + /** + * If non-null, the rectangle in which BuildContainerLayerFor stores the + * visible rect of the layer, in the coordinate system of the created layer. + */ + nsIntRect* mLayerContentsVisibleRect; + + /** + * If non-null, the rectangle which stores the item's visible rect. + */ + nsRect* mItemVisibleRect; + + /** + * An offset to apply to all child layers created. + */ + nsIntPoint mOffset; + + LayerIntPoint Offset() const { + return LayerIntPoint::FromUnknownPoint(mOffset); + } + + nscolor mBackgroundColor; + const ActiveScrolledRoot* mScrollMetadataASR; + const ActiveScrolledRoot* mCompositorASR; + + bool mInTransformedSubtree; + bool mInActiveTransformedSubtree; + bool mDisableSubpixelAntialiasingInDescendants; + layers::LayerManager::PaintedLayerCreationHint mLayerCreationHint; + + /** + * When this is false, PaintedLayer coordinates are drawn to with an integer + * translation and the scale in mXScale/mYScale. + */ + bool AllowResidualTranslation() { + // If we're in a transformed subtree, but no ancestor transform is actively + // changing, we'll use the residual translation when drawing into the + // PaintedLayer to ensure that snapping exactly matches the ideal transform. + return mInTransformedSubtree && !mInActiveTransformedSubtree; + } +}; + +/** + * The FrameLayerBuilder is responsible for converting display lists + * into layer trees. Every LayerManager needs a unique FrameLayerBuilder + * to build layers. + * + * The most important API in this class is BuildContainerLayerFor. This + * method takes a display list as input and constructs a ContainerLayer + * with child layers that render the contents of the display list. It + * records the relationship between frames and layers. + * + * That data enables us to retain layer trees. When constructing a + * ContainerLayer, we first check to see if there's an existing + * ContainerLayer for the same frame that can be recycled. If we recycle + * it, we also try to reuse its existing PaintedLayer children to render + * the display items without layers of their own. The idea is that by + * recycling layers deterministically, we can ensure that when nothing + * changes in a display list, we will reuse the existing layers without + * changes. + * + * We expose a GetLeafLayerFor method that can be called by display items + * that make their own layers (e.g. canvas and video); this method + * locates the last layer used to render the display item, if any, and + * return it as a candidate for recycling. + * + * FrameLayerBuilder sets up PaintedLayers so that 0,0 in the Painted layer + * corresponds to the (pixel-snapped) top-left of the aAnimatedGeometryRoot. + * It sets up ContainerLayers so that 0,0 in the container layer + * corresponds to the snapped top-left of the display item reference frame. + * + * When we construct a container layer, we know the transform that will be + * applied to the layer. If the transform scales the content, we can get + * better results when intermediate buffers are used by pushing some scale + * from the container's transform down to the children. For PaintedLayer + * children, the scaling can be achieved by changing the size of the layer + * and drawing into it with increased or decreased resolution. By convention, + * integer types (nsIntPoint/nsIntSize/nsIntRect/nsIntRegion) are all in layer + * coordinates, post-scaling, whereas appunit types are all pre-scaling. + */ +class FrameLayerBuilder : public layers::LayerUserData { + public: + typedef layers::ContainerLayer ContainerLayer; + typedef layers::Layer Layer; + typedef layers::PaintedLayer PaintedLayer; + typedef layers::ImageLayer ImageLayer; + typedef layers::LayerManager LayerManager; + typedef layers::BasicLayerManager BasicLayerManager; + typedef layers::EventRegions EventRegions; + + FrameLayerBuilder(); + ~FrameLayerBuilder() override; + + static gfx::Size ChooseScale(nsIFrame* aContainerFrame, + nsDisplayItem* aContainerItem, + const nsRect& aVisibleRect, float aXScale, + float aYScale, const gfx::Matrix& aTransform2d, + bool aCanDraw2D); + + static void Shutdown(); + + void Init(nsDisplayListBuilder* aBuilder, LayerManager* aManager, + PaintedLayerData* aLayerData = nullptr, + bool aIsInactiveLayerManager = false, + const DisplayItemClip* aInactiveLayerClip = nullptr); + + /** + * Call this to notify that we have just started a transaction on the + * retained layer manager aManager. + */ + void DidBeginRetainedLayerTransaction(LayerManager* aManager); + + /** + * Call this just before we end a transaction. + */ + void WillEndTransaction(); + + /** + * Call this after we end a transaction. + */ + void DidEndTransaction(); + + enum { + /** + * Set this when pulling an opaque background color from behind the + * container layer into the container doesn't change the visual results, + * given the effects you're going to apply to the container layer. + * For example, this is compatible with opacity or clipping/masking, but + * not with non-OVER blend modes or filters. + */ + CONTAINER_ALLOW_PULL_BACKGROUND_COLOR = 0x01 + }; + /** + * Build a container layer for a display item that contains a child + * list, either reusing an existing one or creating a new one. It + * sets the container layer children to layers which together render + * the contents of the display list. It reuses existing layers from + * the retained layer manager if possible. + * aContainerItem may be null, in which case we construct a root layer. + * This gets called by display list code. It calls BuildLayer on the + * items in the display list, making items with their own layers + * children of the new container, and assigning all other items to + * PaintedLayer children created and managed by the FrameLayerBuilder. + * Returns a layer with clip rect cleared; it is the + * caller's responsibility to add any clip rect. The visible region + * is set based on what's in the layer. + * The container layer is transformed by aTransform (if non-null), and + * the result is transformed by the scale factors in aContainerParameters. + * aChildren is modified due to display item merging and flattening. + * The visible region of the returned layer is set only if aContainerItem + * is null. + */ + already_AddRefed<ContainerLayer> BuildContainerLayerFor( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + nsIFrame* aContainerFrame, nsDisplayItem* aContainerItem, + nsDisplayList* aChildren, + const ContainerLayerParameters& aContainerParameters, + const gfx::Matrix4x4* aTransform, uint32_t aFlags = 0); + + /** + * Get a retained layer for a display item that needs to create its own + * layer for rendering (i.e. under nsDisplayItem::BuildLayer). Returns + * null if no retained layer is available, which usually means that this + * display item didn't have a layer before so the caller will + * need to create one. + * Returns a layer with clip rect cleared; it is the + * caller's responsibility to add any clip rect and set the visible + * region. + */ + Layer* GetLeafLayerFor(nsDisplayListBuilder* aBuilder, nsDisplayItem* aItem); + + /** + * Call this to force all retained layers to be discarded and recreated at + * the next paint. + */ + static void InvalidateAllLayers(LayerManager* aManager); + static void InvalidateAllLayersForFrame(nsIFrame* aFrame); + + /** + * Call this to determine if a frame has a dedicated (non-Painted) layer + * for the given display item key. If there isn't one, we return null, + * otherwise we return the layer. + */ + static Layer* GetDedicatedLayer(nsIFrame* aFrame, + DisplayItemType aDisplayItemType); + + using AnimationGenerationCallback = FunctionRef<bool( + const Maybe<uint64_t>& aGeneration, DisplayItemType aDisplayItemType)>; + /** + * Enumerates layers for the all display item types that correspond to + * properties we can animate on layers and calls |aCallback| + * with the animation generation for the layer. If there is no corresponding + * layer for the display item or the layer has no animation, the animation + * generation is Nothing(). + * + * The enumeration stops if |aCallback| returns false. + */ + static void EnumerateGenerationForDedicatedLayers( + const nsIFrame* aFrame, AnimationGenerationCallback); + + /** + * This callback must be provided to EndTransaction. The callback data + * must be the nsDisplayListBuilder containing this FrameLayerBuilder. + * This function can be called multiple times in a row to draw + * different regions. This will occur when, for example, progressive paint is + * enabled. In these cases aDirtyRegion can be used to specify a larger region + * than aRegionToDraw that will be drawn during the transaction, possibly + * allowing the callback to make optimizations. + */ + static void DrawPaintedLayer(PaintedLayer* aLayer, gfxContext* aContext, + const nsIntRegion& aRegionToDraw, + const nsIntRegion& aDirtyRegion, + mozilla::layers::DrawRegionClip aClip, + const nsIntRegion& aRegionToInvalidate, + void* aCallbackData); + + /** + * Dumps this FrameLayerBuilder's retained layer manager's retained + * layer tree. Defaults to dumping to stdout in non-HTML format. + */ + static void DumpRetainedLayerTree(LayerManager* aManager, + std::stringstream& aStream, + bool aDumpHtml = false); + + /** + * Returns the most recently allocated geometry item for the given display + * item. + * + * XXX(seth): The current implementation must iterate through all display + * items allocated for this display item's frame. This may lead to O(n^2) + * behavior in some situations. + */ + static nsDisplayItemGeometry* GetMostRecentGeometry(nsDisplayItem* aItem); + + /******* PRIVATE METHODS to FrameLayerBuilder.cpp ********/ + /* These are only in the public section because they need + * to be called by file-scope helper functions in FrameLayerBuilder.cpp. + */ + + /** + * Record aItem as a display item that is rendered by the PaintedLayer + * aLayer, with aClipRect, where aContainerLayerFrame is the frame + * for the container layer this ThebesItem belongs to. + * aItem must have an underlying frame. + * @param aTopLeft offset from active scrolled root to reference frame + */ + void AddPaintedDisplayItem(PaintedLayerData* aLayerData, + AssignedDisplayItem& aAssignedDisplayItem, + Layer* aLayer); + + /** + * Calls GetOldLayerForFrame on the underlying frame of the display item, + * and each subsequent merged frame if no layer is found for the underlying + * frame. + */ + Layer* GetOldLayerFor(nsDisplayItem* aItem, + nsDisplayItemGeometry** aOldGeometry = nullptr, + DisplayItemClip** aOldClip = nullptr); + + static DisplayItemData* GetOldDataFor(nsDisplayItem* aItem); + + /** + * Destroy any stored LayerManagerDataProperty and the associated data for + * aFrame. + */ + static void DestroyDisplayItemDataFor(nsIFrame* aFrame); + + LayerManager* GetRetainingLayerManager() { return mRetainingManager; } + + /** + * Returns true if the given display item was rendered during the previous + * paint. Returns false otherwise. + */ + static bool HasRetainedDataFor(nsIFrame* aFrame, uint32_t aDisplayItemKey); + + typedef void (*DisplayItemDataCallback)(nsIFrame* aFrame, + DisplayItemData* aItem); + + /** + * Return the resolution at which we expect to render aFrame's contents, + * assuming they are being painted to retained layers. This takes into account + * the resolution the contents of the ContainerLayer containing aFrame are + * being rendered at, as well as any currently-inactive transforms between + * aFrame and that container layer. + */ + static gfxSize GetPaintedLayerScaleForFrame(nsIFrame* aFrame); + + static void RemoveFrameFromLayerManager( + const nsIFrame* aFrame, SmallPointerArray<DisplayItemData>& aArray); + + /** + * Given a frame and a display item key that uniquely identifies a + * display item for the frame, find the layer that was last used to + * render that display item. Returns null if there is no such layer. + * This could be a dedicated layer for the display item, or a PaintedLayer + * that renders many display items. + */ + DisplayItemData* GetOldLayerForFrame( + nsIFrame* aFrame, uint32_t aDisplayItemKey, + DisplayItemData* aOldData = nullptr, + LayerManager* aOldLayerManager = nullptr); + + /** + * Stores DisplayItemData associated with aFrame, stores the data in + * mNewDisplayItemData. + */ + DisplayItemData* StoreDataForFrame(nsPaintedDisplayItem* aItem, Layer* aLayer, + LayerState aState, DisplayItemData* aData); + void StoreDataForFrame(nsIFrame* aFrame, uint32_t aDisplayItemKey, + Layer* aLayer, LayerState aState); + + protected: + friend class LayerManagerData; + + // Flash the area within the context clip if paint flashing is enabled. + static void FlashPaint(gfxContext* aContext); + + /* + * Get the DisplayItemData array associated with this frame, or null if one + * doesn't exist. + * + * Note that the pointer returned here is only valid so long as you don't + * poke the LayerManagerData's mFramesWithLayers hashtable. + */ + DisplayItemData* GetDisplayItemData(nsIFrame* aFrame, uint32_t aKey); + + /* + * Get the DisplayItemData associated with this display item, + * using the LayerManager instead of FrameLayerBuilder. + */ + static DisplayItemData* GetDisplayItemDataForManager( + nsPaintedDisplayItem* aItem, LayerManager* aManager); + + /** + * We store one of these for each display item associated with a + * PaintedLayer, in a hashtable that maps each PaintedLayer to an array + * of ClippedDisplayItems. (PaintedLayerItemsEntry is the hash entry + * for that hashtable.) + * These are only stored during the paint process, so that the + * DrawPaintedLayer callback can figure out which items to draw for the + * PaintedLayer. + */ + + static void RecomputeVisibilityForItems( + std::vector<AssignedDisplayItem>& aItems, nsDisplayListBuilder* aBuilder, + const nsIntRegion& aRegionToDraw, nsRect& aPreviousRectToDraw, + const nsIntPoint& aOffset, int32_t aAppUnitsPerDevPixel, float aXScale, + float aYScale); + + void PaintItems(std::vector<AssignedDisplayItem>& aItems, + const nsIntRect& aRect, gfxContext* aContext, + nsDisplayListBuilder* aBuilder, nsPresContext* aPresContext, + const nsIntPoint& aOffset, float aXScale, float aYScale); + + /** + * We accumulate ClippedDisplayItem elements in a hashtable during + * the paint process. This is the hashentry for that hashtable. + */ + public: + /** + * Add the PaintedDisplayItemLayerUserData object as being used in this + * transaction so that we do some end-of-paint maintenance on it. + */ + void AddPaintedLayerItemsEntry(PaintedDisplayItemLayerUserData* aData); + + PaintedLayerData* GetContainingPaintedLayerData() { + return mContainingPaintedLayer; + } + + const DisplayItemClip* GetInactiveLayerClip() const { + return mInactiveLayerClip; + } + + /* + * If we're building layers for an item with an inactive layer tree, + * this function saves the item's clip, which will later be applied + * to the event regions. The clip should be relative to + * mContainingPaintedLayer->mReferenceFrame. + */ + void SetInactiveLayerClip(const DisplayItemClip* aClip) { + mInactiveLayerClip = aClip; + } + + bool IsBuildingRetainedLayers() { + return !mIsInactiveLayerManager && mRetainingManager; + } + + /** + * Attempt to build the most compressed layer tree possible, even if it means + * throwing away existing retained buffers. + */ + void SetLayerTreeCompressionMode() { mInLayerTreeCompressionMode = true; } + bool CheckInLayerTreeCompressionMode(); + + void ComputeGeometryChangeForItem(DisplayItemData* aData); + + // Defined and used only in dom/base/nsDOMWindowUtils.cpp + template <class T> + static T* GetDebugSingleOldLayerForFrame(nsIFrame* aFrame); + + protected: + /** + * The layer manager belonging to the widget that is being retained + * across paints. + */ + LayerManager* mRetainingManager; + /** + * The root prescontext for the display list builder reference frame + */ + RefPtr<nsRootPresContext> mRootPresContext; + + /** + * The display list builder being used. + */ + nsDisplayListBuilder* mDisplayListBuilder; + /** + * An array of PaintedLayer user data objects containing the + * list of display items (plus clipping data) to be rendered in the + * layer. We clean these up at the end of the transaction to + * remove references to display items. + */ + AutoTArray<RefPtr<PaintedDisplayItemLayerUserData>, 5> mPaintedLayerItems; + + /** + * When building layers for an inactive layer, this is where the + * inactive layer will be placed. + */ + PaintedLayerData* mContainingPaintedLayer; + + /** + * When building layers for an inactive layer, this stores the clip + * of the display item that built the inactive layer. + */ + const DisplayItemClip* mInactiveLayerClip; + + /** + * Indicates that the entire layer tree should be rerendered + * during this paint. + */ + bool mInvalidateAllLayers; + + bool mInLayerTreeCompressionMode; + + bool mIsInactiveLayerManager; +}; + +} // namespace mozilla + +#endif /* FRAMELAYERBUILDER_H_ */ diff --git a/layout/painting/LayerState.h b/layout/painting/LayerState.h new file mode 100644 index 0000000000..d570ae95b0 --- /dev/null +++ b/layout/painting/LayerState.h @@ -0,0 +1,29 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef LAYERSTATE_H_ +#define LAYERSTATE_H_ + +#include <cstdint> + +namespace mozilla { + +enum class LayerState : uint8_t { + LAYER_NONE, + LAYER_INACTIVE, + LAYER_ACTIVE, + // Force an active layer even if it causes incorrect rendering, e.g. + // when the layer has rounded rect clips. + LAYER_ACTIVE_FORCE, + // Special layer that is metadata only. + LAYER_ACTIVE_EMPTY, + // Inactive style layer for rendering SVG effects. + LAYER_SVG_EFFECTS +}; + +} // namespace mozilla + +#endif diff --git a/layout/painting/MaskLayerImageCache.cpp b/layout/painting/MaskLayerImageCache.cpp new file mode 100644 index 0000000000..2b3b63dc57 --- /dev/null +++ b/layout/painting/MaskLayerImageCache.cpp @@ -0,0 +1,64 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "MaskLayerImageCache.h" + +#include "ImageContainer.h" +#include "mozilla/layers/KnowsCompositor.h" + +using namespace mozilla::layers; + +namespace mozilla { + +MaskLayerImageCache::MaskLayerImageCache() { + MOZ_COUNT_CTOR(MaskLayerImageCache); +} +MaskLayerImageCache::~MaskLayerImageCache() { + MOZ_COUNT_DTOR(MaskLayerImageCache); +} + +void MaskLayerImageCache::Sweep() { + for (auto iter = mMaskImageContainers.Iter(); !iter.Done(); iter.Next()) { + const auto& key = iter.Get()->mKey; + if (key->HasZeroLayerCount()) { + iter.Remove(); + } + } +} + +ImageContainer* MaskLayerImageCache::FindImageFor( + const MaskLayerImageKey** aKey) { + if (MaskLayerImageEntry* entry = mMaskImageContainers.GetEntry(**aKey)) { + *aKey = entry->mKey.get(); + return entry->mContainer; + } + + return nullptr; +} + +void MaskLayerImageCache::PutImage(const MaskLayerImageKey* aKey, + ImageContainer* aContainer) { + MaskLayerImageEntry* entry = mMaskImageContainers.PutEntry(*aKey); + entry->mContainer = aContainer; +} + +MaskLayerImageCache::MaskLayerImageKey::MaskLayerImageKey() + : mRoundedClipRects(), mLayerCount(0) { + MOZ_COUNT_CTOR(MaskLayerImageKey); +} + +MaskLayerImageCache::MaskLayerImageKey::MaskLayerImageKey( + const MaskLayerImageKey& aKey) + : mRoundedClipRects(aKey.mRoundedClipRects.Clone()), + mLayerCount(aKey.mLayerCount) { + MOZ_COUNT_CTOR(MaskLayerImageKey); +} + +MaskLayerImageCache::MaskLayerImageKey::~MaskLayerImageKey() { + MOZ_COUNT_DTOR(MaskLayerImageKey); +} + +} // namespace mozilla diff --git a/layout/painting/MaskLayerImageCache.h b/layout/painting/MaskLayerImageCache.h new file mode 100644 index 0000000000..7e20f72a20 --- /dev/null +++ b/layout/painting/MaskLayerImageCache.h @@ -0,0 +1,257 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MASKLAYERIMAGECACHE_H_ +#define MASKLAYERIMAGECACHE_H_ + +#include "DisplayItemClip.h" +#include "nsPresContext.h" +#include "mozilla/gfx/Matrix.h" +#include "mozilla/UniquePtr.h" + +namespace mozilla { + +namespace layers { +class ImageContainer; +class KnowsCompositor; +} // namespace layers + +/** + * Keeps a record of image containers for mask layers, containers are mapped + * from the rounded rects used to create them. + * The cache stores MaskLayerImageEntries indexed by MaskLayerImageKeys. + * Each MaskLayerImageEntry owns a heap-allocated MaskLayerImageKey + * (heap-allocated so that a mask layer's userdata can keep a pointer to the + * key for its image, in spite of the hashtable moving its entries around). + * The key consists of the rounded rects used to create the mask, + * an nsRefPtr to the ImageContainer containing the image, and a count + * of the number of layers currently using this ImageContainer. + * When the key's layer count is zero, the cache + * may remove the entry, which deletes the key object. + */ +class MaskLayerImageCache { + typedef mozilla::layers::ImageContainer ImageContainer; + typedef mozilla::layers::KnowsCompositor KnowsCompositor; + + public: + MaskLayerImageCache(); + ~MaskLayerImageCache(); + + /** + * Representation of a rounded rectangle in device pixel coordinates, in + * contrast to DisplayItemClip::RoundedRect, which uses app units. + * In particular, our internal representation uses a gfxRect, rather than + * an nsRect, so this class is easier to use with transforms. + */ + struct PixelRoundedRect { + PixelRoundedRect() = delete; + + PixelRoundedRect(const DisplayItemClip::RoundedRect& aRRect, + nsPresContext* aPresContext) + : mRect(aPresContext->AppUnitsToGfxUnits(aRRect.mRect.x), + aPresContext->AppUnitsToGfxUnits(aRRect.mRect.y), + aPresContext->AppUnitsToGfxUnits(aRRect.mRect.width), + aPresContext->AppUnitsToGfxUnits(aRRect.mRect.height)) { + MOZ_COUNT_CTOR(PixelRoundedRect); + for (const auto corner : mozilla::AllPhysicalHalfCorners()) { + mRadii[corner] = + aPresContext->AppUnitsToGfxUnits(aRRect.mRadii[corner]); + } + } + + PixelRoundedRect(const PixelRoundedRect& aPRR) : mRect(aPRR.mRect) { + MOZ_COUNT_CTOR(PixelRoundedRect); + for (const auto corner : mozilla::AllPhysicalHalfCorners()) { + mRadii[corner] = aPRR.mRadii[corner]; + } + } + + MOZ_COUNTED_DTOR(PixelRoundedRect) + + // Applies the scale and translate components of aTransform. + // It is an error to pass a matrix which does more than just scale + // and translate. + void ScaleAndTranslate(const gfx::Matrix& aTransform) { + NS_ASSERTION(aTransform._12 == 0 && aTransform._21 == 0, + "Transform has a component other than scale and translate"); + + mRect = aTransform.TransformBounds(mRect); + + for (size_t i = 0; i < ArrayLength(mRadii); i += 2) { + mRadii[i] *= aTransform._11; + mRadii[i + 1] *= aTransform._22; + } + } + + bool operator==(const PixelRoundedRect& aOther) const { + if (!mRect.IsEqualInterior(aOther.mRect)) { + return false; + } + + for (const auto corner : mozilla::AllPhysicalHalfCorners()) { + if (mRadii[corner] != aOther.mRadii[corner]) { + return false; + } + } + return true; + } + bool operator!=(const PixelRoundedRect& aOther) const { + return !(*this == aOther); + } + + // Create a hash for this object. + PLDHashNumber Hash() const { + PLDHashNumber hash = HashBytes(&mRect.x, 4 * sizeof(gfxFloat)); + hash = AddToHash(hash, HashBytes(mRadii, 8 * sizeof(gfxFloat))); + + return hash; + } + + gfx::Rect mRect; + // Indices into mRadii are the enum HalfCorner constants in gfx/2d/Types.h + gfxFloat mRadii[8]; + }; + + struct MaskLayerImageKeyRef; + + /** + * A key to identify cached image containers. + * The const-ness of this class is with respect to its use as a key into a + * hashtable, so anything not used to create the hash is mutable. + * mLayerCount counts the number of mask layers which have a reference to + * MaskLayerImageEntry::mContainer; it is maintained by MaskLayerUserData, + * which keeps a reference to the key. There will usually be mLayerCount + 1 + * pointers to a key object (the +1 being from the hashtable entry), but this + * invariant may be temporarily broken. + */ + struct MaskLayerImageKey { + friend struct MaskLayerImageKeyRef; + + MaskLayerImageKey(); + MaskLayerImageKey(const MaskLayerImageKey& aKey); + + ~MaskLayerImageKey(); + + bool HasZeroLayerCount() const { return mLayerCount == 0; } + + PLDHashNumber Hash() const { + PLDHashNumber hash = 0; + + for (uint32_t i = 0; i < mRoundedClipRects.Length(); ++i) { + hash = AddToHash(hash, mRoundedClipRects[i].Hash()); + } + hash = AddToHash(hash, mKnowsCompositor.get()); + + return hash; + } + + bool operator==(const MaskLayerImageKey& aOther) const { + return mKnowsCompositor == aOther.mKnowsCompositor && + mRoundedClipRects == aOther.mRoundedClipRects; + } + + nsTArray<PixelRoundedRect> mRoundedClipRects; + RefPtr<KnowsCompositor> mKnowsCompositor; + + private: + void IncLayerCount() const { ++mLayerCount; } + void DecLayerCount() const { + NS_ASSERTION(mLayerCount > 0, "Inconsistent layer count"); + --mLayerCount; + } + mutable uint32_t mLayerCount; + }; + + /** + * This struct maintains a reference to a MaskLayerImageKey, via a variant on + * refcounting. When a key is passed in via Reset(), we increment the + * passed-in key's mLayerCount, and we decrement its mLayerCount when we're + * destructed (or when the key is replaced via a second Reset() call). + * + * However, unlike standard refcounting smart-pointers, this object does + * *not* delete the tracked MaskLayerImageKey -- instead, deletion happens + * in MaskLayerImageCache::Sweep(), for any keys whose mLayerCount is 0. + */ + struct MaskLayerImageKeyRef { + ~MaskLayerImageKeyRef() { + if (mRawPtr) { + mRawPtr->DecLayerCount(); + } + } + + MaskLayerImageKeyRef() : mRawPtr(nullptr) {} + MaskLayerImageKeyRef(const MaskLayerImageKeyRef&) = delete; + void operator=(const MaskLayerImageKeyRef&) = delete; + + void Reset(const MaskLayerImageKey* aPtr) { + MOZ_ASSERT( + aPtr, "Cannot initialize a MaskLayerImageKeyRef with a null pointer"); + aPtr->IncLayerCount(); + if (mRawPtr) { + mRawPtr->DecLayerCount(); + } + mRawPtr = aPtr; + } + + private: + const MaskLayerImageKey* mRawPtr; + }; + + // Find an image container for aKey, returns nullptr if there is no suitable + // cached image. If there is an image, then aKey is set to point at the stored + // key for the image. + ImageContainer* FindImageFor(const MaskLayerImageKey** aKey); + + // Add an image container with a key to the cache + // The image container used will be set as the container in aKey and aKey + // itself will be linked from this cache + void PutImage(const MaskLayerImageKey* aKey, ImageContainer* aContainer); + + // Sweep the cache for old image containers that can be deleted + void Sweep(); + + protected: + class MaskLayerImageEntry : public PLDHashEntryHdr { + public: + typedef const MaskLayerImageKey& KeyType; + typedef const MaskLayerImageKey* KeyTypePointer; + + explicit MaskLayerImageEntry(KeyTypePointer aKey) : mKey(aKey) { + MOZ_COUNT_CTOR(MaskLayerImageEntry); + } + MaskLayerImageEntry(const MaskLayerImageEntry& aOther) + : mKey(aOther.mKey.get()) { + NS_ERROR("ALLOW_MEMMOVE == true, should never be called"); + } + MOZ_COUNTED_DTOR(MaskLayerImageEntry) + + // KeyEquals(): does this entry match this key? + bool KeyEquals(KeyTypePointer aKey) const { return *mKey == *aKey; } + + // KeyToPointer(): Convert KeyType to KeyTypePointer + static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; } + + // HashKey(): calculate the hash number + static PLDHashNumber HashKey(KeyTypePointer aKey) { return aKey->Hash(); } + + // ALLOW_MEMMOVE can we move this class with memmove(), or do we have + // to use the copy constructor? + enum { ALLOW_MEMMOVE = true }; + + bool operator==(const MaskLayerImageEntry& aOther) const { + return KeyEquals(aOther.mKey.get()); + } + + mozilla::UniquePtr<const MaskLayerImageKey> mKey; + RefPtr<ImageContainer> mContainer; + }; + + nsTHashtable<MaskLayerImageEntry> mMaskImageContainers; +}; + +} // namespace mozilla + +#endif diff --git a/layout/painting/MatrixStack.h b/layout/painting/MatrixStack.h new file mode 100644 index 0000000000..320d08317b --- /dev/null +++ b/layout/painting/MatrixStack.h @@ -0,0 +1,61 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_PAINTING_MATRIXSTACK_H +#define MOZILLA_PAINTING_MATRIXSTACK_H + +#include "nsTArray.h" +#include "mozilla/gfx/MatrixFwd.h" + +namespace mozilla { + +/** + * MatrixStack stores a stack of matrices and keeps track of the accumulated + * transform matrix. + */ +template <typename T> +class MatrixStack { + public: + MatrixStack() = default; + + ~MatrixStack() { MOZ_ASSERT(mMatrices.IsEmpty()); } + + /** + * Returns the current accumulated transform matrix. + */ + const T& CurrentMatrix() const { return mCurrentMatrix; } + + /** + * Returns true if any matrices are currently pushed to the stack. + */ + bool HasTransform() const { return mMatrices.Length() > 0; } + + /** + * Pushes the given |aMatrix| to the stack. + */ + void Push(const T& aMatrix) { + mMatrices.AppendElement(mCurrentMatrix); + mCurrentMatrix = aMatrix * mCurrentMatrix; + } + + /** + * Pops the most recently added matrix from the stack. + */ + void Pop() { + MOZ_ASSERT(mMatrices.Length() > 0); + mCurrentMatrix = mMatrices.PopLastElement(); + } + + private: + T mCurrentMatrix; + AutoTArray<T, 2> mMatrices; +}; + +typedef MatrixStack<gfx::Matrix4x4Flagged> MatrixStack4x4; + +} // namespace mozilla + +#endif /* MOZILLA_PAINTING_MATRIXSTACK_H */ diff --git a/layout/painting/PaintTracker.cpp b/layout/painting/PaintTracker.cpp new file mode 100644 index 0000000000..208f037e7a --- /dev/null +++ b/layout/painting/PaintTracker.cpp @@ -0,0 +1,13 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/PaintTracker.h" + +namespace mozilla { + +int PaintTracker::gPaintTracker; + +} // namespace mozilla diff --git a/layout/painting/PaintTracker.h b/layout/painting/PaintTracker.h new file mode 100644 index 0000000000..1f5554f983 --- /dev/null +++ b/layout/painting/PaintTracker.h @@ -0,0 +1,31 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_PaintTracker_h +#define mozilla_PaintTracker_h + +#include "mozilla/Attributes.h" +#include "nsDebug.h" + +namespace mozilla { + +class MOZ_STACK_CLASS PaintTracker { + public: + PaintTracker() { ++gPaintTracker; } + ~PaintTracker() { + NS_ASSERTION(gPaintTracker > 0, "Mismatched constructor/destructor"); + --gPaintTracker; + } + + static bool IsPainting() { return !!gPaintTracker; } + + private: + static int gPaintTracker; +}; + +} // namespace mozilla + +#endif // mozilla_PaintTracker_h diff --git a/layout/painting/RetainedDisplayListBuilder.cpp b/layout/painting/RetainedDisplayListBuilder.cpp new file mode 100644 index 0000000000..8a374d9871 --- /dev/null +++ b/layout/painting/RetainedDisplayListBuilder.cpp @@ -0,0 +1,1506 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include "RetainedDisplayListBuilder.h" + +#include "mozilla/StaticPrefs_layout.h" +#include "nsIFrame.h" +#include "nsIFrameInlines.h" +#include "nsIScrollableFrame.h" +#include "nsPlaceholderFrame.h" +#include "nsSubDocumentFrame.h" +#include "nsViewManager.h" +#include "nsCanvasFrame.h" +#include "mozilla/AutoRestore.h" +#include "mozilla/DisplayPortUtils.h" +#include "mozilla/PresShell.h" + +/** + * Code for doing display list building for a modified subset of the window, + * and then merging it into the existing display list (for the full window). + * + * The approach primarily hinges on the observation that the 'true' ordering + * of display items is represented by a DAG (only items that intersect in 2d + * space have a defined ordering). Our display list is just one of a many + * possible linear representations of this ordering. + * + * Each time a frame changes (gets a new ComputedStyle, or has a size/position + * change), we schedule a paint (as we do currently), but also reord the frame + * that changed. + * + * When the next paint occurs we union the overflow areas (in screen space) of + * the changed frames, and compute a rect/region that contains all changed + * items. We then build a display list just for this subset of the screen and + * merge it into the display list from last paint. + * + * Any items that exist in one list and not the other must not have a defined + * ordering in the DAG, since they need to intersect to have an ordering and + * we would have built both in the new list if they intersected. Given that, we + * can align items that appear in both lists, and any items that appear between + * matched items can be inserted into the merged list in any order. + */ + +using namespace mozilla; +using mozilla::dom::Document; + +void RetainedDisplayListData::AddModifiedFrame(nsIFrame* aFrame) { + MOZ_ASSERT(!aFrame->IsFrameModified()); + Flags(aFrame) |= RetainedDisplayListData::FrameFlags::Modified; + mModifiedFramesCount++; +} + +RetainedDisplayListData* GetRetainedDisplayListData(nsIFrame* aRootFrame) { + RetainedDisplayListData* data = + aRootFrame->GetProperty(RetainedDisplayListData::DisplayListData()); + + return data; +} + +RetainedDisplayListData* GetOrSetRetainedDisplayListData(nsIFrame* aRootFrame) { + RetainedDisplayListData* data = GetRetainedDisplayListData(aRootFrame); + + if (!data) { + data = new RetainedDisplayListData(); + aRootFrame->SetProperty(RetainedDisplayListData::DisplayListData(), data); + } + + MOZ_ASSERT(data); + return data; +} + +static void MarkFramesWithItemsAndImagesModified(nsDisplayList* aList) { + for (nsDisplayItem* i = aList->GetBottom(); i != nullptr; i = i->GetAbove()) { + if (!i->HasDeletedFrame() && i->CanBeReused() && + !i->Frame()->IsFrameModified()) { + // If we have existing cached geometry for this item, then check that for + // whether we need to invalidate for a sync decode. If we don't, then + // use the item's flags. + DisplayItemData* data = FrameLayerBuilder::GetOldDataFor(i); + // XXX: handle webrender case + bool invalidate = false; + if (data && data->GetGeometry()) { + invalidate = data->GetGeometry()->InvalidateForSyncDecodeImages(); + } else if (!(i->GetFlags() & TYPE_RENDERS_NO_IMAGES)) { + invalidate = true; + } + + if (invalidate) { + i->FrameForInvalidation()->MarkNeedsDisplayItemRebuild(); + if (i->GetDependentFrame()) { + i->GetDependentFrame()->MarkNeedsDisplayItemRebuild(); + } + } + } + if (i->GetChildren()) { + MarkFramesWithItemsAndImagesModified(i->GetChildren()); + } + } +} + +static AnimatedGeometryRoot* SelectAGRForFrame( + nsIFrame* aFrame, AnimatedGeometryRoot* aParentAGR) { + if (!aFrame->IsStackingContext() || !aFrame->IsFixedPosContainingBlock()) { + return aParentAGR; + } + + if (!aFrame->HasOverrideDirtyRegion()) { + return nullptr; + } + + nsDisplayListBuilder::DisplayListBuildingData* data = + aFrame->GetProperty(nsDisplayListBuilder::DisplayListBuildingRect()); + + return data && data->mModifiedAGR ? data->mModifiedAGR.get() : nullptr; +} + +void RetainedDisplayListBuilder::AddSizeOfIncludingThis( + nsWindowSizes& aSizes) const { + aSizes.mLayoutRetainedDisplayListSize += aSizes.mState.mMallocSizeOf(this); + mBuilder.AddSizeOfExcludingThis(aSizes); + mList.AddSizeOfExcludingThis(aSizes); +} + +bool AnyContentAncestorModified(nsIFrame* aFrame, nsIFrame* aStopAtFrame) { + nsIFrame* f = aFrame; + while (f) { + if (f->IsFrameModified()) { + return true; + } + + if (aStopAtFrame && f == aStopAtFrame) { + break; + } + + f = nsLayoutUtils::GetDisplayListParent(f); + } + + return false; +} + +// Removes any display items that belonged to a frame that was deleted, +// and mark frames that belong to a different AGR so that get their +// items built again. +// TODO: We currently descend into all children even if we don't have an AGR +// to mark, as child stacking contexts might. It would be nice if we could +// jump into those immediately rather than walking the entire thing. +bool RetainedDisplayListBuilder::PreProcessDisplayList( + RetainedDisplayList* aList, AnimatedGeometryRoot* aAGR, + PartialUpdateResult& aUpdated, nsIFrame* aOuterFrame, uint32_t aCallerKey, + uint32_t aNestingDepth, bool aKeepLinked) { + // The DAG merging algorithm does not have strong mechanisms in place to keep + // the complexity of the resulting DAG under control. In some cases we can + // build up edges very quickly. Detect those cases and force a full display + // list build if we hit them. + static const uint32_t kMaxEdgeRatio = 5; + const bool initializeDAG = !aList->mDAG.Length(); + if (!aKeepLinked && !initializeDAG && + aList->mDAG.mDirectPredecessorList.Length() > + (aList->mDAG.mNodesInfo.Length() * kMaxEdgeRatio)) { + return false; + } + + // If we had aKeepLinked=true for this list on the previous paint, then + // mOldItems will already be initialized as it won't have been consumed during + // a merge. + const bool initializeOldItems = aList->mOldItems.IsEmpty(); + if (initializeOldItems) { + aList->mOldItems.SetCapacity(aList->Count()); + } else { + MOZ_RELEASE_ASSERT(!initializeDAG); + } + + MOZ_RELEASE_ASSERT( + initializeDAG || + aList->mDAG.Length() == + (initializeOldItems ? aList->Count() : aList->mOldItems.Length())); + + nsDisplayList out; + + size_t i = 0; + while (nsDisplayItem* item = aList->RemoveBottom()) { +#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + item->SetMergedPreProcessed(false, true); +#endif + + // If we have a previously initialized old items list, then it can differ + // from the current list due to items removed for having a deleted frame. + // We can't easily remove these, since the DAG has entries for those indices + // and it's hard to rewrite in-place. + // Skip over entries with no current item to keep the iterations in sync. + if (!initializeOldItems) { + while (!aList->mOldItems[i].mItem) { + i++; + } + } + + if (initializeDAG) { + if (i == 0) { + aList->mDAG.AddNode(Span<const MergedListIndex>()); + } else { + MergedListIndex previous(i - 1); + aList->mDAG.AddNode(Span<const MergedListIndex>(&previous, 1)); + } + } + + if (!item->CanBeReused() || item->HasDeletedFrame() || + AnyContentAncestorModified(item->FrameForInvalidation(), aOuterFrame)) { + if (initializeOldItems) { + aList->mOldItems.AppendElement(OldItemInfo(nullptr)); + } else { + MOZ_RELEASE_ASSERT(aList->mOldItems[i].mItem == item); + aList->mOldItems[i].mItem = nullptr; + } + + if (item->IsGlassItem() && item == mBuilder.GetGlassDisplayItem()) { + mBuilder.ClearGlassDisplayItem(); + } + + item->Destroy(&mBuilder); + Metrics()->mRemovedItems++; + + i++; + aUpdated = PartialUpdateResult::Updated; + continue; + } + + if (initializeOldItems) { + aList->mOldItems.AppendElement(OldItemInfo(item)); + } + + // If we're not going to keep the list linked, then this old item entry + // is the only pointer to the item. Let it know that it now strongly + // owns the item, so it can destroy it if it goes away. + aList->mOldItems[i].mOwnsItem = !aKeepLinked; + + item->SetOldListIndex(aList, OldListIndex(i), aCallerKey, aNestingDepth); + + nsIFrame* f = item->Frame(); + + if (item->GetChildren()) { + // If children inside this list were invalid, then we'd have walked the + // ancestors and set ForceDescendIntoVisible on the current frame. If an + // ancestor is modified, then we'll throw this away entirely. Either way, + // we won't need to run merging on this sublist, and we can keep the items + // linked into their display list. + // The caret can move without invalidating, but we always set the force + // descend into frame state bit on that frame, so check for that too. + // TODO: AGR marking below can call MarkFrameForDisplayIfVisible and make + // us think future siblings need to be merged, even though we don't really + // need to. + bool keepLinked = aKeepLinked; + nsIFrame* invalid = item->FrameForInvalidation(); + if (!invalid->ForceDescendIntoIfVisible() && + !invalid->HasAnyStateBits(NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO)) { + keepLinked = true; + } + + if (!PreProcessDisplayList(item->GetChildren(), + SelectAGRForFrame(f, aAGR), aUpdated, + item->Frame(), item->GetPerFrameKey(), + aNestingDepth + 1, keepLinked)) { + MOZ_RELEASE_ASSERT( + !aKeepLinked, + "Can't early return since we need to move the out list back"); + return false; + } + } + + // TODO: We should be able to check the clipped bounds relative + // to the common AGR (of both the existing item and the invalidated + // frame) and determine if they can ever intersect. + // TODO: We only really need to build the ancestor container item that is a + // sibling of the changed thing to get correct ordering. The changed content + // is a frame though, and it's hard to map that to container items in this + // list. + if (aAGR && item->GetAnimatedGeometryRoot()->GetAsyncAGR() != aAGR) { + mBuilder.MarkFrameForDisplayIfVisible(f, mBuilder.RootReferenceFrame()); + } + + // TODO: This is here because we sometimes reuse the previous display list + // completely. For optimization, we could only restore the state for reused + // display items. + if (item->RestoreState()) { + item->InvalidateItemCacheEntry(); + } + + // If we're going to keep this linked list and not merge it, then mark the + // item as used and put it back into the list. + if (aKeepLinked) { + item->SetReused(true); + if (item->GetChildren()) { + item->UpdateBounds(Builder()); + } + if (item->GetType() == DisplayItemType::TYPE_SUBDOCUMENT) { + IncrementSubDocPresShellPaintCount(item); + } + out.AppendToTop(item); + } + i++; + } + + MOZ_RELEASE_ASSERT(aList->mOldItems.Length() == aList->mDAG.Length()); + aList->RestoreState(); + + if (aKeepLinked) { + aList->AppendToTop(&out); + } + return true; +} + +void RetainedDisplayListBuilder::IncrementSubDocPresShellPaintCount( + nsDisplayItem* aItem) { + MOZ_ASSERT(aItem->GetType() == DisplayItemType::TYPE_SUBDOCUMENT); + + nsSubDocumentFrame* subDocFrame = + static_cast<nsDisplaySubDocument*>(aItem)->SubDocumentFrame(); + MOZ_ASSERT(subDocFrame); + + PresShell* presShell = subDocFrame->GetSubdocumentPresShellForPainting(0); + MOZ_ASSERT(presShell); + + mBuilder.IncrementPresShellPaintCount(presShell); +} + +static Maybe<const ActiveScrolledRoot*> SelectContainerASR( + const DisplayItemClipChain* aClipChain, const ActiveScrolledRoot* aItemASR, + Maybe<const ActiveScrolledRoot*>& aContainerASR) { + const ActiveScrolledRoot* itemClipASR = + aClipChain ? aClipChain->mASR : nullptr; + + const ActiveScrolledRoot* finiteBoundsASR = + ActiveScrolledRoot::PickDescendant(itemClipASR, aItemASR); + + if (!aContainerASR) { + return Some(finiteBoundsASR); + } + + return Some( + ActiveScrolledRoot::PickAncestor(*aContainerASR, finiteBoundsASR)); +} + +static void UpdateASR(nsDisplayItem* aItem, + Maybe<const ActiveScrolledRoot*>& aContainerASR) { + Maybe<const ActiveScrolledRoot*> asr; + + if (aItem->HasHitTestInfo()) { + const HitTestInfo& info = + static_cast<nsDisplayHitTestInfoBase*>(aItem)->GetHitTestInfo(); + asr = SelectContainerASR(info.mClipChain, info.mASR, aContainerASR); + } else { + asr = aContainerASR; + } + + if (!asr) { + return; + } + + nsDisplayWrapList* wrapList = aItem->AsDisplayWrapList(); + if (!wrapList) { + aItem->SetActiveScrolledRoot(*asr); + return; + } + + wrapList->SetActiveScrolledRoot(ActiveScrolledRoot::PickAncestor( + wrapList->GetFrameActiveScrolledRoot(), *asr)); + + wrapList->UpdateHitTestInfoActiveScrolledRoot(*asr); +} + +static void CopyASR(nsDisplayItem* aOld, nsDisplayItem* aNew) { + const ActiveScrolledRoot* hitTest = nullptr; + if (aOld->HasHitTestInfo()) { + MOZ_ASSERT(aNew->HasHitTestInfo()); + const HitTestInfo& info = + static_cast<nsDisplayHitTestInfoBase*>(aOld)->GetHitTestInfo(); + hitTest = info.mASR; + } + + aNew->SetActiveScrolledRoot(aOld->GetActiveScrolledRoot()); + + // SetActiveScrolledRoot for most items will also set the hit-test info item's + // asr, so we need to manually set that again to what we saved earlier. + if (aOld->HasHitTestInfo()) { + static_cast<nsDisplayHitTestInfoBase*>(aNew) + ->UpdateHitTestInfoActiveScrolledRoot(hitTest); + } +} + +OldItemInfo::OldItemInfo(nsDisplayItem* aItem) + : mItem(aItem), mUsed(false), mDiscarded(false), mOwnsItem(false) { + if (mItem) { + // Clear cached modified frame state when adding an item to the old list. + mItem->SetModifiedFrame(false); + } +} + +void OldItemInfo::AddedMatchToMergedList(RetainedDisplayListBuilder* aBuilder, + MergedListIndex aIndex) { + AddedToMergedList(aIndex); +} + +void OldItemInfo::Discard(RetainedDisplayListBuilder* aBuilder, + nsTArray<MergedListIndex>&& aDirectPredecessors) { + MOZ_ASSERT(!IsUsed()); + mUsed = mDiscarded = true; + mDirectPredecessors = std::move(aDirectPredecessors); + if (mItem) { + MOZ_ASSERT(mOwnsItem); + mItem->Destroy(aBuilder->Builder()); + aBuilder->Metrics()->mRemovedItems++; + } + mItem = nullptr; +} + +bool OldItemInfo::IsChanged() { + return !mItem || !mItem->CanBeReused() || mItem->HasDeletedFrame(); +} + +/** + * A C++ implementation of Markus Stange's merge-dags algorithm. + * https://github.com/mstange/merge-dags + * + * MergeState handles combining a new list of display items into an existing + * DAG and computes the new DAG in a single pass. + * Each time we add a new item, we resolve all dependencies for it, so that the + * resulting list and DAG are built in topological ordering. + */ +class MergeState { + public: + MergeState(RetainedDisplayListBuilder* aBuilder, + RetainedDisplayList& aOldList, nsDisplayItem* aOuterItem) + : mBuilder(aBuilder), + mOldList(&aOldList), + mOldItems(std::move(aOldList.mOldItems)), + mOldDAG( + std::move(*reinterpret_cast<DirectedAcyclicGraph<OldListUnits>*>( + &aOldList.mDAG))), + mOuterItem(aOuterItem), + mResultIsModified(false) { + mMergedDAG.EnsureCapacityFor(mOldDAG); + MOZ_RELEASE_ASSERT(mOldItems.Length() == mOldDAG.Length()); + } + + Maybe<MergedListIndex> ProcessItemFromNewList( + nsDisplayItem* aNewItem, const Maybe<MergedListIndex>& aPreviousItem) { + OldListIndex oldIndex; + MOZ_DIAGNOSTIC_ASSERT(aNewItem->HasModifiedFrame() == + HasModifiedFrame(aNewItem)); + if (!aNewItem->HasModifiedFrame() && + HasMatchingItemInOldList(aNewItem, &oldIndex)) { + mBuilder->Metrics()->mRebuiltItems++; + nsDisplayItem* oldItem = mOldItems[oldIndex.val].mItem; + MOZ_DIAGNOSTIC_ASSERT(oldItem->GetPerFrameKey() == + aNewItem->GetPerFrameKey() && + oldItem->Frame() == aNewItem->Frame()); + if (!mOldItems[oldIndex.val].IsChanged()) { + MOZ_DIAGNOSTIC_ASSERT(!mOldItems[oldIndex.val].IsUsed()); + nsDisplayItem* destItem; + if (ShouldUseNewItem(aNewItem)) { + destItem = aNewItem; + } else { + destItem = oldItem; + // The building rect can depend on the overflow rect (when the parent + // frame is position:fixed), which can change without invalidating + // the frame/items. If we're using the old item, copy the building + // rect across from the new item. + oldItem->SetBuildingRect(aNewItem->GetBuildingRect()); + } + + if (destItem == aNewItem) { + if (oldItem->IsGlassItem() && + oldItem == mBuilder->Builder()->GetGlassDisplayItem()) { + mBuilder->Builder()->ClearGlassDisplayItem(); + } + } // aNewItem can't be the glass item on the builder yet. + + if (destItem->IsGlassItem()) { + if (destItem != oldItem || + destItem != mBuilder->Builder()->GetGlassDisplayItem()) { + mBuilder->Builder()->SetGlassDisplayItem(destItem); + } + } + + MergeChildLists(aNewItem, oldItem, destItem); + + AutoTArray<MergedListIndex, 2> directPredecessors = + ProcessPredecessorsOfOldNode(oldIndex); + MergedListIndex newIndex = AddNewNode( + destItem, Some(oldIndex), directPredecessors, aPreviousItem); + mOldItems[oldIndex.val].AddedMatchToMergedList(mBuilder, newIndex); + if (destItem == aNewItem) { + oldItem->Destroy(mBuilder->Builder()); + } else { + aNewItem->Destroy(mBuilder->Builder()); + } + return Some(newIndex); + } + } + mResultIsModified = true; + if (aNewItem->IsGlassItem()) { + mBuilder->Builder()->SetGlassDisplayItem(aNewItem); + } + return Some(AddNewNode(aNewItem, Nothing(), Span<MergedListIndex>(), + aPreviousItem)); + } + + void MergeChildLists(nsDisplayItem* aNewItem, nsDisplayItem* aOldItem, + nsDisplayItem* aOutItem) { + if (!aOutItem->GetChildren()) { + return; + } + + Maybe<const ActiveScrolledRoot*> containerASRForChildren; + nsDisplayList empty; + const bool modified = mBuilder->MergeDisplayLists( + aNewItem ? aNewItem->GetChildren() : &empty, aOldItem->GetChildren(), + aOutItem->GetChildren(), containerASRForChildren, aOutItem); + if (modified) { + aOutItem->InvalidateCachedChildInfo(mBuilder->Builder()); + UpdateASR(aOutItem, containerASRForChildren); + mResultIsModified = true; + } else if (aOutItem == aNewItem) { + // If nothing changed, but we copied the contents across to + // the new item, then also copy the ASR data. + CopyASR(aOldItem, aNewItem); + } + // Ideally we'd only UpdateBounds if something changed, but + // nsDisplayWrapList also uses this to update the clip chain for the + // current ASR, which gets reset during RestoreState(), so we always need + // to run it again. + aOutItem->UpdateBounds(mBuilder->Builder()); + } + + bool ShouldUseNewItem(nsDisplayItem* aNewItem) { + // Generally we want to use the old item when the frame isn't marked as + // modified so that any cached information on the item (or referencing the + // item) gets retained. Quite a few FrameLayerBuilder performance + // improvements benefit by this. Sometimes, however, we can end up where the + // new item paints something different from the old item, even though we + // haven't modified the frame, and it's hard to fix. In these cases we just + // always use the new item to be safe. + DisplayItemType type = aNewItem->GetType(); + if (type == DisplayItemType::TYPE_CANVAS_BACKGROUND_COLOR || + type == DisplayItemType::TYPE_SOLID_COLOR) { + // The canvas background color item can paint the color from another + // frame, and even though we schedule a paint, we don't mark the canvas + // frame as invalid. + return true; + } + + if (type == DisplayItemType::TYPE_TABLE_BORDER_COLLAPSE) { + // We intentionally don't mark the root table frame as modified when a + // subframe changes, even though the border collapse item for the root + // frame is what paints the changed border. Marking the root frame as + // modified would rebuild display items for the whole table area, and we + // don't want that. + return true; + } + + if (type == DisplayItemType::TYPE_TEXT_OVERFLOW) { + // Text overflow marker items are created with the wrapping block as their + // frame, and have an index value to note which line they are created for. + // Their rendering can change if the items on that line change, which may + // not mark the block as modified. We rebuild them if we build any item on + // the line, so we should always get new items if they might have changed + // rendering, and it's easier to just use the new items rather than + // computing if we actually need them. + return true; + } + + if (type == DisplayItemType::TYPE_SUBDOCUMENT) { + // nsDisplaySubDocument::mShouldFlatten can change without an invalidation + // (and is the reason we unconditionally build the subdocument item), so + // always use the new one to make sure we get the right value. + return true; + } + + if (type == DisplayItemType::TYPE_CARET) { + // The caret can change position while still being owned by the same frame + // and we don't invalidate in that case. Use the new version since the + // changed bounds are needed for DLBI. + return true; + } + + if (type == DisplayItemType::TYPE_MASK || + type == DisplayItemType::TYPE_FILTER || + type == DisplayItemType::TYPE_SVG_WRAPPER) { + // SVG items have some invalidation issues, see bugs 1494110 and 1494663. + return true; + } + + if (type == DisplayItemType::TYPE_TRANSFORM) { + // Prerendering of transforms can change without frame invalidation. + return true; + } + + return false; + } + + RetainedDisplayList Finalize() { + for (size_t i = 0; i < mOldDAG.Length(); i++) { + if (mOldItems[i].IsUsed()) { + continue; + } + + AutoTArray<MergedListIndex, 2> directPredecessors = + ResolveNodeIndexesOldToMerged( + mOldDAG.GetDirectPredecessors(OldListIndex(i))); + ProcessOldNode(OldListIndex(i), std::move(directPredecessors)); + } + + RetainedDisplayList result; + result.AppendToTop(&mMergedItems); + result.mDAG = std::move(mMergedDAG); + MOZ_RELEASE_ASSERT(result.mDAG.Length() == result.Count()); + return result; + } + + bool HasMatchingItemInOldList(nsDisplayItem* aItem, OldListIndex* aOutIndex) { + nsIFrame::DisplayItemArray* items = + aItem->Frame()->GetProperty(nsIFrame::DisplayItems()); + // Look for an item that matches aItem's frame and per-frame-key, but isn't + // the same item. + uint32_t outerKey = mOuterItem ? mOuterItem->GetPerFrameKey() : 0; + for (nsDisplayItemBase* i : *items) { + if (i != aItem && i->Frame() == aItem->Frame() && + i->GetPerFrameKey() == aItem->GetPerFrameKey()) { + if (i->GetOldListIndex(mOldList, outerKey, aOutIndex)) { + return true; + } + } + } + return false; + } + +#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + bool HasModifiedFrame(nsDisplayItem* aItem) { + nsIFrame* stopFrame = mOuterItem ? mOuterItem->Frame() : nullptr; + return AnyContentAncestorModified(aItem->FrameForInvalidation(), stopFrame); + } +#endif + + void UpdateContainerASR(nsDisplayItem* aItem) { + mContainerASR = SelectContainerASR( + aItem->GetClipChain(), aItem->GetActiveScrolledRoot(), mContainerASR); + } + + MergedListIndex AddNewNode( + nsDisplayItem* aItem, const Maybe<OldListIndex>& aOldIndex, + Span<const MergedListIndex> aDirectPredecessors, + const Maybe<MergedListIndex>& aExtraDirectPredecessor) { + UpdateContainerASR(aItem); + aItem->NotifyUsed(mBuilder->Builder()); + +#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + nsIFrame::DisplayItemArray* items = + aItem->Frame()->GetProperty(nsIFrame::DisplayItems()); + for (nsDisplayItemBase* i : *items) { + if (i->Frame() == aItem->Frame() && + i->GetPerFrameKey() == aItem->GetPerFrameKey()) { + MOZ_DIAGNOSTIC_ASSERT(!i->IsMergedItem()); + } + } + + aItem->SetMergedPreProcessed(true, false); +#endif + + mMergedItems.AppendToTop(aItem); + mBuilder->Metrics()->mTotalItems++; + + MergedListIndex newIndex = + mMergedDAG.AddNode(aDirectPredecessors, aExtraDirectPredecessor); + return newIndex; + } + + void ProcessOldNode(OldListIndex aNode, + nsTArray<MergedListIndex>&& aDirectPredecessors) { + nsDisplayItem* item = mOldItems[aNode.val].mItem; + if (mOldItems[aNode.val].IsChanged()) { + if (item && item->IsGlassItem() && + item == mBuilder->Builder()->GetGlassDisplayItem()) { + mBuilder->Builder()->ClearGlassDisplayItem(); + } + + mOldItems[aNode.val].Discard(mBuilder, std::move(aDirectPredecessors)); + mResultIsModified = true; + } else { + MergeChildLists(nullptr, item, item); + + if (item->GetType() == DisplayItemType::TYPE_SUBDOCUMENT) { + mBuilder->IncrementSubDocPresShellPaintCount(item); + } + item->SetReused(true); + mBuilder->Metrics()->mReusedItems++; + mOldItems[aNode.val].AddedToMergedList( + AddNewNode(item, Some(aNode), aDirectPredecessors, Nothing())); + } + } + + struct PredecessorStackItem { + PredecessorStackItem(OldListIndex aNode, Span<OldListIndex> aPredecessors) + : mNode(aNode), + mDirectPredecessors(aPredecessors), + mCurrentPredecessorIndex(0) {} + + bool IsFinished() { + return mCurrentPredecessorIndex == mDirectPredecessors.Length(); + } + + OldListIndex GetAndIncrementCurrentPredecessor() { + return mDirectPredecessors[mCurrentPredecessorIndex++]; + } + + OldListIndex mNode; + Span<OldListIndex> mDirectPredecessors; + size_t mCurrentPredecessorIndex; + }; + + AutoTArray<MergedListIndex, 2> ProcessPredecessorsOfOldNode( + OldListIndex aNode) { + AutoTArray<PredecessorStackItem, 256> mStack; + mStack.AppendElement( + PredecessorStackItem(aNode, mOldDAG.GetDirectPredecessors(aNode))); + + while (true) { + if (mStack.LastElement().IsFinished()) { + // If we've finished processing all the entries in the current set, then + // pop it off the processing stack and process it. + PredecessorStackItem item = mStack.PopLastElement(); + AutoTArray<MergedListIndex, 2> result = + ResolveNodeIndexesOldToMerged(item.mDirectPredecessors); + + if (mStack.IsEmpty()) { + return result; + } + + ProcessOldNode(item.mNode, std::move(result)); + } else { + // Grab the current predecessor, push predecessors of that onto the + // processing stack (if it hasn't already been processed), and then + // advance to the next entry. + OldListIndex currentIndex = + mStack.LastElement().GetAndIncrementCurrentPredecessor(); + if (!mOldItems[currentIndex.val].IsUsed()) { + mStack.AppendElement(PredecessorStackItem( + currentIndex, mOldDAG.GetDirectPredecessors(currentIndex))); + } + } + } + } + + AutoTArray<MergedListIndex, 2> ResolveNodeIndexesOldToMerged( + Span<OldListIndex> aDirectPredecessors) { + AutoTArray<MergedListIndex, 2> result; + result.SetCapacity(aDirectPredecessors.Length()); + for (OldListIndex index : aDirectPredecessors) { + OldItemInfo& oldItem = mOldItems[index.val]; + if (oldItem.IsDiscarded()) { + for (MergedListIndex inner : oldItem.mDirectPredecessors) { + if (!result.Contains(inner)) { + result.AppendElement(inner); + } + } + } else { + result.AppendElement(oldItem.mIndex); + } + } + return result; + } + + RetainedDisplayListBuilder* mBuilder; + RetainedDisplayList* mOldList; + Maybe<const ActiveScrolledRoot*> mContainerASR; + nsTArray<OldItemInfo> mOldItems; + DirectedAcyclicGraph<OldListUnits> mOldDAG; + // Unfortunately we can't use strong typing for the hashtables + // since they internally encode the type with the mOps pointer, + // and assert when we try swap the contents + nsDisplayList mMergedItems; + DirectedAcyclicGraph<MergedListUnits> mMergedDAG; + nsDisplayItem* mOuterItem; + bool mResultIsModified; +}; + +#ifdef DEBUG +void VerifyNotModified(nsDisplayList* aList) { + for (nsDisplayItem* item = aList->GetBottom(); item; + item = item->GetAbove()) { + MOZ_ASSERT(!AnyContentAncestorModified(item->FrameForInvalidation())); + + if (item->GetChildren()) { + VerifyNotModified(item->GetChildren()); + } + } +} +#endif + +/** + * Takes two display lists and merges them into an output list. + * + * Display lists wthout an explicit DAG are interpreted as linear DAGs (with a + * maximum of one direct predecessor and one direct successor per node). We add + * the two DAGs together, and then output the topological sorted ordering as the + * final display list. + * + * Once we've merged a list, we then retain the DAG (as part of the + * RetainedDisplayList object) to use for future merges. + */ +bool RetainedDisplayListBuilder::MergeDisplayLists( + nsDisplayList* aNewList, RetainedDisplayList* aOldList, + RetainedDisplayList* aOutList, + mozilla::Maybe<const mozilla::ActiveScrolledRoot*>& aOutContainerASR, + nsDisplayItem* aOuterItem) { + AUTO_PROFILER_LABEL_CATEGORY_PAIR(GRAPHICS_DisplayListMerging); + + if (!aOldList->IsEmpty()) { + // If we still have items in the actual list, then it is because + // PreProcessDisplayList decided that it was sure it can't be modified. We + // can just use it directly, and throw any new items away. + + aNewList->DeleteAll(&mBuilder); +#ifdef DEBUG + VerifyNotModified(aOldList); +#endif + + if (aOldList != aOutList) { + *aOutList = std::move(*aOldList); + } + + return false; + } + + MergeState merge(this, *aOldList, aOuterItem); + + Maybe<MergedListIndex> previousItemIndex; + while (nsDisplayItem* item = aNewList->RemoveBottom()) { + Metrics()->mNewItems++; + previousItemIndex = merge.ProcessItemFromNewList(item, previousItemIndex); + } + + *aOutList = merge.Finalize(); + aOutContainerASR = merge.mContainerASR; + return merge.mResultIsModified; +} + +static nsIFrame* GetRootFrameForPainting(nsDisplayListBuilder* aBuilder, + Document& aDocument) { + // Although this is the actual subdocument, it might not be + // what painting uses. Walk up to the nsSubDocumentFrame owning + // us, and then ask that which subdoc it's going to paint. + + PresShell* presShell = aDocument.GetPresShell(); + if (!presShell) { + return nullptr; + } + nsView* rootView = presShell->GetViewManager()->GetRootView(); + if (!rootView) { + return nullptr; + } + + // There should be an anonymous inner view between the root view + // of the subdoc, and the view for the nsSubDocumentFrame. + nsView* innerView = rootView->GetParent(); + if (!innerView) { + return nullptr; + } + + nsView* subDocView = innerView->GetParent(); + if (!subDocView) { + return nullptr; + } + + nsIFrame* subDocFrame = subDocView->GetFrame(); + if (!subDocFrame) { + return nullptr; + } + + nsSubDocumentFrame* subdocumentFrame = do_QueryFrame(subDocFrame); + MOZ_ASSERT(subdocumentFrame); + presShell = subdocumentFrame->GetSubdocumentPresShellForPainting( + aBuilder->IsIgnoringPaintSuppression() + ? nsSubDocumentFrame::IGNORE_PAINT_SUPPRESSION + : 0); + return presShell ? presShell->GetRootFrame() : nullptr; +} + +static void TakeAndAddModifiedAndFramesWithPropsFromRootFrame( + nsDisplayListBuilder* aBuilder, nsTArray<nsIFrame*>* aModifiedFrames, + nsTArray<nsIFrame*>* aFramesWithProps, nsIFrame* aRootFrame, + Document& aDoc) { + MOZ_ASSERT(aRootFrame); + + if (RetainedDisplayListData* data = GetRetainedDisplayListData(aRootFrame)) { + for (auto it = data->Iterator(); !it.Done(); it.Next()) { + nsIFrame* frame = it.Key(); + const RetainedDisplayListData::FrameFlags& flags = it.Data(); + + if (flags & RetainedDisplayListData::FrameFlags::Modified) { + aModifiedFrames->AppendElement(frame); + } + + if (flags & RetainedDisplayListData::FrameFlags::HasProps) { + aFramesWithProps->AppendElement(frame); + } + + if (flags & RetainedDisplayListData::FrameFlags::HadWillChange) { + aBuilder->RemoveFromWillChangeBudgets(frame); + } + } + + data->Clear(); + } + + auto recurse = [&](Document& aSubDoc) { + if (nsIFrame* rootFrame = GetRootFrameForPainting(aBuilder, aSubDoc)) { + TakeAndAddModifiedAndFramesWithPropsFromRootFrame( + aBuilder, aModifiedFrames, aFramesWithProps, rootFrame, aSubDoc); + } + return CallState::Continue; + }; + aDoc.EnumerateSubDocuments(recurse); +} + +static void GetModifiedAndFramesWithProps( + nsDisplayListBuilder* aBuilder, nsTArray<nsIFrame*>* aOutModifiedFrames, + nsTArray<nsIFrame*>* aOutFramesWithProps) { + nsIFrame* rootFrame = aBuilder->RootReferenceFrame(); + MOZ_ASSERT(rootFrame); + + Document* rootDoc = rootFrame->PresContext()->Document(); + TakeAndAddModifiedAndFramesWithPropsFromRootFrame( + aBuilder, aOutModifiedFrames, aOutFramesWithProps, rootFrame, *rootDoc); +} + +// ComputeRebuildRegion debugging +// #define CRR_DEBUG 1 +#if CRR_DEBUG +# define CRR_LOG(...) printf_stderr(__VA_ARGS__) +#else +# define CRR_LOG(...) +#endif + +static nsDisplayItem* GetFirstDisplayItemWithChildren(nsIFrame* aFrame) { + nsIFrame::DisplayItemArray* items = + aFrame->GetProperty(nsIFrame::DisplayItems()); + if (!items) { + return nullptr; + } + + for (nsDisplayItemBase* i : *items) { + if (i->HasChildren()) { + return static_cast<nsDisplayItem*>(i); + } + } + return nullptr; +} + +static bool IsInPreserve3DContext(const nsIFrame* aFrame) { + return aFrame->Extend3DContext() || + aFrame->Combines3DTransformWithAncestors(); +} + +static bool ProcessFrameInternal(nsIFrame* aFrame, + nsDisplayListBuilder* aBuilder, + AnimatedGeometryRoot** aAGR, nsRect& aOverflow, + const nsIFrame* aStopAtFrame, + nsTArray<nsIFrame*>& aOutFramesWithProps, + const bool aStopAtStackingContext) { + nsIFrame* currentFrame = aFrame; + + while (currentFrame != aStopAtFrame) { + CRR_LOG("currentFrame: %p (placeholder=%d), aOverflow: %d %d %d %d\n", + currentFrame, !aStopAtStackingContext, aOverflow.x, aOverflow.y, + aOverflow.width, aOverflow.height); + + // If the current frame is an OOF frame, DisplayListBuildingData needs to be + // set on all the ancestor stacking contexts of the placeholder frame, up + // to the containing block of the OOF frame. This is done to ensure that the + // content that might be behind the OOF frame is built for merging. + nsIFrame* placeholder = currentFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW) + ? currentFrame->GetPlaceholderFrame() + : nullptr; + + if (placeholder) { + nsRect placeholderOverflow = aOverflow; + auto rv = nsLayoutUtils::TransformRect(currentFrame, placeholder, + placeholderOverflow); + if (rv != nsLayoutUtils::TRANSFORM_SUCCEEDED) { + placeholderOverflow = nsRect(); + } + + CRR_LOG("Processing placeholder %p for OOF frame %p\n", placeholder, + currentFrame); + + CRR_LOG("OOF frame draw area: %d %d %d %d\n", placeholderOverflow.x, + placeholderOverflow.y, placeholderOverflow.width, + placeholderOverflow.height); + + // Tracking AGRs for the placeholder processing is not necessary, as the + // goal is to only modify the DisplayListBuildingData rect. + AnimatedGeometryRoot* dummyAGR = nullptr; + + // Find a common ancestor frame to handle frame continuations. + // TODO: It might be possible to write a more specific and efficient + // function for this. + const nsIFrame* ancestor = nsLayoutUtils::FindNearestCommonAncestorFrame( + currentFrame->GetParent(), placeholder->GetParent()); + + if (!ProcessFrameInternal(placeholder, aBuilder, &dummyAGR, + placeholderOverflow, ancestor, + aOutFramesWithProps, false)) { + return false; + } + } + + // Convert 'aOverflow' into the coordinate space of the nearest stacking + // context or display port ancestor and update 'currentFrame' to point to + // that frame. + aOverflow = nsLayoutUtils::TransformFrameRectToAncestor( + currentFrame, aOverflow, aStopAtFrame, nullptr, nullptr, + /* aStopAtStackingContextAndDisplayPortAndOOFFrame = */ true, + ¤tFrame); + if (IsInPreserve3DContext(currentFrame)) { + return false; + } + + MOZ_ASSERT(currentFrame); + + // Check whether the current frame is a scrollable frame with display port. + nsRect displayPort; + nsIScrollableFrame* sf = do_QueryFrame(currentFrame); + nsIContent* content = sf ? currentFrame->GetContent() : nullptr; + + if (content && DisplayPortUtils::GetDisplayPort(content, &displayPort)) { + CRR_LOG("Frame belongs to displayport frame %p\n", currentFrame); + + // Get overflow relative to the scrollport (from the scrollframe) + nsRect r = aOverflow - sf->GetScrollPortRect().TopLeft(); + r.IntersectRect(r, displayPort); + if (!r.IsEmpty()) { + nsRect* rect = currentFrame->GetProperty( + nsDisplayListBuilder::DisplayListBuildingDisplayPortRect()); + if (!rect) { + rect = new nsRect(); + currentFrame->SetProperty( + nsDisplayListBuilder::DisplayListBuildingDisplayPortRect(), rect); + currentFrame->SetHasOverrideDirtyRegion(true); + aOutFramesWithProps.AppendElement(currentFrame); + } + rect->UnionRect(*rect, r); + CRR_LOG("Adding area to displayport draw area: %d %d %d %d\n", r.x, r.y, + r.width, r.height); + + // TODO: Can we just use MarkFrameForDisplayIfVisible, plus + // MarkFramesForDifferentAGR to ensure that this displayport, plus any + // items that move relative to it get rebuilt, and then not contribute + // to the root dirty area? + aOverflow = sf->GetScrollPortRect(); + } else { + // Don't contribute to the root dirty area at all. + aOverflow.SetEmpty(); + } + } else { + aOverflow.IntersectRect(aOverflow, + currentFrame->InkOverflowRectRelativeToSelf()); + } + + if (aOverflow.IsEmpty()) { + break; + } + + if (currentFrame != aBuilder->RootReferenceFrame() && + currentFrame->IsStackingContext() && + currentFrame->IsFixedPosContainingBlock()) { + CRR_LOG("Frame belongs to stacking context frame %p\n", currentFrame); + // If we found an intermediate stacking context with an existing display + // item then we can store the dirty rect there and stop. If we couldn't + // find one then we need to keep bubbling up to the next stacking context. + nsDisplayItem* wrapperItem = + GetFirstDisplayItemWithChildren(currentFrame); + if (!wrapperItem) { + continue; + } + + // Store the stacking context relative dirty area such + // that display list building will pick it up when it + // gets to it. + nsDisplayListBuilder::DisplayListBuildingData* data = + currentFrame->GetProperty( + nsDisplayListBuilder::DisplayListBuildingRect()); + if (!data) { + data = new nsDisplayListBuilder::DisplayListBuildingData(); + currentFrame->SetProperty( + nsDisplayListBuilder::DisplayListBuildingRect(), data); + currentFrame->SetHasOverrideDirtyRegion(true); + aOutFramesWithProps.AppendElement(currentFrame); + } + CRR_LOG("Adding area to stacking context draw area: %d %d %d %d\n", + aOverflow.x, aOverflow.y, aOverflow.width, aOverflow.height); + data->mDirtyRect.UnionRect(data->mDirtyRect, aOverflow); + + if (!aStopAtStackingContext) { + // Continue ascending the frame tree until we reach aStopAtFrame. + continue; + } + + // Grab the visible (display list building) rect for children of this + // wrapper item and convert into into coordinate relative to the current + // frame. + nsRect previousVisible = wrapperItem->GetBuildingRectForChildren(); + if (wrapperItem->ReferenceFrameForChildren() == + wrapperItem->ReferenceFrame()) { + previousVisible -= wrapperItem->ToReferenceFrame(); + } else { + MOZ_ASSERT(wrapperItem->ReferenceFrameForChildren() == + wrapperItem->Frame()); + } + + if (!previousVisible.Contains(aOverflow)) { + // If the overflow area of the changed frame isn't contained within the + // old item, then we might change the size of the item and need to + // update its sorting accordingly. Keep propagating the overflow area up + // so that we build intersecting items for sorting. + continue; + } + + if (!data->mModifiedAGR) { + data->mModifiedAGR = *aAGR; + } else if (data->mModifiedAGR != *aAGR) { + data->mDirtyRect = currentFrame->InkOverflowRectRelativeToSelf(); + CRR_LOG( + "Found multiple modified AGRs within this stacking context, " + "giving up\n"); + } + + // Don't contribute to the root dirty area at all. + aOverflow.SetEmpty(); + *aAGR = nullptr; + + break; + } + } + return true; +} + +bool RetainedDisplayListBuilder::ProcessFrame( + nsIFrame* aFrame, nsDisplayListBuilder* aBuilder, nsIFrame* aStopAtFrame, + nsTArray<nsIFrame*>& aOutFramesWithProps, const bool aStopAtStackingContext, + nsRect* aOutDirty, AnimatedGeometryRoot** aOutModifiedAGR) { + if (aFrame->HasOverrideDirtyRegion()) { + aOutFramesWithProps.AppendElement(aFrame); + } + + if (aFrame->HasAnyStateBits(NS_FRAME_IN_POPUP)) { + return true; + } + + // TODO: There is almost certainly a faster way of doing this, probably can be + // combined with the ancestor walk for TransformFrameRectToAncestor. + AnimatedGeometryRoot* agr = + aBuilder->FindAnimatedGeometryRootFor(aFrame)->GetAsyncAGR(); + + CRR_LOG("Processing frame %p with agr %p\n", aFrame, agr->mFrame); + + // Convert the frame's overflow rect into the coordinate space + // of the nearest stacking context that has an existing display item. + // We store that as a dirty rect on that stacking context so that we build + // all items that intersect the changed frame within the stacking context, + // and then we use MarkFrameForDisplayIfVisible to make sure the stacking + // context itself gets built. We don't need to build items that intersect + // outside of the stacking context, since we know the stacking context item + // exists in the old list, so we can trivially merge without needing other + // items. + nsRect overflow = aFrame->InkOverflowRectRelativeToSelf(); + + // If the modified frame is also a caret frame, include the caret area. + // This is needed because some frames (for example text frames without text) + // might have an empty overflow rect. + if (aFrame == aBuilder->GetCaretFrame()) { + overflow.UnionRect(overflow, aBuilder->GetCaretRect()); + } + + if (!ProcessFrameInternal(aFrame, aBuilder, &agr, overflow, aStopAtFrame, + aOutFramesWithProps, aStopAtStackingContext)) { + return false; + } + + if (!overflow.IsEmpty()) { + aOutDirty->UnionRect(*aOutDirty, overflow); + CRR_LOG("Adding area to root draw area: %d %d %d %d\n", overflow.x, + overflow.y, overflow.width, overflow.height); + + // If we get changed frames from multiple AGRS, then just give up as it gets + // really complex to track which items would need to be marked in + // MarkFramesForDifferentAGR. + if (!*aOutModifiedAGR) { + CRR_LOG("Setting %p as root stacking context AGR\n", agr); + *aOutModifiedAGR = agr; + } else if (agr && *aOutModifiedAGR != agr) { + CRR_LOG("Found multiple AGRs in root stacking context, giving up\n"); + return false; + } + } + return true; +} + +static void AddFramesForContainingBlock(nsIFrame* aBlock, + const nsFrameList& aFrames, + nsTArray<nsIFrame*>& aExtraFrames) { + for (nsIFrame* f : aFrames) { + if (!f->IsFrameModified() && AnyContentAncestorModified(f, aBlock)) { + CRR_LOG("Adding invalid OOF %p\n", f); + aExtraFrames.AppendElement(f); + } + } +} + +// Placeholder descendants of aFrame don't contribute to aFrame's overflow area. +// Find all the containing blocks that might own placeholders under us, walk +// their OOF frames list, and manually invalidate any frames that are +// descendants of a modified frame (us, or another frame we'll get to soon). +// This is combined with the work required for MarkFrameForDisplayIfVisible, +// so that we can avoid an extra ancestor walk, and we can reuse the flag +// to detect when we've already visited an ancestor (and thus all further +// ancestors must also be visited). +static void FindContainingBlocks(nsIFrame* aFrame, + nsTArray<nsIFrame*>& aExtraFrames) { + for (nsIFrame* f = aFrame; f; f = nsLayoutUtils::GetDisplayListParent(f)) { + if (f->ForceDescendIntoIfVisible()) { + return; + } + f->SetForceDescendIntoIfVisible(true); + CRR_LOG("Considering OOFs for %p\n", f); + + AddFramesForContainingBlock(f, f->GetChildList(nsIFrame::kFloatList), + aExtraFrames); + AddFramesForContainingBlock(f, f->GetChildList(f->GetAbsoluteListID()), + aExtraFrames); + } +} + +/** + * Given a list of frames that has been modified, computes the region that we + * need to do display list building for in order to build all modified display + * items. + * + * When a modified frame is within a stacking context (with an existing display + * item), then we only contribute to the build area within the stacking context, + * as well as forcing display list building to descend to the stacking context. + * We don't need to add build area outside of the stacking context (and force + * items above/below the stacking context container item to be built), since + * just matching the position of the stacking context container item is + * sufficient to ensure correct ordering during merging. + * + * We need to rebuild all items that might intersect with the modified frame, + * both now and during async changes on the compositor. We do this by rebuilding + * the area covered by the changed frame, as well as rebuilding all items that + * have a different (async) AGR to the changed frame. If we have changes to + * multiple AGRs (within a stacking context), then we rebuild that stacking + * context entirely. + * + * @param aModifiedFrames The list of modified frames. + * @param aOutDirty The result region to use for display list building. + * @param aOutModifiedAGR The modified AGR for the root stacking context. + * @param aOutFramesWithProps The list of frames to which we attached partial + * build data so that it can be cleaned up. + * + * @return true if we succesfully computed a partial rebuild region, false if a + * full build is required. + */ +bool RetainedDisplayListBuilder::ComputeRebuildRegion( + nsTArray<nsIFrame*>& aModifiedFrames, nsRect* aOutDirty, + AnimatedGeometryRoot** aOutModifiedAGR, + nsTArray<nsIFrame*>& aOutFramesWithProps) { + CRR_LOG("Computing rebuild regions for %zu frames:\n", + aModifiedFrames.Length()); + nsTArray<nsIFrame*> extraFrames; + for (nsIFrame* f : aModifiedFrames) { + MOZ_ASSERT(f); + + mBuilder.AddFrameMarkedForDisplayIfVisible(f); + FindContainingBlocks(f, extraFrames); + + if (!ProcessFrame(f, &mBuilder, mBuilder.RootReferenceFrame(), + aOutFramesWithProps, true, aOutDirty, aOutModifiedAGR)) { + return false; + } + } + + // Since we set modified to true on the extraFrames, add them to + // aModifiedFrames so that it will get reverted. + aModifiedFrames.AppendElements(extraFrames); + + for (nsIFrame* f : extraFrames) { + f->SetFrameIsModified(true); + + if (!ProcessFrame(f, &mBuilder, mBuilder.RootReferenceFrame(), + aOutFramesWithProps, true, aOutDirty, aOutModifiedAGR)) { + return false; + } + } + + return true; +} + +bool RetainedDisplayListBuilder::ShouldBuildPartial( + nsTArray<nsIFrame*>& aModifiedFrames) { + if (mList.IsEmpty()) { + // Partial builds without a previous display list do not make sense. + Metrics()->mPartialUpdateFailReason = PartialUpdateFailReason::EmptyList; + return false; + } + + if (aModifiedFrames.Length() > + StaticPrefs::layout_display_list_rebuild_frame_limit()) { + // Computing a dirty rect with too many modified frames can be slow. + Metrics()->mPartialUpdateFailReason = PartialUpdateFailReason::RebuildLimit; + return false; + } + + // We don't support retaining with overlay scrollbars, since they require + // us to look at the display list and pick the highest z-index, which + // we can't do during partial building. + if (mBuilder.DisablePartialUpdates()) { + mBuilder.SetDisablePartialUpdates(false); + Metrics()->mPartialUpdateFailReason = PartialUpdateFailReason::Disabled; + return false; + } + + for (nsIFrame* f : aModifiedFrames) { + MOZ_ASSERT(f); + + const LayoutFrameType type = f->Type(); + + // If we have any modified frames of the following types, it is likely that + // doing a partial rebuild of the display list will be slower than doing a + // full rebuild. + // This is because these frames either intersect or may intersect with most + // of the page content. This is either due to display port size or different + // async AGR. + if (type == LayoutFrameType::Viewport || + type == LayoutFrameType::PageContent || + type == LayoutFrameType::Canvas || type == LayoutFrameType::Scrollbar) { + Metrics()->mPartialUpdateFailReason = PartialUpdateFailReason::FrameType; + return false; + } + } + + return true; +} + +void RetainedDisplayListBuilder::InvalidateCaretFramesIfNeeded() { + if (mPreviousCaret == mBuilder.GetCaretFrame()) { + // The current caret frame is the same as the previous one. + return; + } + + if (mPreviousCaret) { + mPreviousCaret->MarkNeedsDisplayItemRebuild(); + } + + if (mBuilder.GetCaretFrame()) { + mBuilder.GetCaretFrame()->MarkNeedsDisplayItemRebuild(); + } + + mPreviousCaret = mBuilder.GetCaretFrame(); +} + +static void ClearFrameProps(nsTArray<nsIFrame*>& aFrames) { + for (nsIFrame* f : aFrames) { + if (f->HasOverrideDirtyRegion()) { + f->SetHasOverrideDirtyRegion(false); + f->RemoveProperty(nsDisplayListBuilder::DisplayListBuildingRect()); + f->RemoveProperty( + nsDisplayListBuilder::DisplayListBuildingDisplayPortRect()); + } + + f->SetFrameIsModified(false); + } +} + +class AutoClearFramePropsArray { + public: + explicit AutoClearFramePropsArray(size_t aCapacity) : mFrames(aCapacity) {} + AutoClearFramePropsArray() = default; + ~AutoClearFramePropsArray() { ClearFrameProps(mFrames); } + + nsTArray<nsIFrame*>& Frames() { return mFrames; } + bool IsEmpty() const { return mFrames.IsEmpty(); } + + private: + nsTArray<nsIFrame*> mFrames; +}; + +void RetainedDisplayListBuilder::ClearFramesWithProps() { + AutoClearFramePropsArray modifiedFrames; + AutoClearFramePropsArray framesWithProps; + GetModifiedAndFramesWithProps(&mBuilder, &modifiedFrames.Frames(), + &framesWithProps.Frames()); +} + +PartialUpdateResult RetainedDisplayListBuilder::AttemptPartialUpdate( + nscolor aBackstop) { + mBuilder.RemoveModifiedWindowRegions(); + + if (mBuilder.ShouldSyncDecodeImages()) { + MarkFramesWithItemsAndImagesModified(&mList); + } + + InvalidateCaretFramesIfNeeded(); + + mBuilder.EnterPresShell(mBuilder.RootReferenceFrame()); + + // We set the override dirty regions during ComputeRebuildRegion or in + // DisplayPortUtils::InvalidateForDisplayPortChange. The display port change + // also marks the frame modified, so those regions are cleared here as well. + AutoClearFramePropsArray modifiedFrames(64); + AutoClearFramePropsArray framesWithProps; + GetModifiedAndFramesWithProps(&mBuilder, &modifiedFrames.Frames(), + &framesWithProps.Frames()); + + // Do not allow partial builds if the |ShouldBuildPartial()| heuristic fails. + bool shouldBuildPartial = ShouldBuildPartial(modifiedFrames.Frames()); + + nsRect modifiedDirty; + AnimatedGeometryRoot* modifiedAGR = nullptr; + PartialUpdateResult result = PartialUpdateResult::NoChange; + if (!shouldBuildPartial || + !ComputeRebuildRegion(modifiedFrames.Frames(), &modifiedDirty, + &modifiedAGR, framesWithProps.Frames()) || + !PreProcessDisplayList(&mList, modifiedAGR, result)) { + mBuilder.SetPartialBuildFailed(true); + mBuilder.LeavePresShell(mBuilder.RootReferenceFrame(), nullptr); + mList.DeleteAll(&mBuilder); + return PartialUpdateResult::Failed; + } + + // This is normally handled by EnterPresShell, but we skipped it so that we + // didn't call MarkFrameForDisplayIfVisible before ComputeRebuildRegion. + nsIScrollableFrame* sf = mBuilder.RootReferenceFrame() + ->PresShell() + ->GetRootScrollFrameAsScrollable(); + if (sf) { + nsCanvasFrame* canvasFrame = do_QueryFrame(sf->GetScrolledFrame()); + if (canvasFrame) { + mBuilder.MarkFrameForDisplayIfVisible(canvasFrame, + mBuilder.RootReferenceFrame()); + } + } + + modifiedDirty.IntersectRect( + modifiedDirty, + mBuilder.RootReferenceFrame()->InkOverflowRectRelativeToSelf()); + + mBuilder.SetDirtyRect(modifiedDirty); + mBuilder.SetPartialUpdate(true); + mBuilder.SetPartialBuildFailed(false); + + nsDisplayList modifiedDL; + mBuilder.RootReferenceFrame()->BuildDisplayListForStackingContext( + &mBuilder, &modifiedDL); + if (!modifiedDL.IsEmpty()) { + nsLayoutUtils::AddExtraBackgroundItems( + &mBuilder, &modifiedDL, mBuilder.RootReferenceFrame(), + nsRect(nsPoint(0, 0), mBuilder.RootReferenceFrame()->GetSize()), + mBuilder.RootReferenceFrame()->InkOverflowRectRelativeToSelf(), + aBackstop); + } + mBuilder.SetPartialUpdate(false); + + if (mBuilder.PartialBuildFailed()) { + mBuilder.LeavePresShell(mBuilder.RootReferenceFrame(), nullptr); + mList.DeleteAll(&mBuilder); + modifiedDL.DeleteAll(&mBuilder); + Metrics()->mPartialUpdateFailReason = PartialUpdateFailReason::Content; + return PartialUpdateResult::Failed; + } + + // printf_stderr("Painting --- Modified list (dirty %d,%d,%d,%d):\n", + // modifiedDirty.x, modifiedDirty.y, modifiedDirty.width, + // modifiedDirty.height); + // nsIFrame::PrintDisplayList(&mBuilder, modifiedDL); + + // |modifiedDL| can sometimes be empty here. We still perform the + // display list merging to prune unused items (for example, items that + // are not visible anymore) from the old list. + // TODO: Optimization opportunity. In this case, MergeDisplayLists() + // unnecessarily creates a hashtable of the old items. + // TODO: Ideally we could skip this if result is NoChange, but currently when + // we call RestoreState on nsDisplayWrapList it resets the clip to the base + // clip, and we need the UpdateBounds call (within MergeDisplayLists) to + // move it to the correct inner clip. + Maybe<const ActiveScrolledRoot*> dummy; + if (MergeDisplayLists(&modifiedDL, &mList, &mList, dummy)) { + result = PartialUpdateResult::Updated; + } + + // printf_stderr("Painting --- Merged list:\n"); + // nsIFrame::PrintDisplayList(&mBuilder, mList); + + mBuilder.LeavePresShell(mBuilder.RootReferenceFrame(), List()); + return result; +} diff --git a/layout/painting/RetainedDisplayListBuilder.h b/layout/painting/RetainedDisplayListBuilder.h new file mode 100644 index 0000000000..0c4cd73598 --- /dev/null +++ b/layout/painting/RetainedDisplayListBuilder.h @@ -0,0 +1,261 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef RETAINEDDISPLAYLISTBUILDER_H_ +#define RETAINEDDISPLAYLISTBUILDER_H_ + +#include "nsDisplayList.h" +#include "mozilla/Maybe.h" +#include "mozilla/TypedEnumBits.h" + +class nsWindowSizes; + +/** + * RetainedDisplayListData contains frame invalidation information. It is stored + * in root frames, and used by RetainedDisplayListBuilder. + * Currently this is implemented as a map of frame pointers to flags. + */ +struct RetainedDisplayListData { + NS_DECLARE_FRAME_PROPERTY_DELETABLE(DisplayListData, RetainedDisplayListData) + + enum class FrameFlags : uint8_t { + None = 0, + Modified = 1 << 0, + HasProps = 1 << 1, + HadWillChange = 1 << 2 + }; + + RetainedDisplayListData() : mModifiedFramesCount(0) {} + + /** + * Adds the frame to modified frames list. + */ + void AddModifiedFrame(nsIFrame* aFrame); + + /** + * Removes all the frames from this RetainedDisplayListData. + */ + void Clear() { + mFrames.Clear(); + mModifiedFramesCount = 0; + } + + /** + * Returns a mutable reference to flags set for the given |aFrame|. If the + * frame does not exist in this RetainedDisplayListData, it is added with + * default constructible flags FrameFlags::None. + */ + FrameFlags& Flags(nsIFrame* aFrame) { return mFrames.GetOrInsert(aFrame); } + + /** + * Returns flags set for the given |aFrame|, or FrameFlags::None if the frame + * is not in this RetainedDisplayListData. + */ + FrameFlags GetFlags(nsIFrame* aFrame) const { return mFrames.Get(aFrame); } + + /** + * Returns an iterator to the underlying frame storage. + */ + auto Iterator() { return mFrames.Iter(); } + + /** + * Returns the count of modified frames in this RetainedDisplayListData. + */ + uint32_t ModifiedFramesCount() const { return mModifiedFramesCount; } + + /** + * Removes the given |aFrame| from this RetainedDisplayListData. + */ + bool Remove(nsIFrame* aFrame) { return mFrames.Remove(aFrame); } + + private: + nsDataHashtable<nsPtrHashKey<nsIFrame>, FrameFlags> mFrames; + uint32_t mModifiedFramesCount; +}; + +MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(RetainedDisplayListData::FrameFlags) + +/** + * Returns RetainedDisplayListData property for the given |aRootFrame|, or + * nullptr if the property is not set. + */ +RetainedDisplayListData* GetRetainedDisplayListData(nsIFrame* aRootFrame); + +/** + * Returns RetainedDisplayListData property for the given |aRootFrame|. Creates + * and sets a new RetainedDisplayListData property if it is not already set. + */ +RetainedDisplayListData* GetOrSetRetainedDisplayListData(nsIFrame* aRootFrame); + +enum class PartialUpdateResult { Failed, NoChange, Updated }; + +enum class PartialUpdateFailReason { + NA, + EmptyList, + RebuildLimit, + FrameType, + Disabled, + Content, + VisibleRect, +}; + +struct RetainedDisplayListMetrics { + RetainedDisplayListMetrics() { Reset(); } + + void Reset() { + mNewItems = 0; + mRebuiltItems = 0; + mRemovedItems = 0; + mReusedItems = 0; + mTotalItems = 0; + mPartialBuildDuration = 0; + mFullBuildDuration = 0; + mPartialUpdateFailReason = PartialUpdateFailReason::NA; + mPartialUpdateResult = PartialUpdateResult::NoChange; + } + + void StartBuild() { mStartTime = mozilla::TimeStamp::Now(); } + + void EndFullBuild() { mFullBuildDuration = Elapsed(); } + + void EndPartialBuild(PartialUpdateResult aResult) { + mPartialBuildDuration = Elapsed(); + mPartialUpdateResult = aResult; + } + + double Elapsed() { + return (mozilla::TimeStamp::Now() - mStartTime).ToMilliseconds(); + } + + const char* FailReasonString() const { + switch (mPartialUpdateFailReason) { + case PartialUpdateFailReason::NA: + return "N/A"; + case PartialUpdateFailReason::EmptyList: + return "Empty list"; + case PartialUpdateFailReason::RebuildLimit: + return "Rebuild limit"; + case PartialUpdateFailReason::FrameType: + return "Frame type"; + case PartialUpdateFailReason::Disabled: + return "Disabled"; + case PartialUpdateFailReason::Content: + return "Content"; + case PartialUpdateFailReason::VisibleRect: + return "VisibleRect"; + default: + MOZ_ASSERT_UNREACHABLE("Enum value not handled!"); + } + } + + unsigned int mNewItems; + unsigned int mRebuiltItems; + unsigned int mRemovedItems; + unsigned int mReusedItems; + unsigned int mTotalItems; + + mozilla::TimeStamp mStartTime; + double mPartialBuildDuration; + double mFullBuildDuration; + PartialUpdateFailReason mPartialUpdateFailReason; + PartialUpdateResult mPartialUpdateResult; +}; + +struct RetainedDisplayListBuilder { + RetainedDisplayListBuilder(nsIFrame* aReferenceFrame, + nsDisplayListBuilderMode aMode, bool aBuildCaret) + : mBuilder(aReferenceFrame, aMode, aBuildCaret, true) {} + ~RetainedDisplayListBuilder() { mList.DeleteAll(&mBuilder); } + + nsDisplayListBuilder* Builder() { return &mBuilder; } + + nsDisplayList* List() { return &mList; } + + RetainedDisplayListMetrics* Metrics() { return &mMetrics; } + + PartialUpdateResult AttemptPartialUpdate(nscolor aBackstop); + + /** + * Iterates through the display list builder reference frame document and + * subdocuments, and clears the modified frame lists from the root frames. + * Also clears the frame properties set by RetainedDisplayListBuilder for all + * the frames in the modified frame lists. + */ + void ClearFramesWithProps(); + void AddSizeOfIncludingThis(nsWindowSizes&) const; + + NS_DECLARE_FRAME_PROPERTY_DELETABLE(Cached, RetainedDisplayListBuilder) + + private: + void IncrementSubDocPresShellPaintCount(nsDisplayItem* aItem); + + /** + * Invalidates the current and previous caret frame if they have changed. + */ + void InvalidateCaretFramesIfNeeded(); + + /** + * A simple early exit heuristic to avoid slow partial display list rebuilds. + * Returns true if a partial display list build should be attempted. + */ + bool ShouldBuildPartial(nsTArray<nsIFrame*>& aModifiedFrames); + + /** + * Recursively pre-processes the old display list tree before building the + * new partial display lists, and serializes the old list into an array, + * recording indices on items for fast lookup during merging. Builds an + * initial linear DAG for the list if we don't have an existing one. Finds + * items that have a different AGR from the specified one, and marks them to + * also be built so that we get relative ordering correct. Passes + * aKeepLinked=true internally for sub-lists that can't be changed to keep the + * original list structure linked for fast re-use. + */ + bool PreProcessDisplayList(RetainedDisplayList* aList, + AnimatedGeometryRoot* aAGR, + PartialUpdateResult& aUpdated, + nsIFrame* aOuterFrame = nullptr, + uint32_t aCallerKey = 0, + uint32_t aNestingDepth = 0, + bool aKeepLinked = false); + + /** + * Merges items from aNewList into non-invalidated items from aOldList and + * stores the result in aOutList. + * + * aOuterItem is a pointer to an item that owns one of the lists, if + * available. If both lists are populated, then both outer items must not be + * invalidated, and identical, so either can be passed here. + * + * Returns true if changes were made, and the resulting display list (in + * aOutList) is different from aOldList. + */ + bool MergeDisplayLists( + nsDisplayList* aNewList, RetainedDisplayList* aOldList, + RetainedDisplayList* aOutList, + mozilla::Maybe<const mozilla::ActiveScrolledRoot*>& aOutContainerASR, + nsDisplayItem* aOuterItem = nullptr); + + bool ComputeRebuildRegion(nsTArray<nsIFrame*>& aModifiedFrames, + nsRect* aOutDirty, + AnimatedGeometryRoot** aOutModifiedAGR, + nsTArray<nsIFrame*>& aOutFramesWithProps); + + bool ProcessFrame(nsIFrame* aFrame, nsDisplayListBuilder* aBuilder, + nsIFrame* aStopAtFrame, + nsTArray<nsIFrame*>& aOutFramesWithProps, + const bool aStopAtStackingContext, nsRect* aOutDirty, + AnimatedGeometryRoot** aOutModifiedAGR); + + friend class MergeState; + + nsDisplayListBuilder mBuilder; + RetainedDisplayList mList; + nsRect mPreviousVisibleRect; + WeakFrame mPreviousCaret; + RetainedDisplayListMetrics mMetrics; +}; + +#endif // RETAINEDDISPLAYLISTBUILDER_H_ diff --git a/layout/painting/RetainedDisplayListHelpers.h b/layout/painting/RetainedDisplayListHelpers.h new file mode 100644 index 0000000000..f2f29661cc --- /dev/null +++ b/layout/painting/RetainedDisplayListHelpers.h @@ -0,0 +1,181 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef RETAINEDDISPLAYLISTHELPERS_H_ +#define RETAINEDDISPLAYLISTHELPERS_H_ + +#include "mozilla/Span.h" +#include "PLDHashTable.h" + +struct DisplayItemKey { + bool operator==(const DisplayItemKey& aOther) const { + return mFrame == aOther.mFrame && mPerFrameKey == aOther.mPerFrameKey; + } + + nsIFrame* mFrame; + uint32_t mPerFrameKey; +}; + +class DisplayItemHashEntry : public PLDHashEntryHdr { + public: + typedef DisplayItemKey KeyType; + typedef const DisplayItemKey* KeyTypePointer; + + explicit DisplayItemHashEntry(KeyTypePointer aKey) : mKey(*aKey) {} + DisplayItemHashEntry(DisplayItemHashEntry&&) = default; + + ~DisplayItemHashEntry() = default; + + KeyType GetKey() const { return mKey; } + bool KeyEquals(KeyTypePointer aKey) const { return mKey == *aKey; } + + static KeyTypePointer KeyToPointer(KeyType& aKey) { return &aKey; } + static PLDHashNumber HashKey(KeyTypePointer aKey) { + if (!aKey) { + return 0; + } + + return mozilla::HashGeneric(aKey->mFrame, aKey->mPerFrameKey); + } + enum { ALLOW_MEMMOVE = true }; + + DisplayItemKey mKey; +}; + +template <typename T> +bool SpanContains(mozilla::Span<const T>& aSpan, T aItem) { + for (const T& i : aSpan) { + if (i == aItem) { + return true; + } + } + return false; +} + +class OldListUnits {}; +class MergedListUnits {}; + +template <typename Units> +struct Index { + Index() : val(0) {} + explicit Index(size_t aVal) : val(aVal) { + MOZ_RELEASE_ASSERT(aVal < std::numeric_limits<uint32_t>::max(), + "List index overflowed"); + } + + bool operator==(const Index<Units>& aOther) const { + return val == aOther.val; + } + + uint32_t val; +}; +typedef Index<OldListUnits> OldListIndex; +typedef Index<MergedListUnits> MergedListIndex; + +template <typename T> +class DirectedAcyclicGraph { + public: + DirectedAcyclicGraph() = default; + DirectedAcyclicGraph(DirectedAcyclicGraph&& aOther) + : mNodesInfo(std::move(aOther.mNodesInfo)), + mDirectPredecessorList(std::move(aOther.mDirectPredecessorList)) {} + + DirectedAcyclicGraph& operator=(DirectedAcyclicGraph&& aOther) { + mNodesInfo = std::move(aOther.mNodesInfo); + mDirectPredecessorList = std::move(aOther.mDirectPredecessorList); + return *this; + } + + Index<T> AddNode( + mozilla::Span<const Index<T>> aDirectPredecessors, + const mozilla::Maybe<Index<T>>& aExtraPredecessor = mozilla::Nothing()) { + size_t index = mNodesInfo.Length(); + mNodesInfo.AppendElement(NodeInfo(mDirectPredecessorList.Length(), + aDirectPredecessors.Length())); + if (aExtraPredecessor && + !SpanContains(aDirectPredecessors, aExtraPredecessor.value())) { + mNodesInfo.LastElement().mDirectPredecessorCount++; + mDirectPredecessorList.SetCapacity(mDirectPredecessorList.Length() + + aDirectPredecessors.Length() + 1); + mDirectPredecessorList.AppendElements(aDirectPredecessors); + mDirectPredecessorList.AppendElement(aExtraPredecessor.value()); + } else { + mDirectPredecessorList.AppendElements(aDirectPredecessors); + } + return Index<T>(index); + } + + size_t Length() { return mNodesInfo.Length(); } + + mozilla::Span<Index<T>> GetDirectPredecessors(Index<T> aNodeIndex) { + NodeInfo& node = mNodesInfo[aNodeIndex.val]; + const auto span = mozilla::Span{mDirectPredecessorList}; + return span.Subspan(node.mIndexInDirectPredecessorList, + node.mDirectPredecessorCount); + } + + template <typename OtherUnits> + void EnsureCapacityFor(const DirectedAcyclicGraph<OtherUnits>& aOther) { + mNodesInfo.SetCapacity(aOther.mNodesInfo.Length()); + mDirectPredecessorList.SetCapacity(aOther.mDirectPredecessorList.Length()); + } + + void Clear() { + mNodesInfo.Clear(); + mDirectPredecessorList.Clear(); + } + + struct NodeInfo { + NodeInfo(size_t aIndexInDirectPredecessorList, + size_t aDirectPredecessorCount) + : mIndexInDirectPredecessorList(aIndexInDirectPredecessorList), + mDirectPredecessorCount(aDirectPredecessorCount) {} + size_t mIndexInDirectPredecessorList; + size_t mDirectPredecessorCount; + }; + + nsTArray<NodeInfo> mNodesInfo; + nsTArray<Index<T>> mDirectPredecessorList; +}; + +struct RetainedDisplayListBuilder; +class nsDisplayItem; + +struct OldItemInfo { + explicit OldItemInfo(nsDisplayItem* aItem); + + void AddedToMergedList(MergedListIndex aIndex) { + MOZ_ASSERT(!IsUsed()); + mUsed = true; + mIndex = aIndex; + mItem = nullptr; + } + + void AddedMatchToMergedList(RetainedDisplayListBuilder* aBuilder, + MergedListIndex aIndex); + void Discard(RetainedDisplayListBuilder* aBuilder, + nsTArray<MergedListIndex>&& aDirectPredecessors); + bool IsUsed() { return mUsed; } + + bool IsDiscarded() { + MOZ_ASSERT(IsUsed()); + return mDiscarded; + } + + bool IsChanged(); + + nsDisplayItem* mItem; + nsTArray<MergedListIndex> mDirectPredecessors; + MergedListIndex mIndex; + bool mUsed; + bool mDiscarded; + bool mOwnsItem; +}; + +bool AnyContentAncestorModified(nsIFrame* aFrame, + nsIFrame* aStopAtFrame = nullptr); + +#endif // RETAINEDDISPLAYLISTHELPERS_H_ diff --git a/layout/painting/TransformClipNode.h b/layout/painting/TransformClipNode.h new file mode 100644 index 0000000000..a28dc3dbc0 --- /dev/null +++ b/layout/painting/TransformClipNode.h @@ -0,0 +1,138 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_PAINTING_TRANSFORMCLIPNODE_H +#define MOZILLA_PAINTING_TRANSFORMCLIPNODE_H + +#include "mozilla/gfx/MatrixFwd.h" +#include "mozilla/gfx/Rect.h" +#include "mozilla/Maybe.h" +#include "nsISupports.h" +#include "nsRegionFwd.h" + +namespace mozilla { + +/** + * TransformClipNode stores a transformation matrix and a post-transform + * clip rect. + * They can be used to transform and clip a display item inside a flattened + * nsDisplayTransform to the coordinate space of that nsDisplayTransform. + */ +class TransformClipNode { + NS_INLINE_DECL_REFCOUNTING(TransformClipNode); + + public: + TransformClipNode(const RefPtr<TransformClipNode>& aParent, + const gfx::Matrix4x4Flagged& aTransform, + const Maybe<gfx::IntRect>& aClip) + : mParent(aParent), mTransform(aTransform), mClip(aClip) { + MOZ_COUNT_CTOR(TransformClipNode); + } + + /** + * Returns the parent node, or nullptr if this is the root node. + */ + const RefPtr<TransformClipNode>& Parent() const { return mParent; } + + /** + * Transforms and clips |aRect| up to the root transform node. + * |aRect| is expected to be in app units. + */ + nsRect TransformRect(const nsRect& aRect, const int32_t aA2D) const { + if (aRect.IsEmpty()) { + return aRect; + } + + gfx::Rect result(NSAppUnitsToFloatPixels(aRect.x, aA2D), + NSAppUnitsToFloatPixels(aRect.y, aA2D), + NSAppUnitsToFloatPixels(aRect.width, aA2D), + NSAppUnitsToFloatPixels(aRect.height, aA2D)); + TransformRect(result); + return nsRect(NSFloatPixelsToAppUnits(result.x, aA2D), + NSFloatPixelsToAppUnits(result.y, aA2D), + NSFloatPixelsToAppUnits(result.width, aA2D), + NSFloatPixelsToAppUnits(result.height, aA2D)); + } + + /** + * Transforms and clips |aRect| up to the root transform node. + * |aRect| is expected to be in integer pixels. + */ + gfx::IntRect TransformRect(const gfx::IntRect& aRect) const { + if (aRect.IsEmpty()) { + return aRect; + } + + gfx::Rect result(IntRectToRect(aRect)); + TransformRect(result); + return RoundedToInt(result); + } + + /** + * Transforms and clips |aRegion| up to the root transform node. + * |aRegion| is expected be in integer pixels. + */ + nsIntRegion TransformRegion(const nsIntRegion& aRegion) { + if (aRegion.IsEmpty()) { + return aRegion; + } + + nsIntRegion result = aRegion; + + const TransformClipNode* node = this; + while (node) { + const gfx::Matrix4x4Flagged& transform = node->Transform(); + result = result.Transform(transform.GetMatrix()); + + if (node->Clip()) { + const gfx::IntRect clipRect = *node->Clip(); + result.AndWith(clipRect); + } + + node = node->Parent(); + } + + return result; + } + + protected: + /** + * Returns the post-transform clip, if there is one. + */ + const Maybe<gfx::IntRect>& Clip() const { return mClip; } + + /** + * Returns the matrix that transforms the item bounds to the coordinate space + * of the flattened nsDisplayTransform. + */ + const gfx::Matrix4x4Flagged& Transform() const { return mTransform; } + + void TransformRect(gfx::Rect& aRect) const { + const TransformClipNode* node = this; + while (node) { + const gfx::Matrix4x4Flagged& transform = node->Transform(); + gfx::Rect maxBounds = gfx::Rect::MaxIntRect(); + + if (node->Clip()) { + maxBounds = IntRectToRect(*node->Clip()); + } + + aRect = transform.TransformAndClipBounds(aRect, maxBounds); + node = node->Parent(); + } + } + + private: + MOZ_COUNTED_DTOR(TransformClipNode) + + const RefPtr<TransformClipNode> mParent; + const gfx::Matrix4x4Flagged mTransform; + const Maybe<gfx::IntRect> mClip; +}; + +} // namespace mozilla + +#endif /* MOZILLA_PAINTING_TRANSFORMCLIPNODE_H */ diff --git a/layout/painting/crashtests/1402183-1.html b/layout/painting/crashtests/1402183-1.html new file mode 100644 index 0000000000..ea54512894 --- /dev/null +++ b/layout/painting/crashtests/1402183-1.html @@ -0,0 +1,20 @@ +<!DOCTYPE html> +<html> +<head> +<title>Bug 1402183</title> +<style type="text/css"> +:not(article) { + -webkit-text-stroke-width: 1px; + box-decoration-break: clone; + clip-path: polygon(69px 1px, 49px 1px, 0px 0px); +} +</style> +</head> +<body> +<ins> +<p> +r6O#i/q] +</p> +</ins> +</body> +</html> diff --git a/layout/painting/crashtests/1405881-1.html b/layout/painting/crashtests/1405881-1.html new file mode 100644 index 0000000000..44c8ab1ac0 --- /dev/null +++ b/layout/painting/crashtests/1405881-1.html @@ -0,0 +1,24 @@ +<style type="text/css">
+ #container {
+ height: 300px;
+ width: 300px;
+ }
+ #box {
+ height: 100px;
+ width: 100px;
+ background: red;
+ animation: 2s anim;
+ }
+ @keyframes anim {
+ from {
+ -moz-transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 10%, 10%, 0, 1);
+ }
+ to {
+ -moz-transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 40%, 40%, 0, 1);
+ }
+ }
+
+</style>
+<div id=container>
+ <div id=box></div>
+</div>
diff --git a/layout/painting/crashtests/1407470-1.html b/layout/painting/crashtests/1407470-1.html new file mode 100644 index 0000000000..0ddf957182 --- /dev/null +++ b/layout/painting/crashtests/1407470-1.html @@ -0,0 +1,19 @@ +<body id='test_body'> +<script> +let o = []; +o[0] = document.createElement("tt"); +test_body.appendChild(o[0]); +o[1] = document.createElement("center"); +o[2] = document.createElement("footer"); +o[0].appendChild(o[2]); +o[1].animate([{ + "padding": "80.40vw 0.0vmax", + "transform": "matrix(6287.56,268.76,237.34,222.80,186.72,287.94) rotateX(2.046rad)" + }], { duration:3845.75 }); +o[2].appendChild(o[1]); +test_body.animate([{ + "transform": "scale3d(0.0,5961462.820,250.41)", + "outline": "auto thick", + "mask": "exclude no-clip url(data:image/gif;base64,R0lGODlhAQABAAD/ACH/C05FVFNDQVBFMi4wAwEAAAAsAAAAAAEAAQAAAgAsAAAAAAEAAQAAAgA7),exclude", + }], 3617.63433129); +</script>
\ No newline at end of file diff --git a/layout/painting/crashtests/1413073-1.html b/layout/painting/crashtests/1413073-1.html new file mode 100644 index 0000000000..ddecfb2424 --- /dev/null +++ b/layout/painting/crashtests/1413073-1.html @@ -0,0 +1,15 @@ +<html class="reftest-wait"> +<body> +<div style="position:fixed; width:200px; height:200px; background-color:blue"> + <div style="position:fixed; width:200px; height:200px; left:400px; background-color:red" id="inner"></div> +</div> +</body> +<script> + function doTest() { + var d = document.getElementById("inner"); + d.style.backgroundColor = "green"; + document.documentElement.className = ""; + } + window.addEventListener("MozReftestInvalidate", doTest); +</script> +</html> diff --git a/layout/painting/crashtests/1413073-2.html b/layout/painting/crashtests/1413073-2.html new file mode 100644 index 0000000000..01d1718745 --- /dev/null +++ b/layout/painting/crashtests/1413073-2.html @@ -0,0 +1,18 @@ +<html class="reftest-wait"> +<body> +<div style="position:absolute; width:200px; height:200px; opacity:0.5"> + <div style="opacity:0.9"> + <div style="position:fixed; width:200px; height:200px; left:400px; background-color:blue" id="fixed"></div> + </div> + <div style="width:200px; height:200px; background-color:red" id="inner"></div> +</div> +</body> +<script> + function doTest() { + var d = document.getElementById("inner"); + d.style.backgroundColor = "green"; + document.documentElement.className = ""; + } + window.addEventListener("MozReftestInvalidate", doTest); +</script> +</html> diff --git a/layout/painting/crashtests/1418177-1.html b/layout/painting/crashtests/1418177-1.html new file mode 100644 index 0000000000..6420814bce --- /dev/null +++ b/layout/painting/crashtests/1418177-1.html @@ -0,0 +1,34 @@ +<html> + <head> + <style> + * { + background: url(16.png), url(16.png) transparent; + -webkit-background-clip: text, text; + -moz-tab-size: calc(1 + 1) !important; + background-blend-mode: screen; + align-content: baseline; + background-color: green; + } + </style> + <script> + function fun_0() { + try { o3 = document.createElement('th') } catch (e) {}; + try { o1.appendChild(o3) } catch (e) {} + } + + try { o1 = document.createElement('tr') } catch (e) {} + try { o2 = document.createElement('ol') } catch (e) {} + try { xhr = new XMLHttpRequest({mozAnon: false }) } catch (e) {} + try { document.documentElement.appendChild(o1) } catch (e) {} + try { document.documentElement.appendChild(o2) } catch (e) {} + for (let i = 0; i < 100; i++) { + try { xhr.open('GET', 'data:text/html,1', false); } catch (e) {}; + try { xhr.send(); } catch (e) {}; + try { fuzzPriv.GC(); fuzzPriv.CC(); fuzzPriv.GC(); fuzzPriv.CC(); } catch (e) {}; + try { xhr.addEventListener('readystatechange', fun_0, true) } catch (e) {}; + try { o2.offsetLeft } catch (e) {}; + try { document.styleSheets[0].cssRules[0].style['background-origin'] = 'border-box, border-box' } catch (e) {} + } + </script> + </head> +</html> diff --git a/layout/painting/crashtests/1418722-1.html b/layout/painting/crashtests/1418722-1.html new file mode 100644 index 0000000000..93415d3012 --- /dev/null +++ b/layout/painting/crashtests/1418722-1.html @@ -0,0 +1,17 @@ +<html class="reftest-wait"> +<body> + <div style="width:200px; height:200px; perspective:1000px"> + <div style="width:200px; height:200px; transform:translateZ(2px); background-color:green" id="transformed"></div> + </div> + <div style="width: 200px; height:200px; background-color:red" id="helper"></div> +</body> +<script> + function doTest() { + var element = document.getElementById("transformed"); + element.parentNode.removeChild(element); + document.getElementById("helper").style.backgroundColor = "blue"; + document.documentElement.className = ""; + } + window.addEventListener("MozReftestInvalidate", doTest); +</script> +</html> diff --git a/layout/painting/crashtests/1419917.html b/layout/painting/crashtests/1419917.html new file mode 100644 index 0000000000..a60dd15403 --- /dev/null +++ b/layout/painting/crashtests/1419917.html @@ -0,0 +1,15 @@ +<html> + <head> + <style> + :nth-last-child(2) { -moz-appearance:listbox } + </style> + <script> + try { o1 = document.createElement('tr') } catch(e) { } + try { o2 = document.createElement('th') } catch(e) { } + try { o3 = document.createElement('canvas') } catch(e) { } + try { document.documentElement.appendChild(o1) } catch(e) { } + try { o1.appendChild(o2) } catch(e) { } + try { o1.appendChild(o3) } catch(e) { } + </script> + </head> +</html> diff --git a/layout/painting/crashtests/1425271-1.html b/layout/painting/crashtests/1425271-1.html new file mode 100644 index 0000000000..164cb0f11f --- /dev/null +++ b/layout/painting/crashtests/1425271-1.html @@ -0,0 +1,54 @@ +<!DOCTYPE html> +<html class="reftest-wait"> + +<head> + <meta http-equiv="content-type" content="text/html; charset=UTF-8"> + <meta charset="utf-8"> +</head> + +<body> +<div id="container"> + <div id="element"> + <!-- + The HTML code for this element has no other meaning than to create + display items that are merged together. + --> + <div style="column-count:2; column-count:2; width:300px; height:100px;"> + <div id="o" style="opacity:0.5; width:100px; height:200px; background:lime;"> + <div id="d" style="height:50px; width:80px; background:red; padding:2px">Text</div> + </div> + </div> + </div> +</div> + +<script type="text/javascript"> +function redirect() { + document.documentElement.removeAttribute("class"); + + // Trigger root frame deletion. + window.location.replace("about:blank"); +} + +function removeElements(container) { + document.body.removeChild(container); + + setTimeout(redirect, 0); +} + +function createElements() { + var c = document.getElementById("container"); + var e = document.getElementById("element"); + for (var i = 0; i < 1000; ++i) { + // Populate the container with elements that cause display item merges. + c.appendChild(e.cloneNode(true)); + } + + setTimeout(() => removeElements(c), 0); +} + +document.addEventListener("MozReftestInvalidate", createElements); +// window.addEventListener("load", createElements); +</script> + +</body> +</html> diff --git a/layout/painting/crashtests/1428906-1.html b/layout/painting/crashtests/1428906-1.html new file mode 100644 index 0000000000..03e6d3aaf8 --- /dev/null +++ b/layout/painting/crashtests/1428906-1.html @@ -0,0 +1,16 @@ +<html><head> +<meta http-equiv="content-type" content="text/html; charset=UTF-8"><style> +*,m{background:url()repeat center top fixed} +#a{transform:scale(1)} +</style> +<script> +function eh1(){ + try{c=a.insertRow()}catch(e){} + try{c.appendChild(b)}catch(e){} +} +</script> +</head><body><table id="a"> +<tbody><tr><d id="b"> +<video> +<source onerror="eh1()"> +</video></d></tr></tbody></table></body></html> diff --git a/layout/painting/crashtests/1430589-1.html b/layout/painting/crashtests/1430589-1.html new file mode 100644 index 0000000000..88bb0494a7 --- /dev/null +++ b/layout/painting/crashtests/1430589-1.html @@ -0,0 +1,55 @@ +<html class="reftest-wait"><head> +<meta http-equiv="content-type" content="text/html; charset=UTF-8"> + <style> + :root { + flex-wrap: wrap-reverse; + } + menuitem { + -webkit-mask-image: url(data:image/gif;base64,R0lGODlhEAAQAMQAAORHHOVSKudfOulrSOp3WOyDZu6QdvCchPGolfO0o/XBs/fNwfjZ0frl3/zy7////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAkAABAALAAAAAAQABAAAAVVICSOZGlCQAosJ6mu7fiyZeKqNKToQGDsM8hBADgUXoGAiqhSvp5QAnQKGIgUhwFUYLCVDFCrKUE1lBavAViFIDlTImbKC5Gm2hB0SlBCBMQiB0UjIQA7); + } + + * { + margin-right: -1px; + display: -webkit-box; + overflow: scroll; + } + + + .css1 { + -webkit-border-bottom-left-radius: 10px; + } + + + </style> +<script> +function BOOM(){ + setTimeout(() => { + var elem = document.getElementById("vuln"); + elem.style.width = 600; + elem.style.height = 600; + window.requestAnimationFrame(() => { + window.requestAnimationFrame(() => { + document.documentElement.removeAttribute("class"); + }); + }); + }, 0); +} + +document.addEventListener("MozReftestInvalidate", BOOM); +//window.addEventListener("load", BOOM); +</script></head> + +<body> + <footer> + <textarea id="vuln" style="width: 500px; height: 500px;">Ashitaka</textarea> + </footer> + <menu> + <menuitem> + <iframe></iframe> + </menuitem></menu> + <menuitem> + <hr> + <dialog class="css1"></dialog> + + +</menuitem></body></html> diff --git a/layout/painting/crashtests/1454105-1.html b/layout/painting/crashtests/1454105-1.html new file mode 100644 index 0000000000..946d45992b --- /dev/null +++ b/layout/painting/crashtests/1454105-1.html @@ -0,0 +1,22 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="UTF-8"> +<style> +#a { + filter: brightness(1); + border-style: solid; +} +#b { + opacity: 0.2; +} +</style> +</head> +<body> +<span id="b"> + <span id="a"> + <div></div> + </span> +</span> +</body> +</html> diff --git a/layout/painting/crashtests/1455944-1.html b/layout/painting/crashtests/1455944-1.html new file mode 100644 index 0000000000..c115f4dd8e --- /dev/null +++ b/layout/painting/crashtests/1455944-1.html @@ -0,0 +1,14 @@ +<!DOCTYPE html> +<html> +<head> +<meta http-equiv="content-type" content="text/html; charset=UTF-8"> +<meta charset="utf-8"> +</head> + +<body> +<div style="opacity: 0.9;"> + <iframe style="border: none;" src=""></iframe> +</div> + +</body> +</html> diff --git a/layout/painting/crashtests/1458145.html b/layout/painting/crashtests/1458145.html new file mode 100644 index 0000000000..dc5f07c204 --- /dev/null +++ b/layout/painting/crashtests/1458145.html @@ -0,0 +1,11 @@ +<style> +* { position: fixed; } +#a { +perspective: 0px; +overflow: scroll; +} +.cl2 { -webkit-transform-style: preserve-3d; } +.cl1 { border-bottom-right-radius: 0px 1px; } +</style> +<del id="a" class="cl1"> +<dl class="cl2">#_k</dl> diff --git a/layout/painting/crashtests/1465305-1.html b/layout/painting/crashtests/1465305-1.html new file mode 100644 index 0000000000..084f524fba --- /dev/null +++ b/layout/painting/crashtests/1465305-1.html @@ -0,0 +1,8 @@ +<style> +:not(cursor) { + -webkit-background-clip: text; + border-top-right-radius: 0vh; +} +</style> +><input> +<dialog open=""> diff --git a/layout/painting/crashtests/1468124-1.html b/layout/painting/crashtests/1468124-1.html new file mode 100644 index 0000000000..9acf1427db --- /dev/null +++ b/layout/painting/crashtests/1468124-1.html @@ -0,0 +1,27 @@ +<html class="reftest-wait"> +<style> +:not(feFuncB) { + position: fixed; +} +a:last-child { + -webkit-transform-style: preserve-3d; +} +* { + -webkit-backface-visibility: hidden; +</style> +<script> +window.requestIdleCallback(function() { + document.documentElement.getBoundingClientRect(); +}); +function go() { + var c = document.createElement("a") + c.text = "-"; + try { c.replaceChild(b, c.childNodes[0]); } catch(e) { } + try { document.body.appendChild(c); } catch(e) { } + document.documentElement.className = ""; +} +</script> +<body onload=go()> +<d id="b">| +<audio controls=""> +</html> diff --git a/layout/painting/crashtests/1469472.html b/layout/painting/crashtests/1469472.html new file mode 100644 index 0000000000..f53d027704 --- /dev/null +++ b/layout/painting/crashtests/1469472.html @@ -0,0 +1,7 @@ +<style> +:not(polygon) { +background-image: url(#x); +background-blend-mode: hue, normal; +</style> +<table> +<th> diff --git a/layout/painting/crashtests/1477831-1.html b/layout/painting/crashtests/1477831-1.html new file mode 100644 index 0000000000..d6483bde02 --- /dev/null +++ b/layout/painting/crashtests/1477831-1.html @@ -0,0 +1,11 @@ +<style> +* { + margin-left: 1vw; + columns: 2; + opacity: 0.2; + -webkit-transform: rotate(0deg); +} +#a { float: left; } +</style> +<content id="a"> +<dd>A</dd> diff --git a/layout/painting/crashtests/1504033.html b/layout/painting/crashtests/1504033.html new file mode 100644 index 0000000000..9aeb7973be --- /dev/null +++ b/layout/painting/crashtests/1504033.html @@ -0,0 +1,13 @@ +<style> +* { -webkit-transform-style: preserve-3d } +</style> +<script> +function go() { + a.append("x"); +} +</script> +<body onload=go()> +<svg overflow="auto"> +<use xlink:href="#b" style="-webkit-transform-style: flat"/> +<use id="b" xlink:href="#a"> +<text id="a"> diff --git a/layout/painting/crashtests/1514544-1.html b/layout/painting/crashtests/1514544-1.html new file mode 100644 index 0000000000..8e9efdc3c4 --- /dev/null +++ b/layout/painting/crashtests/1514544-1.html @@ -0,0 +1,15 @@ +<html class="reftest-wait"> +<script> +setTimeout(function() { + a.appendChild(b); + document.documentElement.className = ""; +}, 100) +</script> +<style> +:root { opacity: 0 } +</style> +A +<textarea id="a" hidden=""></textarea> +<object id="b" > +A +</html> diff --git a/layout/painting/crashtests/1547420-1.html b/layout/painting/crashtests/1547420-1.html new file mode 100644 index 0000000000..ce54d67d09 --- /dev/null +++ b/layout/painting/crashtests/1547420-1.html @@ -0,0 +1,20 @@ +<script></script> +<style> +* { + text-align-last: right; + min-height: max-content; + min-width: 1vmin; + writing-mode: vertical-rl; +} +</style> +<q style="writing-mode: lr"> +<marquee></marquee> +<style></style> +</q> +<dl style="-webkit-transform: skew(0deg); mso-ignore: colspan"> +<dd> +<table> +<dt style="margin-left: 67%; scale: 7 46 0.006057077979"> +</dt> +<marquee bgcolor="-moz-mac-accentdarkestshadow"> +<button autofocus="autofocus"> diff --git a/layout/painting/crashtests/1549909.html b/layout/painting/crashtests/1549909.html new file mode 100644 index 0000000000..0542ee91a8 --- /dev/null +++ b/layout/painting/crashtests/1549909.html @@ -0,0 +1,9 @@ +<style> +* { + -webkit-column-break-after: always; + float: left; + column-width: 0px; +} +</style> +} +<video controls="controls"> diff --git a/layout/painting/crashtests/1551389-1.html b/layout/painting/crashtests/1551389-1.html new file mode 100644 index 0000000000..4ee12c1ce9 --- /dev/null +++ b/layout/painting/crashtests/1551389-1.html @@ -0,0 +1,6 @@ +<style> +dl::first-letter { float: right } +* { -webkit-box-shadow: -moz-cellhighlighttext 0px 34px 1px } +</style> +<s dir="RTL"> +<dl style="break-inside: avoid">AA</iframe> diff --git a/layout/painting/crashtests/1555819-1.html b/layout/painting/crashtests/1555819-1.html new file mode 100644 index 0000000000..0327e3690f --- /dev/null +++ b/layout/painting/crashtests/1555819-1.html @@ -0,0 +1,12 @@ +<style> +body { + width: 1px; + height: 5vmax; + -webkit-filter: brightness(1); +} +* { + grid-template-areas: ''; + columns: 1px; +} +</style> +<keygen autofocus="">aaaa diff --git a/layout/painting/crashtests/1574392.html b/layout/painting/crashtests/1574392.html new file mode 100644 index 0000000000..2e8c4d15aa --- /dev/null +++ b/layout/painting/crashtests/1574392.html @@ -0,0 +1 @@ +<u>🏴</u> diff --git a/layout/painting/crashtests/1589800-1.html b/layout/painting/crashtests/1589800-1.html new file mode 100644 index 0000000000..8af28028a0 --- /dev/null +++ b/layout/painting/crashtests/1589800-1.html @@ -0,0 +1,47 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta http-equiv="content-type" content="text/html; charset=utf-8"> + <style> + #parent { + transform: rotateZ(1deg); + position: absolute; + opacity: 0.5; + overflow-x: hidden; + } + + #child { + width: 10px; + height: 10px; + } + + .blend { + mix-blend-mode: color-burn; + } + </style> +</head> + +<body> + <div id="parent"> + <div id="child">a</div> + </div> + <div> + <div id="blend">a</div> + </div> + <script type="text/javascript"> + function modify() { + var e = document.getElementById("child"); + e.style.backgroundColor = "red"; + setTimeout(addBlend, 0); + } + + function addBlend() { + var e = document.getElementById("blend"); + e.classList.add("blend"); + } + + // setTimeout(modify, 3000); + window.addEventListener("MozAfterPaint", modify); + </script> +</body> +</html> diff --git a/layout/painting/crashtests/crashtests.list b/layout/painting/crashtests/crashtests.list new file mode 100644 index 0000000000..aea54e7537 --- /dev/null +++ b/layout/painting/crashtests/crashtests.list @@ -0,0 +1,26 @@ +load 1402183-1.html +load 1407470-1.html +load 1413073-1.html +load 1413073-2.html +load 1405881-1.html +load 1418177-1.html +load 1418722-1.html +load 1419917.html +load 1425271-1.html +load 1428906-1.html +load 1430589-1.html +load 1454105-1.html +load 1455944-1.html +load 1458145.html +load 1465305-1.html +load 1468124-1.html +load 1469472.html +load 1477831-1.html +load 1504033.html +load 1514544-1.html +asserts(0-1) load 1547420-1.html +load 1549909.html +asserts(6) load 1551389-1.html # bug 847368 +asserts(0-2) load 1555819-1.html +load 1574392.html +load 1589800-1.html diff --git a/layout/painting/moz.build b/layout/painting/moz.build new file mode 100644 index 0000000000..4a1aad01ee --- /dev/null +++ b/layout/painting/moz.build @@ -0,0 +1,74 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +with Files("**"): + BUG_COMPONENT = ("Core", "Web Painting") + +EXPORTS += [ + "ActiveLayerTracker.h", + "DisplayItemClip.h", + "DisplayItemClipChain.h", + "DisplayListClipState.h", + "FrameLayerBuilder.h", + "LayerState.h", + "MatrixStack.h", + "nsCSSRenderingBorders.h", + "nsCSSRenderingGradients.h", + "nsDisplayItemTypes.h", + "nsDisplayItemTypesList.h", + "nsDisplayList.h", + "nsDisplayListArenaTypes.h", + "nsDisplayListInvalidation.h", + "nsImageRenderer.h", + "RetainedDisplayListBuilder.h", + "RetainedDisplayListHelpers.h", + "TransformClipNode.h", +] + +EXPORTS.mozilla += [ + "PaintTracker.h", +] + +UNIFIED_SOURCES += [ + "ActiveLayerTracker.cpp", + "DashedCornerFinder.cpp", + "DisplayItemClip.cpp", + "DisplayItemClipChain.cpp", + "DisplayListClipState.cpp", + "DottedCornerFinder.cpp", + "FrameLayerBuilder.cpp", + "MaskLayerImageCache.cpp", + "nsCSSRendering.cpp", + "nsCSSRenderingBorders.cpp", + "nsCSSRenderingGradients.cpp", + "nsDisplayList.cpp", + "nsDisplayListInvalidation.cpp", + "nsImageRenderer.cpp", + "PaintTracker.cpp", + "RetainedDisplayListBuilder.cpp", +] + +include("/ipc/chromium/chromium-config.mozbuild") + +LOCAL_INCLUDES += [ + "/docshell/base", + "/dom/base", + "/gfx/2d", + "/layout/base", + "/layout/generic", + "/layout/style", + "/layout/tables", + "/layout/xul", +] + +LOCAL_INCLUDES += CONFIG["SKIA_INCLUDES"] + +FINAL_LIBRARY = "xul" + +CXXFLAGS += CONFIG["MOZ_CAIRO_CFLAGS"] + +if CONFIG["CC_TYPE"] in ("clang", "gcc"): + CXXFLAGS += ["-Wno-error=shadow"] diff --git a/layout/painting/nsCSSRendering.cpp b/layout/painting/nsCSSRendering.cpp new file mode 100644 index 0000000000..7fb4fbda4c --- /dev/null +++ b/layout/painting/nsCSSRendering.cpp @@ -0,0 +1,4903 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* utility functions for drawing borders and backgrounds */ + +#include <ctime> + +#include "gfx2DGlue.h" +#include "gfxContext.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/ComputedStyle.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/StaticPrefs_layout.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/Helpers.h" +#include "mozilla/gfx/PathHelpers.h" +#include "mozilla/HashFunctions.h" +#include "mozilla/MathAlgorithms.h" +#include "mozilla/PresShell.h" +#include "mozilla/SVGImageContext.h" +#include "gfxFont.h" +#include "ScaledFontBase.h" +#include "skia/include/core/SkTextBlob.h" + +#include "BorderConsts.h" +#include "nsStyleConsts.h" +#include "nsPresContext.h" +#include "nsIFrame.h" +#include "nsIFrameInlines.h" +#include "nsPoint.h" +#include "nsRect.h" +#include "nsFrameManager.h" +#include "nsGkAtoms.h" +#include "nsCSSAnonBoxes.h" +#include "nsIContent.h" +#include "mozilla/dom/DocumentInlines.h" +#include "nsIScrollableFrame.h" +#include "imgIContainer.h" +#include "ImageOps.h" +#include "nsCSSRendering.h" +#include "nsCSSColorUtils.h" +#include "nsITheme.h" +#include "nsLayoutUtils.h" +#include "nsBlockFrame.h" +#include "nsStyleStructInlines.h" +#include "nsCSSFrameConstructor.h" +#include "nsCSSProps.h" +#include "nsContentUtils.h" +#include "gfxDrawable.h" +#include "GeckoProfiler.h" +#include "nsCSSRenderingBorders.h" +#include "mozilla/css/ImageLoader.h" +#include "ImageContainer.h" +#include "mozilla/StaticPrefs_layout.h" +#include "mozilla/Telemetry.h" +#include "gfxUtils.h" +#include "gfxGradientCache.h" +#include "nsInlineFrame.h" +#include "nsRubyTextContainerFrame.h" +#include <algorithm> +#include "TextDrawTarget.h" + +using namespace mozilla; +using namespace mozilla::css; +using namespace mozilla::gfx; +using namespace mozilla::image; +using mozilla::CSSSizeOrRatio; +using mozilla::dom::Document; + +static int gFrameTreeLockCount = 0; + +// To avoid storing this data on nsInlineFrame (bloat) and to avoid +// recalculating this for each frame in a continuation (perf), hold +// a cache of various coordinate information that we need in order +// to paint inline backgrounds. +struct InlineBackgroundData { + InlineBackgroundData() + : mFrame(nullptr), + mLineContainer(nullptr), + mContinuationPoint(0), + mUnbrokenMeasure(0), + mLineContinuationPoint(0), + mPIStartBorderData{}, + mBidiEnabled(false), + mVertical(false) {} + + ~InlineBackgroundData() = default; + + void Reset() { + mBoundingBox.SetRect(0, 0, 0, 0); + mContinuationPoint = mLineContinuationPoint = mUnbrokenMeasure = 0; + mFrame = mLineContainer = nullptr; + mPIStartBorderData.Reset(); + } + + /** + * Return a continuous rect for (an inline) aFrame relative to the + * continuation that draws the left-most part of the background. + * This is used when painting backgrounds. + */ + nsRect GetContinuousRect(nsIFrame* aFrame) { + MOZ_ASSERT(static_cast<nsInlineFrame*>(do_QueryFrame(aFrame))); + + SetFrame(aFrame); + + nscoord pos; // an x coordinate if writing-mode is horizontal; + // y coordinate if vertical + if (mBidiEnabled) { + pos = mLineContinuationPoint; + + // Scan continuations on the same line as aFrame and accumulate the widths + // of frames that are to the left (if this is an LTR block) or right + // (if it's RTL) of the current one. + bool isRtlBlock = (mLineContainer->StyleVisibility()->mDirection == + StyleDirection::Rtl); + nscoord curOffset = mVertical ? aFrame->GetOffsetTo(mLineContainer).y + : aFrame->GetOffsetTo(mLineContainer).x; + + // If the continuation is fluid we know inlineFrame is not on the same + // line. If it's not fluid, we need to test further to be sure. + nsIFrame* inlineFrame = aFrame->GetPrevContinuation(); + while (inlineFrame && !inlineFrame->GetNextInFlow() && + AreOnSameLine(aFrame, inlineFrame)) { + nscoord frameOffset = mVertical + ? inlineFrame->GetOffsetTo(mLineContainer).y + : inlineFrame->GetOffsetTo(mLineContainer).x; + if (isRtlBlock == (frameOffset >= curOffset)) { + pos += mVertical ? inlineFrame->GetSize().height + : inlineFrame->GetSize().width; + } + inlineFrame = inlineFrame->GetPrevContinuation(); + } + + inlineFrame = aFrame->GetNextContinuation(); + while (inlineFrame && !inlineFrame->GetPrevInFlow() && + AreOnSameLine(aFrame, inlineFrame)) { + nscoord frameOffset = mVertical + ? inlineFrame->GetOffsetTo(mLineContainer).y + : inlineFrame->GetOffsetTo(mLineContainer).x; + if (isRtlBlock == (frameOffset >= curOffset)) { + pos += mVertical ? inlineFrame->GetSize().height + : inlineFrame->GetSize().width; + } + inlineFrame = inlineFrame->GetNextContinuation(); + } + if (isRtlBlock) { + // aFrame itself is also to the right of its left edge, so add its + // width. + pos += mVertical ? aFrame->GetSize().height : aFrame->GetSize().width; + // pos is now the distance from the left [top] edge of aFrame to the + // right [bottom] edge of the unbroken content. Change it to indicate + // the distance from the left [top] edge of the unbroken content to the + // left [top] edge of aFrame. + pos = mUnbrokenMeasure - pos; + } + } else { + pos = mContinuationPoint; + } + + // Assume background-origin: border and return a rect with offsets + // relative to (0,0). If we have a different background-origin, + // then our rect should be deflated appropriately by our caller. + return mVertical + ? nsRect(0, -pos, mFrame->GetSize().width, mUnbrokenMeasure) + : nsRect(-pos, 0, mUnbrokenMeasure, mFrame->GetSize().height); + } + + /** + * Return a continuous rect for (an inline) aFrame relative to the + * continuation that should draw the left[top]-border. This is used when + * painting borders and clipping backgrounds. This may NOT be the same + * continuous rect as for drawing backgrounds; the continuation with the + * left[top]-border might be somewhere in the middle of that rect (e.g. BIDI), + * in those cases we need the reverse background order starting at the + * left[top]-border continuation. + */ + nsRect GetBorderContinuousRect(nsIFrame* aFrame, nsRect aBorderArea) { + // Calling GetContinuousRect(aFrame) here may lead to Reset/Init which + // resets our mPIStartBorderData so we save it ... + PhysicalInlineStartBorderData saved(mPIStartBorderData); + nsRect joinedBorderArea = GetContinuousRect(aFrame); + if (!saved.mIsValid || saved.mFrame != mPIStartBorderData.mFrame) { + if (aFrame == mPIStartBorderData.mFrame) { + if (mVertical) { + mPIStartBorderData.SetCoord(joinedBorderArea.y); + } else { + mPIStartBorderData.SetCoord(joinedBorderArea.x); + } + } else if (mPIStartBorderData.mFrame) { + // Copy data to a temporary object so that computing the + // continous rect here doesn't clobber our normal state. + InlineBackgroundData temp = *this; + if (mVertical) { + mPIStartBorderData.SetCoord( + temp.GetContinuousRect(mPIStartBorderData.mFrame).y); + } else { + mPIStartBorderData.SetCoord( + temp.GetContinuousRect(mPIStartBorderData.mFrame).x); + } + } + } else { + // ... and restore it when possible. + mPIStartBorderData.SetCoord(saved.mCoord); + } + if (mVertical) { + if (joinedBorderArea.y > mPIStartBorderData.mCoord) { + joinedBorderArea.y = + -(mUnbrokenMeasure + joinedBorderArea.y - aBorderArea.height); + } else { + joinedBorderArea.y -= mPIStartBorderData.mCoord; + } + } else { + if (joinedBorderArea.x > mPIStartBorderData.mCoord) { + joinedBorderArea.x = + -(mUnbrokenMeasure + joinedBorderArea.x - aBorderArea.width); + } else { + joinedBorderArea.x -= mPIStartBorderData.mCoord; + } + } + return joinedBorderArea; + } + + nsRect GetBoundingRect(nsIFrame* aFrame) { + SetFrame(aFrame); + + // Move the offsets relative to (0,0) which puts the bounding box into + // our coordinate system rather than our parent's. We do this by + // moving it the back distance from us to the bounding box. + // This also assumes background-origin: border, so our caller will + // need to deflate us if needed. + nsRect boundingBox(mBoundingBox); + nsPoint point = mFrame->GetPosition(); + boundingBox.MoveBy(-point.x, -point.y); + + return boundingBox; + } + + protected: + // This is a coordinate on the inline axis, but is not a true logical inline- + // coord because it is always measured from left to right (if horizontal) or + // from top to bottom (if vertical), ignoring any bidi RTL directionality. + // We'll call this "physical inline start", or PIStart for short. + struct PhysicalInlineStartBorderData { + nsIFrame* mFrame; // the continuation that may have a left-border + nscoord mCoord; // cached GetContinuousRect(mFrame).x or .y + bool mIsValid; // true if mCoord is valid + void Reset() { + mFrame = nullptr; + mIsValid = false; + } + void SetCoord(nscoord aCoord) { + mCoord = aCoord; + mIsValid = true; + } + }; + + nsIFrame* mFrame; + nsIFrame* mLineContainer; + nsRect mBoundingBox; + nscoord mContinuationPoint; + nscoord mUnbrokenMeasure; + nscoord mLineContinuationPoint; + PhysicalInlineStartBorderData mPIStartBorderData; + bool mBidiEnabled; + bool mVertical; + + void SetFrame(nsIFrame* aFrame) { + MOZ_ASSERT(aFrame, "Need a frame"); + NS_ASSERTION(gFrameTreeLockCount > 0, + "Can't call this when frame tree is not locked"); + + if (aFrame == mFrame) { + return; + } + + nsIFrame* prevContinuation = GetPrevContinuation(aFrame); + + if (!prevContinuation || mFrame != prevContinuation) { + // Ok, we've got the wrong frame. We have to start from scratch. + Reset(); + Init(aFrame); + return; + } + + // Get our last frame's size and add its width to our continuation + // point before we cache the new frame. + mContinuationPoint += + mVertical ? mFrame->GetSize().height : mFrame->GetSize().width; + + // If this a new line, update mLineContinuationPoint. + if (mBidiEnabled && + (aFrame->GetPrevInFlow() || !AreOnSameLine(mFrame, aFrame))) { + mLineContinuationPoint = mContinuationPoint; + } + + mFrame = aFrame; + } + + nsIFrame* GetPrevContinuation(nsIFrame* aFrame) { + nsIFrame* prevCont = aFrame->GetPrevContinuation(); + if (!prevCont && aFrame->HasAnyStateBits(NS_FRAME_PART_OF_IBSPLIT)) { + nsIFrame* block = aFrame->GetProperty(nsIFrame::IBSplitPrevSibling()); + if (block) { + // The {ib} properties are only stored on first continuations + NS_ASSERTION(!block->GetPrevContinuation(), + "Incorrect value for IBSplitPrevSibling"); + prevCont = block->GetProperty(nsIFrame::IBSplitPrevSibling()); + NS_ASSERTION(prevCont, "How did that happen?"); + } + } + return prevCont; + } + + nsIFrame* GetNextContinuation(nsIFrame* aFrame) { + nsIFrame* nextCont = aFrame->GetNextContinuation(); + if (!nextCont && aFrame->HasAnyStateBits(NS_FRAME_PART_OF_IBSPLIT)) { + // The {ib} properties are only stored on first continuations + aFrame = aFrame->FirstContinuation(); + nsIFrame* block = aFrame->GetProperty(nsIFrame::IBSplitSibling()); + if (block) { + nextCont = block->GetProperty(nsIFrame::IBSplitSibling()); + NS_ASSERTION(nextCont, "How did that happen?"); + } + } + return nextCont; + } + + void Init(nsIFrame* aFrame) { + mPIStartBorderData.Reset(); + mBidiEnabled = aFrame->PresContext()->BidiEnabled(); + if (mBidiEnabled) { + // Find the line container frame + mLineContainer = aFrame; + while (mLineContainer && + mLineContainer->IsFrameOfType(nsIFrame::eLineParticipant)) { + mLineContainer = mLineContainer->GetParent(); + } + + MOZ_ASSERT(mLineContainer, "Cannot find line containing frame."); + MOZ_ASSERT(mLineContainer != aFrame, + "line container frame " + "should be an ancestor of the target frame."); + } + + mVertical = aFrame->GetWritingMode().IsVertical(); + + // Start with the previous flow frame as our continuation point + // is the total of the widths of the previous frames. + nsIFrame* inlineFrame = GetPrevContinuation(aFrame); + bool changedLines = false; + while (inlineFrame) { + if (!mPIStartBorderData.mFrame && + !(mVertical ? inlineFrame->GetSkipSides().Top() + : inlineFrame->GetSkipSides().Left())) { + mPIStartBorderData.mFrame = inlineFrame; + } + nsRect rect = inlineFrame->GetRect(); + mContinuationPoint += mVertical ? rect.height : rect.width; + if (mBidiEnabled && + (changedLines || !AreOnSameLine(aFrame, inlineFrame))) { + mLineContinuationPoint += mVertical ? rect.height : rect.width; + changedLines = true; + } + mUnbrokenMeasure += mVertical ? rect.height : rect.width; + mBoundingBox.UnionRect(mBoundingBox, rect); + inlineFrame = GetPrevContinuation(inlineFrame); + } + + // Next add this frame and subsequent frames to the bounding box and + // unbroken width. + inlineFrame = aFrame; + while (inlineFrame) { + if (!mPIStartBorderData.mFrame && + !(mVertical ? inlineFrame->GetSkipSides().Top() + : inlineFrame->GetSkipSides().Left())) { + mPIStartBorderData.mFrame = inlineFrame; + } + nsRect rect = inlineFrame->GetRect(); + mUnbrokenMeasure += mVertical ? rect.height : rect.width; + mBoundingBox.UnionRect(mBoundingBox, rect); + inlineFrame = GetNextContinuation(inlineFrame); + } + + mFrame = aFrame; + } + + bool AreOnSameLine(nsIFrame* aFrame1, nsIFrame* aFrame2) { + if (nsBlockFrame* blockFrame = do_QueryFrame(mLineContainer)) { + bool isValid1, isValid2; + nsBlockInFlowLineIterator it1(blockFrame, aFrame1, &isValid1); + nsBlockInFlowLineIterator it2(blockFrame, aFrame2, &isValid2); + return isValid1 && isValid2 && + // Make sure aFrame1 and aFrame2 are in the same continuation of + // blockFrame. + it1.GetContainer() == it2.GetContainer() && + // And on the same line in it + it1.GetLine().get() == it2.GetLine().get(); + } + if (nsRubyTextContainerFrame* rtcFrame = do_QueryFrame(mLineContainer)) { + nsBlockFrame* block = nsLayoutUtils::FindNearestBlockAncestor(rtcFrame); + // Ruby text container can only hold one line of text, so if they + // are in the same continuation, they are in the same line. Since + // ruby text containers are bidi isolate, they are never split for + // bidi reordering, which means being in different continuation + // indicates being in different lines. + for (nsIFrame* frame = rtcFrame->FirstContinuation(); frame; + frame = frame->GetNextContinuation()) { + bool isDescendant1 = + nsLayoutUtils::IsProperAncestorFrame(frame, aFrame1, block); + bool isDescendant2 = + nsLayoutUtils::IsProperAncestorFrame(frame, aFrame2, block); + if (isDescendant1 && isDescendant2) { + return true; + } + if (isDescendant1 || isDescendant2) { + return false; + } + } + MOZ_ASSERT_UNREACHABLE("None of the frames is a descendant of this rtc?"); + } + MOZ_ASSERT_UNREACHABLE("Do we have any other type of line container?"); + return false; + } +}; + +static InlineBackgroundData* gInlineBGData = nullptr; + +// Initialize any static variables used by nsCSSRendering. +void nsCSSRendering::Init() { + NS_ASSERTION(!gInlineBGData, "Init called twice"); + gInlineBGData = new InlineBackgroundData(); +} + +// Clean up any global variables used by nsCSSRendering. +void nsCSSRendering::Shutdown() { + delete gInlineBGData; + gInlineBGData = nullptr; +} + +/** + * Make a bevel color + */ +static nscolor MakeBevelColor(mozilla::Side whichSide, StyleBorderStyle style, + nscolor aBorderColor) { + nscolor colors[2]; + nscolor theColor; + + // Given a background color and a border color + // calculate the color used for the shading + NS_GetSpecial3DColors(colors, aBorderColor); + + if ((style == StyleBorderStyle::Outset) || + (style == StyleBorderStyle::Ridge)) { + // Flip colors for these two border styles + switch (whichSide) { + case eSideBottom: + whichSide = eSideTop; + break; + case eSideRight: + whichSide = eSideLeft; + break; + case eSideTop: + whichSide = eSideBottom; + break; + case eSideLeft: + whichSide = eSideRight; + break; + } + } + + switch (whichSide) { + case eSideBottom: + theColor = colors[1]; + break; + case eSideRight: + theColor = colors[1]; + break; + case eSideTop: + theColor = colors[0]; + break; + case eSideLeft: + default: + theColor = colors[0]; + break; + } + return theColor; +} + +static bool GetRadii(nsIFrame* aForFrame, const nsStyleBorder& aBorder, + const nsRect& aOrigBorderArea, const nsRect& aBorderArea, + nscoord aRadii[8]) { + bool haveRoundedCorners; + nsSize sz = aBorderArea.Size(); + nsSize frameSize = aForFrame->GetSize(); + if (&aBorder == aForFrame->StyleBorder() && + frameSize == aOrigBorderArea.Size()) { + haveRoundedCorners = aForFrame->GetBorderRadii(sz, sz, Sides(), aRadii); + } else { + haveRoundedCorners = nsIFrame::ComputeBorderRadii( + aBorder.mBorderRadius, frameSize, sz, Sides(), aRadii); + } + + return haveRoundedCorners; +} + +static bool GetRadii(nsIFrame* aForFrame, const nsStyleBorder& aBorder, + const nsRect& aOrigBorderArea, const nsRect& aBorderArea, + RectCornerRadii* aBgRadii) { + nscoord radii[8]; + bool haveRoundedCorners = + GetRadii(aForFrame, aBorder, aOrigBorderArea, aBorderArea, radii); + + if (haveRoundedCorners) { + auto d2a = aForFrame->PresContext()->AppUnitsPerDevPixel(); + nsCSSRendering::ComputePixelRadii(radii, d2a, aBgRadii); + } + return haveRoundedCorners; +} + +static nsRect JoinBoxesForBlockAxisSlice(nsIFrame* aFrame, + const nsRect& aBorderArea) { + // Inflate the block-axis size as if our continuations were laid out + // adjacent in that axis. Note that we don't touch the inline size. + const auto wm = aFrame->GetWritingMode(); + const nsSize dummyContainerSize; + LogicalRect borderArea(wm, aBorderArea, dummyContainerSize); + nscoord bSize = 0; + nsIFrame* f = aFrame->GetNextContinuation(); + for (; f; f = f->GetNextContinuation()) { + bSize += f->BSize(wm); + } + borderArea.BSize(wm) += bSize; + bSize = 0; + f = aFrame->GetPrevContinuation(); + for (; f; f = f->GetPrevContinuation()) { + bSize += f->BSize(wm); + } + borderArea.BStart(wm) -= bSize; + borderArea.BSize(wm) += bSize; + return borderArea.GetPhysicalRect(wm, dummyContainerSize); +} + +/** + * Inflate aBorderArea which is relative to aFrame's origin to calculate + * a hypothetical non-split frame area for all the continuations. + * See "Joining Boxes for 'slice'" in + * http://dev.w3.org/csswg/css-break/#break-decoration + */ +enum InlineBoxOrder { eForBorder, eForBackground }; +static nsRect JoinBoxesForSlice(nsIFrame* aFrame, const nsRect& aBorderArea, + InlineBoxOrder aOrder) { + if (static_cast<nsInlineFrame*>(do_QueryFrame(aFrame))) { + return (aOrder == eForBorder + ? gInlineBGData->GetBorderContinuousRect(aFrame, aBorderArea) + : gInlineBGData->GetContinuousRect(aFrame)) + + aBorderArea.TopLeft(); + } + return JoinBoxesForBlockAxisSlice(aFrame, aBorderArea); +} + +/* static */ +bool nsCSSRendering::IsBoxDecorationSlice(const nsStyleBorder& aStyleBorder) { + return aStyleBorder.mBoxDecorationBreak == StyleBoxDecorationBreak::Slice; +} + +/* static */ +nsRect nsCSSRendering::BoxDecorationRectForBorder( + nsIFrame* aFrame, const nsRect& aBorderArea, Sides aSkipSides, + const nsStyleBorder* aStyleBorder) { + if (!aStyleBorder) { + aStyleBorder = aFrame->StyleBorder(); + } + // If aSkipSides.IsEmpty() then there are no continuations, or it's + // a ::first-letter that wants all border sides on the first continuation. + return IsBoxDecorationSlice(*aStyleBorder) && !aSkipSides.IsEmpty() + ? ::JoinBoxesForSlice(aFrame, aBorderArea, eForBorder) + : aBorderArea; +} + +/* static */ +nsRect nsCSSRendering::BoxDecorationRectForBackground( + nsIFrame* aFrame, const nsRect& aBorderArea, Sides aSkipSides, + const nsStyleBorder* aStyleBorder) { + if (!aStyleBorder) { + aStyleBorder = aFrame->StyleBorder(); + } + // If aSkipSides.IsEmpty() then there are no continuations, or it's + // a ::first-letter that wants all border sides on the first continuation. + return IsBoxDecorationSlice(*aStyleBorder) && !aSkipSides.IsEmpty() + ? ::JoinBoxesForSlice(aFrame, aBorderArea, eForBackground) + : aBorderArea; +} + +//---------------------------------------------------------------------- +// Thebes Border Rendering Code Start + +/* + * Compute the float-pixel radii that should be used for drawing + * this border/outline, given the various input bits. + */ +/* static */ +void nsCSSRendering::ComputePixelRadii(const nscoord* aAppUnitsRadii, + nscoord aAppUnitsPerPixel, + RectCornerRadii* oBorderRadii) { + Float radii[8]; + for (const auto corner : mozilla::AllPhysicalHalfCorners()) { + radii[corner] = Float(aAppUnitsRadii[corner]) / aAppUnitsPerPixel; + } + + (*oBorderRadii)[C_TL] = Size(radii[eCornerTopLeftX], radii[eCornerTopLeftY]); + (*oBorderRadii)[C_TR] = + Size(radii[eCornerTopRightX], radii[eCornerTopRightY]); + (*oBorderRadii)[C_BR] = + Size(radii[eCornerBottomRightX], radii[eCornerBottomRightY]); + (*oBorderRadii)[C_BL] = + Size(radii[eCornerBottomLeftX], radii[eCornerBottomLeftY]); +} + +static Maybe<nsStyleBorder> GetBorderIfVisited(const ComputedStyle& aStyle) { + Maybe<nsStyleBorder> result; + // Don't check RelevantLinkVisited here, since we want to take the + // same amount of time whether or not it's true. + const ComputedStyle* styleIfVisited = aStyle.GetStyleIfVisited(); + if (MOZ_LIKELY(!styleIfVisited)) { + return result; + } + + result.emplace(*aStyle.StyleBorder()); + auto& newBorder = result.ref(); + for (const auto side : mozilla::AllPhysicalSides()) { + nscolor color = aStyle.GetVisitedDependentColor( + nsStyleBorder::BorderColorFieldFor(side)); + newBorder.BorderColorFor(side) = StyleColor::FromColor(color); + } + + return result; +} + +ImgDrawResult nsCSSRendering::PaintBorder( + nsPresContext* aPresContext, gfxContext& aRenderingContext, + nsIFrame* aForFrame, const nsRect& aDirtyRect, const nsRect& aBorderArea, + ComputedStyle* aStyle, PaintBorderFlags aFlags, Sides aSkipSides) { + AUTO_PROFILER_LABEL("nsCSSRendering::PaintBorder", GRAPHICS); + + Maybe<nsStyleBorder> visitedBorder = GetBorderIfVisited(*aStyle); + return PaintBorderWithStyleBorder( + aPresContext, aRenderingContext, aForFrame, aDirtyRect, aBorderArea, + visitedBorder.refOr(*aStyle->StyleBorder()), aStyle, aFlags, aSkipSides); +} + +Maybe<nsCSSBorderRenderer> nsCSSRendering::CreateBorderRenderer( + nsPresContext* aPresContext, DrawTarget* aDrawTarget, nsIFrame* aForFrame, + const nsRect& aDirtyRect, const nsRect& aBorderArea, ComputedStyle* aStyle, + bool* aOutBorderIsEmpty, Sides aSkipSides) { + Maybe<nsStyleBorder> visitedBorder = GetBorderIfVisited(*aStyle); + return CreateBorderRendererWithStyleBorder( + aPresContext, aDrawTarget, aForFrame, aDirtyRect, aBorderArea, + visitedBorder.refOr(*aStyle->StyleBorder()), aStyle, aOutBorderIsEmpty, + aSkipSides); +} + +ImgDrawResult nsCSSRendering::CreateWebRenderCommandsForBorder( + nsDisplayItem* aItem, nsIFrame* aForFrame, const nsRect& aBorderArea, + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const mozilla::layers::StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + const auto* style = aForFrame->Style(); + Maybe<nsStyleBorder> visitedBorder = GetBorderIfVisited(*style); + return nsCSSRendering::CreateWebRenderCommandsForBorderWithStyleBorder( + aItem, aForFrame, aBorderArea, aBuilder, aResources, aSc, aManager, + aDisplayListBuilder, visitedBorder.refOr(*style->StyleBorder())); +} + +void nsCSSRendering::CreateWebRenderCommandsForNullBorder( + nsDisplayItem* aItem, nsIFrame* aForFrame, const nsRect& aBorderArea, + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const mozilla::layers::StackingContextHelper& aSc, + const nsStyleBorder& aStyleBorder) { + bool borderIsEmpty = false; + Maybe<nsCSSBorderRenderer> br = + nsCSSRendering::CreateNullBorderRendererWithStyleBorder( + aForFrame->PresContext(), nullptr, aForFrame, nsRect(), aBorderArea, + aStyleBorder, aForFrame->Style(), &borderIsEmpty, + aForFrame->GetSkipSides()); + if (!borderIsEmpty && br) { + br->CreateWebRenderCommands(aItem, aBuilder, aResources, aSc); + } +} + +ImgDrawResult nsCSSRendering::CreateWebRenderCommandsForBorderWithStyleBorder( + nsDisplayItem* aItem, nsIFrame* aForFrame, const nsRect& aBorderArea, + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const mozilla::layers::StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder, + const nsStyleBorder& aStyleBorder) { + auto& borderImage = aStyleBorder.mBorderImageSource; + // First try to create commands for simple borders. + if (borderImage.IsNone()) { + CreateWebRenderCommandsForNullBorder( + aItem, aForFrame, aBorderArea, aBuilder, aResources, aSc, aStyleBorder); + return ImgDrawResult::SUCCESS; + } + + // Next we try image and gradient borders. Gradients are not supported at + // this very moment. + if (!borderImage.IsImageRequestType()) { + return ImgDrawResult::NOT_SUPPORTED; + } + + if (aStyleBorder.mBorderImageRepeatH == StyleBorderImageRepeat::Space || + aStyleBorder.mBorderImageRepeatV == StyleBorderImageRepeat::Space) { + return ImgDrawResult::NOT_SUPPORTED; + } + + uint32_t flags = 0; + if (aDisplayListBuilder->IsPaintingToWindow()) { + flags |= nsImageRenderer::FLAG_PAINTING_TO_WINDOW; + } + if (aDisplayListBuilder->ShouldSyncDecodeImages()) { + flags |= nsImageRenderer::FLAG_SYNC_DECODE_IMAGES; + } + + image::ImgDrawResult result; + Maybe<nsCSSBorderImageRenderer> bir = + nsCSSBorderImageRenderer::CreateBorderImageRenderer( + aForFrame->PresContext(), aForFrame, aBorderArea, aStyleBorder, + aItem->GetPaintRect(), aForFrame->GetSkipSides(), flags, &result); + + if (!bir) { + // We aren't ready. Try to fallback to the null border image if present but + // return the draw result for the border image renderer. + CreateWebRenderCommandsForNullBorder( + aItem, aForFrame, aBorderArea, aBuilder, aResources, aSc, aStyleBorder); + return result; + } + + return bir->CreateWebRenderCommands(aItem, aForFrame, aBuilder, aResources, + aSc, aManager, aDisplayListBuilder); +} + +static nsCSSBorderRenderer ConstructBorderRenderer( + nsPresContext* aPresContext, ComputedStyle* aStyle, DrawTarget* aDrawTarget, + nsIFrame* aForFrame, const nsRect& aDirtyRect, const nsRect& aBorderArea, + const nsStyleBorder& aStyleBorder, Sides aSkipSides, bool* aNeedsClip) { + nsMargin border = aStyleBorder.GetComputedBorder(); + + // Compute the outermost boundary of the area that might be painted. + // Same coordinate space as aBorderArea & aBGClipRect. + nsRect joinedBorderArea = nsCSSRendering::BoxDecorationRectForBorder( + aForFrame, aBorderArea, aSkipSides, &aStyleBorder); + RectCornerRadii bgRadii; + ::GetRadii(aForFrame, aStyleBorder, aBorderArea, joinedBorderArea, &bgRadii); + + PrintAsFormatString(" joinedBorderArea: %d %d %d %d\n", joinedBorderArea.x, + joinedBorderArea.y, joinedBorderArea.width, + joinedBorderArea.height); + + // start drawing + if (nsCSSRendering::IsBoxDecorationSlice(aStyleBorder)) { + if (joinedBorderArea.IsEqualEdges(aBorderArea)) { + // No need for a clip, just skip the sides we don't want. + border.ApplySkipSides(aSkipSides); + } else { + // We're drawing borders around the joined continuation boxes so we need + // to clip that to the slice that we want for this frame. + *aNeedsClip = true; + } + } else { + MOZ_ASSERT(joinedBorderArea.IsEqualEdges(aBorderArea), + "Should use aBorderArea for box-decoration-break:clone"); + MOZ_ASSERT( + aForFrame->GetSkipSides().IsEmpty() || + aForFrame->IsTrueOverflowContainer() || + aForFrame->IsColumnSetFrame(), // a little broader than column-rule + "Should not skip sides for box-decoration-break:clone except " + "::first-letter/line continuations or other frame types that " + "don't have borders but those shouldn't reach this point. " + "Overflow containers do reach this point though, as does " + "column-rule drawing (which always involves a columnset)."); + border.ApplySkipSides(aSkipSides); + } + + // Convert to dev pixels. + nscoord oneDevPixel = aPresContext->DevPixelsToAppUnits(1); + Rect joinedBorderAreaPx = NSRectToRect(joinedBorderArea, oneDevPixel); + Float borderWidths[4] = { + Float(border.top) / oneDevPixel, Float(border.right) / oneDevPixel, + Float(border.bottom) / oneDevPixel, Float(border.left) / oneDevPixel}; + Rect dirtyRect = NSRectToRect(aDirtyRect, oneDevPixel); + + StyleBorderStyle borderStyles[4]; + nscolor borderColors[4]; + + // pull out styles, colors + for (const auto i : mozilla::AllPhysicalSides()) { + borderStyles[i] = aStyleBorder.GetBorderStyle(i); + borderColors[i] = aStyleBorder.BorderColorFor(i).CalcColor(*aStyle); + } + + PrintAsFormatString( + " borderStyles: %d %d %d %d\n", static_cast<int>(borderStyles[0]), + static_cast<int>(borderStyles[1]), static_cast<int>(borderStyles[2]), + static_cast<int>(borderStyles[3])); + + Document* document = nullptr; + nsIContent* content = aForFrame->GetContent(); + if (content) { + document = content->OwnerDoc(); + } + + return nsCSSBorderRenderer( + aPresContext, document, aDrawTarget, dirtyRect, joinedBorderAreaPx, + borderStyles, borderWidths, bgRadii, borderColors, + !aForFrame->BackfaceIsHidden(), + *aNeedsClip ? Some(NSRectToRect(aBorderArea, oneDevPixel)) : Nothing()); +} + +ImgDrawResult nsCSSRendering::PaintBorderWithStyleBorder( + nsPresContext* aPresContext, gfxContext& aRenderingContext, + nsIFrame* aForFrame, const nsRect& aDirtyRect, const nsRect& aBorderArea, + const nsStyleBorder& aStyleBorder, ComputedStyle* aStyle, + PaintBorderFlags aFlags, Sides aSkipSides) { + DrawTarget& aDrawTarget = *aRenderingContext.GetDrawTarget(); + + PrintAsStringNewline("++ PaintBorder"); + + // Check to see if we have an appearance defined. If so, we let the theme + // renderer draw the border. DO not get the data from aForFrame, since the + // passed in ComputedStyle may be different! Always use |aStyle|! + StyleAppearance appearance = aStyle->StyleDisplay()->EffectiveAppearance(); + if (appearance != StyleAppearance::None) { + nsITheme* theme = aPresContext->Theme(); + if (theme->ThemeSupportsWidget(aPresContext, aForFrame, appearance)) { + return ImgDrawResult::SUCCESS; // Let the theme handle it. + } + } + + if (!aStyleBorder.mBorderImageSource.IsNone()) { + ImgDrawResult result = ImgDrawResult::SUCCESS; + + uint32_t irFlags = 0; + if (aFlags & PaintBorderFlags::SyncDecodeImages) { + irFlags |= nsImageRenderer::FLAG_SYNC_DECODE_IMAGES; + } + + // Creating the border image renderer will request a decode, and we rely on + // that happening. + Maybe<nsCSSBorderImageRenderer> renderer = + nsCSSBorderImageRenderer::CreateBorderImageRenderer( + aPresContext, aForFrame, aBorderArea, aStyleBorder, aDirtyRect, + aSkipSides, irFlags, &result); + // renderer was created successfully, which means border image is ready to + // be used. + if (renderer) { + MOZ_ASSERT(result == ImgDrawResult::SUCCESS); + return renderer->DrawBorderImage(aPresContext, aRenderingContext, + aForFrame, aDirtyRect); + } + } + + ImgDrawResult result = ImgDrawResult::SUCCESS; + + // If we had a border-image, but it wasn't loaded, then we should return + // ImgDrawResult::NOT_READY; we'll want to try again if we do a paint with + // sync decoding enabled. + if (!aStyleBorder.mBorderImageSource.IsNone()) { + result = ImgDrawResult::NOT_READY; + } + + nsMargin border = aStyleBorder.GetComputedBorder(); + if (0 == border.left && 0 == border.right && 0 == border.top && + 0 == border.bottom) { + // Empty border area + return result; + } + + bool needsClip = false; + nsCSSBorderRenderer br = ConstructBorderRenderer( + aPresContext, aStyle, &aDrawTarget, aForFrame, aDirtyRect, aBorderArea, + aStyleBorder, aSkipSides, &needsClip); + if (needsClip) { + aDrawTarget.PushClipRect(NSRectToSnappedRect( + aBorderArea, aForFrame->PresContext()->AppUnitsPerDevPixel(), + aDrawTarget)); + } + + br.DrawBorders(); + + if (needsClip) { + aDrawTarget.PopClip(); + } + + PrintAsStringNewline(); + + return result; +} + +Maybe<nsCSSBorderRenderer> nsCSSRendering::CreateBorderRendererWithStyleBorder( + nsPresContext* aPresContext, DrawTarget* aDrawTarget, nsIFrame* aForFrame, + const nsRect& aDirtyRect, const nsRect& aBorderArea, + const nsStyleBorder& aStyleBorder, ComputedStyle* aStyle, + bool* aOutBorderIsEmpty, Sides aSkipSides) { + if (!aStyleBorder.mBorderImageSource.IsNone()) { + return Nothing(); + } + return CreateNullBorderRendererWithStyleBorder( + aPresContext, aDrawTarget, aForFrame, aDirtyRect, aBorderArea, + aStyleBorder, aStyle, aOutBorderIsEmpty, aSkipSides); +} + +Maybe<nsCSSBorderRenderer> +nsCSSRendering::CreateNullBorderRendererWithStyleBorder( + nsPresContext* aPresContext, DrawTarget* aDrawTarget, nsIFrame* aForFrame, + const nsRect& aDirtyRect, const nsRect& aBorderArea, + const nsStyleBorder& aStyleBorder, ComputedStyle* aStyle, + bool* aOutBorderIsEmpty, Sides aSkipSides) { + StyleAppearance appearance = aStyle->StyleDisplay()->EffectiveAppearance(); + if (appearance != StyleAppearance::None) { + nsITheme* theme = aPresContext->Theme(); + if (theme->ThemeSupportsWidget(aPresContext, aForFrame, appearance)) { + return Nothing(); + } + } + + nsMargin border = aStyleBorder.GetComputedBorder(); + if (0 == border.left && 0 == border.right && 0 == border.top && + 0 == border.bottom) { + // Empty border area + if (aOutBorderIsEmpty) { + *aOutBorderIsEmpty = true; + } + return Nothing(); + } + + bool needsClip = false; + nsCSSBorderRenderer br = ConstructBorderRenderer( + aPresContext, aStyle, aDrawTarget, aForFrame, aDirtyRect, aBorderArea, + aStyleBorder, aSkipSides, &needsClip); + return Some(br); +} + +static nsRect GetOutlineInnerRect(nsIFrame* aFrame) { + nsRect* savedOutlineInnerRect = + aFrame->GetProperty(nsIFrame::OutlineInnerRectProperty()); + if (savedOutlineInnerRect) { + return *savedOutlineInnerRect; + } + + // FIXME bug 1221888 + NS_ERROR("we should have saved a frame property"); + return nsRect(nsPoint(0, 0), aFrame->GetSize()); +} + +Maybe<nsCSSBorderRenderer> nsCSSRendering::CreateBorderRendererForOutline( + nsPresContext* aPresContext, gfxContext* aRenderingContext, + nsIFrame* aForFrame, const nsRect& aDirtyRect, const nsRect& aBorderArea, + ComputedStyle* aStyle) { + nscoord twipsRadii[8]; + + // Get our ComputedStyle's color struct. + const nsStyleOutline* ourOutline = aStyle->StyleOutline(); + + if (!ourOutline->ShouldPaintOutline()) { + // Empty outline + return Nothing(); + } + + nsRect innerRect; + if ( +#ifdef MOZ_XUL + aStyle->GetPseudoType() == PseudoStyleType::XULTree +#else + false +#endif + ) { + innerRect = aBorderArea; + } else { + innerRect = GetOutlineInnerRect(aForFrame) + aBorderArea.TopLeft(); + } + nscoord offset = ourOutline->mOutlineOffset.ToAppUnits(); + innerRect.Inflate(offset); + // If the dirty rect is completely inside the border area (e.g., only the + // content is being painted), then we can skip out now + // XXX this isn't exactly true for rounded borders, where the inside curves + // may encroach into the content area. A safer calculation would be to + // shorten insideRect by the radius one each side before performing this test. + if (innerRect.Contains(aDirtyRect)) return Nothing(); + + nscoord width = ourOutline->GetOutlineWidth(); + + nsRect outerRect = innerRect; + outerRect.Inflate(width); + + // get the radius for our outline + nsIFrame::ComputeBorderRadii(ourOutline->mOutlineRadius, aBorderArea.Size(), + outerRect.Size(), Sides(), twipsRadii); + + // Get our conversion values + nscoord oneDevPixel = aPresContext->DevPixelsToAppUnits(1); + + // get the outer rectangles + Rect oRect(NSRectToRect(outerRect, oneDevPixel)); + + // convert the radii + nsMargin outlineMargin(width, width, width, width); + RectCornerRadii outlineRadii; + ComputePixelRadii(twipsRadii, oneDevPixel, &outlineRadii); + + StyleBorderStyle outlineStyle; + if (ourOutline->mOutlineStyle.IsAuto()) { + if (StaticPrefs::layout_css_outline_style_auto_enabled()) { + nsITheme* theme = aPresContext->Theme(); + if (theme->ThemeSupportsWidget(aPresContext, aForFrame, + StyleAppearance::FocusOutline)) { + theme->DrawWidgetBackground(aRenderingContext, aForFrame, + StyleAppearance::FocusOutline, innerRect, + aDirtyRect); + return Nothing(); + } + } + if (width == 0) { + return Nothing(); // empty outline + } + // http://dev.w3.org/csswg/css-ui/#outline + // "User agents may treat 'auto' as 'solid'." + outlineStyle = StyleBorderStyle::Solid; + } else { + outlineStyle = ourOutline->mOutlineStyle.AsBorderStyle(); + } + + StyleBorderStyle outlineStyles[4] = {outlineStyle, outlineStyle, outlineStyle, + outlineStyle}; + + // This handles treating the initial color as 'currentColor'; if we + // ever want 'invert' back we'll need to do a bit of work here too. + nscolor outlineColor = + aStyle->GetVisitedDependentColor(&nsStyleOutline::mOutlineColor); + nscolor outlineColors[4] = {outlineColor, outlineColor, outlineColor, + outlineColor}; + + // convert the border widths + Float outlineWidths[4] = { + Float(width) / oneDevPixel, Float(width) / oneDevPixel, + Float(width) / oneDevPixel, Float(width) / oneDevPixel}; + Rect dirtyRect = NSRectToRect(aDirtyRect, oneDevPixel); + + Document* document = nullptr; + nsIContent* content = aForFrame->GetContent(); + if (content) { + document = content->OwnerDoc(); + } + + DrawTarget* dt = + aRenderingContext ? aRenderingContext->GetDrawTarget() : nullptr; + nsCSSBorderRenderer br(aPresContext, document, dt, dirtyRect, oRect, + outlineStyles, outlineWidths, outlineRadii, + outlineColors, !aForFrame->BackfaceIsHidden(), + Nothing()); + + return Some(br); +} + +void nsCSSRendering::PaintOutline(nsPresContext* aPresContext, + gfxContext& aRenderingContext, + nsIFrame* aForFrame, const nsRect& aDirtyRect, + const nsRect& aBorderArea, + ComputedStyle* aStyle) { + Maybe<nsCSSBorderRenderer> br = CreateBorderRendererForOutline( + aPresContext, &aRenderingContext, aForFrame, aDirtyRect, aBorderArea, + aStyle); + if (!br) { + return; + } + + // start drawing + br->DrawBorders(); + + PrintAsStringNewline(); +} + +void nsCSSRendering::PaintFocus(nsPresContext* aPresContext, + DrawTarget* aDrawTarget, + const nsRect& aFocusRect, nscolor aColor) { + nscoord oneCSSPixel = nsPresContext::CSSPixelsToAppUnits(1); + nscoord oneDevPixel = aPresContext->DevPixelsToAppUnits(1); + + Rect focusRect(NSRectToRect(aFocusRect, oneDevPixel)); + + RectCornerRadii focusRadii; + { + nscoord twipsRadii[8] = {0, 0, 0, 0, 0, 0, 0, 0}; + ComputePixelRadii(twipsRadii, oneDevPixel, &focusRadii); + } + Float focusWidths[4] = { + Float(oneCSSPixel) / oneDevPixel, Float(oneCSSPixel) / oneDevPixel, + Float(oneCSSPixel) / oneDevPixel, Float(oneCSSPixel) / oneDevPixel}; + + StyleBorderStyle focusStyles[4] = { + StyleBorderStyle::Dotted, StyleBorderStyle::Dotted, + StyleBorderStyle::Dotted, StyleBorderStyle::Dotted}; + nscolor focusColors[4] = {aColor, aColor, aColor, aColor}; + + // Because this renders a dotted border, the background color + // should not be used. Therefore, we provide a value that will + // be blatantly wrong if it ever does get used. (If this becomes + // something that CSS can style, this function will then have access + // to a ComputedStyle and can use the same logic that PaintBorder + // and PaintOutline do.) + // + // WebRender layers-free mode don't use PaintFocus function. Just assign + // the backface-visibility to true for this case. + nsCSSBorderRenderer br(aPresContext, nullptr, aDrawTarget, focusRect, + focusRect, focusStyles, focusWidths, focusRadii, + focusColors, true, Nothing()); + br.DrawBorders(); + + PrintAsStringNewline(); +} + +// Thebes Border Rendering Code End +//---------------------------------------------------------------------- + +//---------------------------------------------------------------------- + +/** + * Helper for ComputeObjectAnchorPoint; parameters are the same as for + * that function, except they're for a single coordinate / a single size + * dimension. (so, x/width vs. y/height) + */ +static void ComputeObjectAnchorCoord(const LengthPercentage& aCoord, + const nscoord aOriginBounds, + const nscoord aImageSize, + nscoord* aTopLeftCoord, + nscoord* aAnchorPointCoord) { + nscoord extraSpace = aOriginBounds - aImageSize; + + // The anchor-point doesn't care about our image's size; just the size + // of the region we're rendering into. + *aAnchorPointCoord = aCoord.Resolve(aOriginBounds, NSToCoordRoundWithClamp); + // Adjust aTopLeftCoord by the specified % of the extra space. + *aTopLeftCoord = aCoord.Resolve(extraSpace, NSToCoordRoundWithClamp); +} + +void nsImageRenderer::ComputeObjectAnchorPoint(const Position& aPos, + const nsSize& aOriginBounds, + const nsSize& aImageSize, + nsPoint* aTopLeft, + nsPoint* aAnchorPoint) { + ComputeObjectAnchorCoord(aPos.horizontal, aOriginBounds.width, + aImageSize.width, &aTopLeft->x, &aAnchorPoint->x); + + ComputeObjectAnchorCoord(aPos.vertical, aOriginBounds.height, + aImageSize.height, &aTopLeft->y, &aAnchorPoint->y); +} + +nsIFrame* nsCSSRendering::FindNonTransparentBackgroundFrame( + nsIFrame* aFrame, bool aStartAtParent /*= false*/) { + NS_ASSERTION(aFrame, + "Cannot find NonTransparentBackgroundFrame in a null frame"); + + nsIFrame* frame = nullptr; + if (aStartAtParent) { + frame = nsLayoutUtils::GetParentOrPlaceholderFor(aFrame); + } + if (!frame) { + frame = aFrame; + } + + while (frame) { + // No need to call GetVisitedDependentColor because it always uses + // this alpha component anyway. + if (NS_GET_A(frame->StyleBackground()->BackgroundColor(frame)) > 0) { + break; + } + + if (frame->IsThemed()) { + break; + } + + nsIFrame* parent = nsLayoutUtils::GetParentOrPlaceholderFor(frame); + if (!parent) { + break; + } + + frame = parent; + } + return frame; +} + +// Returns true if aFrame is a canvas frame. +// We need to treat the viewport as canvas because, even though +// it does not actually paint a background, we need to get the right +// background style so we correctly detect transparent documents. +bool nsCSSRendering::IsCanvasFrame(const nsIFrame* aFrame) { + LayoutFrameType frameType = aFrame->Type(); + return frameType == LayoutFrameType::Canvas || + frameType == LayoutFrameType::XULRoot || + frameType == LayoutFrameType::PageContent || + frameType == LayoutFrameType::Viewport; +} + +nsIFrame* nsCSSRendering::FindBackgroundStyleFrame(nsIFrame* aForFrame) { + const nsStyleBackground* result = aForFrame->StyleBackground(); + + // Check if we need to do propagation from BODY rather than HTML. + if (!result->IsTransparent(aForFrame)) { + return aForFrame; + } + + nsIContent* content = aForFrame->GetContent(); + // The root element content can't be null. We wouldn't know what + // frame to create for aFrame. + // Use |OwnerDoc| so it works during destruction. + if (!content) { + return aForFrame; + } + + Document* document = content->OwnerDoc(); + + dom::Element* bodyContent = document->GetBodyElement(); + // We need to null check the body node (bug 118829) since + // there are cases, thanks to the fix for bug 5569, where we + // will reflow a document with no body. In particular, if a + // SCRIPT element in the head blocks the parser and then has a + // SCRIPT that does "document.location.href = 'foo'", then + // nsParser::Terminate will call |DidBuildModel| methods + // through to the content sink, which will call |StartLayout| + // and thus |Initialize| on the pres shell. See bug 119351 + // for the ugly details. + if (!bodyContent) { + return aForFrame; + } + + nsIFrame* bodyFrame = bodyContent->GetPrimaryFrame(); + if (!bodyFrame) { + return aForFrame; + } + + return nsLayoutUtils::GetStyleFrame(bodyFrame); +} + +/** + * |FindBackground| finds the correct style data to use to paint the + * background. It is responsible for handling the following two + * statements in section 14.2 of CSS2: + * + * The background of the box generated by the root element covers the + * entire canvas. + * + * For HTML documents, however, we recommend that authors specify the + * background for the BODY element rather than the HTML element. User + * agents should observe the following precedence rules to fill in the + * background: if the value of the 'background' property for the HTML + * element is different from 'transparent' then use it, else use the + * value of the 'background' property for the BODY element. If the + * resulting value is 'transparent', the rendering is undefined. + * + * Thus, in our implementation, it is responsible for ensuring that: + * + we paint the correct background on the |nsCanvasFrame|, + * |nsRootBoxFrame|, or |nsPageFrame|, + * + we don't paint the background on the root element, and + * + we don't paint the background on the BODY element in *some* cases, + * and for SGML-based HTML documents only. + * + * |FindBackground| returns true if a background should be painted, and + * the resulting ComputedStyle to use for the background information + * will be filled in to |aBackground|. + */ +ComputedStyle* nsCSSRendering::FindRootFrameBackground(nsIFrame* aForFrame) { + return FindBackgroundStyleFrame(aForFrame)->Style(); +} + +inline bool FindElementBackground(const nsIFrame* aForFrame, + nsIFrame* aRootElementFrame) { + if (aForFrame == aRootElementFrame) { + // We must have propagated our background to the viewport or canvas. Abort. + return false; + } + + // Return true unless the frame is for a BODY element whose background + // was propagated to the viewport. + + nsIContent* content = aForFrame->GetContent(); + if (!content || content->NodeInfo()->NameAtom() != nsGkAtoms::body) + return true; // not frame for a "body" element + // It could be a non-HTML "body" element but that's OK, we'd fail the + // bodyContent check below + + if (aForFrame->Style()->GetPseudoType() != PseudoStyleType::NotPseudo) { + return true; // A pseudo-element frame. + } + + // We should only look at the <html> background if we're in an HTML document + Document* document = content->OwnerDoc(); + + dom::Element* bodyContent = document->GetBodyElement(); + if (bodyContent != content) + return true; // this wasn't the background that was propagated + + // This can be called even when there's no root element yet, during frame + // construction, via nsLayoutUtils::FrameHasTransparency and + // nsContainerFrame::SyncFrameViewProperties. + if (!aRootElementFrame) { + return true; + } + + const nsStyleBackground* htmlBG = aRootElementFrame->StyleBackground(); + return !htmlBG->IsTransparent(aRootElementFrame); +} + +bool nsCSSRendering::FindBackgroundFrame(const nsIFrame* aForFrame, + nsIFrame** aBackgroundFrame) { + nsIFrame* rootElementFrame = + aForFrame->PresShell()->FrameConstructor()->GetRootElementStyleFrame(); + if (IsCanvasFrame(aForFrame)) { + *aBackgroundFrame = FindCanvasBackgroundFrame(aForFrame, rootElementFrame); + return true; + } + + *aBackgroundFrame = const_cast<nsIFrame*>(aForFrame); + return FindElementBackground(aForFrame, rootElementFrame); +} + +bool nsCSSRendering::FindBackground(const nsIFrame* aForFrame, + ComputedStyle** aBackgroundSC) { + nsIFrame* backgroundFrame = nullptr; + if (FindBackgroundFrame(aForFrame, &backgroundFrame)) { + *aBackgroundSC = backgroundFrame->Style(); + return true; + } + return false; +} + +void nsCSSRendering::BeginFrameTreesLocked() { ++gFrameTreeLockCount; } + +void nsCSSRendering::EndFrameTreesLocked() { + NS_ASSERTION(gFrameTreeLockCount > 0, "Unbalanced EndFrameTreeLocked"); + --gFrameTreeLockCount; + if (gFrameTreeLockCount == 0) { + gInlineBGData->Reset(); + } +} + +bool nsCSSRendering::HasBoxShadowNativeTheme(nsIFrame* aFrame, + bool& aMaybeHasBorderRadius) { + const nsStyleDisplay* styleDisplay = aFrame->StyleDisplay(); + nsITheme::Transparency transparency; + if (aFrame->IsThemed(styleDisplay, &transparency)) { + aMaybeHasBorderRadius = false; + // For opaque (rectangular) theme widgets we can take the generic + // border-box path with border-radius disabled. + return transparency != nsITheme::eOpaque; + } + + aMaybeHasBorderRadius = true; + return false; +} + +gfx::sRGBColor nsCSSRendering::GetShadowColor(const StyleSimpleShadow& aShadow, + nsIFrame* aFrame, + float aOpacity) { + // Get the shadow color; if not specified, use the foreground color + nscolor shadowColor = aShadow.color.CalcColor(aFrame); + sRGBColor color = sRGBColor::FromABGR(shadowColor); + color.a *= aOpacity; + return color; +} + +nsRect nsCSSRendering::GetShadowRect(const nsRect& aFrameArea, + bool aNativeTheme, nsIFrame* aForFrame) { + nsRect frameRect = aNativeTheme ? aForFrame->InkOverflowRectRelativeToSelf() + + aFrameArea.TopLeft() + : aFrameArea; + Sides skipSides = aForFrame->GetSkipSides(); + frameRect = BoxDecorationRectForBorder(aForFrame, frameRect, skipSides); + + // Explicitly do not need to account for the spread radius here + // Webrender does it for us or PaintBoxShadow will for non-WR + return frameRect; +} + +bool nsCSSRendering::GetBorderRadii(const nsRect& aFrameRect, + const nsRect& aBorderRect, nsIFrame* aFrame, + RectCornerRadii& aOutRadii) { + const nscoord oneDevPixel = aFrame->PresContext()->DevPixelsToAppUnits(1); + nscoord twipsRadii[8]; + NS_ASSERTION( + aBorderRect.Size() == aFrame->VisualBorderRectRelativeToSelf().Size(), + "unexpected size"); + nsSize sz = aFrameRect.Size(); + bool hasBorderRadius = aFrame->GetBorderRadii(sz, sz, Sides(), twipsRadii); + if (hasBorderRadius) { + ComputePixelRadii(twipsRadii, oneDevPixel, &aOutRadii); + } + + return hasBorderRadius; +} + +void nsCSSRendering::PaintBoxShadowOuter(nsPresContext* aPresContext, + gfxContext& aRenderingContext, + nsIFrame* aForFrame, + const nsRect& aFrameArea, + const nsRect& aDirtyRect, + float aOpacity) { + DrawTarget& aDrawTarget = *aRenderingContext.GetDrawTarget(); + auto shadows = aForFrame->StyleEffects()->mBoxShadow.AsSpan(); + if (shadows.IsEmpty()) { + return; + } + + bool hasBorderRadius; + // mutually exclusive with hasBorderRadius + bool nativeTheme = HasBoxShadowNativeTheme(aForFrame, hasBorderRadius); + const nsStyleDisplay* styleDisplay = aForFrame->StyleDisplay(); + + nsRect frameRect = GetShadowRect(aFrameArea, nativeTheme, aForFrame); + + // Get any border radius, since box-shadow must also have rounded corners if + // the frame does. + RectCornerRadii borderRadii; + const nscoord oneDevPixel = aPresContext->DevPixelsToAppUnits(1); + if (hasBorderRadius) { + nscoord twipsRadii[8]; + NS_ASSERTION( + aFrameArea.Size() == aForFrame->VisualBorderRectRelativeToSelf().Size(), + "unexpected size"); + nsSize sz = frameRect.Size(); + hasBorderRadius = aForFrame->GetBorderRadii(sz, sz, Sides(), twipsRadii); + if (hasBorderRadius) { + ComputePixelRadii(twipsRadii, oneDevPixel, &borderRadii); + } + } + + // We don't show anything that intersects with the frame we're blurring on. So + // tell the blurrer not to do unnecessary work there. + gfxRect skipGfxRect = ThebesRect(NSRectToRect(frameRect, oneDevPixel)); + skipGfxRect.Round(); + bool useSkipGfxRect = true; + if (nativeTheme) { + // Optimize non-leaf native-themed frames by skipping computing pixels + // in the padding-box. We assume the padding-box is going to be painted + // opaquely for non-leaf frames. + // XXX this may not be a safe assumption; we should make this go away + // by optimizing box-shadow drawing more for the cases where we don't have a + // skip-rect. + useSkipGfxRect = !aForFrame->IsLeaf(); + nsRect paddingRect = + aForFrame->GetPaddingRectRelativeToSelf() + aFrameArea.TopLeft(); + skipGfxRect = nsLayoutUtils::RectToGfxRect(paddingRect, oneDevPixel); + } else if (hasBorderRadius) { + skipGfxRect.Deflate(gfxMargin( + std::max(borderRadii[C_TL].height, borderRadii[C_TR].height), 0, + std::max(borderRadii[C_BL].height, borderRadii[C_BR].height), 0)); + } + + for (const StyleBoxShadow& shadow : Reversed(shadows)) { + if (shadow.inset) { + continue; + } + + nsRect shadowRect = frameRect; + nsPoint shadowOffset(shadow.base.horizontal.ToAppUnits(), + shadow.base.vertical.ToAppUnits()); + shadowRect.MoveBy(shadowOffset); + nscoord shadowSpread = shadow.spread.ToAppUnits(); + if (!nativeTheme) { + shadowRect.Inflate(shadowSpread); + } + + // shadowRect won't include the blur, so make an extra rect here that + // includes the blur for use in the even-odd rule below. + nsRect shadowRectPlusBlur = shadowRect; + nscoord blurRadius = shadow.base.blur.ToAppUnits(); + shadowRectPlusBlur.Inflate( + nsContextBoxBlur::GetBlurRadiusMargin(blurRadius, oneDevPixel)); + + Rect shadowGfxRectPlusBlur = NSRectToRect(shadowRectPlusBlur, oneDevPixel); + shadowGfxRectPlusBlur.RoundOut(); + MaybeSnapToDevicePixels(shadowGfxRectPlusBlur, aDrawTarget, true); + + sRGBColor gfxShadowColor = GetShadowColor(shadow.base, aForFrame, aOpacity); + + if (nativeTheme) { + nsContextBoxBlur blurringArea; + + // When getting the widget shape from the native theme, we're going + // to draw the widget into the shadow surface to create a mask. + // We need to ensure that there actually *is* a shadow surface + // and that we're not going to draw directly into aRenderingContext. + gfxContext* shadowContext = blurringArea.Init( + shadowRect, shadowSpread, blurRadius, oneDevPixel, &aRenderingContext, + aDirtyRect, useSkipGfxRect ? &skipGfxRect : nullptr, + nsContextBoxBlur::FORCE_MASK); + if (!shadowContext) continue; + + MOZ_ASSERT(shadowContext == blurringArea.GetContext()); + + aRenderingContext.Save(); + aRenderingContext.SetColor(gfxShadowColor); + + // Draw the shape of the frame so it can be blurred. Recall how + // nsContextBoxBlur doesn't make any temporary surfaces if blur is 0 and + // it just returns the original surface? If we have no blur, we're + // painting this fill on the actual content surface (aRenderingContext == + // shadowContext) which is why we set up the color and clip before doing + // this. + + // We don't clip the border-box from the shadow, nor any other box. + // We assume that the native theme is going to paint over the shadow. + + // Draw the widget shape + gfxContextMatrixAutoSaveRestore save(shadowContext); + gfxPoint devPixelOffset = nsLayoutUtils::PointToGfxPoint( + shadowOffset, aPresContext->AppUnitsPerDevPixel()); + shadowContext->SetMatrixDouble( + shadowContext->CurrentMatrixDouble().PreTranslate(devPixelOffset)); + + nsRect nativeRect = aDirtyRect; + nativeRect.MoveBy(-shadowOffset); + nativeRect.IntersectRect(frameRect, nativeRect); + aPresContext->Theme()->DrawWidgetBackground( + shadowContext, aForFrame, styleDisplay->EffectiveAppearance(), + aFrameArea, nativeRect); + + blurringArea.DoPaint(); + aRenderingContext.Restore(); + } else { + aRenderingContext.Save(); + + { + Rect innerClipRect = NSRectToRect(frameRect, oneDevPixel); + if (!MaybeSnapToDevicePixels(innerClipRect, aDrawTarget, true)) { + innerClipRect.Round(); + } + + // Clip out the interior of the frame's border edge so that the shadow + // is only painted outside that area. + RefPtr<PathBuilder> builder = + aDrawTarget.CreatePathBuilder(FillRule::FILL_EVEN_ODD); + AppendRectToPath(builder, shadowGfxRectPlusBlur); + if (hasBorderRadius) { + AppendRoundedRectToPath(builder, innerClipRect, borderRadii); + } else { + AppendRectToPath(builder, innerClipRect); + } + RefPtr<Path> path = builder->Finish(); + aRenderingContext.Clip(path); + } + + // Clip the shadow so that we only get the part that applies to aForFrame. + nsRect fragmentClip = shadowRectPlusBlur; + Sides skipSides = aForFrame->GetSkipSides(); + if (!skipSides.IsEmpty()) { + if (skipSides.Left()) { + nscoord xmost = fragmentClip.XMost(); + fragmentClip.x = aFrameArea.x; + fragmentClip.width = xmost - fragmentClip.x; + } + if (skipSides.Right()) { + nscoord xmost = fragmentClip.XMost(); + nscoord overflow = xmost - aFrameArea.XMost(); + if (overflow > 0) { + fragmentClip.width -= overflow; + } + } + if (skipSides.Top()) { + nscoord ymost = fragmentClip.YMost(); + fragmentClip.y = aFrameArea.y; + fragmentClip.height = ymost - fragmentClip.y; + } + if (skipSides.Bottom()) { + nscoord ymost = fragmentClip.YMost(); + nscoord overflow = ymost - aFrameArea.YMost(); + if (overflow > 0) { + fragmentClip.height -= overflow; + } + } + } + fragmentClip = fragmentClip.Intersect(aDirtyRect); + aRenderingContext.Clip(NSRectToSnappedRect( + fragmentClip, aForFrame->PresContext()->AppUnitsPerDevPixel(), + aDrawTarget)); + + RectCornerRadii clipRectRadii; + if (hasBorderRadius) { + Float spreadDistance = Float(shadowSpread / oneDevPixel); + + Float borderSizes[4]; + + borderSizes[eSideLeft] = spreadDistance; + borderSizes[eSideTop] = spreadDistance; + borderSizes[eSideRight] = spreadDistance; + borderSizes[eSideBottom] = spreadDistance; + + nsCSSBorderRenderer::ComputeOuterRadii(borderRadii, borderSizes, + &clipRectRadii); + } + nsContextBoxBlur::BlurRectangle( + &aRenderingContext, shadowRect, oneDevPixel, + hasBorderRadius ? &clipRectRadii : nullptr, blurRadius, + gfxShadowColor, aDirtyRect, skipGfxRect); + aRenderingContext.Restore(); + } + } +} + +nsRect nsCSSRendering::GetBoxShadowInnerPaddingRect(nsIFrame* aFrame, + const nsRect& aFrameArea) { + Sides skipSides = aFrame->GetSkipSides(); + nsRect frameRect = BoxDecorationRectForBorder(aFrame, aFrameArea, skipSides); + + nsRect paddingRect = frameRect; + nsMargin border = aFrame->GetUsedBorder(); + paddingRect.Deflate(border); + return paddingRect; +} + +bool nsCSSRendering::ShouldPaintBoxShadowInner(nsIFrame* aFrame) { + const Span<const StyleBoxShadow> shadows = + aFrame->StyleEffects()->mBoxShadow.AsSpan(); + if (shadows.IsEmpty()) { + return false; + } + + if (aFrame->IsThemed() && aFrame->GetContent() && + !nsContentUtils::IsChromeDoc(aFrame->GetContent()->GetComposedDoc())) { + // There's no way of getting hold of a shape corresponding to a + // "padding-box" for native-themed widgets, so just don't draw + // inner box-shadows for them. But we allow chrome to paint inner + // box shadows since chrome can be aware of the platform theme. + return false; + } + + return true; +} + +bool nsCSSRendering::GetShadowInnerRadii(nsIFrame* aFrame, + const nsRect& aFrameArea, + RectCornerRadii& aOutInnerRadii) { + // Get any border radius, since box-shadow must also have rounded corners + // if the frame does. + nscoord twipsRadii[8]; + nsRect frameRect = + BoxDecorationRectForBorder(aFrame, aFrameArea, aFrame->GetSkipSides()); + nsSize sz = frameRect.Size(); + nsMargin border = aFrame->GetUsedBorder(); + aFrame->GetBorderRadii(sz, sz, Sides(), twipsRadii); + const nscoord oneDevPixel = aFrame->PresContext()->DevPixelsToAppUnits(1); + + RectCornerRadii borderRadii; + + const bool hasBorderRadius = + GetBorderRadii(frameRect, aFrameArea, aFrame, borderRadii); + + if (hasBorderRadius) { + ComputePixelRadii(twipsRadii, oneDevPixel, &borderRadii); + + Float borderSizes[4] = { + Float(border.top) / oneDevPixel, Float(border.right) / oneDevPixel, + Float(border.bottom) / oneDevPixel, Float(border.left) / oneDevPixel}; + nsCSSBorderRenderer::ComputeInnerRadii(borderRadii, borderSizes, + &aOutInnerRadii); + } + + return hasBorderRadius; +} + +void nsCSSRendering::PaintBoxShadowInner(nsPresContext* aPresContext, + gfxContext& aRenderingContext, + nsIFrame* aForFrame, + const nsRect& aFrameArea) { + if (!ShouldPaintBoxShadowInner(aForFrame)) { + return; + } + + const Span<const StyleBoxShadow> shadows = + aForFrame->StyleEffects()->mBoxShadow.AsSpan(); + NS_ASSERTION( + aForFrame->IsFieldSetFrame() || aFrameArea.Size() == aForFrame->GetSize(), + "unexpected size"); + + nsRect paddingRect = GetBoxShadowInnerPaddingRect(aForFrame, aFrameArea); + + RectCornerRadii innerRadii; + bool hasBorderRadius = GetShadowInnerRadii(aForFrame, aFrameArea, innerRadii); + + const nscoord oneDevPixel = aPresContext->DevPixelsToAppUnits(1); + + for (const StyleBoxShadow& shadow : Reversed(shadows)) { + if (!shadow.inset) { + continue; + } + + // shadowPaintRect: the area to paint on the temp surface + // shadowClipRect: the area on the temporary surface within shadowPaintRect + // that we will NOT paint in + nscoord blurRadius = shadow.base.blur.ToAppUnits(); + nsMargin blurMargin = + nsContextBoxBlur::GetBlurRadiusMargin(blurRadius, oneDevPixel); + nsRect shadowPaintRect = paddingRect; + shadowPaintRect.Inflate(blurMargin); + + // Round the spread radius to device pixels (by truncation). + // This mostly matches what we do for borders, except that we don't round + // up values between zero and one device pixels to one device pixel. + // This way of rounding is symmetric around zero, which makes sense for + // the spread radius. + int32_t spreadDistance = shadow.spread.ToAppUnits() / oneDevPixel; + nscoord spreadDistanceAppUnits = + aPresContext->DevPixelsToAppUnits(spreadDistance); + + nsRect shadowClipRect = paddingRect; + shadowClipRect.MoveBy(shadow.base.horizontal.ToAppUnits(), + shadow.base.vertical.ToAppUnits()); + shadowClipRect.Deflate(spreadDistanceAppUnits, spreadDistanceAppUnits); + + Rect shadowClipGfxRect = NSRectToRect(shadowClipRect, oneDevPixel); + shadowClipGfxRect.Round(); + + RectCornerRadii clipRectRadii; + if (hasBorderRadius) { + // Calculate the radii the inner clipping rect will have + Float borderSizes[4] = {0, 0, 0, 0}; + + // See PaintBoxShadowOuter and bug 514670 + if (innerRadii[C_TL].width > 0 || innerRadii[C_BL].width > 0) { + borderSizes[eSideLeft] = spreadDistance; + } + + if (innerRadii[C_TL].height > 0 || innerRadii[C_TR].height > 0) { + borderSizes[eSideTop] = spreadDistance; + } + + if (innerRadii[C_TR].width > 0 || innerRadii[C_BR].width > 0) { + borderSizes[eSideRight] = spreadDistance; + } + + if (innerRadii[C_BL].height > 0 || innerRadii[C_BR].height > 0) { + borderSizes[eSideBottom] = spreadDistance; + } + + nsCSSBorderRenderer::ComputeInnerRadii(innerRadii, borderSizes, + &clipRectRadii); + } + + // Set the "skip rect" to the area within the frame that we don't paint in, + // including after blurring. + nsRect skipRect = shadowClipRect; + skipRect.Deflate(blurMargin); + gfxRect skipGfxRect = nsLayoutUtils::RectToGfxRect(skipRect, oneDevPixel); + if (hasBorderRadius) { + skipGfxRect.Deflate(gfxMargin( + std::max(clipRectRadii[C_TL].height, clipRectRadii[C_TR].height), 0, + std::max(clipRectRadii[C_BL].height, clipRectRadii[C_BR].height), 0)); + } + + // When there's a blur radius, gfxAlphaBoxBlur leaves the skiprect area + // unchanged. And by construction the gfxSkipRect is not touched by the + // rendered shadow (even after blurring), so those pixels must be completely + // transparent in the shadow, so drawing them changes nothing. + DrawTarget* drawTarget = aRenderingContext.GetDrawTarget(); + + // Clip the context to the area of the frame's padding rect, so no part of + // the shadow is painted outside. Also cut out anything beyond where the + // inset shadow will be. + Rect shadowGfxRect = NSRectToRect(paddingRect, oneDevPixel); + shadowGfxRect.Round(); + + sRGBColor shadowColor = GetShadowColor(shadow.base, aForFrame, 1.0); + aRenderingContext.Save(); + + // This clips the outside border radius. + // clipRectRadii is the border radius inside the inset shadow. + if (hasBorderRadius) { + RefPtr<Path> roundedRect = + MakePathForRoundedRect(*drawTarget, shadowGfxRect, innerRadii); + aRenderingContext.Clip(roundedRect); + } else { + aRenderingContext.Clip(shadowGfxRect); + } + + nsContextBoxBlur insetBoxBlur; + gfxRect destRect = + nsLayoutUtils::RectToGfxRect(shadowPaintRect, oneDevPixel); + Point shadowOffset(shadow.base.horizontal.ToAppUnits() / oneDevPixel, + shadow.base.vertical.ToAppUnits() / oneDevPixel); + + insetBoxBlur.InsetBoxBlur( + &aRenderingContext, ToRect(destRect), shadowClipGfxRect, shadowColor, + blurRadius, spreadDistanceAppUnits, oneDevPixel, hasBorderRadius, + clipRectRadii, ToRect(skipGfxRect), shadowOffset); + aRenderingContext.Restore(); + } +} + +/* static */ +nsCSSRendering::PaintBGParams nsCSSRendering::PaintBGParams::ForAllLayers( + nsPresContext& aPresCtx, const nsRect& aDirtyRect, + const nsRect& aBorderArea, nsIFrame* aFrame, uint32_t aPaintFlags, + float aOpacity) { + MOZ_ASSERT(aFrame); + + PaintBGParams result(aPresCtx, aDirtyRect, aBorderArea, aFrame, aPaintFlags, + -1, CompositionOp::OP_OVER, aOpacity); + + return result; +} + +/* static */ +nsCSSRendering::PaintBGParams nsCSSRendering::PaintBGParams::ForSingleLayer( + nsPresContext& aPresCtx, const nsRect& aDirtyRect, + const nsRect& aBorderArea, nsIFrame* aFrame, uint32_t aPaintFlags, + int32_t aLayer, CompositionOp aCompositionOp, float aOpacity) { + MOZ_ASSERT(aFrame && (aLayer != -1)); + + PaintBGParams result(aPresCtx, aDirtyRect, aBorderArea, aFrame, aPaintFlags, + aLayer, aCompositionOp, aOpacity); + + return result; +} + +ImgDrawResult nsCSSRendering::PaintStyleImageLayer(const PaintBGParams& aParams, + gfxContext& aRenderingCtx) { + AUTO_PROFILER_LABEL("nsCSSRendering::PaintStyleImageLayer", GRAPHICS); + + MOZ_ASSERT(aParams.frame, + "Frame is expected to be provided to PaintStyleImageLayer"); + + ComputedStyle* sc; + if (!FindBackground(aParams.frame, &sc)) { + // We don't want to bail out if moz-appearance is set on a root + // node. If it has a parent content node, bail because it's not + // a root, otherwise keep going in order to let the theme stuff + // draw the background. The canvas really should be drawing the + // bg, but there's no way to hook that up via css. + if (!aParams.frame->StyleDisplay()->HasAppearance()) { + return ImgDrawResult::SUCCESS; + } + + nsIContent* content = aParams.frame->GetContent(); + if (!content || content->GetParent()) { + return ImgDrawResult::SUCCESS; + } + + sc = aParams.frame->Style(); + } + + return PaintStyleImageLayerWithSC(aParams, aRenderingCtx, sc, + *aParams.frame->StyleBorder()); +} + +bool nsCSSRendering::CanBuildWebRenderDisplayItemsForStyleImageLayer( + LayerManager* aManager, nsPresContext& aPresCtx, nsIFrame* aFrame, + const nsStyleBackground* aBackgroundStyle, int32_t aLayer, + uint32_t aPaintFlags) { + if (!aBackgroundStyle) { + return false; + } + + MOZ_ASSERT(aFrame && aLayer >= 0 && + (uint32_t)aLayer < aBackgroundStyle->mImage.mLayers.Length()); + + // We cannot draw native themed backgrounds + StyleAppearance appearance = aFrame->StyleDisplay()->EffectiveAppearance(); + if (appearance != StyleAppearance::None) { + nsITheme* theme = aPresCtx.Theme(); + if (theme->ThemeSupportsWidget(&aPresCtx, aFrame, appearance)) { + return false; + } + } + + // We only support painting gradients and image for a single style image + // layer, and we don't support crop-rects. + const auto& styleImage = + aBackgroundStyle->mImage.mLayers[aLayer].mImage.FinalImage(); + if (styleImage.IsImageRequestType()) { + if (styleImage.IsRect()) { + return false; + } + + imgRequestProxy* requestProxy = styleImage.GetImageRequest(); + if (!requestProxy) { + return false; + } + + uint32_t imageFlags = imgIContainer::FLAG_NONE; + if (aPaintFlags & nsCSSRendering::PAINTBG_SYNC_DECODE_IMAGES) { + imageFlags |= imgIContainer::FLAG_SYNC_DECODE; + } + + nsCOMPtr<imgIContainer> srcImage; + requestProxy->GetImage(getter_AddRefs(srcImage)); + if (!srcImage || + !srcImage->IsImageContainerAvailable(aManager, imageFlags)) { + return false; + } + + return true; + } + + if (styleImage.IsGradient()) { + return true; + } + + return false; +} + +ImgDrawResult nsCSSRendering::BuildWebRenderDisplayItemsForStyleImageLayer( + const PaintBGParams& aParams, mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const mozilla::layers::StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, nsDisplayItem* aItem) { + MOZ_ASSERT(aParams.frame, + "Frame is expected to be provided to " + "BuildWebRenderDisplayItemsForStyleImageLayer"); + + ComputedStyle* sc; + if (!FindBackground(aParams.frame, &sc)) { + // We don't want to bail out if moz-appearance is set on a root + // node. If it has a parent content node, bail because it's not + // a root, otherwise keep going in order to let the theme stuff + // draw the background. The canvas really should be drawing the + // bg, but there's no way to hook that up via css. + if (!aParams.frame->StyleDisplay()->HasAppearance()) { + return ImgDrawResult::SUCCESS; + } + + nsIContent* content = aParams.frame->GetContent(); + if (!content || content->GetParent()) { + return ImgDrawResult::SUCCESS; + } + + sc = aParams.frame->Style(); + } + return BuildWebRenderDisplayItemsForStyleImageLayerWithSC( + aParams, aBuilder, aResources, aSc, aManager, aItem, sc, + *aParams.frame->StyleBorder()); +} + +static bool IsOpaqueBorderEdge(const nsStyleBorder& aBorder, + mozilla::Side aSide) { + if (aBorder.GetComputedBorder().Side(aSide) == 0) return true; + switch (aBorder.GetBorderStyle(aSide)) { + case StyleBorderStyle::Solid: + case StyleBorderStyle::Groove: + case StyleBorderStyle::Ridge: + case StyleBorderStyle::Inset: + case StyleBorderStyle::Outset: + break; + default: + return false; + } + + // If we're using a border image, assume it's not fully opaque, + // because we may not even have the image loaded at this point, and + // even if we did, checking whether the relevant tile is fully + // opaque would be too much work. + if (!aBorder.mBorderImageSource.IsNone()) { + return false; + } + + StyleColor color = aBorder.BorderColorFor(aSide); + // We don't know the foreground color here, so if it's being used + // we must assume it might be transparent. + return !color.MaybeTransparent(); +} + +/** + * Returns true if all border edges are either missing or opaque. + */ +static bool IsOpaqueBorder(const nsStyleBorder& aBorder) { + for (const auto i : mozilla::AllPhysicalSides()) { + if (!IsOpaqueBorderEdge(aBorder, i)) { + return false; + } + } + return true; +} + +static inline void SetupDirtyRects(const nsRect& aBGClipArea, + const nsRect& aCallerDirtyRect, + nscoord aAppUnitsPerPixel, + /* OUT: */ + nsRect* aDirtyRect, gfxRect* aDirtyRectGfx) { + aDirtyRect->IntersectRect(aBGClipArea, aCallerDirtyRect); + + // Compute the Thebes equivalent of the dirtyRect. + *aDirtyRectGfx = nsLayoutUtils::RectToGfxRect(*aDirtyRect, aAppUnitsPerPixel); + NS_WARNING_ASSERTION(aDirtyRect->IsEmpty() || !aDirtyRectGfx->IsEmpty(), + "converted dirty rect should not be empty"); + MOZ_ASSERT(!aDirtyRect->IsEmpty() || aDirtyRectGfx->IsEmpty(), + "second should be empty if first is"); +} + +static bool IsSVGStyleGeometryBox(StyleGeometryBox aBox) { + return (aBox == StyleGeometryBox::FillBox || + aBox == StyleGeometryBox::StrokeBox || + aBox == StyleGeometryBox::ViewBox); +} + +static bool IsHTMLStyleGeometryBox(StyleGeometryBox aBox) { + return (aBox == StyleGeometryBox::ContentBox || + aBox == StyleGeometryBox::PaddingBox || + aBox == StyleGeometryBox::BorderBox || + aBox == StyleGeometryBox::MarginBox); +} + +static StyleGeometryBox ComputeBoxValue(nsIFrame* aForFrame, + StyleGeometryBox aBox) { + if (!aForFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) { + // For elements with associated CSS layout box, the values fill-box, + // stroke-box and view-box compute to the initial value of mask-clip. + if (IsSVGStyleGeometryBox(aBox)) { + return StyleGeometryBox::BorderBox; + } + } else { + // For SVG elements without associated CSS layout box, the values + // content-box, padding-box, border-box and margin-box compute to fill-box. + if (IsHTMLStyleGeometryBox(aBox)) { + return StyleGeometryBox::FillBox; + } + } + + return aBox; +} + +bool nsCSSRendering::ImageLayerClipState::IsValid() const { + // mDirtyRectInDevPx comes from mDirtyRectInAppUnits. mDirtyRectInAppUnits + // can not be empty if mDirtyRectInDevPx is not. + if (!mDirtyRectInDevPx.IsEmpty() && mDirtyRectInAppUnits.IsEmpty()) { + return false; + } + + if (mHasRoundedCorners == mClippedRadii.IsEmpty()) { + return false; + } + + return true; +} + +/* static */ +void nsCSSRendering::GetImageLayerClip( + const nsStyleImageLayers::Layer& aLayer, nsIFrame* aForFrame, + const nsStyleBorder& aBorder, const nsRect& aBorderArea, + const nsRect& aCallerDirtyRect, bool aWillPaintBorder, + nscoord aAppUnitsPerPixel, + /* out */ ImageLayerClipState* aClipState) { + StyleGeometryBox layerClip = ComputeBoxValue(aForFrame, aLayer.mClip); + if (IsSVGStyleGeometryBox(layerClip)) { + MOZ_ASSERT(aForFrame->IsFrameOfType(nsIFrame::eSVG) && + !aForFrame->IsSVGOuterSVGFrame()); + + // The coordinate space of clipArea is svg user space. + nsRect clipArea = nsLayoutUtils::ComputeGeometryBox(aForFrame, layerClip); + + nsRect strokeBox = (layerClip == StyleGeometryBox::StrokeBox) + ? clipArea + : nsLayoutUtils::ComputeGeometryBox( + aForFrame, StyleGeometryBox::StrokeBox); + nsRect clipAreaRelativeToStrokeBox = clipArea - strokeBox.TopLeft(); + + // aBorderArea is the stroke-box area in a coordinate space defined by + // the caller. This coordinate space can be svg user space of aForFrame, + // the space of aForFrame's reference-frame, or anything else. + // + // Which coordinate space chosen for aBorderArea is not matter. What + // matter is to ensure returning aClipState->mBGClipArea in the consistent + // coordiante space with aBorderArea. So we evaluate the position of clip + // area base on the position of aBorderArea here. + aClipState->mBGClipArea = + clipAreaRelativeToStrokeBox + aBorderArea.TopLeft(); + + SetupDirtyRects(aClipState->mBGClipArea, aCallerDirtyRect, + aAppUnitsPerPixel, &aClipState->mDirtyRectInAppUnits, + &aClipState->mDirtyRectInDevPx); + MOZ_ASSERT(aClipState->IsValid()); + return; + } + + if (layerClip == StyleGeometryBox::NoClip) { + aClipState->mBGClipArea = aCallerDirtyRect; + + SetupDirtyRects(aClipState->mBGClipArea, aCallerDirtyRect, + aAppUnitsPerPixel, &aClipState->mDirtyRectInAppUnits, + &aClipState->mDirtyRectInDevPx); + MOZ_ASSERT(aClipState->IsValid()); + return; + } + + MOZ_ASSERT(!aForFrame->IsFrameOfType(nsIFrame::eSVG) || + aForFrame->IsSVGOuterSVGFrame()); + + // Compute the outermost boundary of the area that might be painted. + // Same coordinate space as aBorderArea. + Sides skipSides = aForFrame->GetSkipSides(); + nsRect clipBorderArea = + BoxDecorationRectForBorder(aForFrame, aBorderArea, skipSides, &aBorder); + + bool haveRoundedCorners = false; + LayoutFrameType fType = aForFrame->Type(); + if (fType != LayoutFrameType::TableColGroup && + fType != LayoutFrameType::TableCol && + fType != LayoutFrameType::TableRow && + fType != LayoutFrameType::TableRowGroup) { + haveRoundedCorners = GetRadii(aForFrame, aBorder, aBorderArea, + clipBorderArea, aClipState->mRadii); + } + bool isSolidBorder = aWillPaintBorder && IsOpaqueBorder(aBorder); + if (isSolidBorder && layerClip == StyleGeometryBox::BorderBox) { + // If we have rounded corners, we need to inflate the background + // drawing area a bit to avoid seams between the border and + // background. + layerClip = haveRoundedCorners ? StyleGeometryBox::MozAlmostPadding + : StyleGeometryBox::PaddingBox; + } + + aClipState->mBGClipArea = clipBorderArea; + + if (aForFrame->IsScrollFrame() && + StyleImageLayerAttachment::Local == aLayer.mAttachment) { + // As of this writing, this is still in discussion in the CSS Working Group + // http://lists.w3.org/Archives/Public/www-style/2013Jul/0250.html + + // The rectangle for 'background-clip' scrolls with the content, + // but the background is also clipped at a non-scrolling 'padding-box' + // like the content. (See below.) + // Therefore, only 'content-box' makes a difference here. + if (layerClip == StyleGeometryBox::ContentBox) { + nsIScrollableFrame* scrollableFrame = do_QueryFrame(aForFrame); + // Clip at a rectangle attached to the scrolled content. + aClipState->mHasAdditionalBGClipArea = true; + aClipState->mAdditionalBGClipArea = + nsRect(aClipState->mBGClipArea.TopLeft() + + scrollableFrame->GetScrolledFrame()->GetPosition() + // For the dir=rtl case: + + scrollableFrame->GetScrollRange().TopLeft(), + scrollableFrame->GetScrolledRect().Size()); + nsMargin padding = aForFrame->GetUsedPadding(); + // padding-bottom is ignored on scrollable frames: + // https://bugzilla.mozilla.org/show_bug.cgi?id=748518 + padding.bottom = 0; + padding.ApplySkipSides(skipSides); + aClipState->mAdditionalBGClipArea.Deflate(padding); + } + + // Also clip at a non-scrolling, rounded-corner 'padding-box', + // same as the scrolled content because of the 'overflow' property. + layerClip = StyleGeometryBox::PaddingBox; + } + + // See the comment of StyleGeometryBox::Margin. + // Hitting this assertion means we decide to turn on margin-box support for + // positioned mask from CSS parser and style system. In this case, you + // should *inflate* mBGClipArea by the margin returning from + // aForFrame->GetUsedMargin() in the code chunk bellow. + MOZ_ASSERT(layerClip != StyleGeometryBox::MarginBox, + "StyleGeometryBox::MarginBox rendering is not supported yet.\n"); + + if (layerClip != StyleGeometryBox::BorderBox && + layerClip != StyleGeometryBox::Text) { + nsMargin border = aForFrame->GetUsedBorder(); + if (layerClip == StyleGeometryBox::MozAlmostPadding) { + // Reduce |border| by 1px (device pixels) on all sides, if + // possible, so that we don't get antialiasing seams between the + // {background|mask} and border. + border.top = std::max(0, border.top - aAppUnitsPerPixel); + border.right = std::max(0, border.right - aAppUnitsPerPixel); + border.bottom = std::max(0, border.bottom - aAppUnitsPerPixel); + border.left = std::max(0, border.left - aAppUnitsPerPixel); + } else if (layerClip != StyleGeometryBox::PaddingBox) { + NS_ASSERTION(layerClip == StyleGeometryBox::ContentBox, + "unexpected background-clip"); + border += aForFrame->GetUsedPadding(); + } + border.ApplySkipSides(skipSides); + aClipState->mBGClipArea.Deflate(border); + + if (haveRoundedCorners) { + nsIFrame::InsetBorderRadii(aClipState->mRadii, border); + } + } + + if (haveRoundedCorners) { + auto d2a = aForFrame->PresContext()->AppUnitsPerDevPixel(); + nsCSSRendering::ComputePixelRadii(aClipState->mRadii, d2a, + &aClipState->mClippedRadii); + aClipState->mHasRoundedCorners = !aClipState->mClippedRadii.IsEmpty(); + } + + if (!haveRoundedCorners && aClipState->mHasAdditionalBGClipArea) { + // Do the intersection here to account for the fast path(?) below. + aClipState->mBGClipArea = + aClipState->mBGClipArea.Intersect(aClipState->mAdditionalBGClipArea); + aClipState->mHasAdditionalBGClipArea = false; + } + + SetupDirtyRects(aClipState->mBGClipArea, aCallerDirtyRect, aAppUnitsPerPixel, + &aClipState->mDirtyRectInAppUnits, + &aClipState->mDirtyRectInDevPx); + + MOZ_ASSERT(aClipState->IsValid()); +} + +static void SetupImageLayerClip(nsCSSRendering::ImageLayerClipState& aClipState, + gfxContext* aCtx, nscoord aAppUnitsPerPixel, + gfxContextAutoSaveRestore* aAutoSR) { + if (aClipState.mDirtyRectInDevPx.IsEmpty()) { + // Our caller won't draw anything under this condition, so no need + // to set more up. + return; + } + + if (aClipState.mCustomClip) { + // We don't support custom clips and rounded corners, arguably a bug, but + // table painting seems to depend on it. + return; + } + + // If we have rounded corners, clip all subsequent drawing to the + // rounded rectangle defined by bgArea and bgRadii (we don't know + // whether the rounded corners intrude on the dirtyRect or not). + // Do not do this if we have a caller-provided clip rect -- + // as above with bgArea, arguably a bug, but table painting seems + // to depend on it. + + if (aClipState.mHasAdditionalBGClipArea) { + gfxRect bgAreaGfx = nsLayoutUtils::RectToGfxRect( + aClipState.mAdditionalBGClipArea, aAppUnitsPerPixel); + bgAreaGfx.Round(); + gfxUtils::ConditionRect(bgAreaGfx); + + aAutoSR->EnsureSaved(aCtx); + aCtx->SnappedClip(bgAreaGfx); + } + + if (aClipState.mHasRoundedCorners) { + Rect bgAreaGfx = NSRectToRect(aClipState.mBGClipArea, aAppUnitsPerPixel); + bgAreaGfx.Round(); + + if (bgAreaGfx.IsEmpty()) { + // I think it's become possible to hit this since + // https://hg.mozilla.org/mozilla-central/rev/50e934e4979b landed. + NS_WARNING("converted background area should not be empty"); + // Make our caller not do anything. + aClipState.mDirtyRectInDevPx.SizeTo(gfxSize(0.0, 0.0)); + return; + } + + aAutoSR->EnsureSaved(aCtx); + + RefPtr<Path> roundedRect = MakePathForRoundedRect( + *aCtx->GetDrawTarget(), bgAreaGfx, aClipState.mClippedRadii); + aCtx->Clip(roundedRect); + } +} + +static void DrawBackgroundColor(nsCSSRendering::ImageLayerClipState& aClipState, + gfxContext* aCtx, nscoord aAppUnitsPerPixel) { + if (aClipState.mDirtyRectInDevPx.IsEmpty()) { + // Our caller won't draw anything under this condition, so no need + // to set more up. + return; + } + + DrawTarget* drawTarget = aCtx->GetDrawTarget(); + + // We don't support custom clips and rounded corners, arguably a bug, but + // table painting seems to depend on it. + if (!aClipState.mHasRoundedCorners || aClipState.mCustomClip) { + aCtx->NewPath(); + aCtx->SnappedRectangle(aClipState.mDirtyRectInDevPx); + aCtx->Fill(); + return; + } + + Rect bgAreaGfx = NSRectToRect(aClipState.mBGClipArea, aAppUnitsPerPixel); + bgAreaGfx.Round(); + + if (bgAreaGfx.IsEmpty()) { + // I think it's become possible to hit this since + // https://hg.mozilla.org/mozilla-central/rev/50e934e4979b landed. + NS_WARNING("converted background area should not be empty"); + // Make our caller not do anything. + aClipState.mDirtyRectInDevPx.SizeTo(gfxSize(0.0, 0.0)); + return; + } + + aCtx->Save(); + gfxRect dirty = ThebesRect(bgAreaGfx).Intersect(aClipState.mDirtyRectInDevPx); + + aCtx->SnappedClip(dirty); + + if (aClipState.mHasAdditionalBGClipArea) { + gfxRect bgAdditionalAreaGfx = nsLayoutUtils::RectToGfxRect( + aClipState.mAdditionalBGClipArea, aAppUnitsPerPixel); + bgAdditionalAreaGfx.Round(); + gfxUtils::ConditionRect(bgAdditionalAreaGfx); + aCtx->SnappedClip(bgAdditionalAreaGfx); + } + + RefPtr<Path> roundedRect = + MakePathForRoundedRect(*drawTarget, bgAreaGfx, aClipState.mClippedRadii); + aCtx->SetPath(roundedRect); + aCtx->Fill(); + aCtx->Restore(); +} + +enum class ScrollbarColorKind { + Thumb, + Track, +}; + +static Maybe<nscolor> CalcScrollbarColor(nsIFrame* aFrame, + ScrollbarColorKind aKind) { + ComputedStyle* scrollbarStyle = nsLayoutUtils::StyleForScrollbar(aFrame); + const auto& colors = scrollbarStyle->StyleUI()->mScrollbarColor; + if (colors.IsAuto()) { + return Nothing(); + } + const auto& color = aKind == ScrollbarColorKind::Thumb + ? colors.AsColors().thumb + : colors.AsColors().track; + return Some(color.CalcColor(*scrollbarStyle)); +} + +static nscolor GetBackgroundColor(nsIFrame* aFrame, ComputedStyle* aStyle) { + switch (aStyle->StyleDisplay()->EffectiveAppearance()) { + case StyleAppearance::ScrollbarthumbVertical: + case StyleAppearance::ScrollbarthumbHorizontal: { + if (Maybe<nscolor> overrideColor = + CalcScrollbarColor(aFrame, ScrollbarColorKind::Thumb)) { + return *overrideColor; + } + break; + } + case StyleAppearance::ScrollbarVertical: + case StyleAppearance::ScrollbarHorizontal: + case StyleAppearance::Scrollcorner: { + if (Maybe<nscolor> overrideColor = + CalcScrollbarColor(aFrame, ScrollbarColorKind::Track)) { + return *overrideColor; + } + break; + } + default: + break; + } + return aStyle->GetVisitedDependentColor(&nsStyleBackground::mBackgroundColor); +} + +nscolor nsCSSRendering::DetermineBackgroundColor(nsPresContext* aPresContext, + ComputedStyle* aStyle, + nsIFrame* aFrame, + bool& aDrawBackgroundImage, + bool& aDrawBackgroundColor) { + auto shouldPaint = aFrame->ComputeShouldPaintBackground(); + aDrawBackgroundImage = shouldPaint.mImage; + aDrawBackgroundColor = shouldPaint.mColor; + + const nsStyleBackground* bg = aStyle->StyleBackground(); + nscolor bgColor; + if (aDrawBackgroundColor) { + bgColor = GetBackgroundColor(aFrame, aStyle); + if (NS_GET_A(bgColor) == 0) { + aDrawBackgroundColor = false; + } + } else { + // If GetBackgroundColorDraw() is false, we are still expected to + // draw color in the background of any frame that's not completely + // transparent, but we are expected to use white instead of whatever + // color was specified. + bgColor = NS_RGB(255, 255, 255); + if (aDrawBackgroundImage || !bg->IsTransparent(aStyle)) { + aDrawBackgroundColor = true; + } else { + bgColor = NS_RGBA(0, 0, 0, 0); + } + } + + // We can skip painting the background color if a background image is opaque. + nsStyleImageLayers::Repeat repeat = bg->BottomLayer().mRepeat; + bool xFullRepeat = repeat.mXRepeat == StyleImageLayerRepeat::Repeat || + repeat.mXRepeat == StyleImageLayerRepeat::Round; + bool yFullRepeat = repeat.mYRepeat == StyleImageLayerRepeat::Repeat || + repeat.mYRepeat == StyleImageLayerRepeat::Round; + if (aDrawBackgroundColor && xFullRepeat && yFullRepeat && + bg->BottomLayer().mImage.IsOpaque() && + bg->BottomLayer().mBlendMode == StyleBlend::Normal) { + aDrawBackgroundColor = false; + } + + return bgColor; +} + +static CompositionOp DetermineCompositionOp( + const nsCSSRendering::PaintBGParams& aParams, + const nsStyleImageLayers& aLayers, uint32_t aLayerIndex) { + if (aParams.layer >= 0) { + // When drawing a single layer, use the specified composition op. + return aParams.compositionOp; + } + + const nsStyleImageLayers::Layer& layer = aLayers.mLayers[aLayerIndex]; + // When drawing all layers, get the compositon op from each image layer. + if (aParams.paintFlags & nsCSSRendering::PAINTBG_MASK_IMAGE) { + // Always using OP_OVER mode while drawing the bottom mask layer. + if (aLayerIndex == (aLayers.mImageCount - 1)) { + return CompositionOp::OP_OVER; + } + + return nsCSSRendering::GetGFXCompositeMode(layer.mComposite); + } + + return nsCSSRendering::GetGFXBlendMode(layer.mBlendMode); +} + +ImgDrawResult nsCSSRendering::PaintStyleImageLayerWithSC( + const PaintBGParams& aParams, gfxContext& aRenderingCtx, + ComputedStyle* aBackgroundSC, const nsStyleBorder& aBorder) { + MOZ_ASSERT(aParams.frame, + "Frame is expected to be provided to PaintStyleImageLayerWithSC"); + + // If we're drawing all layers, aCompositonOp is ignored, so make sure that + // it was left at its default value. + MOZ_ASSERT(aParams.layer != -1 || + aParams.compositionOp == CompositionOp::OP_OVER); + + // Check to see if we have an appearance defined. If so, we let the theme + // renderer draw the background and bail out. + // XXXzw this ignores aParams.bgClipRect. + StyleAppearance appearance = + aParams.frame->StyleDisplay()->EffectiveAppearance(); + if (appearance != StyleAppearance::None) { + nsITheme* theme = aParams.presCtx.Theme(); + if (theme->ThemeSupportsWidget(&aParams.presCtx, aParams.frame, + appearance)) { + nsRect drawing(aParams.borderArea); + theme->GetWidgetOverflow(aParams.presCtx.DeviceContext(), aParams.frame, + appearance, &drawing); + drawing.IntersectRect(drawing, aParams.dirtyRect); + theme->DrawWidgetBackground(&aRenderingCtx, aParams.frame, appearance, + aParams.borderArea, drawing); + return ImgDrawResult::SUCCESS; + } + } + + // For canvas frames (in the CSS sense) we draw the background color using + // a solid color item that gets added in nsLayoutUtils::PaintFrame, + // or nsSubDocumentFrame::BuildDisplayList (bug 488242). (The solid + // color may be moved into nsDisplayCanvasBackground by + // PresShell::AddCanvasBackgroundColorItem(), and painted by + // nsDisplayCanvasBackground directly.) Either way we don't need to + // paint the background color here. + bool isCanvasFrame = IsCanvasFrame(aParams.frame); + const bool paintMask = aParams.paintFlags & PAINTBG_MASK_IMAGE; + + // Determine whether we are drawing background images and/or + // background colors. + bool drawBackgroundImage = true; + bool drawBackgroundColor = !paintMask; + nscolor bgColor = NS_RGBA(0, 0, 0, 0); + if (!paintMask) { + bgColor = + DetermineBackgroundColor(&aParams.presCtx, aBackgroundSC, aParams.frame, + drawBackgroundImage, drawBackgroundColor); + } + + // Masks shouldn't be suppressed for print. + MOZ_ASSERT_IF(paintMask, drawBackgroundImage); + + const nsStyleImageLayers& layers = + paintMask ? aBackgroundSC->StyleSVGReset()->mMask + : aBackgroundSC->StyleBackground()->mImage; + // If we're drawing a specific layer, we don't want to draw the + // background color. + if (drawBackgroundColor && aParams.layer >= 0) { + drawBackgroundColor = false; + } + + // At this point, drawBackgroundImage and drawBackgroundColor are + // true if and only if we are actually supposed to paint an image or + // color into aDirtyRect, respectively. + if (!drawBackgroundImage && !drawBackgroundColor) { + return ImgDrawResult::SUCCESS; + } + + // The 'bgClipArea' (used only by the image tiling logic, far below) + // is the caller-provided aParams.bgClipRect if any, or else the area + // determined by the value of 'background-clip' in + // SetupCurrentBackgroundClip. (Arguably it should be the + // intersection, but that breaks the table painter -- in particular, + // taking the intersection breaks reftests/bugs/403249-1[ab].) + nscoord appUnitsPerPixel = aParams.presCtx.AppUnitsPerDevPixel(); + ImageLayerClipState clipState; + if (aParams.bgClipRect) { + clipState.mBGClipArea = *aParams.bgClipRect; + clipState.mCustomClip = true; + clipState.mHasRoundedCorners = false; + SetupDirtyRects(clipState.mBGClipArea, aParams.dirtyRect, appUnitsPerPixel, + &clipState.mDirtyRectInAppUnits, + &clipState.mDirtyRectInDevPx); + } else { + GetImageLayerClip(layers.BottomLayer(), aParams.frame, aBorder, + aParams.borderArea, aParams.dirtyRect, + (aParams.paintFlags & PAINTBG_WILL_PAINT_BORDER), + appUnitsPerPixel, &clipState); + } + + // If we might be using a background color, go ahead and set it now. + if (drawBackgroundColor && !isCanvasFrame) { + aRenderingCtx.SetColor(sRGBColor::FromABGR(bgColor)); + } + + // If there is no background image, draw a color. (If there is + // neither a background image nor a color, we wouldn't have gotten + // this far.) + if (!drawBackgroundImage) { + if (!isCanvasFrame) { + DrawBackgroundColor(clipState, &aRenderingCtx, appUnitsPerPixel); + } + return ImgDrawResult::SUCCESS; + } + + if (layers.mImageCount < 1) { + // Return if there are no background layers, all work from this point + // onwards happens iteratively on these. + return ImgDrawResult::SUCCESS; + } + + MOZ_ASSERT((aParams.layer < 0) || + (layers.mImageCount > uint32_t(aParams.layer))); + + // The background color is rendered over the entire dirty area, + // even if the image isn't. + if (drawBackgroundColor && !isCanvasFrame) { + DrawBackgroundColor(clipState, &aRenderingCtx, appUnitsPerPixel); + } + + // Compute the outermost boundary of the area that might be painted. + // Same coordinate space as aParams.borderArea & aParams.bgClipRect. + Sides skipSides = aParams.frame->GetSkipSides(); + nsRect paintBorderArea = BoxDecorationRectForBackground( + aParams.frame, aParams.borderArea, skipSides, &aBorder); + nsRect clipBorderArea = BoxDecorationRectForBorder( + aParams.frame, aParams.borderArea, skipSides, &aBorder); + + ImgDrawResult result = ImgDrawResult::SUCCESS; + StyleGeometryBox currentBackgroundClip = StyleGeometryBox::BorderBox; + const bool drawAllLayers = (aParams.layer < 0); + uint32_t count = drawAllLayers + ? layers.mImageCount // iterate all image layers. + : layers.mImageCount - + aParams.layer; // iterate from the bottom layer to + // the 'aParams.layer-th' layer. + NS_FOR_VISIBLE_IMAGE_LAYERS_BACK_TO_FRONT_WITH_RANGE( + i, layers, layers.mImageCount - 1, count) { + // NOTE: no Save() yet, we do that later by calling autoSR.EnsureSaved(ctx) + // in the cases we need it. + gfxContextAutoSaveRestore autoSR; + const nsStyleImageLayers::Layer& layer = layers.mLayers[i]; + + if (!aParams.bgClipRect) { + bool isBottomLayer = (i == layers.mImageCount - 1); + if (currentBackgroundClip != layer.mClip || isBottomLayer) { + currentBackgroundClip = layer.mClip; + ImageLayerClipState currentLayerClipState; + if (isBottomLayer) { + currentLayerClipState = clipState; + } else { + // For the bottom layer, we already called GetImageLayerClip above + // and it stored its results in clipState. + GetImageLayerClip(layer, aParams.frame, aBorder, aParams.borderArea, + aParams.dirtyRect, + (aParams.paintFlags & PAINTBG_WILL_PAINT_BORDER), + appUnitsPerPixel, ¤tLayerClipState); + } + SetupImageLayerClip(currentLayerClipState, &aRenderingCtx, + appUnitsPerPixel, &autoSR); + if (!clipBorderArea.IsEqualEdges(aParams.borderArea)) { + // We're drawing the background for the joined continuation boxes + // so we need to clip that to the slice that we want for this + // frame. + gfxRect clip = nsLayoutUtils::RectToGfxRect(aParams.borderArea, + appUnitsPerPixel); + autoSR.EnsureSaved(&aRenderingCtx); + aRenderingCtx.SnappedClip(clip); + } + } + } + + // Skip the following layer preparing and painting code if the current + // layer is not selected for drawing. + if (aParams.layer >= 0 && i != (uint32_t)aParams.layer) { + continue; + } + nsBackgroundLayerState state = PrepareImageLayer( + &aParams.presCtx, aParams.frame, aParams.paintFlags, paintBorderArea, + clipState.mBGClipArea, layer, nullptr); + result &= state.mImageRenderer.PrepareResult(); + + // Skip the layer painting code if we found the dirty region is empty. + if (clipState.mDirtyRectInDevPx.IsEmpty()) { + continue; + } + + if (!state.mFillArea.IsEmpty()) { + CompositionOp co = DetermineCompositionOp(aParams, layers, i); + if (co != CompositionOp::OP_OVER) { + NS_ASSERTION(aRenderingCtx.CurrentOp() == CompositionOp::OP_OVER, + "It is assumed the initial op is OP_OVER, when it is " + "restored later"); + aRenderingCtx.SetOp(co); + } + + result &= state.mImageRenderer.DrawLayer( + &aParams.presCtx, aRenderingCtx, state.mDestArea, state.mFillArea, + state.mAnchor + paintBorderArea.TopLeft(), + clipState.mDirtyRectInAppUnits, state.mRepeatSize, aParams.opacity); + + if (co != CompositionOp::OP_OVER) { + aRenderingCtx.SetOp(CompositionOp::OP_OVER); + } + } + } + + return result; +} + +ImgDrawResult +nsCSSRendering::BuildWebRenderDisplayItemsForStyleImageLayerWithSC( + const PaintBGParams& aParams, mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const mozilla::layers::StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, nsDisplayItem* aItem, + ComputedStyle* aBackgroundSC, const nsStyleBorder& aBorder) { + MOZ_ASSERT(!(aParams.paintFlags & PAINTBG_MASK_IMAGE)); + + nscoord appUnitsPerPixel = aParams.presCtx.AppUnitsPerDevPixel(); + ImageLayerClipState clipState; + + clipState.mBGClipArea = *aParams.bgClipRect; + clipState.mCustomClip = true; + clipState.mHasRoundedCorners = false; + SetupDirtyRects(clipState.mBGClipArea, aParams.dirtyRect, appUnitsPerPixel, + &clipState.mDirtyRectInAppUnits, + &clipState.mDirtyRectInDevPx); + + // Compute the outermost boundary of the area that might be painted. + // Same coordinate space as aParams.borderArea & aParams.bgClipRect. + Sides skipSides = aParams.frame->GetSkipSides(); + nsRect paintBorderArea = BoxDecorationRectForBackground( + aParams.frame, aParams.borderArea, skipSides, &aBorder); + + const nsStyleImageLayers& layers = aBackgroundSC->StyleBackground()->mImage; + const nsStyleImageLayers::Layer& layer = layers.mLayers[aParams.layer]; + + // Skip the following layer painting code if we found the dirty region is + // empty or the current layer is not selected for drawing. + if (clipState.mDirtyRectInDevPx.IsEmpty()) { + return ImgDrawResult::SUCCESS; + } + + ImgDrawResult result = ImgDrawResult::SUCCESS; + nsBackgroundLayerState state = + PrepareImageLayer(&aParams.presCtx, aParams.frame, aParams.paintFlags, + paintBorderArea, clipState.mBGClipArea, layer, nullptr); + result &= state.mImageRenderer.PrepareResult(); + + if (!state.mFillArea.IsEmpty()) { + return state.mImageRenderer.BuildWebRenderDisplayItemsForLayer( + &aParams.presCtx, aBuilder, aResources, aSc, aManager, aItem, + state.mDestArea, state.mFillArea, + state.mAnchor + paintBorderArea.TopLeft(), + clipState.mDirtyRectInAppUnits, state.mRepeatSize, aParams.opacity); + } + + return result; +} + +nsRect nsCSSRendering::ComputeImageLayerPositioningArea( + nsPresContext* aPresContext, nsIFrame* aForFrame, const nsRect& aBorderArea, + const nsStyleImageLayers::Layer& aLayer, nsIFrame** aAttachedToFrame, + bool* aOutIsTransformedFixed) { + // Compute {background|mask} origin area relative to aBorderArea now as we + // may need it to compute the effective image size for a CSS gradient. + nsRect positionArea; + + StyleGeometryBox layerOrigin = ComputeBoxValue(aForFrame, aLayer.mOrigin); + + if (IsSVGStyleGeometryBox(layerOrigin)) { + MOZ_ASSERT(aForFrame->IsFrameOfType(nsIFrame::eSVG) && + !aForFrame->IsSVGOuterSVGFrame()); + *aAttachedToFrame = aForFrame; + + positionArea = nsLayoutUtils::ComputeGeometryBox(aForFrame, layerOrigin); + + nsPoint toStrokeBoxOffset = nsPoint(0, 0); + if (layerOrigin != StyleGeometryBox::StrokeBox) { + nsRect strokeBox = nsLayoutUtils::ComputeGeometryBox( + aForFrame, StyleGeometryBox::StrokeBox); + toStrokeBoxOffset = positionArea.TopLeft() - strokeBox.TopLeft(); + } + + // For SVG frames, the return value is relative to the stroke box + return nsRect(toStrokeBoxOffset, positionArea.Size()); + } + + MOZ_ASSERT(!aForFrame->IsFrameOfType(nsIFrame::eSVG) || + aForFrame->IsSVGOuterSVGFrame()); + + LayoutFrameType frameType = aForFrame->Type(); + nsIFrame* geometryFrame = aForFrame; + if (MOZ_UNLIKELY(frameType == LayoutFrameType::Scroll && + StyleImageLayerAttachment::Local == aLayer.mAttachment)) { + nsIScrollableFrame* scrollableFrame = do_QueryFrame(aForFrame); + positionArea = nsRect(scrollableFrame->GetScrolledFrame()->GetPosition() + // For the dir=rtl case: + + scrollableFrame->GetScrollRange().TopLeft(), + scrollableFrame->GetScrolledRect().Size()); + // The ScrolledRect’s size does not include the borders or scrollbars, + // reverse the handling of background-origin + // compared to the common case below. + if (layerOrigin == StyleGeometryBox::BorderBox) { + nsMargin border = geometryFrame->GetUsedBorder(); + border.ApplySkipSides(geometryFrame->GetSkipSides()); + positionArea.Inflate(border); + positionArea.Inflate(scrollableFrame->GetActualScrollbarSizes()); + } else if (layerOrigin != StyleGeometryBox::PaddingBox) { + nsMargin padding = geometryFrame->GetUsedPadding(); + padding.ApplySkipSides(geometryFrame->GetSkipSides()); + positionArea.Deflate(padding); + NS_ASSERTION(layerOrigin == StyleGeometryBox::ContentBox, + "unknown background-origin value"); + } + *aAttachedToFrame = aForFrame; + return positionArea; + } + + if (MOZ_UNLIKELY(frameType == LayoutFrameType::Canvas)) { + geometryFrame = aForFrame->PrincipalChildList().FirstChild(); + // geometryFrame might be null if this canvas is a page created + // as an overflow container (e.g. the in-flow content has already + // finished and this page only displays the continuations of + // absolutely positioned content). + if (geometryFrame) { + positionArea = + nsPlaceholderFrame::GetRealFrameFor(geometryFrame)->GetRect(); + } + } else { + positionArea = nsRect(nsPoint(0, 0), aBorderArea.Size()); + } + + // See the comment of StyleGeometryBox::MarginBox. + // Hitting this assertion means we decide to turn on margin-box support for + // positioned mask from CSS parser and style system. In this case, you + // should *inflate* positionArea by the margin returning from + // geometryFrame->GetUsedMargin() in the code chunk bellow. + MOZ_ASSERT(aLayer.mOrigin != StyleGeometryBox::MarginBox, + "StyleGeometryBox::MarginBox rendering is not supported yet.\n"); + + // {background|mask} images are tiled over the '{background|mask}-clip' area + // but the origin of the tiling is based on the '{background|mask}-origin' + // area. + if (layerOrigin != StyleGeometryBox::BorderBox && geometryFrame) { + nsMargin border = geometryFrame->GetUsedBorder(); + if (layerOrigin != StyleGeometryBox::PaddingBox) { + border += geometryFrame->GetUsedPadding(); + NS_ASSERTION(layerOrigin == StyleGeometryBox::ContentBox, + "unknown background-origin value"); + } + positionArea.Deflate(border); + } + + nsIFrame* attachedToFrame = aForFrame; + if (StyleImageLayerAttachment::Fixed == aLayer.mAttachment) { + // If it's a fixed background attachment, then the image is placed + // relative to the viewport, which is the area of the root frame + // in a screen context or the page content frame in a print context. + attachedToFrame = aPresContext->PresShell()->GetRootFrame(); + NS_ASSERTION(attachedToFrame, "no root frame"); + nsIFrame* pageContentFrame = nullptr; + if (aPresContext->IsPaginated()) { + pageContentFrame = nsLayoutUtils::GetClosestFrameOfType( + aForFrame, LayoutFrameType::PageContent); + if (pageContentFrame) { + attachedToFrame = pageContentFrame; + } + // else this is an embedded shell and its root frame is what we want + } + + // If the background is affected by a transform, treat is as if it + // wasn't fixed. + if (nsLayoutUtils::IsTransformed(aForFrame, attachedToFrame)) { + attachedToFrame = aForFrame; + *aOutIsTransformedFixed = true; + } else { + // Set the background positioning area to the viewport's area + // (relative to aForFrame) + positionArea = nsRect(-aForFrame->GetOffsetTo(attachedToFrame), + attachedToFrame->GetSize()); + + if (!pageContentFrame) { + // Subtract the size of scrollbars. + nsIScrollableFrame* scrollableFrame = + aPresContext->PresShell()->GetRootScrollFrameAsScrollable(); + if (scrollableFrame) { + nsMargin scrollbars = scrollableFrame->GetActualScrollbarSizes(); + positionArea.Deflate(scrollbars); + } + } + } + } + *aAttachedToFrame = attachedToFrame; + + return positionArea; +} + +/* static */ +nscoord nsCSSRendering::ComputeRoundedSize(nscoord aCurrentSize, + nscoord aPositioningSize) { + float repeatCount = NS_roundf(float(aPositioningSize) / float(aCurrentSize)); + if (repeatCount < 1.0f) { + return aPositioningSize; + } + return nscoord(NS_lround(float(aPositioningSize) / repeatCount)); +} + +// Apply the CSS image sizing algorithm as it applies to background images. +// See http://www.w3.org/TR/css3-background/#the-background-size . +// aIntrinsicSize is the size that the background image 'would like to be'. +// It can be found by calling nsImageRenderer::ComputeIntrinsicSize. +static nsSize ComputeDrawnSizeForBackground( + const CSSSizeOrRatio& aIntrinsicSize, const nsSize& aBgPositioningArea, + const StyleBackgroundSize& aLayerSize, StyleImageLayerRepeat aXRepeat, + StyleImageLayerRepeat aYRepeat) { + nsSize imageSize; + + // Size is dictated by cover or contain rules. + if (aLayerSize.IsContain() || aLayerSize.IsCover()) { + nsImageRenderer::FitType fitType = aLayerSize.IsCover() + ? nsImageRenderer::COVER + : nsImageRenderer::CONTAIN; + imageSize = nsImageRenderer::ComputeConstrainedSize( + aBgPositioningArea, aIntrinsicSize.mRatio, fitType); + } else { + MOZ_ASSERT(aLayerSize.IsExplicitSize()); + const auto& width = aLayerSize.explicit_size.width; + const auto& height = aLayerSize.explicit_size.height; + // No cover/contain constraint, use default algorithm. + CSSSizeOrRatio specifiedSize; + if (width.IsLengthPercentage()) { + specifiedSize.SetWidth( + width.AsLengthPercentage().Resolve(aBgPositioningArea.width)); + } + if (height.IsLengthPercentage()) { + specifiedSize.SetHeight( + height.AsLengthPercentage().Resolve(aBgPositioningArea.height)); + } + + imageSize = nsImageRenderer::ComputeConcreteSize( + specifiedSize, aIntrinsicSize, aBgPositioningArea); + } + + // See https://www.w3.org/TR/css3-background/#background-size . + // "If 'background-repeat' is 'round' for one (or both) dimensions, there is a + // second + // step. The UA must scale the image in that dimension (or both dimensions) + // so that it fits a whole number of times in the background positioning + // area." + // "If 'background-repeat' is 'round' for one dimension only and if + // 'background-size' + // is 'auto' for the other dimension, then there is a third step: that other + // dimension is scaled so that the original aspect ratio is restored." + bool isRepeatRoundInBothDimensions = + aXRepeat == StyleImageLayerRepeat::Round && + aYRepeat == StyleImageLayerRepeat::Round; + + // Calculate the rounded size only if the background-size computation + // returned a correct size for the image. + if (imageSize.width && aXRepeat == StyleImageLayerRepeat::Round) { + imageSize.width = nsCSSRendering::ComputeRoundedSize( + imageSize.width, aBgPositioningArea.width); + if (!isRepeatRoundInBothDimensions && aLayerSize.IsExplicitSize() && + aLayerSize.explicit_size.height.IsAuto()) { + // Restore intrinsic ratio + if (aIntrinsicSize.mRatio) { + imageSize.height = + aIntrinsicSize.mRatio.Inverted().ApplyTo(imageSize.width); + } + } + } + + // Calculate the rounded size only if the background-size computation + // returned a correct size for the image. + if (imageSize.height && aYRepeat == StyleImageLayerRepeat::Round) { + imageSize.height = nsCSSRendering::ComputeRoundedSize( + imageSize.height, aBgPositioningArea.height); + if (!isRepeatRoundInBothDimensions && aLayerSize.IsExplicitSize() && + aLayerSize.explicit_size.width.IsAuto()) { + // Restore intrinsic ratio + if (aIntrinsicSize.mRatio) { + imageSize.width = aIntrinsicSize.mRatio.ApplyTo(imageSize.height); + } + } + } + + return imageSize; +} + +/* ComputeSpacedRepeatSize + * aImageDimension: the image width/height + * aAvailableSpace: the background positioning area width/height + * aRepeat: determine whether the image is repeated + * Returns the image size plus gap size of app units for use as spacing + */ +static nscoord ComputeSpacedRepeatSize(nscoord aImageDimension, + nscoord aAvailableSpace, bool& aRepeat) { + float ratio = static_cast<float>(aAvailableSpace) / aImageDimension; + + if (ratio < 2.0f) { // If you can't repeat at least twice, then don't repeat. + aRepeat = false; + return aImageDimension; + } + + aRepeat = true; + return (aAvailableSpace - aImageDimension) / (NSToIntFloor(ratio) - 1); +} + +/* static */ +nscoord nsCSSRendering::ComputeBorderSpacedRepeatSize(nscoord aImageDimension, + nscoord aAvailableSpace, + nscoord& aSpace) { + int32_t count = aImageDimension ? (aAvailableSpace / aImageDimension) : 0; + aSpace = (aAvailableSpace - aImageDimension * count) / (count + 1); + return aSpace + aImageDimension; +} + +nsBackgroundLayerState nsCSSRendering::PrepareImageLayer( + nsPresContext* aPresContext, nsIFrame* aForFrame, uint32_t aFlags, + const nsRect& aBorderArea, const nsRect& aBGClipRect, + const nsStyleImageLayers::Layer& aLayer, bool* aOutIsTransformedFixed) { + /* + * The properties we need to keep in mind when drawing style image + * layers are: + * + * background-image/ mask-image + * background-repeat/ mask-repeat + * background-attachment + * background-position/ mask-position + * background-clip/ mask-clip + * background-origin/ mask-origin + * background-size/ mask-size + * background-blend-mode + * box-decoration-break + * mask-mode + * mask-composite + * + * (background-color applies to the entire element and not to individual + * layers, so it is irrelevant to this method.) + * + * These properties have the following dependencies upon each other when + * determining rendering: + * + * background-image/ mask-image + * no dependencies + * background-repeat/ mask-repeat + * no dependencies + * background-attachment + * no dependencies + * background-position/ mask-position + * depends upon background-size/mask-size (for the image's scaled size) + * and background-break (for the background positioning area) + * background-clip/ mask-clip + * no dependencies + * background-origin/ mask-origin + * depends upon background-attachment (only in the case where that value + * is 'fixed') + * background-size/ mask-size + * depends upon box-decoration-break (for the background positioning area + * for resolving percentages), background-image (for the image's intrinsic + * size), background-repeat (if that value is 'round'), and + * background-origin (for the background painting area, when + * background-repeat is 'round') + * background-blend-mode + * no dependencies + * mask-mode + * no dependencies + * mask-composite + * no dependencies + * box-decoration-break + * no dependencies + * + * As a result of only-if dependencies we don't strictly do a topological + * sort of the above properties when processing, but it's pretty close to one: + * + * background-clip/mask-clip (by caller) + * background-image/ mask-image + * box-decoration-break, background-origin/ mask origin + * background-attachment (postfix for background-origin if 'fixed') + * background-size/ mask-size + * background-position/ mask-position + * background-repeat/ mask-repeat + */ + + uint32_t irFlags = 0; + if (aFlags & nsCSSRendering::PAINTBG_SYNC_DECODE_IMAGES) { + irFlags |= nsImageRenderer::FLAG_SYNC_DECODE_IMAGES; + } + if (aFlags & nsCSSRendering::PAINTBG_TO_WINDOW) { + irFlags |= nsImageRenderer::FLAG_PAINTING_TO_WINDOW; + } + if (aFlags & nsCSSRendering::PAINTBG_HIGH_QUALITY_SCALING) { + irFlags |= nsImageRenderer::FLAG_HIGH_QUALITY_SCALING; + } + + nsBackgroundLayerState state(aForFrame, &aLayer.mImage, irFlags); + if (!state.mImageRenderer.PrepareImage()) { + // There's no image or it's not ready to be painted. + if (aOutIsTransformedFixed && + StyleImageLayerAttachment::Fixed == aLayer.mAttachment) { + nsIFrame* attachedToFrame = aPresContext->PresShell()->GetRootFrame(); + NS_ASSERTION(attachedToFrame, "no root frame"); + nsIFrame* pageContentFrame = nullptr; + if (aPresContext->IsPaginated()) { + pageContentFrame = nsLayoutUtils::GetClosestFrameOfType( + aForFrame, LayoutFrameType::PageContent); + if (pageContentFrame) { + attachedToFrame = pageContentFrame; + } + // else this is an embedded shell and its root frame is what we want + } + + *aOutIsTransformedFixed = + nsLayoutUtils::IsTransformed(aForFrame, attachedToFrame); + } + return state; + } + + // The frame to which the background is attached + nsIFrame* attachedToFrame = aForFrame; + // Is the background marked 'fixed', but affected by a transform? + bool transformedFixed = false; + // Compute background origin area relative to aBorderArea now as we may need + // it to compute the effective image size for a CSS gradient. + nsRect positionArea = ComputeImageLayerPositioningArea( + aPresContext, aForFrame, aBorderArea, aLayer, &attachedToFrame, + &transformedFixed); + if (aOutIsTransformedFixed) { + *aOutIsTransformedFixed = transformedFixed; + } + + // For background-attachment:fixed backgrounds, we'll override the area + // where the background can be drawn to the viewport. + nsRect bgClipRect = aBGClipRect; + + if (StyleImageLayerAttachment::Fixed == aLayer.mAttachment && + !transformedFixed && (aFlags & nsCSSRendering::PAINTBG_TO_WINDOW)) { + bgClipRect = positionArea + aBorderArea.TopLeft(); + } + + StyleImageLayerRepeat repeatX = aLayer.mRepeat.mXRepeat; + StyleImageLayerRepeat repeatY = aLayer.mRepeat.mYRepeat; + + // Scale the image as specified for background-size and background-repeat. + // Also as required for proper background positioning when background-position + // is defined with percentages. + CSSSizeOrRatio intrinsicSize = state.mImageRenderer.ComputeIntrinsicSize(); + nsSize bgPositionSize = positionArea.Size(); + nsSize imageSize = ComputeDrawnSizeForBackground( + intrinsicSize, bgPositionSize, aLayer.mSize, repeatX, repeatY); + + if (imageSize.width <= 0 || imageSize.height <= 0) return state; + + state.mImageRenderer.SetPreferredSize(intrinsicSize, imageSize); + + // Compute the anchor point. + // + // relative to aBorderArea.TopLeft() (which is where the top-left + // of aForFrame's border-box will be rendered) + nsPoint imageTopLeft; + + // Compute the position of the background now that the background's size is + // determined. + nsImageRenderer::ComputeObjectAnchorPoint(aLayer.mPosition, bgPositionSize, + imageSize, &imageTopLeft, + &state.mAnchor); + state.mRepeatSize = imageSize; + if (repeatX == StyleImageLayerRepeat::Space) { + bool isRepeat; + state.mRepeatSize.width = ComputeSpacedRepeatSize( + imageSize.width, bgPositionSize.width, isRepeat); + if (isRepeat) { + imageTopLeft.x = 0; + state.mAnchor.x = 0; + } else { + repeatX = StyleImageLayerRepeat::NoRepeat; + } + } + + if (repeatY == StyleImageLayerRepeat::Space) { + bool isRepeat; + state.mRepeatSize.height = ComputeSpacedRepeatSize( + imageSize.height, bgPositionSize.height, isRepeat); + if (isRepeat) { + imageTopLeft.y = 0; + state.mAnchor.y = 0; + } else { + repeatY = StyleImageLayerRepeat::NoRepeat; + } + } + + imageTopLeft += positionArea.TopLeft(); + state.mAnchor += positionArea.TopLeft(); + state.mDestArea = nsRect(imageTopLeft + aBorderArea.TopLeft(), imageSize); + state.mFillArea = state.mDestArea; + + ExtendMode repeatMode = ExtendMode::CLAMP; + if (repeatX == StyleImageLayerRepeat::Repeat || + repeatX == StyleImageLayerRepeat::Round || + repeatX == StyleImageLayerRepeat::Space) { + state.mFillArea.x = bgClipRect.x; + state.mFillArea.width = bgClipRect.width; + repeatMode = ExtendMode::REPEAT_X; + } + if (repeatY == StyleImageLayerRepeat::Repeat || + repeatY == StyleImageLayerRepeat::Round || + repeatY == StyleImageLayerRepeat::Space) { + state.mFillArea.y = bgClipRect.y; + state.mFillArea.height = bgClipRect.height; + + /*** + * We're repeating on the X axis already, + * so if we have to repeat in the Y axis, + * we really need to repeat in both directions. + */ + if (repeatMode == ExtendMode::REPEAT_X) { + repeatMode = ExtendMode::REPEAT; + } else { + repeatMode = ExtendMode::REPEAT_Y; + } + } + state.mImageRenderer.SetExtendMode(repeatMode); + state.mImageRenderer.SetMaskOp(aLayer.mMaskMode); + + state.mFillArea.IntersectRect(state.mFillArea, bgClipRect); + + return state; +} + +nsRect nsCSSRendering::GetBackgroundLayerRect( + nsPresContext* aPresContext, nsIFrame* aForFrame, const nsRect& aBorderArea, + const nsRect& aClipRect, const nsStyleImageLayers::Layer& aLayer, + uint32_t aFlags) { + Sides skipSides = aForFrame->GetSkipSides(); + nsRect borderArea = + BoxDecorationRectForBackground(aForFrame, aBorderArea, skipSides); + nsBackgroundLayerState state = PrepareImageLayer( + aPresContext, aForFrame, aFlags, borderArea, aClipRect, aLayer); + return state.mFillArea; +} + +// Begin table border-collapsing section +// These functions were written to not disrupt the normal ones and yet satisfy +// some additional requirements At some point, all functions should be unified +// to include the additional functionality that these provide + +static nscoord RoundIntToPixel(nscoord aValue, nscoord aOneDevPixel, + bool aRoundDown = false) { + if (aOneDevPixel <= 0) { + // We must be rendering to a device that has a resolution greater than + // one device pixel! + // In that case, aValue is as accurate as it's going to get. + return aValue; + } + + nscoord halfPixel = NSToCoordRound(aOneDevPixel / 2.0f); + nscoord extra = aValue % aOneDevPixel; + nscoord finalValue = (!aRoundDown && (extra >= halfPixel)) + ? aValue + (aOneDevPixel - extra) + : aValue - extra; + return finalValue; +} + +static nscoord RoundFloatToPixel(float aValue, nscoord aOneDevPixel, + bool aRoundDown = false) { + return RoundIntToPixel(NSToCoordRound(aValue), aOneDevPixel, aRoundDown); +} + +static void SetPoly(const Rect& aRect, Point* poly) { + poly[0].x = aRect.x; + poly[0].y = aRect.y; + poly[1].x = aRect.x + aRect.width; + poly[1].y = aRect.y; + poly[2].x = aRect.x + aRect.width; + poly[2].y = aRect.y + aRect.height; + poly[3].x = aRect.x; + poly[3].y = aRect.y + aRect.height; +} + +static void DrawDashedSegment(DrawTarget& aDrawTarget, nsRect aRect, + nscoord aDashLength, nscolor aColor, + int32_t aAppUnitsPerDevPixel, bool aHorizontal) { + ColorPattern color(ToDeviceColor(aColor)); + DrawOptions drawOptions(1.f, CompositionOp::OP_OVER, AntialiasMode::NONE); + StrokeOptions strokeOptions; + + Float dash[2]; + dash[0] = Float(aDashLength) / aAppUnitsPerDevPixel; + dash[1] = dash[0]; + + strokeOptions.mDashPattern = dash; + strokeOptions.mDashLength = MOZ_ARRAY_LENGTH(dash); + + if (aHorizontal) { + nsPoint left = (aRect.TopLeft() + aRect.BottomLeft()) / 2; + nsPoint right = (aRect.TopRight() + aRect.BottomRight()) / 2; + strokeOptions.mLineWidth = Float(aRect.height) / aAppUnitsPerDevPixel; + StrokeLineWithSnapping(left, right, aAppUnitsPerDevPixel, aDrawTarget, + color, strokeOptions, drawOptions); + } else { + nsPoint top = (aRect.TopLeft() + aRect.TopRight()) / 2; + nsPoint bottom = (aRect.BottomLeft() + aRect.BottomRight()) / 2; + strokeOptions.mLineWidth = Float(aRect.width) / aAppUnitsPerDevPixel; + StrokeLineWithSnapping(top, bottom, aAppUnitsPerDevPixel, aDrawTarget, + color, strokeOptions, drawOptions); + } +} + +static void DrawSolidBorderSegment( + DrawTarget& aDrawTarget, nsRect aRect, nscolor aColor, + int32_t aAppUnitsPerDevPixel, + mozilla::Side aStartBevelSide = mozilla::eSideTop, + nscoord aStartBevelOffset = 0, + mozilla::Side aEndBevelSide = mozilla::eSideTop, + nscoord aEndBevelOffset = 0) { + ColorPattern color(ToDeviceColor(aColor)); + DrawOptions drawOptions(1.f, CompositionOp::OP_OVER, AntialiasMode::NONE); + + nscoord oneDevPixel = NSIntPixelsToAppUnits(1, aAppUnitsPerDevPixel); + // We don't need to bevel single pixel borders + if ((aRect.width == oneDevPixel) || (aRect.height == oneDevPixel) || + ((0 == aStartBevelOffset) && (0 == aEndBevelOffset))) { + // simple rectangle + aDrawTarget.FillRect( + NSRectToSnappedRect(aRect, aAppUnitsPerDevPixel, aDrawTarget), color, + drawOptions); + } else { + // polygon with beveling + Point poly[4]; + SetPoly(NSRectToSnappedRect(aRect, aAppUnitsPerDevPixel, aDrawTarget), + poly); + + Float startBevelOffset = + NSAppUnitsToFloatPixels(aStartBevelOffset, aAppUnitsPerDevPixel); + switch (aStartBevelSide) { + case eSideTop: + poly[0].x += startBevelOffset; + break; + case eSideBottom: + poly[3].x += startBevelOffset; + break; + case eSideRight: + poly[1].y += startBevelOffset; + break; + case eSideLeft: + poly[0].y += startBevelOffset; + } + + Float endBevelOffset = + NSAppUnitsToFloatPixels(aEndBevelOffset, aAppUnitsPerDevPixel); + switch (aEndBevelSide) { + case eSideTop: + poly[1].x -= endBevelOffset; + break; + case eSideBottom: + poly[2].x -= endBevelOffset; + break; + case eSideRight: + poly[2].y -= endBevelOffset; + break; + case eSideLeft: + poly[3].y -= endBevelOffset; + } + + RefPtr<PathBuilder> builder = aDrawTarget.CreatePathBuilder(); + builder->MoveTo(poly[0]); + builder->LineTo(poly[1]); + builder->LineTo(poly[2]); + builder->LineTo(poly[3]); + builder->Close(); + RefPtr<Path> path = builder->Finish(); + aDrawTarget.Fill(path, color, drawOptions); + } +} + +static void GetDashInfo(nscoord aBorderLength, nscoord aDashLength, + nscoord aOneDevPixel, int32_t& aNumDashSpaces, + nscoord& aStartDashLength, nscoord& aEndDashLength) { + aNumDashSpaces = 0; + if (aStartDashLength + aDashLength + aEndDashLength >= aBorderLength) { + aStartDashLength = aBorderLength; + aEndDashLength = 0; + } else { + aNumDashSpaces = + (aBorderLength - aDashLength) / (2 * aDashLength); // round down + nscoord extra = aBorderLength - aStartDashLength - aEndDashLength - + (((2 * aNumDashSpaces) - 1) * aDashLength); + if (extra > 0) { + nscoord half = RoundIntToPixel(extra / 2, aOneDevPixel); + aStartDashLength += half; + aEndDashLength += (extra - half); + } + } +} + +void nsCSSRendering::DrawTableBorderSegment( + DrawTarget& aDrawTarget, StyleBorderStyle aBorderStyle, + nscolor aBorderColor, const nsRect& aBorder, int32_t aAppUnitsPerDevPixel, + mozilla::Side aStartBevelSide, nscoord aStartBevelOffset, + mozilla::Side aEndBevelSide, nscoord aEndBevelOffset) { + bool horizontal = + ((eSideTop == aStartBevelSide) || (eSideBottom == aStartBevelSide)); + nscoord oneDevPixel = NSIntPixelsToAppUnits(1, aAppUnitsPerDevPixel); + + if ((oneDevPixel >= aBorder.width) || (oneDevPixel >= aBorder.height) || + (StyleBorderStyle::Dashed == aBorderStyle) || + (StyleBorderStyle::Dotted == aBorderStyle)) { + // no beveling for 1 pixel border, dash or dot + aStartBevelOffset = 0; + aEndBevelOffset = 0; + } + + switch (aBorderStyle) { + case StyleBorderStyle::None: + case StyleBorderStyle::Hidden: + // NS_ASSERTION(false, "style of none or hidden"); + break; + case StyleBorderStyle::Dotted: + case StyleBorderStyle::Dashed: { + nscoord dashLength = + (StyleBorderStyle::Dashed == aBorderStyle) ? DASH_LENGTH : DOT_LENGTH; + // make the dash length proportional to the border thickness + dashLength *= (horizontal) ? aBorder.height : aBorder.width; + // make the min dash length for the ends 1/2 the dash length + nscoord minDashLength = + (StyleBorderStyle::Dashed == aBorderStyle) + ? RoundFloatToPixel(((float)dashLength) / 2.0f, + aAppUnitsPerDevPixel) + : dashLength; + minDashLength = std::max(minDashLength, oneDevPixel); + nscoord numDashSpaces = 0; + nscoord startDashLength = minDashLength; + nscoord endDashLength = minDashLength; + if (horizontal) { + GetDashInfo(aBorder.width, dashLength, aAppUnitsPerDevPixel, + numDashSpaces, startDashLength, endDashLength); + nsRect rect(aBorder.x, aBorder.y, startDashLength, aBorder.height); + DrawSolidBorderSegment(aDrawTarget, rect, aBorderColor, + aAppUnitsPerDevPixel); + + rect.x += startDashLength + dashLength; + rect.width = + aBorder.width - (startDashLength + endDashLength + dashLength); + DrawDashedSegment(aDrawTarget, rect, dashLength, aBorderColor, + aAppUnitsPerDevPixel, horizontal); + + rect.x += rect.width; + rect.width = endDashLength; + DrawSolidBorderSegment(aDrawTarget, rect, aBorderColor, + aAppUnitsPerDevPixel); + } else { + GetDashInfo(aBorder.height, dashLength, aAppUnitsPerDevPixel, + numDashSpaces, startDashLength, endDashLength); + nsRect rect(aBorder.x, aBorder.y, aBorder.width, startDashLength); + DrawSolidBorderSegment(aDrawTarget, rect, aBorderColor, + aAppUnitsPerDevPixel); + + rect.y += rect.height + dashLength; + rect.height = + aBorder.height - (startDashLength + endDashLength + dashLength); + DrawDashedSegment(aDrawTarget, rect, dashLength, aBorderColor, + aAppUnitsPerDevPixel, horizontal); + + rect.y += rect.height; + rect.height = endDashLength; + DrawSolidBorderSegment(aDrawTarget, rect, aBorderColor, + aAppUnitsPerDevPixel); + } + } break; + default: + AutoTArray<SolidBeveledBorderSegment, 3> segments; + GetTableBorderSolidSegments( + segments, aBorderStyle, aBorderColor, aBorder, aAppUnitsPerDevPixel, + aStartBevelSide, aStartBevelOffset, aEndBevelSide, aEndBevelOffset); + for (const auto& segment : segments) { + DrawSolidBorderSegment( + aDrawTarget, segment.mRect, segment.mColor, aAppUnitsPerDevPixel, + segment.mStartBevel.mSide, segment.mStartBevel.mOffset, + segment.mEndBevel.mSide, segment.mEndBevel.mOffset); + } + break; + } +} + +void nsCSSRendering::GetTableBorderSolidSegments( + nsTArray<SolidBeveledBorderSegment>& aSegments, + StyleBorderStyle aBorderStyle, nscolor aBorderColor, const nsRect& aBorder, + int32_t aAppUnitsPerDevPixel, mozilla::Side aStartBevelSide, + nscoord aStartBevelOffset, mozilla::Side aEndBevelSide, + nscoord aEndBevelOffset) { + const bool horizontal = + eSideTop == aStartBevelSide || eSideBottom == aStartBevelSide; + const nscoord oneDevPixel = NSIntPixelsToAppUnits(1, aAppUnitsPerDevPixel); + + switch (aBorderStyle) { + case StyleBorderStyle::None: + case StyleBorderStyle::Hidden: + return; + case StyleBorderStyle::Dotted: + case StyleBorderStyle::Dashed: + MOZ_ASSERT_UNREACHABLE("Caller should have checked"); + return; + case StyleBorderStyle::Groove: + case StyleBorderStyle::Ridge: + if ((horizontal && (oneDevPixel >= aBorder.height)) || + (!horizontal && (oneDevPixel >= aBorder.width))) { + aSegments.AppendElement( + SolidBeveledBorderSegment{aBorder, + aBorderColor, + {aStartBevelSide, aStartBevelOffset}, + {aEndBevelSide, aEndBevelOffset}}); + } else { + nscoord startBevel = + (aStartBevelOffset > 0) + ? RoundFloatToPixel(0.5f * (float)aStartBevelOffset, + aAppUnitsPerDevPixel, true) + : 0; + nscoord endBevel = + (aEndBevelOffset > 0) + ? RoundFloatToPixel(0.5f * (float)aEndBevelOffset, + aAppUnitsPerDevPixel, true) + : 0; + mozilla::Side ridgeGrooveSide = (horizontal) ? eSideTop : eSideLeft; + // FIXME: In theory, this should use the visited-dependent + // background color, but I don't care. + nscolor bevelColor = + MakeBevelColor(ridgeGrooveSide, aBorderStyle, aBorderColor); + nsRect rect(aBorder); + nscoord half; + if (horizontal) { // top, bottom + half = RoundFloatToPixel(0.5f * (float)aBorder.height, + aAppUnitsPerDevPixel); + rect.height = half; + if (eSideTop == aStartBevelSide) { + rect.x += startBevel; + rect.width -= startBevel; + } + if (eSideTop == aEndBevelSide) { + rect.width -= endBevel; + } + aSegments.AppendElement( + SolidBeveledBorderSegment{rect, + bevelColor, + {aStartBevelSide, startBevel}, + {aEndBevelSide, endBevel}}); + } else { // left, right + half = RoundFloatToPixel(0.5f * (float)aBorder.width, + aAppUnitsPerDevPixel); + rect.width = half; + if (eSideLeft == aStartBevelSide) { + rect.y += startBevel; + rect.height -= startBevel; + } + if (eSideLeft == aEndBevelSide) { + rect.height -= endBevel; + } + aSegments.AppendElement( + SolidBeveledBorderSegment{rect, + bevelColor, + {aStartBevelSide, startBevel}, + {aEndBevelSide, endBevel}}); + } + + rect = aBorder; + ridgeGrooveSide = + (eSideTop == ridgeGrooveSide) ? eSideBottom : eSideRight; + // FIXME: In theory, this should use the visited-dependent + // background color, but I don't care. + bevelColor = + MakeBevelColor(ridgeGrooveSide, aBorderStyle, aBorderColor); + if (horizontal) { + rect.y = rect.y + half; + rect.height = aBorder.height - half; + if (eSideBottom == aStartBevelSide) { + rect.x += startBevel; + rect.width -= startBevel; + } + if (eSideBottom == aEndBevelSide) { + rect.width -= endBevel; + } + aSegments.AppendElement( + SolidBeveledBorderSegment{rect, + bevelColor, + {aStartBevelSide, startBevel}, + {aEndBevelSide, endBevel}}); + } else { + rect.x = rect.x + half; + rect.width = aBorder.width - half; + if (eSideRight == aStartBevelSide) { + rect.y += aStartBevelOffset - startBevel; + rect.height -= startBevel; + } + if (eSideRight == aEndBevelSide) { + rect.height -= endBevel; + } + aSegments.AppendElement( + SolidBeveledBorderSegment{rect, + bevelColor, + {aStartBevelSide, startBevel}, + {aEndBevelSide, endBevel}}); + } + } + break; + case StyleBorderStyle::Double: + // We can only do "double" borders if the thickness of the border + // is more than 2px. Otherwise, we fall through to painting a + // solid border. + if ((aBorder.width > 2 * oneDevPixel || horizontal) && + (aBorder.height > 2 * oneDevPixel || !horizontal)) { + nscoord startBevel = + (aStartBevelOffset > 0) + ? RoundFloatToPixel(0.333333f * (float)aStartBevelOffset, + aAppUnitsPerDevPixel) + : 0; + nscoord endBevel = + (aEndBevelOffset > 0) + ? RoundFloatToPixel(0.333333f * (float)aEndBevelOffset, + aAppUnitsPerDevPixel) + : 0; + if (horizontal) { // top, bottom + nscoord thirdHeight = RoundFloatToPixel( + 0.333333f * (float)aBorder.height, aAppUnitsPerDevPixel); + + // draw the top line or rect + nsRect topRect(aBorder.x, aBorder.y, aBorder.width, thirdHeight); + if (eSideTop == aStartBevelSide) { + topRect.x += aStartBevelOffset - startBevel; + topRect.width -= aStartBevelOffset - startBevel; + } + if (eSideTop == aEndBevelSide) { + topRect.width -= aEndBevelOffset - endBevel; + } + + aSegments.AppendElement( + SolidBeveledBorderSegment{topRect, + aBorderColor, + {aStartBevelSide, startBevel}, + {aEndBevelSide, endBevel}}); + + // draw the botom line or rect + nscoord heightOffset = aBorder.height - thirdHeight; + nsRect bottomRect(aBorder.x, aBorder.y + heightOffset, aBorder.width, + aBorder.height - heightOffset); + if (eSideBottom == aStartBevelSide) { + bottomRect.x += aStartBevelOffset - startBevel; + bottomRect.width -= aStartBevelOffset - startBevel; + } + if (eSideBottom == aEndBevelSide) { + bottomRect.width -= aEndBevelOffset - endBevel; + } + aSegments.AppendElement( + SolidBeveledBorderSegment{bottomRect, + aBorderColor, + {aStartBevelSide, startBevel}, + {aEndBevelSide, endBevel}}); + } else { // left, right + nscoord thirdWidth = RoundFloatToPixel( + 0.333333f * (float)aBorder.width, aAppUnitsPerDevPixel); + + nsRect leftRect(aBorder.x, aBorder.y, thirdWidth, aBorder.height); + if (eSideLeft == aStartBevelSide) { + leftRect.y += aStartBevelOffset - startBevel; + leftRect.height -= aStartBevelOffset - startBevel; + } + if (eSideLeft == aEndBevelSide) { + leftRect.height -= aEndBevelOffset - endBevel; + } + + aSegments.AppendElement( + SolidBeveledBorderSegment{leftRect, + aBorderColor, + {aStartBevelSide, startBevel}, + {aEndBevelSide, endBevel}}); + + nscoord widthOffset = aBorder.width - thirdWidth; + nsRect rightRect(aBorder.x + widthOffset, aBorder.y, + aBorder.width - widthOffset, aBorder.height); + if (eSideRight == aStartBevelSide) { + rightRect.y += aStartBevelOffset - startBevel; + rightRect.height -= aStartBevelOffset - startBevel; + } + if (eSideRight == aEndBevelSide) { + rightRect.height -= aEndBevelOffset - endBevel; + } + aSegments.AppendElement( + SolidBeveledBorderSegment{rightRect, + aBorderColor, + {aStartBevelSide, startBevel}, + {aEndBevelSide, endBevel}}); + } + break; + } + // else fall through to solid + [[fallthrough]]; + case StyleBorderStyle::Solid: + aSegments.AppendElement( + SolidBeveledBorderSegment{aBorder, + aBorderColor, + {aStartBevelSide, aStartBevelOffset}, + {aEndBevelSide, aEndBevelOffset}}); + break; + case StyleBorderStyle::Outset: + case StyleBorderStyle::Inset: + MOZ_ASSERT_UNREACHABLE( + "inset, outset should have been converted to groove, ridge"); + break; + } +} + +// End table border-collapsing section + +Rect nsCSSRendering::ExpandPaintingRectForDecorationLine( + nsIFrame* aFrame, const uint8_t aStyle, const Rect& aClippedRect, + const Float aICoordInFrame, const Float aCycleLength, bool aVertical) { + switch (aStyle) { + case NS_STYLE_TEXT_DECORATION_STYLE_DOTTED: + case NS_STYLE_TEXT_DECORATION_STYLE_DASHED: + case NS_STYLE_TEXT_DECORATION_STYLE_WAVY: + break; + default: + NS_ERROR("Invalid style was specified"); + return aClippedRect; + } + + nsBlockFrame* block = nullptr; + // Note that when we paint the decoration lines in relative positioned + // box, we should paint them like all of the boxes are positioned as static. + nscoord framePosInBlockAppUnits = 0; + for (nsIFrame* f = aFrame; f; f = f->GetParent()) { + block = do_QueryFrame(f); + if (block) { + break; + } + framePosInBlockAppUnits += + aVertical ? f->GetNormalPosition().y : f->GetNormalPosition().x; + } + + NS_ENSURE_TRUE(block, aClippedRect); + + nsPresContext* pc = aFrame->PresContext(); + Float framePosInBlock = + Float(pc->AppUnitsToGfxUnits(framePosInBlockAppUnits)); + int32_t rectPosInBlock = int32_t(NS_round(framePosInBlock + aICoordInFrame)); + int32_t extraStartEdge = + rectPosInBlock - (rectPosInBlock / int32_t(aCycleLength) * aCycleLength); + Rect rect(aClippedRect); + if (aVertical) { + rect.y -= extraStartEdge; + rect.height += extraStartEdge; + } else { + rect.x -= extraStartEdge; + rect.width += extraStartEdge; + } + return rect; +} + +// Converts a GfxFont to an SkFont +// Either returns true if it was successful, or false if something went wrong +static bool GetSkFontFromGfxFont(DrawTarget& aDrawTarget, gfxFont* aFont, + SkFont& aSkFont) { + RefPtr<ScaledFont> scaledFont = aFont->GetScaledFont(&aDrawTarget); + if (!scaledFont) { + return false; + } + + ScaledFontBase* fontBase = static_cast<ScaledFontBase*>(scaledFont.get()); + + SkTypeface* typeface = fontBase->GetSkTypeface(); + if (!typeface) { + return false; + } + + aSkFont = SkFont(sk_ref_sp(typeface), SkFloatToScalar(fontBase->GetSize())); + return true; +} + +// Computes data used to position the decoration line within a +// SkTextBlob, data is returned through aBounds +static void GetPositioning( + const nsCSSRendering::PaintDecorationLineParams& aParams, const Rect& aRect, + Float aOneCSSPixel, Float aCenterBaselineOffset, SkScalar aBounds[]) { + /** + * How Positioning in Skia Works + * Take the letter "n" for example + * We set textPos as 0, 0 + * This is represented in Skia like so (not to scale) + * ^ + * -10px | _ __ + * | | '_ \ + * -5px | | | | | + * y-axis | |_| |_| + * (0,0) -----------------------> + * | 5px 10px + * 5px | + * | + * 10px | + * v + * 0 on the x axis is a line that touches the bottom of the n + * (0,0) is the bottom left-hand corner of the n character + * Moving "up" from the n is going in a negative y direction + * Moving "down" from the n is going in a positive y direction + * + * The intercepts that are returned in this arrangement will be + * offset by the original point it starts at. (This happens in + * the SkipInk function below). + * + * In Skia, text MUST be laid out such that the next character + * in the RunBuffer is further along the x-axis than the previous + * character, otherwise there is undefined/strange behavior. + */ + + Float rectThickness = aParams.vertical ? aRect.Width() : aRect.Height(); + + // the upper and lower lines/edges of the under or over line + SkScalar upperLine, lowerLine; + if (aParams.decoration == mozilla::StyleTextDecorationLine::OVERLINE) { + lowerLine = + -aParams.offset + aParams.defaultLineThickness - aCenterBaselineOffset; + upperLine = lowerLine - rectThickness; + } else { + // underlines in vertical text are offset from the center of + // the text, and not the baseline + // Skia sets the text at it's baseline so we have to offset it + // for text in vertical-* writing modes + upperLine = -aParams.offset - aCenterBaselineOffset; + lowerLine = upperLine + rectThickness; + } + + // set up the bounds, add in a little padding to the thickness of the line + // (unless the line is <= 1 CSS pixel thick) + Float lineThicknessPadding = aParams.lineSize.height > aOneCSSPixel + ? 0.25f * aParams.lineSize.height + : 0; + // don't allow padding greater than 0.75 CSS pixel + lineThicknessPadding = std::min(lineThicknessPadding, 0.75f * aOneCSSPixel); + aBounds[0] = upperLine - lineThicknessPadding; + aBounds[1] = lowerLine + lineThicknessPadding; +} + +// positions an individual glyph according to the given offset +static SkPoint GlyphPosition(const gfxTextRun::DetailedGlyph& aGlyph, + const SkPoint& aTextPos, + int32_t aAppUnitsPerDevPixel) { + SkPoint point = {aGlyph.mOffset.x, aGlyph.mOffset.y}; + + // convert to device pixels + point.fX /= (float)aAppUnitsPerDevPixel; + point.fY /= (float)aAppUnitsPerDevPixel; + + // add offsets + point.fX += aTextPos.fX; + point.fY += aTextPos.fY; + return point; +} + +// returns a count of all the glyphs that will be rendered +// excludes ligature continuations, includes the number of individual +// glyph records. This includes the number of DetailedGlyphs that a single +// CompressedGlyph record points to. This function is necessary because Skia +// needs the total length of glyphs to add to it's run buffer before it creates +// the RunBuffer object, and this cannot be resized later. +static uint32_t CountAllGlyphs( + const gfxTextRun* aTextRun, + const gfxTextRun::CompressedGlyph* aCompressedGlyph, uint32_t aStringStart, + uint32_t aStringEnd) { + uint32_t totalGlyphCount = 0; + + for (const gfxTextRun::CompressedGlyph* cg = aCompressedGlyph + aStringStart; + cg < aCompressedGlyph + aStringEnd; ++cg) { + totalGlyphCount += cg->IsSimpleGlyph() ? 1 : cg->GetGlyphCount(); + } + + return totalGlyphCount; +} + +static void AddDetailedGlyph(const SkTextBlobBuilder::RunBuffer& aRunBuffer, + const gfxTextRun::DetailedGlyph& aGlyph, + int aIndex, float aAppUnitsPerDevPixel, + SkPoint& aTextPos) { + // add glyph ID to the run buffer at i + aRunBuffer.glyphs[aIndex] = aGlyph.mGlyphID; + + // position the glyph correctly using the detailed offsets + SkPoint position = GlyphPosition(aGlyph, aTextPos, aAppUnitsPerDevPixel); + aRunBuffer.pos[2 * aIndex] = position.fX; + aRunBuffer.pos[(2 * aIndex) + 1] = position.fY; + + // increase aTextPos.fx by the advance + aTextPos.fX += ((float)aGlyph.mAdvance / aAppUnitsPerDevPixel); +} + +static void AddSimpleGlyph(const SkTextBlobBuilder::RunBuffer& aRunBuffer, + const gfxTextRun::CompressedGlyph& aGlyph, + int aIndex, float aAppUnitsPerDevPixel, + SkPoint& aTextPos) { + aRunBuffer.glyphs[aIndex] = aGlyph.GetSimpleGlyph(); + + // simple glyphs are offset from 0, so we'll just use textPos + aRunBuffer.pos[2 * aIndex] = aTextPos.fX; + aRunBuffer.pos[(2 * aIndex) + 1] = aTextPos.fY; + + // increase aTextPos.fX by the advance + aTextPos.fX += ((float)aGlyph.GetSimpleAdvance() / aAppUnitsPerDevPixel); +} + +// Sets up a Skia TextBlob of the specified font, text position, and made up of +// the glyphs between aStringStart and aStringEnd. Handles RTL and LTR text +// and positions each glyph within the text blob +static sk_sp<const SkTextBlob> CreateTextBlob( + const gfxTextRun* aTextRun, + const gfxTextRun::CompressedGlyph* aCompressedGlyph, const SkFont& aFont, + const gfxTextRun::PropertyProvider::Spacing* aSpacing, + uint32_t aStringStart, uint32_t aStringEnd, float aAppUnitsPerDevPixel, + SkPoint& aTextPos, int32_t& aSpacingOffset) { + // allocate space for the run buffer, then fill it with the glyphs + uint32_t len = + CountAllGlyphs(aTextRun, aCompressedGlyph, aStringStart, aStringEnd); + if (len <= 0) { + return nullptr; + } + + SkTextBlobBuilder builder; + const SkTextBlobBuilder::RunBuffer& run = builder.allocRunPos(aFont, len); + + // RTL text should be read in by glyph starting at aStringEnd - 1 down until + // aStringStart. + bool isRTL = aTextRun->IsRightToLeft(); + uint32_t currIndex = isRTL ? aStringEnd - 1 : aStringStart; // textRun index + // currIndex will be advanced by |step| until it reaches |limit|, which is the + // final index to be handled (NOT one beyond the final index) + int step = isRTL ? -1 : 1; + uint32_t limit = isRTL ? aStringStart : aStringEnd - 1; + + uint32_t i = 0; // index into the SkTextBlob we're building + while (true) { + // Loop exit test is below, just before we update currIndex. + aTextPos.fX += + isRTL ? aSpacing[aSpacingOffset].mAfter / aAppUnitsPerDevPixel + : aSpacing[aSpacingOffset].mBefore / aAppUnitsPerDevPixel; + + if (aCompressedGlyph[currIndex].IsSimpleGlyph()) { + MOZ_ASSERT(i < len, "glyph count error!"); + AddSimpleGlyph(run, aCompressedGlyph[currIndex], i, aAppUnitsPerDevPixel, + aTextPos); + i++; + } else { + // if it's detailed, potentially add multiple into run.glyphs + uint32_t count = aCompressedGlyph[currIndex].GetGlyphCount(); + if (count > 0) { + gfxTextRun::DetailedGlyph* detailGlyph = + aTextRun->GetDetailedGlyphs(currIndex); + for (uint32_t d = isRTL ? count - 1 : 0; count; count--, d += step) { + MOZ_ASSERT(i < len, "glyph count error!"); + AddDetailedGlyph(run, detailGlyph[d], i, aAppUnitsPerDevPixel, + aTextPos); + i++; + } + } + } + aTextPos.fX += isRTL + ? aSpacing[aSpacingOffset].mBefore / aAppUnitsPerDevPixel + : aSpacing[aSpacingOffset].mAfter / aAppUnitsPerDevPixel; + aSpacingOffset += step; + + if (currIndex == limit) { + break; + } + currIndex += step; + } + + MOZ_ASSERT(i == len, "glyph count error!"); + + return builder.make(); +} + +// Given a TextBlob, the bounding lines, and the set of current intercepts this +// function adds the intercepts for the current TextBlob into the given set of +// previoulsy calculated intercepts. This set is either of length 0, or a +// multiple of 2 (since every intersection with a piece of text results in two +// intercepts: entering/exiting) +static void GetTextIntercepts(const sk_sp<const SkTextBlob>& aBlob, + const SkScalar aBounds[], + nsTArray<SkScalar>& aIntercepts) { + // https://skia.org/user/api/SkTextBlob_Reference#Text_Blob_Text_Intercepts + int count = aBlob->getIntercepts(aBounds, nullptr); + if (count < 2) { + return; + } + aBlob->getIntercepts(aBounds, aIntercepts.AppendElements(count)); +} + +// This function, given a set of intercepts that represent each intersection +// between an under/overline and text, makes a series of calls to +// PaintDecorationLineInternal that paints a series of clip rects which +// implement the text-decoration-skip-ink property +// Logic for where to place each clipped rect, and the length of each rect is +// included here +static void SkipInk(nsIFrame* aFrame, DrawTarget& aDrawTarget, + const nsCSSRendering::PaintDecorationLineParams& aParams, + const nsTArray<SkScalar>& aIntercepts, Float aPadding, + Rect& aRect) { + nsCSSRendering::PaintDecorationLineParams clipParams = aParams; + int length = aIntercepts.Length(); + + SkScalar startIntercept = 0; + SkScalar endIntercept = 0; + + // keep track of the direction we are drawing the clipped rects in + // for sideways text, our intercepts from the first glyph are actually + // decreasing (towards the top edge of the page), so we use a negative + // direction + Float dir = 1.0f; + Float lineStart = aParams.vertical ? aParams.pt.y : aParams.pt.x; + Float lineEnd = lineStart + aParams.lineSize.width; + if (aParams.sidewaysLeft) { + dir = -1.0f; + std::swap(lineStart, lineEnd); + } + + for (int i = 0; i <= length; i += 2) { + // handle start/end edge cases and set up general case + startIntercept = (i > 0) ? (dir * aIntercepts[i - 1]) + lineStart + : lineStart - (dir * aPadding); + endIntercept = (i < length) ? (dir * aIntercepts[i]) + lineStart + : lineEnd + (dir * aPadding); + + // remove padding at both ends for width + // the start of the line is calculated so the padding removes just + // enough so that the line starts at its normal position + clipParams.lineSize.width = + (dir * (endIntercept - startIntercept)) - (2.0 * aPadding); + + // Don't draw decoration lines that have a smaller width than 1, or half + // the line-end padding dimension. + if (clipParams.lineSize.width < std::max(aPadding * 0.5, 1.0)) { + continue; + } + + // Start the line right after the intercept's location plus room for + // padding; snap the rect edges to device pixels for consistent rendering + // of dots across separate fragments of a dotted line. + if (aParams.vertical) { + clipParams.pt.y = aParams.sidewaysLeft ? endIntercept + aPadding + : startIntercept + aPadding; + aRect.y = std::floor(clipParams.pt.y + 0.5); + aRect.SetBottomEdge( + std::floor(clipParams.pt.y + clipParams.lineSize.width + 0.5)); + } else { + clipParams.pt.x = startIntercept + aPadding; + aRect.x = std::floor(clipParams.pt.x + 0.5); + aRect.SetRightEdge( + std::floor(clipParams.pt.x + clipParams.lineSize.width + 0.5)); + } + + nsCSSRendering::PaintDecorationLineInternal(aFrame, aDrawTarget, clipParams, + aRect); + } +} + +void nsCSSRendering::PaintDecorationLine( + nsIFrame* aFrame, DrawTarget& aDrawTarget, + const PaintDecorationLineParams& aParams) { + NS_ASSERTION(aParams.style != NS_STYLE_TEXT_DECORATION_STYLE_NONE, + "aStyle is none"); + + Rect rect = ToRect(GetTextDecorationRectInternal(aParams.pt, aParams)); + if (rect.IsEmpty() || !rect.Intersects(aParams.dirtyRect)) { + return; + } + + if (aParams.decoration != StyleTextDecorationLine::UNDERLINE && + aParams.decoration != StyleTextDecorationLine::OVERLINE && + aParams.decoration != StyleTextDecorationLine::LINE_THROUGH) { + MOZ_ASSERT_UNREACHABLE("Invalid text decoration value"); + return; + } + + // Check if decoration line will skip past ascenders/descenders + // text-decoration-skip-ink only applies to overlines/underlines + mozilla::StyleTextDecorationSkipInk skipInk = + aFrame->StyleText()->mTextDecorationSkipInk; + bool skipInkEnabled = + skipInk != mozilla::StyleTextDecorationSkipInk::None && + aParams.decoration != StyleTextDecorationLine::LINE_THROUGH; + + if (!skipInkEnabled || aParams.glyphRange.Length() == 0) { + PaintDecorationLineInternal(aFrame, aDrawTarget, aParams, rect); + return; + } + + // check if the frame is a text frame or not + nsTextFrame* textFrame = nullptr; + if (aFrame->IsTextFrame()) { + textFrame = static_cast<nsTextFrame*>(aFrame); + } else { + PaintDecorationLineInternal(aFrame, aDrawTarget, aParams, rect); + return; + } + + // get text run and current text offset (for line wrapping) + gfxTextRun* textRun = + textFrame->GetTextRun(nsTextFrame::TextRunType::eInflated); + + // used for conversions from app units to device pixels + int32_t appUnitsPerDevPixel = aFrame->PresContext()->AppUnitsPerDevPixel(); + + // pointer to the array of glyphs for this TextRun + gfxTextRun::CompressedGlyph* characterGlyphs = textRun->GetCharacterGlyphs(); + + // get positioning info + SkPoint textPos = {0, aParams.baselineOffset}; + SkScalar bounds[] = {0, 0}; + Float oneCSSPixel = aFrame->PresContext()->CSSPixelsToDevPixels(1.0f); + if (!textRun->UseCenterBaseline()) { + GetPositioning(aParams, rect, oneCSSPixel, 0, bounds); + } + + // array for the text intercepts + AutoTArray<SkScalar, 256> intercepts; + + // array for spacing data + AutoTArray<gfxTextRun::PropertyProvider::Spacing, 64> spacing; + spacing.SetLength(aParams.glyphRange.Length()); + if (aParams.provider != nullptr) { + aParams.provider->GetSpacing(aParams.glyphRange, spacing.Elements()); + } + + // loop through each glyph run + // in most cases there will only be one + bool isRTL = textRun->IsRightToLeft(); + int32_t spacingOffset = isRTL ? aParams.glyphRange.Length() - 1 : 0; + gfxTextRun::GlyphRunIterator iter(textRun, aParams.glyphRange, isRTL); + + // For any glyph run where we don't actually do skipping, we'll need to + // advance the current position by its width. + // (For runs we do process, CreateTextBlob will update the position.) + auto currentGlyphRunAdvance = [&]() { + return textRun->GetAdvanceWidth( + gfxTextRun::Range(iter.GetStringStart(), iter.GetStringEnd()), + aParams.provider) / + appUnitsPerDevPixel; + }; + + while (iter.NextRun()) { + if (iter.GetGlyphRun()->mOrientation == + mozilla::gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT || + (iter.GetGlyphRun()->mIsCJK && + skipInk == mozilla::StyleTextDecorationSkipInk::Auto)) { + // We don't support upright text in vertical modes currently + // (see https://bugzilla.mozilla.org/show_bug.cgi?id=1572294), + // but we do need to update textPos so that following runs will be + // correctly positioned. + // We also don't apply skip-ink to CJK text runs because many fonts + // have an underline that looks really bad if this is done + // (see https://bugzilla.mozilla.org/show_bug.cgi?id=1573249), + // when skip-ink is set to 'auto'. + textPos.fX += currentGlyphRunAdvance(); + continue; + } + + gfxFont* font = iter.GetGlyphRun()->mFont; + // Don't try to apply skip-ink to 'sbix' fonts like Apple Color Emoji, + // because old macOS (10.9) may crash trying to retrieve glyph paths + // that don't exist. + if (font->GetFontEntry()->HasFontTable(TRUETYPE_TAG('s', 'b', 'i', 'x'))) { + textPos.fX += currentGlyphRunAdvance(); + continue; + } + + // get a Skia version of the glyph run's font + SkFont skiafont; + if (!GetSkFontFromGfxFont(aDrawTarget, font, skiafont)) { + PaintDecorationLineInternal(aFrame, aDrawTarget, aParams, rect); + return; + } + + // Create a text blob with correctly positioned glyphs. This also updates + // textPos.fX with the advance of the glyphs. + sk_sp<const SkTextBlob> textBlob = + CreateTextBlob(textRun, characterGlyphs, skiafont, spacing.Elements(), + iter.GetStringStart(), iter.GetStringEnd(), + (float)appUnitsPerDevPixel, textPos, spacingOffset); + + if (!textBlob) { + textPos.fX += currentGlyphRunAdvance(); + continue; + } + + if (textRun->UseCenterBaseline()) { + // writing modes that use a center baseline need to be adjusted on a + // font-by-font basis since Skia lines up the text on a alphabetic + // baseline, but for some vertical-* writing modes the offset is from the + // center. + gfxFont::Metrics metrics = font->GetMetrics(nsFontMetrics::eHorizontal); + Float centerToBaseline = (metrics.emAscent - metrics.emDescent) / 2.0f; + GetPositioning(aParams, rect, oneCSSPixel, centerToBaseline, bounds); + } + + // compute the text intercepts that need to be skipped + GetTextIntercepts(textBlob, bounds, intercepts); + } + bool needsSkipInk = intercepts.Length() > 0; + + if (needsSkipInk) { + // Padding between glyph intercepts and the decoration line: we use the + // decoration line thickness, clamped to a minimum of 1px and a maximum + // of 0.2em. + Float padding = + std::min(std::max(aParams.lineSize.height, oneCSSPixel), + Float(textRun->GetFontGroup()->GetStyle()->size / 5.0)); + SkipInk(aFrame, aDrawTarget, aParams, intercepts, padding, rect); + } else { + PaintDecorationLineInternal(aFrame, aDrawTarget, aParams, rect); + } +} + +void nsCSSRendering::PaintDecorationLineInternal( + nsIFrame* aFrame, DrawTarget& aDrawTarget, + const PaintDecorationLineParams& aParams, Rect aRect) { + Float lineThickness = std::max(NS_round(aParams.lineSize.height), 1.0); + + DeviceColor color = ToDeviceColor(aParams.color); + ColorPattern colorPat(color); + StrokeOptions strokeOptions(lineThickness); + DrawOptions drawOptions; + + Float dash[2]; + + AutoPopClips autoPopClips(&aDrawTarget); + + mozilla::layout::TextDrawTarget* textDrawer = nullptr; + if (aDrawTarget.GetBackendType() == BackendType::WEBRENDER_TEXT) { + textDrawer = static_cast<mozilla::layout::TextDrawTarget*>(&aDrawTarget); + } + + switch (aParams.style) { + case NS_STYLE_TEXT_DECORATION_STYLE_SOLID: + case NS_STYLE_TEXT_DECORATION_STYLE_DOUBLE: + break; + case NS_STYLE_TEXT_DECORATION_STYLE_DASHED: { + autoPopClips.PushClipRect(aRect); + Float dashWidth = lineThickness * DOT_LENGTH * DASH_LENGTH; + dash[0] = dashWidth; + dash[1] = dashWidth; + strokeOptions.mDashPattern = dash; + strokeOptions.mDashLength = MOZ_ARRAY_LENGTH(dash); + strokeOptions.mLineCap = CapStyle::BUTT; + aRect = ExpandPaintingRectForDecorationLine( + aFrame, aParams.style, aRect, aParams.icoordInFrame, dashWidth * 2, + aParams.vertical); + // We should continue to draw the last dash even if it is not in the rect. + aRect.width += dashWidth; + break; + } + case NS_STYLE_TEXT_DECORATION_STYLE_DOTTED: { + autoPopClips.PushClipRect(aRect); + Float dashWidth = lineThickness * DOT_LENGTH; + if (lineThickness > 2.0) { + dash[0] = 0.f; + dash[1] = dashWidth * 2.f; + strokeOptions.mLineCap = CapStyle::ROUND; + } else { + dash[0] = dashWidth; + dash[1] = dashWidth; + } + strokeOptions.mDashPattern = dash; + strokeOptions.mDashLength = MOZ_ARRAY_LENGTH(dash); + aRect = ExpandPaintingRectForDecorationLine( + aFrame, aParams.style, aRect, aParams.icoordInFrame, dashWidth * 2, + aParams.vertical); + // We should continue to draw the last dot even if it is not in the rect. + aRect.width += dashWidth; + break; + } + case NS_STYLE_TEXT_DECORATION_STYLE_WAVY: + autoPopClips.PushClipRect(aRect); + if (lineThickness > 2.0) { + drawOptions.mAntialiasMode = AntialiasMode::SUBPIXEL; + } else { + // Don't use anti-aliasing here. Because looks like lighter color wavy + // line at this case. And probably, users don't think the + // non-anti-aliased wavy line is not pretty. + drawOptions.mAntialiasMode = AntialiasMode::NONE; + } + break; + default: + NS_ERROR("Invalid style value!"); + return; + } + + // The block-direction position should be set to the middle of the line. + if (aParams.vertical) { + aRect.x += lineThickness / 2; + } else { + aRect.y += lineThickness / 2; + } + + switch (aParams.style) { + case NS_STYLE_TEXT_DECORATION_STYLE_SOLID: + case NS_STYLE_TEXT_DECORATION_STYLE_DOTTED: + case NS_STYLE_TEXT_DECORATION_STYLE_DASHED: { + Point p1 = aRect.TopLeft(); + Point p2 = aParams.vertical ? aRect.BottomLeft() : aRect.TopRight(); + if (textDrawer) { + textDrawer->AppendDecoration(p1, p2, lineThickness, aParams.vertical, + color, aParams.style); + } else { + aDrawTarget.StrokeLine(p1, p2, colorPat, strokeOptions, drawOptions); + } + return; + } + case NS_STYLE_TEXT_DECORATION_STYLE_DOUBLE: { + /** + * We are drawing double line as: + * + * +-------------------------------------------+ + * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| ^ + * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| | lineThickness + * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| v + * | | + * | | + * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| ^ + * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| | lineThickness + * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| v + * +-------------------------------------------+ + */ + Point p1a = aRect.TopLeft(); + Point p2a = aParams.vertical ? aRect.BottomLeft() : aRect.TopRight(); + + if (aParams.vertical) { + aRect.width -= lineThickness; + } else { + aRect.height -= lineThickness; + } + + Point p1b = aParams.vertical ? aRect.TopRight() : aRect.BottomLeft(); + Point p2b = aRect.BottomRight(); + + if (textDrawer) { + textDrawer->AppendDecoration(p1a, p2a, lineThickness, aParams.vertical, + color, + NS_STYLE_TEXT_DECORATION_STYLE_SOLID); + textDrawer->AppendDecoration(p1b, p2b, lineThickness, aParams.vertical, + color, + NS_STYLE_TEXT_DECORATION_STYLE_SOLID); + } else { + aDrawTarget.StrokeLine(p1a, p2a, colorPat, strokeOptions, drawOptions); + aDrawTarget.StrokeLine(p1b, p2b, colorPat, strokeOptions, drawOptions); + } + return; + } + case NS_STYLE_TEXT_DECORATION_STYLE_WAVY: { + /** + * We are drawing wavy line as: + * + * P: Path, X: Painted pixel + * + * +---------------------------------------+ + * XX|X XXXXXX XXXXXX | + * PP|PX XPPPPPPX XPPPPPPX | ^ + * XX|XPX XPXXXXXXPX XPXXXXXXPX| | + * | XPX XPX XPX XPX XP|X |adv + * | XPXXXXXXPX XPXXXXXXPX X|PX | + * | XPPPPPPX XPPPPPPX |XPX v + * | XXXXXX XXXXXX | XX + * +---------------------------------------+ + * <---><---> ^ + * adv flatLengthAtVertex rightMost + * + * 1. Always starts from top-left of the drawing area, however, we need + * to draw the line from outside of the rect. Because the start + * point of the line is not good style if we draw from inside it. + * 2. First, draw horizontal line from outside the rect to top-left of + * the rect; + * 3. Goes down to bottom of the area at 45 degrees. + * 4. Slides to right horizontaly, see |flatLengthAtVertex|. + * 5. Goes up to top of the area at 45 degrees. + * 6. Slides to right horizontaly. + * 7. Repeat from 2 until reached to right-most edge of the area. + * + * In the vertical case, swap horizontal and vertical coordinates and + * directions in the above description. + */ + + Float& rectICoord = aParams.vertical ? aRect.y : aRect.x; + Float& rectISize = aParams.vertical ? aRect.height : aRect.width; + const Float rectBSize = aParams.vertical ? aRect.width : aRect.height; + + const Float adv = rectBSize - lineThickness; + const Float flatLengthAtVertex = + std::max((lineThickness - 1.0) * 2.0, 1.0); + + // Align the start of wavy lines to the nearest ancestor block. + const Float cycleLength = 2 * (adv + flatLengthAtVertex); + aRect = ExpandPaintingRectForDecorationLine( + aFrame, aParams.style, aRect, aParams.icoordInFrame, cycleLength, + aParams.vertical); + + if (textDrawer) { + // Undo attempted centering + Float& rectBCoord = aParams.vertical ? aRect.x : aRect.y; + rectBCoord -= lineThickness / 2; + + textDrawer->AppendWavyDecoration(aRect, lineThickness, aParams.vertical, + color); + return; + } + + // figure out if we can trim whole cycles from the left and right edges + // of the line, to try and avoid creating an unnecessarily long and + // complex path (but don't do this for webrender, ) + const Float dirtyRectICoord = + aParams.vertical ? aParams.dirtyRect.y : aParams.dirtyRect.x; + int32_t skipCycles = floor((dirtyRectICoord - rectICoord) / cycleLength); + if (skipCycles > 0) { + rectICoord += skipCycles * cycleLength; + rectISize -= skipCycles * cycleLength; + } + + rectICoord += lineThickness / 2.0; + + Point pt(aRect.TopLeft()); + Float& ptICoord = aParams.vertical ? pt.y : pt.x; + Float& ptBCoord = aParams.vertical ? pt.x : pt.y; + if (aParams.vertical) { + ptBCoord += adv; + } + Float iCoordLimit = ptICoord + rectISize + lineThickness; + + const Float dirtyRectIMost = aParams.vertical ? aParams.dirtyRect.YMost() + : aParams.dirtyRect.XMost(); + skipCycles = floor((iCoordLimit - dirtyRectIMost) / cycleLength); + if (skipCycles > 0) { + iCoordLimit -= skipCycles * cycleLength; + } + + RefPtr<PathBuilder> builder = aDrawTarget.CreatePathBuilder(); + RefPtr<Path> path; + + ptICoord -= lineThickness; + builder->MoveTo(pt); // 1 + + ptICoord = rectICoord; + builder->LineTo(pt); // 2 + + // In vertical mode, to go "down" relative to the text we need to + // decrease the block coordinate, whereas in horizontal we increase + // it. So the sense of this flag is effectively inverted. + bool goDown = !aParams.vertical; + uint32_t iter = 0; + while (ptICoord < iCoordLimit) { + if (++iter > 1000) { + // stroke the current path and start again, to avoid pathological + // behavior in cairo with huge numbers of path segments + path = builder->Finish(); + aDrawTarget.Stroke(path, colorPat, strokeOptions, drawOptions); + builder = aDrawTarget.CreatePathBuilder(); + builder->MoveTo(pt); + iter = 0; + } + ptICoord += adv; + ptBCoord += goDown ? adv : -adv; + + builder->LineTo(pt); // 3 and 5 + + ptICoord += flatLengthAtVertex; + builder->LineTo(pt); // 4 and 6 + + goDown = !goDown; + } + path = builder->Finish(); + aDrawTarget.Stroke(path, colorPat, strokeOptions, drawOptions); + return; + } + default: + NS_ERROR("Invalid style value!"); + } +} + +Rect nsCSSRendering::DecorationLineToPath( + const PaintDecorationLineParams& aParams) { + NS_ASSERTION(aParams.style != NS_STYLE_TEXT_DECORATION_STYLE_NONE, + "aStyle is none"); + + Rect path; // To benefit from RVO, we return this from all return points + + Rect rect = ToRect(GetTextDecorationRectInternal(aParams.pt, aParams)); + if (rect.IsEmpty() || !rect.Intersects(aParams.dirtyRect)) { + return path; + } + + if (aParams.decoration != StyleTextDecorationLine::UNDERLINE && + aParams.decoration != StyleTextDecorationLine::OVERLINE && + aParams.decoration != StyleTextDecorationLine::LINE_THROUGH) { + MOZ_ASSERT_UNREACHABLE("Invalid text decoration value"); + return path; + } + + if (aParams.style != NS_STYLE_TEXT_DECORATION_STYLE_SOLID) { + // For the moment, we support only solid text decorations. + return path; + } + + Float lineThickness = std::max(NS_round(aParams.lineSize.height), 1.0); + + // The block-direction position should be set to the middle of the line. + if (aParams.vertical) { + rect.x += lineThickness / 2; + path = Rect(rect.TopLeft() - Point(lineThickness / 2, 0.0), + Size(lineThickness, rect.Height())); + } else { + rect.y += lineThickness / 2; + path = Rect(rect.TopLeft() - Point(0.0, lineThickness / 2), + Size(rect.Width(), lineThickness)); + } + + return path; +} + +nsRect nsCSSRendering::GetTextDecorationRect( + nsPresContext* aPresContext, const DecorationRectParams& aParams) { + NS_ASSERTION(aPresContext, "aPresContext is null"); + NS_ASSERTION(aParams.style != NS_STYLE_TEXT_DECORATION_STYLE_NONE, + "aStyle is none"); + + gfxRect rect = GetTextDecorationRectInternal(Point(0, 0), aParams); + // The rect values are already rounded to nearest device pixels. + nsRect r; + r.x = aPresContext->GfxUnitsToAppUnits(rect.X()); + r.y = aPresContext->GfxUnitsToAppUnits(rect.Y()); + r.width = aPresContext->GfxUnitsToAppUnits(rect.Width()); + r.height = aPresContext->GfxUnitsToAppUnits(rect.Height()); + return r; +} + +gfxRect nsCSSRendering::GetTextDecorationRectInternal( + const Point& aPt, const DecorationRectParams& aParams) { + NS_ASSERTION(aParams.style <= NS_STYLE_TEXT_DECORATION_STYLE_WAVY, + "Invalid aStyle value"); + + if (aParams.style == NS_STYLE_TEXT_DECORATION_STYLE_NONE) + return gfxRect(0, 0, 0, 0); + + bool canLiftUnderline = aParams.descentLimit >= 0.0; + + gfxFloat iCoord = aParams.vertical ? aPt.y : aPt.x; + gfxFloat bCoord = aParams.vertical ? aPt.x : aPt.y; + + // 'left' and 'right' are relative to the line, so for vertical writing modes + // they will actually become top and bottom of the rendered line. + // Similarly, aLineSize.width and .height are actually length and thickness + // of the line, which runs horizontally or vertically according to aVertical. + const gfxFloat left = floor(iCoord + 0.5), + right = floor(iCoord + aParams.lineSize.width + 0.5); + + // We compute |r| as if for a horizontal text run, and then swap vertical + // and horizontal coordinates at the end if vertical was requested. + gfxRect r(left, 0, right - left, 0); + + gfxFloat lineThickness = NS_round(aParams.lineSize.height); + lineThickness = std::max(lineThickness, 1.0); + gfxFloat defaultLineThickness = NS_round(aParams.defaultLineThickness); + defaultLineThickness = std::max(defaultLineThickness, 1.0); + + gfxFloat ascent = NS_round(aParams.ascent); + gfxFloat descentLimit = floor(aParams.descentLimit); + + gfxFloat suggestedMaxRectHeight = + std::max(std::min(ascent, descentLimit), 1.0); + r.height = lineThickness; + if (aParams.style == NS_STYLE_TEXT_DECORATION_STYLE_DOUBLE) { + /** + * We will draw double line as: + * + * +-------------------------------------------+ + * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| ^ + * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| | lineThickness + * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| v + * | | ^ + * | | | gap + * | | v + * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| ^ + * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| | lineThickness + * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| v + * +-------------------------------------------+ + */ + gfxFloat gap = NS_round(lineThickness / 2.0); + gap = std::max(gap, 1.0); + r.height = lineThickness * 2.0 + gap; + if (canLiftUnderline) { + if (r.Height() > suggestedMaxRectHeight) { + // Don't shrink the line height, because the thickness has some meaning. + // We can just shrink the gap at this time. + r.height = std::max(suggestedMaxRectHeight, lineThickness * 2.0 + 1.0); + } + } + } else if (aParams.style == NS_STYLE_TEXT_DECORATION_STYLE_WAVY) { + /** + * We will draw wavy line as: + * + * +-------------------------------------------+ + * |XXXXX XXXXXX XXXXXX | ^ + * |XXXXXX XXXXXXXX XXXXXXXX | | lineThickness + * |XXXXXXX XXXXXXXXXX XXXXXXXXXX| v + * | XXX XXX XXX XXX XX| + * | XXXXXXXXXX XXXXXXXXXX X| + * | XXXXXXXX XXXXXXXX | + * | XXXXXX XXXXXX | + * +-------------------------------------------+ + */ + r.height = lineThickness > 2.0 ? lineThickness * 4.0 : lineThickness * 3.0; + if (canLiftUnderline) { + if (r.Height() > suggestedMaxRectHeight) { + // Don't shrink the line height even if there is not enough space, + // because the thickness has some meaning. E.g., the 1px wavy line and + // 2px wavy line can be used for different meaning in IME selections + // at same time. + r.height = std::max(suggestedMaxRectHeight, lineThickness * 2.0); + } + } + } + + gfxFloat baseline = floor(bCoord + aParams.ascent + 0.5); + + // Calculate adjusted offset based on writing-mode/orientation and thickness + // of decoration line. The input value aParams.offset is the nominal position + // (offset from baseline) where we would draw a single, infinitely-thin line; + // but for a wavy or double line, we'll need to move the bounding rect of the + // decoration outwards from the baseline so that an underline remains below + // the glyphs, and an overline above them, despite the increased block-dir + // extent of the decoration. + // + // So adjustments by r.Height() are used to make the wider line styles (wavy + // and double) "grow" in the appropriate direction compared to the basic + // single line. + // + // Note that at this point, the decoration rect is being calculated in line- + // relative coordinates, where 'x' is line-rightwards, and 'y' is line- + // upwards. We'll swap them to be physical coords at the end. + gfxFloat offset = 0.0; + + if (aParams.decoration == StyleTextDecorationLine::UNDERLINE) { + offset = aParams.offset; + if (canLiftUnderline) { + if (descentLimit < -offset + r.Height()) { + // If we can ignore the offset and the decoration line is overflowing, + // we should align the bottom edge of the decoration line rect if it's + // possible. Otherwise, we should lift up the top edge of the rect as + // far as possible. + gfxFloat offsetBottomAligned = -descentLimit + r.Height(); + gfxFloat offsetTopAligned = 0.0; + offset = std::min(offsetBottomAligned, offsetTopAligned); + } + } + } else if (aParams.decoration == StyleTextDecorationLine::OVERLINE) { + // For overline, we adjust the offset by defaultlineThickness (the default + // thickness of a single decoration line) because empirically it looks + // better to draw the overline just inside rather than outside the font's + // ascent, which is what nsTextFrame passes as aParams.offset (as fonts + // don't provide an explicit overline-offset). + offset = aParams.offset - defaultLineThickness + r.Height(); + } else if (aParams.decoration == StyleTextDecorationLine::LINE_THROUGH) { + // To maintain a consistent mid-point for line-through decorations, + // we adjust the offset by half of the decoration rect's height. + gfxFloat extra = floor(r.Height() / 2.0 + 0.5); + extra = std::max(extra, lineThickness); + // computes offset for when user specifies a decoration width since + // aParams.offset is derived from the font metric's line height + gfxFloat decorationThicknessOffset = + (lineThickness - defaultLineThickness) / 2.0; + offset = aParams.offset - lineThickness + extra + decorationThicknessOffset; + } else { + MOZ_ASSERT_UNREACHABLE("Invalid text decoration value"); + } + + // Convert line-relative coordinate system (x = line-right, y = line-up) + // to physical coords, and move the decoration rect to the calculated + // offset from baseline. + if (aParams.vertical) { + std::swap(r.x, r.y); + std::swap(r.width, r.height); + // line-upwards in vertical mode = physical-right, so we /add/ offset + // to baseline. Except in sideways-lr mode, where line-upwards will be + // physical leftwards. + if (aParams.sidewaysLeft) { + r.x = baseline - floor(offset + 0.5); + } else { + r.x = baseline + floor(offset - r.Width() + 0.5); + } + } else { + // line-upwards in horizontal mode = physical-up, but our physical coord + // system works downwards, so we /subtract/ offset from baseline. + r.y = baseline - floor(offset + 0.5); + } + + return r; +} + +#define MAX_BLUR_RADIUS 300 +#define MAX_SPREAD_RADIUS 50 + +static inline gfxPoint ComputeBlurStdDev(nscoord aBlurRadius, + int32_t aAppUnitsPerDevPixel, + gfxFloat aScaleX, gfxFloat aScaleY) { + // http://dev.w3.org/csswg/css3-background/#box-shadow says that the + // standard deviation of the blur should be half the given blur value. + gfxFloat blurStdDev = gfxFloat(aBlurRadius) / gfxFloat(aAppUnitsPerDevPixel); + + return gfxPoint( + std::min((blurStdDev * aScaleX), gfxFloat(MAX_BLUR_RADIUS)) / 2.0, + std::min((blurStdDev * aScaleY), gfxFloat(MAX_BLUR_RADIUS)) / 2.0); +} + +static inline IntSize ComputeBlurRadius(nscoord aBlurRadius, + int32_t aAppUnitsPerDevPixel, + gfxFloat aScaleX = 1.0, + gfxFloat aScaleY = 1.0) { + gfxPoint scaledBlurStdDev = + ComputeBlurStdDev(aBlurRadius, aAppUnitsPerDevPixel, aScaleX, aScaleY); + return gfxAlphaBoxBlur::CalculateBlurRadius(scaledBlurStdDev); +} + +// ----- +// nsContextBoxBlur +// ----- +gfxContext* nsContextBoxBlur::Init(const nsRect& aRect, nscoord aSpreadRadius, + nscoord aBlurRadius, + int32_t aAppUnitsPerDevPixel, + gfxContext* aDestinationCtx, + const nsRect& aDirtyRect, + const gfxRect* aSkipRect, uint32_t aFlags) { + if (aRect.IsEmpty()) { + mContext = nullptr; + return nullptr; + } + + IntSize blurRadius; + IntSize spreadRadius; + GetBlurAndSpreadRadius(aDestinationCtx->GetDrawTarget(), aAppUnitsPerDevPixel, + aBlurRadius, aSpreadRadius, blurRadius, spreadRadius); + + mDestinationCtx = aDestinationCtx; + + // If not blurring, draw directly onto the destination device + if (blurRadius.width <= 0 && blurRadius.height <= 0 && + spreadRadius.width <= 0 && spreadRadius.height <= 0 && + !(aFlags & FORCE_MASK)) { + mContext = aDestinationCtx; + return mContext; + } + + // Convert from app units to device pixels + gfxRect rect = nsLayoutUtils::RectToGfxRect(aRect, aAppUnitsPerDevPixel); + + gfxRect dirtyRect = + nsLayoutUtils::RectToGfxRect(aDirtyRect, aAppUnitsPerDevPixel); + dirtyRect.RoundOut(); + + gfxMatrix transform = aDestinationCtx->CurrentMatrixDouble(); + rect = transform.TransformBounds(rect); + + mPreTransformed = !transform.IsIdentity(); + + // Create the temporary surface for blurring + dirtyRect = transform.TransformBounds(dirtyRect); + bool useHardwareAccel = !(aFlags & DISABLE_HARDWARE_ACCELERATION_BLUR); + if (aSkipRect) { + gfxRect skipRect = transform.TransformBounds(*aSkipRect); + mContext = + mAlphaBoxBlur.Init(aDestinationCtx, rect, spreadRadius, blurRadius, + &dirtyRect, &skipRect, useHardwareAccel); + } else { + mContext = + mAlphaBoxBlur.Init(aDestinationCtx, rect, spreadRadius, blurRadius, + &dirtyRect, nullptr, useHardwareAccel); + } + + if (mContext) { + // we don't need to blur if skipRect is equal to rect + // and mContext will be nullptr + mContext->Multiply(transform); + } + return mContext; +} + +void nsContextBoxBlur::DoPaint() { + if (mContext == mDestinationCtx) { + return; + } + + gfxContextMatrixAutoSaveRestore saveMatrix(mDestinationCtx); + + if (mPreTransformed) { + mDestinationCtx->SetMatrix(Matrix()); + } + + mAlphaBoxBlur.Paint(mDestinationCtx); +} + +gfxContext* nsContextBoxBlur::GetContext() { return mContext; } + +/* static */ +nsMargin nsContextBoxBlur::GetBlurRadiusMargin(nscoord aBlurRadius, + int32_t aAppUnitsPerDevPixel) { + IntSize blurRadius = ComputeBlurRadius(aBlurRadius, aAppUnitsPerDevPixel); + + nsMargin result; + result.top = result.bottom = blurRadius.height * aAppUnitsPerDevPixel; + result.left = result.right = blurRadius.width * aAppUnitsPerDevPixel; + return result; +} + +/* static */ +void nsContextBoxBlur::BlurRectangle( + gfxContext* aDestinationCtx, const nsRect& aRect, + int32_t aAppUnitsPerDevPixel, RectCornerRadii* aCornerRadii, + nscoord aBlurRadius, const sRGBColor& aShadowColor, + const nsRect& aDirtyRect, const gfxRect& aSkipRect) { + DrawTarget& aDestDrawTarget = *aDestinationCtx->GetDrawTarget(); + + if (aRect.IsEmpty()) { + return; + } + + Rect shadowGfxRect = NSRectToRect(aRect, aAppUnitsPerDevPixel); + + if (aBlurRadius <= 0) { + ColorPattern color(ToDeviceColor(aShadowColor)); + if (aCornerRadii) { + RefPtr<Path> roundedRect = + MakePathForRoundedRect(aDestDrawTarget, shadowGfxRect, *aCornerRadii); + aDestDrawTarget.Fill(roundedRect, color); + } else { + aDestDrawTarget.FillRect(shadowGfxRect, color); + } + return; + } + + gfxFloat scaleX = 1; + gfxFloat scaleY = 1; + + // Do blurs in device space when possible. + // Chrome/Skia always does the blurs in device space + // and will sometimes get incorrect results (e.g. rotated blurs) + gfxMatrix transform = aDestinationCtx->CurrentMatrixDouble(); + // XXX: we could probably handle negative scales but for now it's easier just + // to fallback + if (!transform.HasNonAxisAlignedTransform() && transform._11 > 0.0 && + transform._22 > 0.0) { + scaleX = transform._11; + scaleY = transform._22; + aDestinationCtx->SetMatrix(Matrix()); + } else { + transform = gfxMatrix(); + } + + gfxPoint blurStdDev = + ComputeBlurStdDev(aBlurRadius, aAppUnitsPerDevPixel, scaleX, scaleY); + + gfxRect dirtyRect = + nsLayoutUtils::RectToGfxRect(aDirtyRect, aAppUnitsPerDevPixel); + dirtyRect.RoundOut(); + + gfxRect shadowThebesRect = + transform.TransformBounds(ThebesRect(shadowGfxRect)); + dirtyRect = transform.TransformBounds(dirtyRect); + gfxRect skipRect = transform.TransformBounds(aSkipRect); + + if (aCornerRadii) { + aCornerRadii->Scale(scaleX, scaleY); + } + + gfxAlphaBoxBlur::BlurRectangle(aDestinationCtx, shadowThebesRect, + aCornerRadii, blurStdDev, aShadowColor, + dirtyRect, skipRect); +} + +/* static */ +void nsContextBoxBlur::GetBlurAndSpreadRadius( + DrawTarget* aDestDrawTarget, int32_t aAppUnitsPerDevPixel, + nscoord aBlurRadius, nscoord aSpreadRadius, IntSize& aOutBlurRadius, + IntSize& aOutSpreadRadius, bool aConstrainSpreadRadius) { + // Do blurs in device space when possible. + // Chrome/Skia always does the blurs in device space + // and will sometimes get incorrect results (e.g. rotated blurs) + Matrix transform = aDestDrawTarget->GetTransform(); + // XXX: we could probably handle negative scales but for now it's easier just + // to fallback + gfxFloat scaleX, scaleY; + if (transform.HasNonAxisAlignedTransform() || transform._11 <= 0.0 || + transform._22 <= 0.0) { + scaleX = 1; + scaleY = 1; + } else { + scaleX = transform._11; + scaleY = transform._22; + } + + // compute a large or smaller blur radius + aOutBlurRadius = + ComputeBlurRadius(aBlurRadius, aAppUnitsPerDevPixel, scaleX, scaleY); + aOutSpreadRadius = + IntSize(int32_t(aSpreadRadius * scaleX / aAppUnitsPerDevPixel), + int32_t(aSpreadRadius * scaleY / aAppUnitsPerDevPixel)); + + if (aConstrainSpreadRadius) { + aOutSpreadRadius.width = + std::min(aOutSpreadRadius.width, int32_t(MAX_SPREAD_RADIUS)); + aOutSpreadRadius.height = + std::min(aOutSpreadRadius.height, int32_t(MAX_SPREAD_RADIUS)); + } +} + +/* static */ +bool nsContextBoxBlur::InsetBoxBlur( + gfxContext* aDestinationCtx, Rect aDestinationRect, Rect aShadowClipRect, + sRGBColor& aShadowColor, nscoord aBlurRadiusAppUnits, + nscoord aSpreadDistanceAppUnits, int32_t aAppUnitsPerDevPixel, + bool aHasBorderRadius, RectCornerRadii& aInnerClipRectRadii, Rect aSkipRect, + Point aShadowOffset) { + if (aDestinationRect.IsEmpty()) { + mContext = nullptr; + return false; + } + + gfxContextAutoSaveRestore autoRestore(aDestinationCtx); + + IntSize blurRadius; + IntSize spreadRadius; + // Convert the blur and spread radius to device pixels + bool constrainSpreadRadius = false; + GetBlurAndSpreadRadius(aDestinationCtx->GetDrawTarget(), aAppUnitsPerDevPixel, + aBlurRadiusAppUnits, aSpreadDistanceAppUnits, + blurRadius, spreadRadius, constrainSpreadRadius); + + // The blur and spread radius are scaled already, so scale all + // input data to the blur. This way, we don't have to scale the min + // inset blur to the invert of the dest context, then rescale it back + // when we draw to the destination surface. + gfx::Size scale = aDestinationCtx->CurrentMatrix().ScaleFactors(); + Matrix transform = aDestinationCtx->CurrentMatrix(); + + // XXX: we could probably handle negative scales but for now it's easier just + // to fallback + if (!transform.HasNonAxisAlignedTransform() && transform._11 > 0.0 && + transform._22 > 0.0) { + // If we don't have a rotation, we're pre-transforming all the rects. + aDestinationCtx->SetMatrix(Matrix()); + } else { + // Don't touch anything, we have a rotation. + transform = Matrix(); + } + + Rect transformedDestRect = transform.TransformBounds(aDestinationRect); + Rect transformedShadowClipRect = transform.TransformBounds(aShadowClipRect); + Rect transformedSkipRect = transform.TransformBounds(aSkipRect); + + transformedDestRect.Round(); + transformedShadowClipRect.Round(); + transformedSkipRect.RoundIn(); + + for (size_t i = 0; i < 4; i++) { + aInnerClipRectRadii[i].width = + std::floor(scale.width * aInnerClipRectRadii[i].width); + aInnerClipRectRadii[i].height = + std::floor(scale.height * aInnerClipRectRadii[i].height); + } + + mAlphaBoxBlur.BlurInsetBox(aDestinationCtx, transformedDestRect, + transformedShadowClipRect, blurRadius, + aShadowColor, + aHasBorderRadius ? &aInnerClipRectRadii : nullptr, + transformedSkipRect, aShadowOffset); + return true; +} diff --git a/layout/painting/nsCSSRendering.h b/layout/painting/nsCSSRendering.h new file mode 100644 index 0000000000..262ecdc239 --- /dev/null +++ b/layout/painting/nsCSSRendering.h @@ -0,0 +1,938 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* utility functions for drawing borders and backgrounds */ + +#ifndef nsCSSRendering_h___ +#define nsCSSRendering_h___ + +#include "gfxBlur.h" +#include "gfxContext.h" +#include "mozilla/gfx/PathHelpers.h" +#include "mozilla/gfx/Rect.h" +#include "mozilla/TypedEnumBits.h" +#include "nsStyleStruct.h" +#include "nsIFrame.h" +#include "nsImageRenderer.h" +#include "nsCSSRenderingBorders.h" +#include "gfxTextRun.h" + +class gfxContext; +class nsPresContext; + +namespace mozilla { + +class ComputedStyle; + +namespace gfx { +struct sRGBColor; +class DrawTarget; +} // namespace gfx + +namespace layers { +class ImageContainer; +class StackingContextHelper; +class WebRenderParentCommand; +class LayerManager; +class RenderRootStateManager; +} // namespace layers + +namespace wr { +class DisplayListBuilder; +} // namespace wr + +enum class PaintBorderFlags : uint8_t { SyncDecodeImages = 1 << 0 }; +MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(PaintBorderFlags) + +} // namespace mozilla + +/** + * A struct representing all the information needed to paint a background + * image to some target, taking into account all CSS background-* properties. + * See PrepareImageLayer. + */ +struct nsBackgroundLayerState { + typedef mozilla::gfx::CompositionOp CompositionOp; + typedef mozilla::nsImageRenderer nsImageRenderer; + + /** + * @param aFlags some combination of nsCSSRendering::PAINTBG_* flags + */ + nsBackgroundLayerState(nsIFrame* aForFrame, const mozilla::StyleImage* aImage, + uint32_t aFlags) + : mImageRenderer(aForFrame, aImage, aFlags) {} + + /** + * The nsImageRenderer that will be used to draw the background. + */ + nsImageRenderer mImageRenderer; + /** + * A rectangle that one copy of the image tile is mapped onto. Same + * coordinate system as aBorderArea/aBGClipRect passed into + * PrepareImageLayer. + */ + nsRect mDestArea; + /** + * The actual rectangle that should be filled with (complete or partial) + * image tiles. Same coordinate system as aBorderArea/aBGClipRect passed into + * PrepareImageLayer. + */ + nsRect mFillArea; + /** + * The anchor point that should be snapped to a pixel corner. Same + * coordinate system as aBorderArea/aBGClipRect passed into + * PrepareImageLayer. + */ + nsPoint mAnchor; + /** + * The background-repeat property space keyword computes the + * repeat size which is image size plus spacing. + */ + nsSize mRepeatSize; +}; + +struct nsCSSRendering { + typedef mozilla::gfx::sRGBColor sRGBColor; + typedef mozilla::gfx::CompositionOp CompositionOp; + typedef mozilla::gfx::DrawTarget DrawTarget; + typedef mozilla::gfx::Float Float; + typedef mozilla::gfx::Point Point; + typedef mozilla::gfx::Rect Rect; + typedef mozilla::gfx::Size Size; + typedef mozilla::gfx::RectCornerRadii RectCornerRadii; + typedef mozilla::layers::LayerManager LayerManager; + typedef mozilla::image::ImgDrawResult ImgDrawResult; + typedef nsIFrame::Sides Sides; + + /** + * Initialize any static variables used by nsCSSRendering. + */ + static void Init(); + + /** + * Clean up any static variables used by nsCSSRendering. + */ + static void Shutdown(); + + static bool IsBoxDecorationSlice(const nsStyleBorder& aStyleBorder); + static nsRect BoxDecorationRectForBorder( + nsIFrame* aFrame, const nsRect& aBorderArea, Sides aSkipSides, + const nsStyleBorder* aStyleBorder = nullptr); + static nsRect BoxDecorationRectForBackground( + nsIFrame* aFrame, const nsRect& aBorderArea, Sides aSkipSides, + const nsStyleBorder* aStyleBorder = nullptr); + + static bool GetShadowInnerRadii(nsIFrame* aFrame, const nsRect& aFrameArea, + RectCornerRadii& aOutInnerRadii); + static nsRect GetBoxShadowInnerPaddingRect(nsIFrame* aFrame, + const nsRect& aFrameArea); + static bool ShouldPaintBoxShadowInner(nsIFrame* aFrame); + static void PaintBoxShadowInner(nsPresContext* aPresContext, + gfxContext& aRenderingContext, + nsIFrame* aForFrame, + const nsRect& aFrameArea); + + static bool GetBorderRadii(const nsRect& aFrameRect, + const nsRect& aBorderRect, nsIFrame* aFrame, + RectCornerRadii& aOutRadii); + static nsRect GetShadowRect(const nsRect& aFrameArea, bool aNativeTheme, + nsIFrame* aForFrame); + static mozilla::gfx::sRGBColor GetShadowColor( + const mozilla::StyleSimpleShadow&, nsIFrame* aFrame, float aOpacity); + // Returns if the frame has a themed frame. + // aMaybeHasBorderRadius will return false if we can early detect + // that we don't have a border radius. + static bool HasBoxShadowNativeTheme(nsIFrame* aFrame, + bool& aMaybeHasBorderRadius); + static void PaintBoxShadowOuter(nsPresContext* aPresContext, + gfxContext& aRenderingContext, + nsIFrame* aForFrame, const nsRect& aFrameArea, + const nsRect& aDirtyRect, + float aOpacity = 1.0); + + static void ComputePixelRadii(const nscoord* aAppUnitsRadii, + nscoord aAppUnitsPerPixel, + RectCornerRadii* oBorderRadii); + + /** + * Render the border for an element using css rendering rules + * for borders. aSkipSides says which sides to skip + * when rendering, the default is to skip none. + */ + static ImgDrawResult PaintBorder( + nsPresContext* aPresContext, gfxContext& aRenderingContext, + nsIFrame* aForFrame, const nsRect& aDirtyRect, const nsRect& aBorderArea, + mozilla::ComputedStyle* aStyle, mozilla::PaintBorderFlags aFlags, + Sides aSkipSides = Sides()); + + /** + * Like PaintBorder, but taking an nsStyleBorder argument instead of + * getting it from aStyle. aSkipSides says which sides to skip + * when rendering, the default is to skip none. + */ + static ImgDrawResult PaintBorderWithStyleBorder( + nsPresContext* aPresContext, gfxContext& aRenderingContext, + nsIFrame* aForFrame, const nsRect& aDirtyRect, const nsRect& aBorderArea, + const nsStyleBorder& aBorderStyle, mozilla::ComputedStyle* aStyle, + mozilla::PaintBorderFlags aFlags, Sides aSkipSides = Sides()); + + static mozilla::Maybe<nsCSSBorderRenderer> CreateBorderRenderer( + nsPresContext* aPresContext, DrawTarget* aDrawTarget, nsIFrame* aForFrame, + const nsRect& aDirtyRect, const nsRect& aBorderArea, + mozilla::ComputedStyle* aStyle, bool* aOutBorderIsEmpty, + Sides aSkipSides = Sides()); + + static mozilla::Maybe<nsCSSBorderRenderer> + CreateBorderRendererWithStyleBorder( + nsPresContext* aPresContext, DrawTarget* aDrawTarget, nsIFrame* aForFrame, + const nsRect& aDirtyRect, const nsRect& aBorderArea, + const nsStyleBorder& aBorderStyle, mozilla::ComputedStyle* aStyle, + bool* aOutBorderIsEmpty, Sides aSkipSides = Sides()); + + static mozilla::Maybe<nsCSSBorderRenderer> + CreateNullBorderRendererWithStyleBorder( + nsPresContext* aPresContext, DrawTarget* aDrawTarget, nsIFrame* aForFrame, + const nsRect& aDirtyRect, const nsRect& aBorderArea, + const nsStyleBorder& aBorderStyle, mozilla::ComputedStyle* aStyle, + bool* aOutBorderIsEmpty, Sides aSkipSides = Sides()); + + static mozilla::Maybe<nsCSSBorderRenderer> CreateBorderRendererForOutline( + nsPresContext* aPresContext, gfxContext* aRenderingContext, + nsIFrame* aForFrame, const nsRect& aDirtyRect, const nsRect& aBorderArea, + mozilla::ComputedStyle* aStyle); + + static ImgDrawResult CreateWebRenderCommandsForBorder( + nsDisplayItem* aItem, nsIFrame* aForFrame, const nsRect& aBorderArea, + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const mozilla::layers::StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder); + + static void CreateWebRenderCommandsForNullBorder( + nsDisplayItem* aItem, nsIFrame* aForFrame, const nsRect& aBorderArea, + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const mozilla::layers::StackingContextHelper& aSc, + const nsStyleBorder& aStyleBorder); + + static ImgDrawResult CreateWebRenderCommandsForBorderWithStyleBorder( + nsDisplayItem* aItem, nsIFrame* aForFrame, const nsRect& aBorderArea, + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const mozilla::layers::StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder, + const nsStyleBorder& aStyleBorder); + + /** + * Render the outline for an element using css rendering rules + * for borders. + */ + static void PaintOutline(nsPresContext* aPresContext, + gfxContext& aRenderingContext, nsIFrame* aForFrame, + const nsRect& aDirtyRect, const nsRect& aBorderArea, + mozilla::ComputedStyle* aStyle); + + /** + * Render keyboard focus on an element. + * |aFocusRect| is the outer rectangle of the focused element. + * Uses a fixed style equivalent to "1px dotted |aColor|". + * Not used for controls, because the native theme may differ. + */ + static void PaintFocus(nsPresContext* aPresContext, DrawTarget* aDrawTarget, + const nsRect& aFocusRect, nscolor aColor); + + /** + * Render a gradient for an element. + * aDest is the rect for a single tile of the gradient on the destination. + * aFill is the rect on the destination to be covered by repeated tiling of + * the gradient. + * aSrc is the part of the gradient to be rendered into a tile (aDest), if + * aSrc and aDest are different sizes, the image will be scaled to map aSrc + * onto aDest. + * aIntrinsicSize is the size of the source gradient. + */ + static void PaintGradient(nsPresContext* aPresContext, gfxContext& aContext, + const mozilla::StyleGradient& aGradient, + const nsRect& aDirtyRect, const nsRect& aDest, + const nsRect& aFill, const nsSize& aRepeatSize, + const mozilla::CSSIntRect& aSrc, + const nsSize& aIntrinsiceSize, + float aOpacity = 1.0); + + /** + * Find the frame whose background style should be used to draw the + * canvas background. aForFrame must be the frame for the root element + * whose background style should be used. This function will return + * aForFrame unless the <body> background should be propagated, in + * which case we return the frame associated with the <body>'s background. + */ + static nsIFrame* FindBackgroundStyleFrame(nsIFrame* aForFrame); + + /** + * @return true if |aFrame| is a canvas frame, in the CSS sense. + */ + static bool IsCanvasFrame(const nsIFrame* aFrame); + + /** + * Fill in an aBackgroundSC to be used to paint the background + * for an element. This applies the rules for propagating + * backgrounds between BODY, the root element, and the canvas. + * @return true if there is some meaningful background. + */ + static bool FindBackground(const nsIFrame* aForFrame, + mozilla::ComputedStyle** aBackgroundSC); + static bool FindBackgroundFrame(const nsIFrame* aForFrame, + nsIFrame** aBackgroundFrame); + + /** + * As FindBackground, but the passed-in frame is known to be a root frame + * (returned from nsCSSFrameConstructor::GetRootElementStyleFrame()) + * and there is always some meaningful background returned. + */ + static mozilla::ComputedStyle* FindRootFrameBackground(nsIFrame* aForFrame); + + /** + * Returns background style information for the canvas. + * + * @param aForFrame + * the frame used to represent the canvas, in the CSS sense (i.e. + * nsCSSRendering::IsCanvasFrame(aForFrame) must be true) + * @param aRootElementFrame + * the frame representing the root element of the document + * @param aBackground + * contains background style information for the canvas on return + */ + + static nsIFrame* FindCanvasBackgroundFrame(const nsIFrame* aForFrame, + nsIFrame* aRootElementFrame) { + MOZ_ASSERT(IsCanvasFrame(aForFrame), "not a canvas frame"); + if (aRootElementFrame) { + return FindBackgroundStyleFrame(aRootElementFrame); + } + + // This should always give transparent, so we'll fill it in with the + // default color if needed. This seems to happen a bit while a page is + // being loaded. + return const_cast<nsIFrame*>(aForFrame); + } + + static mozilla::ComputedStyle* FindCanvasBackground( + nsIFrame* aForFrame, nsIFrame* aRootElementFrame) { + return FindCanvasBackgroundFrame(aForFrame, aRootElementFrame)->Style(); + } + + /** + * Find a frame which draws a non-transparent background, + * for various table-related and HR-related backwards-compatibility hacks. + * This function will also stop if it finds themed frame which might draw + * background. + * + * Be very hesitant if you're considering calling this function -- it's + * usually not what you want. + */ + static nsIFrame* FindNonTransparentBackgroundFrame( + nsIFrame* aFrame, bool aStartAtParent = false); + + /** + * Determine the background color to draw taking into account print settings. + */ + static nscolor DetermineBackgroundColor(nsPresContext* aPresContext, + mozilla::ComputedStyle* aStyle, + nsIFrame* aFrame, + bool& aDrawBackgroundImage, + bool& aDrawBackgroundColor); + + static nsRect ComputeImageLayerPositioningArea( + nsPresContext* aPresContext, nsIFrame* aForFrame, + const nsRect& aBorderArea, const nsStyleImageLayers::Layer& aLayer, + nsIFrame** aAttachedToFrame, bool* aOutTransformedFixed); + + // Implementation of the formula for computation of background-repeat round + // See http://dev.w3.org/csswg/css3-background/#the-background-size + // This function returns the adjusted size of the background image. + static nscoord ComputeRoundedSize(nscoord aCurrentSize, + nscoord aPositioningSize); + + /* ComputeBorderSpacedRepeatSize + * aImageDimension: the image width/height + * aAvailableSpace: the background positioning area width/height + * aSpace: the space between each image + * Returns the image size plus gap size of app units for use as spacing + */ + static nscoord ComputeBorderSpacedRepeatSize(nscoord aImageDimension, + nscoord aAvailableSpace, + nscoord& aSpace); + + static nsBackgroundLayerState PrepareImageLayer( + nsPresContext* aPresContext, nsIFrame* aForFrame, uint32_t aFlags, + const nsRect& aBorderArea, const nsRect& aBGClipRect, + const nsStyleImageLayers::Layer& aLayer, + bool* aOutIsTransformedFixed = nullptr); + + struct ImageLayerClipState { + nsRect mBGClipArea; // Affected by mClippedRadii + nsRect mAdditionalBGClipArea; // Not affected by mClippedRadii + nsRect mDirtyRectInAppUnits; + gfxRect mDirtyRectInDevPx; + + nscoord mRadii[8]; + RectCornerRadii mClippedRadii; + bool mHasRoundedCorners; + bool mHasAdditionalBGClipArea; + + // Whether we are being asked to draw with a caller provided background + // clipping area. If this is true we also disable rounded corners. + bool mCustomClip; + + ImageLayerClipState() + : mHasRoundedCorners(false), + mHasAdditionalBGClipArea(false), + mCustomClip(false) { + memset(mRadii, 0, sizeof(nscoord) * 8); + } + + bool IsValid() const; + }; + + static void GetImageLayerClip(const nsStyleImageLayers::Layer& aLayer, + nsIFrame* aForFrame, + const nsStyleBorder& aBorder, + const nsRect& aBorderArea, + const nsRect& aCallerDirtyRect, + bool aWillPaintBorder, + nscoord aAppUnitsPerPixel, + /* out */ ImageLayerClipState* aClipState); + + /** + * Render the background for an element using css rendering rules + * for backgrounds or mask. + */ + enum { + /** + * When this flag is passed, the element's nsDisplayBorder will be + * painted immediately on top of this background. + */ + PAINTBG_WILL_PAINT_BORDER = 0x01, + /** + * When this flag is passed, images are synchronously decoded. + */ + PAINTBG_SYNC_DECODE_IMAGES = 0x02, + /** + * When this flag is passed, painting will go to the screen so we can + * take advantage of the fact that it will be clipped to the viewport. + */ + PAINTBG_TO_WINDOW = 0x04, + /** + * When this flag is passed, painting will read properties of mask-image + * style, instead of background-image. + */ + PAINTBG_MASK_IMAGE = 0x08, + /** + * When this flag is passed, images are downscaled during decode. This + * is also implied by PAINTBG_TO_WINDOW. + */ + PAINTBG_HIGH_QUALITY_SCALING = 0x16, + }; + + struct PaintBGParams { + nsPresContext& presCtx; + nsRect dirtyRect; + nsRect borderArea; + nsIFrame* frame; + uint32_t paintFlags; + nsRect* bgClipRect = nullptr; + int32_t layer; // -1 means painting all layers; other + // value means painting one specific + // layer only. + CompositionOp compositionOp; + float opacity; + + static PaintBGParams ForAllLayers(nsPresContext& aPresCtx, + const nsRect& aDirtyRect, + const nsRect& aBorderArea, + nsIFrame* aFrame, uint32_t aPaintFlags, + float aOpacity = 1.0); + static PaintBGParams ForSingleLayer( + nsPresContext& aPresCtx, const nsRect& aDirtyRect, + const nsRect& aBorderArea, nsIFrame* aFrame, uint32_t aPaintFlags, + int32_t aLayer, CompositionOp aCompositionOp = CompositionOp::OP_OVER, + float aOpacity = 1.0); + + private: + PaintBGParams(nsPresContext& aPresCtx, const nsRect& aDirtyRect, + const nsRect& aBorderArea, nsIFrame* aFrame, + uint32_t aPaintFlags, int32_t aLayer, + CompositionOp aCompositionOp, float aOpacity) + : presCtx(aPresCtx), + dirtyRect(aDirtyRect), + borderArea(aBorderArea), + frame(aFrame), + paintFlags(aPaintFlags), + layer(aLayer), + compositionOp(aCompositionOp), + opacity(aOpacity) {} + }; + + static ImgDrawResult PaintStyleImageLayer(const PaintBGParams& aParams, + gfxContext& aRenderingCtx); + + /** + * Same as |PaintStyleImageLayer|, except using the provided style structs. + * This short-circuits the code that ensures that the root element's + * {background|mask} is drawn on the canvas. + * The aLayer parameter allows you to paint a single layer of the + * {background|mask}. + * The default value for aLayer, -1, means that all layers will be painted. + * The background color will only be painted if the back-most layer is also + * being painted and (aParams.paintFlags & PAINTBG_MASK_IMAGE) is false. + * aCompositionOp is only respected if a single layer is specified (aLayer != + * -1). If all layers are painted, the image layer's blend mode (or the mask + * layer's composition mode) will be used. + */ + static ImgDrawResult PaintStyleImageLayerWithSC( + const PaintBGParams& aParams, gfxContext& aRenderingCtx, + mozilla::ComputedStyle* mBackgroundSC, const nsStyleBorder& aBorder); + + static bool CanBuildWebRenderDisplayItemsForStyleImageLayer( + LayerManager* aManager, nsPresContext& aPresCtx, nsIFrame* aFrame, + const nsStyleBackground* aBackgroundStyle, int32_t aLayer, + uint32_t aPaintFlags); + static ImgDrawResult BuildWebRenderDisplayItemsForStyleImageLayer( + const PaintBGParams& aParams, mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const mozilla::layers::StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, nsDisplayItem* aItem); + + static ImgDrawResult BuildWebRenderDisplayItemsForStyleImageLayerWithSC( + const PaintBGParams& aParams, mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const mozilla::layers::StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, nsDisplayItem* aItem, + mozilla::ComputedStyle* mBackgroundSC, const nsStyleBorder& aBorder); + + /** + * Returns the rectangle covered by the given background layer image, taking + * into account background positioning, sizing, and repetition, but not + * clipping. + */ + static nsRect GetBackgroundLayerRect(nsPresContext* aPresContext, + nsIFrame* aForFrame, + const nsRect& aBorderArea, + const nsRect& aClipRect, + const nsStyleImageLayers::Layer& aLayer, + uint32_t aFlags); + + /** + * Called when we start creating a display list. The frame tree will not + * change until a matching EndFrameTreeLocked is called. + */ + static void BeginFrameTreesLocked(); + /** + * Called when we've finished using a display list. When all + * BeginFrameTreeLocked calls have been balanced by an EndFrameTreeLocked, + * the frame tree may start changing again. + */ + static void EndFrameTreesLocked(); + + // Draw a border segment in the table collapsing border model with beveling + // corners. + static void DrawTableBorderSegment( + DrawTarget& aDrawTarget, mozilla::StyleBorderStyle aBorderStyle, + nscolor aBorderColor, const nsRect& aBorderRect, + int32_t aAppUnitsPerDevPixel, mozilla::Side aStartBevelSide, + nscoord aStartBevelOffset, mozilla::Side aEndBevelSide, + nscoord aEndBevelOffset); + + // A single border bevel. + struct Bevel { + mozilla::Side mSide; + nscoord mOffset; + }; + + // A single solid beveled border segment. + struct SolidBeveledBorderSegment { + nsRect mRect; + nscolor mColor; + Bevel mStartBevel; + Bevel mEndBevel; + }; + + // Collect the table border segments with beveling. Can't be called with + // dashed / dotted borders, since we don't support beveling those. + static void GetTableBorderSolidSegments( + nsTArray<SolidBeveledBorderSegment>& aSegments, + mozilla::StyleBorderStyle aBorderStyle, nscolor aBorderColor, + const nsRect& aBorderRect, int32_t aAppUnitsPerDevPixel, + mozilla::Side aStartBevelSide, nscoord aStartBevelOffset, + mozilla::Side aEndBevelSide, nscoord aEndBevelOffset); + + // NOTE: pt, dirtyRect, lineSize, ascent, offset in the following + // structs are non-rounded device pixels, not app units. + struct DecorationRectParams { + // The width [length] and the height [thickness] of the decoration + // line. This is a "logical" size in textRun orientation, so that + // for a vertical textrun, width will actually be a physical height; + // and conversely, height will be a physical width. + Size lineSize; + // The default height [thickness] of the line given by the font metrics. + // This is used for obtaining the correct offset for the decoration line + // when CSS specifies a unique thickness for a text-decoration, + // since the offset given by the font is derived from the font metric's + // assumed line height + Float defaultLineThickness = 0.0f; + // The ascent of the text. + Float ascent = 0.0f; + // The offset of the decoration line from the baseline of the text + // (if the value is positive, the line is lifted up). + Float offset = 0.0f; + // If descentLimit is zero or larger and the underline overflows + // from the descent space, the underline should be lifted up as far + // as possible. Note that this does not mean the underline never + // overflows from this limitation, because if the underline is + // positioned to the baseline or upper, it causes unreadability. + // Note that if this is zero or larger, the underline rect may be + // shrunken if it's possible. Therefore, this value is used for + // strikeout line and overline too. + Float descentLimit = -1.0f; + // Which line will be painted. The value can be + // UNDERLINE or OVERLINE or LINE_THROUGH. + mozilla::StyleTextDecorationLine decoration = + mozilla::StyleTextDecorationLine::UNDERLINE; + // The style of the decoration line such as + // NS_STYLE_TEXT_DECORATION_STYLE_*. + uint8_t style = NS_STYLE_TEXT_DECORATION_STYLE_NONE; + bool vertical = false; + bool sidewaysLeft = false; + gfxTextRun::Range glyphRange; + gfxTextRun::PropertyProvider* provider; + }; + + struct PaintDecorationLineParams : DecorationRectParams { + // No need to paint outside this rect. + Rect dirtyRect; + // The top/left edge of the text. + Point pt; + // The color of the decoration line. + nscolor color = NS_RGBA(0, 0, 0, 0); + // The distance between the left edge of the given frame and the + // position of the text as positioned without offset of the shadow. + Float icoordInFrame = 0.0f; + // Baseline offset being applied to this text (block-direction adjustment + // applied to glyph positions when computing skip-ink intercepts). + Float baselineOffset = 0.0f; + }; + + /** + * Function for painting the clipped decoration lines for the text. + * Takes into account the rect clipping that occurs when + * text-decoration-skip-ink is being used for underlines or overlines + * + * input: + * @param aFrame the frame which needs the decoration line + * @param aDrawTarget the target/backend being drawn to + * @param aParams the parameters for the decoration line + * being drawn + * @param aRect the rect representing the decoration line + */ + static void PaintDecorationLineInternal( + nsIFrame* aFrame, DrawTarget& aDrawTarget, + const PaintDecorationLineParams& aParams, Rect aRect); + + /** + * Function for painting the decoration lines for the text. + * + * input: + * @param aFrame the frame which needs the decoration line + * @param aDrawTarget the target/backend being drawn to + * @param aParams the parameters for the decoration line + * being drawn + */ + static void PaintDecorationLine(nsIFrame* aFrame, DrawTarget& aDrawTarget, + const PaintDecorationLineParams& aParams); + + /** + * Returns a Rect corresponding to the outline of the decoration line for the + * given text metrics. Arguments have the same meaning as for + * PaintDecorationLine. Currently this only works for solid + * decorations; for other decoration styles the returned Rect will be empty. + */ + static Rect DecorationLineToPath(const PaintDecorationLineParams& aParams); + + /** + * Function for getting the decoration line rect for the text. + * NOTE: aLineSize, aAscent and aOffset are non-rounded device pixels, + * not app units. + * input: + * @param aPresContext + * output: + * @return the decoration line rect for the input, + * the each values are app units. + */ + static nsRect GetTextDecorationRect(nsPresContext* aPresContext, + const DecorationRectParams& aParams); + + static CompositionOp GetGFXBlendMode(mozilla::StyleBlend mBlendMode) { + switch (mBlendMode) { + case mozilla::StyleBlend::Normal: + return CompositionOp::OP_OVER; + case mozilla::StyleBlend::Multiply: + return CompositionOp::OP_MULTIPLY; + case mozilla::StyleBlend::Screen: + return CompositionOp::OP_SCREEN; + case mozilla::StyleBlend::Overlay: + return CompositionOp::OP_OVERLAY; + case mozilla::StyleBlend::Darken: + return CompositionOp::OP_DARKEN; + case mozilla::StyleBlend::Lighten: + return CompositionOp::OP_LIGHTEN; + case mozilla::StyleBlend::ColorDodge: + return CompositionOp::OP_COLOR_DODGE; + case mozilla::StyleBlend::ColorBurn: + return CompositionOp::OP_COLOR_BURN; + case mozilla::StyleBlend::HardLight: + return CompositionOp::OP_HARD_LIGHT; + case mozilla::StyleBlend::SoftLight: + return CompositionOp::OP_SOFT_LIGHT; + case mozilla::StyleBlend::Difference: + return CompositionOp::OP_DIFFERENCE; + case mozilla::StyleBlend::Exclusion: + return CompositionOp::OP_EXCLUSION; + case mozilla::StyleBlend::Hue: + return CompositionOp::OP_HUE; + case mozilla::StyleBlend::Saturation: + return CompositionOp::OP_SATURATION; + case mozilla::StyleBlend::Color: + return CompositionOp::OP_COLOR; + case mozilla::StyleBlend::Luminosity: + return CompositionOp::OP_LUMINOSITY; + default: + MOZ_ASSERT(false); + return CompositionOp::OP_OVER; + } + } + + static CompositionOp GetGFXCompositeMode( + mozilla::StyleMaskComposite aCompositeMode) { + switch (aCompositeMode) { + case mozilla::StyleMaskComposite::Add: + return CompositionOp::OP_OVER; + case mozilla::StyleMaskComposite::Subtract: + return CompositionOp::OP_OUT; + case mozilla::StyleMaskComposite::Intersect: + return CompositionOp::OP_IN; + case mozilla::StyleMaskComposite::Exclude: + return CompositionOp::OP_XOR; + default: + MOZ_ASSERT(false); + return CompositionOp::OP_OVER; + } + } + + protected: + static gfxRect GetTextDecorationRectInternal( + const Point& aPt, const DecorationRectParams& aParams); + + /** + * Returns inflated rect for painting a decoration line. + * Complex style decoration lines should be painted from leftmost of nearest + * ancestor block box because that makes better look of connection of lines + * for different nodes. ExpandPaintingRectForDecorationLine() returns + * a rect for actual painting rect for the clipped rect. + * + * input: + * @param aFrame the frame which needs the decoration line. + * @param aStyle the style of the complex decoration line + * NS_STYLE_TEXT_DECORATION_STYLE_DOTTED or + * NS_STYLE_TEXT_DECORATION_STYLE_DASHED or + * NS_STYLE_TEXT_DECORATION_STYLE_WAVY. + * @param aClippedRect the clipped rect for the decoration line. + * in other words, visible area of the line. + * @param aICoordInFrame the distance between inline-start edge of aFrame + * and aClippedRect.pos. + * @param aCycleLength the width of one cycle of the line style. + */ + static Rect ExpandPaintingRectForDecorationLine( + nsIFrame* aFrame, const uint8_t aStyle, const Rect& aClippedRect, + const Float aICoordInFrame, const Float aCycleLength, bool aVertical); +}; + +/* + * nsContextBoxBlur + * Creates an 8-bit alpha channel context for callers to draw in, blurs the + * contents of that context and applies it as a 1-color mask on a + * different existing context. Uses gfxAlphaBoxBlur as its back end. + * + * You must call Init() first to create a suitable temporary surface to draw + * on. You must then draw any desired content onto the given context, then + * call DoPaint() to apply the blurred content as a single-color mask. You + * can only call Init() once, so objects cannot be reused. + * + * This is very useful for creating drop shadows or silhouettes. + */ +class nsContextBoxBlur { + typedef mozilla::gfx::sRGBColor sRGBColor; + typedef mozilla::gfx::DrawTarget DrawTarget; + typedef mozilla::gfx::RectCornerRadii RectCornerRadii; + + public: + enum { FORCE_MASK = 0x01, DISABLE_HARDWARE_ACCELERATION_BLUR = 0x02 }; + /** + * Prepares a gfxContext to draw on. Do not call this twice; if you want + * to get the gfxContext again use GetContext(). + * + * @param aRect The coordinates of the surface to create. + * All coordinates must be in app units. + * This must not include the blur radius, pass + * it as the second parameter and everything + * is taken care of. + * + * @param aBlurRadius The blur radius in app units. + * + * @param aAppUnitsPerDevPixel The number of app units in a device pixel, + * for conversion. Most of the time you'll + * pass this from the current PresContext if + * available. + * + * @param aDestinationCtx The graphics context to apply the blurred + * mask to when you call DoPaint(). Make sure + * it is not destroyed before you call + * DoPaint(). To set the color of the + * resulting blurred graphic mask, you must + * set the color on this context before + * calling Init(). + * + * @param aDirtyRect The absolute dirty rect in app units. Used to + * optimize the temporary surface size and speed + * up blur. + * + * @param aSkipRect An area in device pixels (NOT app units!) to + * avoid blurring over, to prevent unnecessary work. + * + * @param aFlags FORCE_MASK to ensure that the content drawn to + * the returned gfxContext is used as a mask, and not drawn directly to + * aDestinationCtx. + * + * @return A blank 8-bit alpha-channel-only graphics context to + * draw on, or null on error. Must not be freed. The + * context has a device offset applied to it given by + * aRect. This means you can use coordinates as if it + * were at the desired position at aRect and you don't + * need to worry about translating any coordinates to + * draw on this temporary surface. + * + * If aBlurRadius is 0, the returned context is aDestinationCtx and + * DoPaint() does nothing, because no blurring is required. Therefore, you + * should prepare the destination context as if you were going to draw + * directly on it instead of any temporary surface created in this class. + */ + gfxContext* Init(const nsRect& aRect, nscoord aSpreadRadius, + nscoord aBlurRadius, int32_t aAppUnitsPerDevPixel, + gfxContext* aDestinationCtx, const nsRect& aDirtyRect, + const gfxRect* aSkipRect, uint32_t aFlags = 0); + + /** + * Does the actual blurring and mask applying. Users of this object *must* + * have called Init() first, then have drawn whatever they want to be + * blurred onto the internal gfxContext before calling this. + */ + void DoPaint(); + + /** + * Gets the internal gfxContext at any time. Must not be freed. Avoid + * calling this before calling Init() since the context would not be + * constructed at that point. + */ + gfxContext* GetContext(); + + /** + * Get the margin associated with the given blur radius, i.e., the + * additional area that might be painted as a result of it. (The + * margin for a spread radius is itself, on all sides.) + */ + static nsMargin GetBlurRadiusMargin(nscoord aBlurRadius, + int32_t aAppUnitsPerDevPixel); + + /** + * Blurs a coloured rectangle onto aDestinationCtx. This is equivalent + * to calling Init(), drawing a rectangle onto the returned surface + * and then calling DoPaint, but may let us optimize better in the + * backend. + * + * @param aDestinationCtx The destination to blur to. + * @param aRect The rectangle to blur in app units. + * @param aAppUnitsPerDevPixel The number of app units in a device pixel, + * for conversion. Most of the time you'll + * pass this from the current PresContext if + * available. + * @param aCornerRadii Corner radii for aRect, if it is a rounded + * rectangle. + * @param aBlurRadius The blur radius in app units. + * @param aShadowColor The color to draw the blurred shadow. + * @param aDirtyRect The absolute dirty rect in app units. Used to + * optimize the temporary surface size and speed + * up blur. + * @param aSkipRect An area in device pixels (NOT app units!) to + * avoid blurring over, to prevent unnecessary work. + */ + static void BlurRectangle(gfxContext* aDestinationCtx, const nsRect& aRect, + int32_t aAppUnitsPerDevPixel, + RectCornerRadii* aCornerRadii, nscoord aBlurRadius, + const sRGBColor& aShadowColor, + const nsRect& aDirtyRect, const gfxRect& aSkipRect); + + /** + * Draws a blurred inset box shadow shape onto the destination surface. + * Like BlurRectangle, this is equivalent to calling Init(), + * drawing a rectangle onto the returned surface + * and then calling DoPaint, but may let us optimize better in the + * backend. + * + * @param aDestinationCtx The destination to blur to. + * @param aDestinationRect The rectangle to blur in app units. + * @param aShadowClipRect The inside clip rect that creates the path. + * @param aShadowColor The color of the blur + * @param aBlurRadiusAppUnits The blur radius in app units + * @param aSpreadRadiusAppUnits The spread radius in app units. + * @param aAppUnitsPerDevPixel The number of app units in a device pixel, + * for conversion. Most of the time you'll + * pass this from the current PresContext if + * available. + * @param aHasBorderRadius If this inset box blur has a border radius + * @param aInnerClipRectRadii The clip rect radii used for the inside rect's + * path. + * @param aSkipRect An area in device pixels (NOT app units!) to + * avoid blurring over, to prevent unnecessary work. + */ + bool InsetBoxBlur(gfxContext* aDestinationCtx, + mozilla::gfx::Rect aDestinationRect, + mozilla::gfx::Rect aShadowClipRect, + mozilla::gfx::sRGBColor& aShadowColor, + nscoord aBlurRadiusAppUnits, nscoord aSpreadRadiusAppUnits, + int32_t aAppUnitsPerDevPixel, bool aHasBorderRadius, + RectCornerRadii& aInnerClipRectRadii, + mozilla::gfx::Rect aSkipRect, + mozilla::gfx::Point aShadowOffset); + + protected: + static void GetBlurAndSpreadRadius(DrawTarget* aDestDrawTarget, + int32_t aAppUnitsPerDevPixel, + nscoord aBlurRadius, nscoord aSpreadRadius, + mozilla::gfx::IntSize& aOutBlurRadius, + mozilla::gfx::IntSize& aOutSpreadRadius, + bool aConstrainSpreadRadius = true); + + gfxAlphaBoxBlur mAlphaBoxBlur; + RefPtr<gfxContext> mContext; + gfxContext* mDestinationCtx; + + /* This is true if the blur already has it's content transformed + * by mDestinationCtx's transform */ + bool mPreTransformed; +}; + +#endif /* nsCSSRendering_h___ */ diff --git a/layout/painting/nsCSSRenderingBorders.cpp b/layout/painting/nsCSSRenderingBorders.cpp new file mode 100644 index 0000000000..5ab531f244 --- /dev/null +++ b/layout/painting/nsCSSRenderingBorders.cpp @@ -0,0 +1,3899 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsCSSRenderingBorders.h" + +#include "gfxUtils.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/Helpers.h" +#include "mozilla/gfx/PathHelpers.h" +#include "BorderConsts.h" +#include "DashedCornerFinder.h" +#include "DottedCornerFinder.h" +#include "nsLayoutUtils.h" +#include "nsStyleConsts.h" +#include "nsContentUtils.h" +#include "nsCSSColorUtils.h" +#include "nsCSSRendering.h" +#include "nsCSSRenderingGradients.h" +#include "nsDisplayList.h" +#include "GeckoProfiler.h" +#include "nsExpirationTracker.h" +#include "nsIScriptError.h" +#include "nsClassHashtable.h" +#include "nsPresContext.h" +#include "nsStyleStruct.h" +#include "gfx2DGlue.h" +#include "gfxGradientCache.h" +#include "mozilla/layers/StackingContextHelper.h" +#include "mozilla/layers/RenderRootStateManager.h" +#include "mozilla/layers/WebRenderLayerManager.h" +#include "mozilla/Range.h" +#include <algorithm> + +using namespace mozilla; +using namespace mozilla::gfx; +using namespace mozilla::image; +using mozilla::dom::Document; + +#define MAX_COMPOSITE_BORDER_WIDTH LayoutDeviceIntCoord(10000) + +/** + * nsCSSRendering::PaintBorder + * nsCSSRendering::PaintOutline + * -> DrawBorders + * + * DrawBorders + * -> Ability to use specialized approach? + * |- Draw using specialized function + * |- separate corners? + * |- dashed side mask + * | + * -> can border be drawn in 1 pass? (e.g., solid border same color all + * around) + * |- DrawBorderSides with all 4 sides + * -> more than 1 pass? + * |- for each corner + * |- clip to DoCornerClipSubPath + * |- for each side adjacent to corner + * |- clip to GetSideClipSubPath + * |- DrawBorderSides with one side + * |- for each side + * |- GetSideClipWithoutCornersRect + * |- DrawDashedOrDottedSide || DrawBorderSides with one side + */ + +static void ComputeBorderCornerDimensions(const Float* aBorderWidths, + const RectCornerRadii& aRadii, + RectCornerRadii* aDimsResult); + +// given a side index, get the previous and next side index +#define NEXT_SIDE(_s) mozilla::Side(((_s) + 1) & 3) +#define PREV_SIDE(_s) mozilla::Side(((_s) + 3) & 3) + +// given a corner index, get the previous and next corner index +#define NEXT_CORNER(_s) Corner(((_s) + 1) & 3) +#define PREV_CORNER(_s) Corner(((_s) + 3) & 3) + +// from the given base color and the background color, turn +// color into a color for the given border pattern style +static sRGBColor MakeBorderColor(nscolor aColor, + BorderColorStyle aBorderColorStyle); + +// Given a line index (an index starting from the outside of the +// border going inwards) and an array of line styles, calculate the +// color that that stripe of the border should be rendered in. +static sRGBColor ComputeColorForLine(uint32_t aLineIndex, + const BorderColorStyle* aBorderColorStyle, + uint32_t aBorderColorStyleCount, + nscolor aBorderColor); + +// little helper function to check if the array of 4 floats given are +// equal to the given value +static bool CheckFourFloatsEqual(const Float* vals, Float k) { + return (vals[0] == k && vals[1] == k && vals[2] == k && vals[3] == k); +} + +static bool IsZeroSize(const Size& sz) { + return sz.width == 0.0 || sz.height == 0.0; +} + +/* static */ +bool nsCSSBorderRenderer::AllCornersZeroSize(const RectCornerRadii& corners) { + return IsZeroSize(corners[eCornerTopLeft]) && + IsZeroSize(corners[eCornerTopRight]) && + IsZeroSize(corners[eCornerBottomRight]) && + IsZeroSize(corners[eCornerBottomLeft]); +} + +static mozilla::Side GetHorizontalSide(Corner aCorner) { + return (aCorner == C_TL || aCorner == C_TR) ? eSideTop : eSideBottom; +} + +static mozilla::Side GetVerticalSide(Corner aCorner) { + return (aCorner == C_TL || aCorner == C_BL) ? eSideLeft : eSideRight; +} + +static Corner GetCWCorner(mozilla::Side aSide) { + return Corner(NEXT_SIDE(aSide)); +} + +static Corner GetCCWCorner(mozilla::Side aSide) { return Corner(aSide); } + +static bool IsSingleSide(mozilla::SideBits aSides) { + return aSides == SideBits::eTop || aSides == SideBits::eRight || + aSides == SideBits::eBottom || aSides == SideBits::eLeft; +} + +static bool IsHorizontalSide(mozilla::Side aSide) { + return aSide == eSideTop || aSide == eSideBottom; +} + +typedef enum { + // Normal solid square corner. Will be rectangular, the size of the + // adjacent sides. If the corner has a border radius, the corner + // will always be solid, since we don't do dotted/dashed etc. + CORNER_NORMAL, + + // Paint the corner in whatever style is not dotted/dashed of the + // adjacent corners. + CORNER_SOLID, + + // Paint the corner as a dot, the size of the bigger of the adjacent + // sides. + CORNER_DOT +} CornerStyle; + +nsCSSBorderRenderer::nsCSSBorderRenderer( + nsPresContext* aPresContext, const Document* aDocument, + DrawTarget* aDrawTarget, const Rect& aDirtyRect, Rect& aOuterRect, + const StyleBorderStyle* aBorderStyles, const Float* aBorderWidths, + RectCornerRadii& aBorderRadii, const nscolor* aBorderColors, + bool aBackfaceIsVisible, const Maybe<Rect>& aClipRect) + : mPresContext(aPresContext), + mDocument(aDocument), + mDrawTarget(aDrawTarget), + mDirtyRect(aDirtyRect), + mOuterRect(aOuterRect), + mBorderRadii(aBorderRadii), + mBackfaceIsVisible(aBackfaceIsVisible), + mLocalClip(aClipRect) { + PodCopy(mBorderStyles, aBorderStyles, 4); + PodCopy(mBorderWidths, aBorderWidths, 4); + PodCopy(mBorderColors, aBorderColors, 4); + mInnerRect = mOuterRect; + mInnerRect.Deflate(Margin( + mBorderStyles[0] != StyleBorderStyle::None ? mBorderWidths[0] : 0, + mBorderStyles[1] != StyleBorderStyle::None ? mBorderWidths[1] : 0, + mBorderStyles[2] != StyleBorderStyle::None ? mBorderWidths[2] : 0, + mBorderStyles[3] != StyleBorderStyle::None ? mBorderWidths[3] : 0)); + + ComputeBorderCornerDimensions(mBorderWidths, mBorderRadii, + &mBorderCornerDimensions); + + mOneUnitBorder = CheckFourFloatsEqual(mBorderWidths, 1.0); + mNoBorderRadius = AllCornersZeroSize(mBorderRadii); + mAllBordersSameStyle = AreBorderSideFinalStylesSame(SideBits::eAll); + mAllBordersSameWidth = AllBordersSameWidth(); + mAvoidStroke = false; +} + +/* static */ +void nsCSSBorderRenderer::ComputeInnerRadii(const RectCornerRadii& aRadii, + const Float* aBorderSizes, + RectCornerRadii* aInnerRadiiRet) { + RectCornerRadii& iRadii = *aInnerRadiiRet; + + iRadii[C_TL].width = + std::max(0.f, aRadii[C_TL].width - aBorderSizes[eSideLeft]); + iRadii[C_TL].height = + std::max(0.f, aRadii[C_TL].height - aBorderSizes[eSideTop]); + + iRadii[C_TR].width = + std::max(0.f, aRadii[C_TR].width - aBorderSizes[eSideRight]); + iRadii[C_TR].height = + std::max(0.f, aRadii[C_TR].height - aBorderSizes[eSideTop]); + + iRadii[C_BR].width = + std::max(0.f, aRadii[C_BR].width - aBorderSizes[eSideRight]); + iRadii[C_BR].height = + std::max(0.f, aRadii[C_BR].height - aBorderSizes[eSideBottom]); + + iRadii[C_BL].width = + std::max(0.f, aRadii[C_BL].width - aBorderSizes[eSideLeft]); + iRadii[C_BL].height = + std::max(0.f, aRadii[C_BL].height - aBorderSizes[eSideBottom]); +} + +/* static */ +void nsCSSBorderRenderer::ComputeOuterRadii(const RectCornerRadii& aRadii, + const Float* aBorderSizes, + RectCornerRadii* aOuterRadiiRet) { + RectCornerRadii& oRadii = *aOuterRadiiRet; + + // default all corners to sharp corners + oRadii = RectCornerRadii(0.f); + + // round the edges that have radii > 0.0 to start with + if (aRadii[C_TL].width > 0.f && aRadii[C_TL].height > 0.f) { + oRadii[C_TL].width = + std::max(0.f, aRadii[C_TL].width + aBorderSizes[eSideLeft]); + oRadii[C_TL].height = + std::max(0.f, aRadii[C_TL].height + aBorderSizes[eSideTop]); + } + + if (aRadii[C_TR].width > 0.f && aRadii[C_TR].height > 0.f) { + oRadii[C_TR].width = + std::max(0.f, aRadii[C_TR].width + aBorderSizes[eSideRight]); + oRadii[C_TR].height = + std::max(0.f, aRadii[C_TR].height + aBorderSizes[eSideTop]); + } + + if (aRadii[C_BR].width > 0.f && aRadii[C_BR].height > 0.f) { + oRadii[C_BR].width = + std::max(0.f, aRadii[C_BR].width + aBorderSizes[eSideRight]); + oRadii[C_BR].height = + std::max(0.f, aRadii[C_BR].height + aBorderSizes[eSideBottom]); + } + + if (aRadii[C_BL].width > 0.f && aRadii[C_BL].height > 0.f) { + oRadii[C_BL].width = + std::max(0.f, aRadii[C_BL].width + aBorderSizes[eSideLeft]); + oRadii[C_BL].height = + std::max(0.f, aRadii[C_BL].height + aBorderSizes[eSideBottom]); + } +} + +/*static*/ void ComputeBorderCornerDimensions(const Float* aBorderWidths, + const RectCornerRadii& aRadii, + RectCornerRadii* aDimsRet) { + Float leftWidth = aBorderWidths[eSideLeft]; + Float topWidth = aBorderWidths[eSideTop]; + Float rightWidth = aBorderWidths[eSideRight]; + Float bottomWidth = aBorderWidths[eSideBottom]; + + if (nsCSSBorderRenderer::AllCornersZeroSize(aRadii)) { + // These will always be in pixel units from CSS + (*aDimsRet)[C_TL] = Size(leftWidth, topWidth); + (*aDimsRet)[C_TR] = Size(rightWidth, topWidth); + (*aDimsRet)[C_BR] = Size(rightWidth, bottomWidth); + (*aDimsRet)[C_BL] = Size(leftWidth, bottomWidth); + } else { + // Always round up to whole pixels for the corners; it's safe to + // make the corners bigger than necessary, and this way we ensure + // that we avoid seams. + (*aDimsRet)[C_TL] = Size(ceil(std::max(leftWidth, aRadii[C_TL].width)), + ceil(std::max(topWidth, aRadii[C_TL].height))); + (*aDimsRet)[C_TR] = Size(ceil(std::max(rightWidth, aRadii[C_TR].width)), + ceil(std::max(topWidth, aRadii[C_TR].height))); + (*aDimsRet)[C_BR] = Size(ceil(std::max(rightWidth, aRadii[C_BR].width)), + ceil(std::max(bottomWidth, aRadii[C_BR].height))); + (*aDimsRet)[C_BL] = Size(ceil(std::max(leftWidth, aRadii[C_BL].width)), + ceil(std::max(bottomWidth, aRadii[C_BL].height))); + } +} + +bool nsCSSBorderRenderer::AreBorderSideFinalStylesSame( + mozilla::SideBits aSides) { + NS_ASSERTION(aSides != SideBits::eNone && + (aSides & ~SideBits::eAll) == SideBits::eNone, + "AreBorderSidesSame: invalid whichSides!"); + + /* First check if the specified styles and colors are the same for all sides + */ + int firstStyle = 0; + for (const auto i : mozilla::AllPhysicalSides()) { + if (firstStyle == i) { + if ((static_cast<mozilla::SideBits>(1 << i) & aSides) == + SideBits::eNone) { + firstStyle++; + } + continue; + } + + if ((static_cast<mozilla::SideBits>(1 << i) & aSides) == SideBits::eNone) { + continue; + } + + if (mBorderStyles[firstStyle] != mBorderStyles[i] || + mBorderColors[firstStyle] != mBorderColors[i]) { + return false; + } + } + + /* Then if it's one of the two-tone styles and we're not + * just comparing the TL or BR sides */ + switch (mBorderStyles[firstStyle]) { + case StyleBorderStyle::Groove: + case StyleBorderStyle::Ridge: + case StyleBorderStyle::Inset: + case StyleBorderStyle::Outset: + return ((aSides & ~(SideBits::eTop | SideBits::eLeft)) == + SideBits::eNone || + (aSides & ~(SideBits::eBottom | SideBits::eRight)) == + SideBits::eNone); + default: + return true; + } +} + +bool nsCSSBorderRenderer::IsSolidCornerStyle(StyleBorderStyle aStyle, + Corner aCorner) { + switch (aStyle) { + case StyleBorderStyle::Solid: + return true; + + case StyleBorderStyle::Inset: + case StyleBorderStyle::Outset: + return (aCorner == eCornerTopLeft || aCorner == eCornerBottomRight); + + case StyleBorderStyle::Groove: + case StyleBorderStyle::Ridge: + return mOneUnitBorder && + (aCorner == eCornerTopLeft || aCorner == eCornerBottomRight); + + case StyleBorderStyle::Double: + return mOneUnitBorder; + + default: + return false; + } +} + +bool nsCSSBorderRenderer::IsCornerMergeable(Corner aCorner) { + // Corner between dotted borders with same width and small radii is + // merged into single dot. + // + // widthH / 2.0 + // |<---------->| + // | | + // |radius.width| + // |<--->| | + // | | | + // | _+------+------------+----- + // | / ###|### | + // |/ #######|####### | + // + #########|######### | + // | ##########|########## | + // | ###########|########### | + // | ###########|########### | + // |############|############| + // +------------+############| + // |#########################| + // | ####################### | + // | ####################### | + // | ##################### | + // | ################### | + // | ############### | + // | ####### | + // +-------------------------+---- + // | | + // | | + mozilla::Side sideH(GetHorizontalSide(aCorner)); + mozilla::Side sideV(GetVerticalSide(aCorner)); + StyleBorderStyle styleH = mBorderStyles[sideH]; + StyleBorderStyle styleV = mBorderStyles[sideV]; + if (styleH != styleV || styleH != StyleBorderStyle::Dotted) { + return false; + } + + Float widthH = mBorderWidths[sideH]; + Float widthV = mBorderWidths[sideV]; + if (widthH != widthV) { + return false; + } + + Size radius = mBorderRadii[aCorner]; + return IsZeroSize(radius) || + (radius.width < widthH / 2.0f && radius.height < widthH / 2.0f); +} + +BorderColorStyle nsCSSBorderRenderer::BorderColorStyleForSolidCorner( + StyleBorderStyle aStyle, Corner aCorner) { + // note that this function assumes that the corner is already solid, + // as per the earlier function + switch (aStyle) { + case StyleBorderStyle::Solid: + case StyleBorderStyle::Double: + return BorderColorStyleSolid; + + case StyleBorderStyle::Inset: + case StyleBorderStyle::Groove: + if (aCorner == eCornerTopLeft) { + return BorderColorStyleDark; + } else if (aCorner == eCornerBottomRight) { + return BorderColorStyleLight; + } + break; + + case StyleBorderStyle::Outset: + case StyleBorderStyle::Ridge: + if (aCorner == eCornerTopLeft) { + return BorderColorStyleLight; + } else if (aCorner == eCornerBottomRight) { + return BorderColorStyleDark; + } + break; + default: + return BorderColorStyleNone; + } + + return BorderColorStyleNone; +} + +Rect nsCSSBorderRenderer::GetCornerRect(Corner aCorner) { + Point offset(0.f, 0.f); + + if (aCorner == C_TR || aCorner == C_BR) + offset.x = mOuterRect.Width() - mBorderCornerDimensions[aCorner].width; + if (aCorner == C_BR || aCorner == C_BL) + offset.y = mOuterRect.Height() - mBorderCornerDimensions[aCorner].height; + + return Rect(mOuterRect.TopLeft() + offset, mBorderCornerDimensions[aCorner]); +} + +Rect nsCSSBorderRenderer::GetSideClipWithoutCornersRect(mozilla::Side aSide) { + Point offset(0.f, 0.f); + + // The offset from the outside rect to the start of this side's + // box. For the top and bottom sides, the height of the box + // must be the border height; the x start must take into account + // the corner size (which may be bigger than the right or left + // side's width). The same applies to the right and left sides. + if (aSide == eSideTop) { + offset.x = mBorderCornerDimensions[C_TL].width; + } else if (aSide == eSideRight) { + offset.x = mOuterRect.Width() - mBorderWidths[eSideRight]; + offset.y = mBorderCornerDimensions[C_TR].height; + } else if (aSide == eSideBottom) { + offset.x = mBorderCornerDimensions[C_BL].width; + offset.y = mOuterRect.Height() - mBorderWidths[eSideBottom]; + } else if (aSide == eSideLeft) { + offset.y = mBorderCornerDimensions[C_TL].height; + } + + // The sum of the width & height of the corners adjacent to the + // side. This relies on the relationship between side indexing and + // corner indexing; that is, 0 == SIDE_TOP and 0 == CORNER_TOP_LEFT, + // with both proceeding clockwise. + Size sideCornerSum = mBorderCornerDimensions[GetCCWCorner(aSide)] + + mBorderCornerDimensions[GetCWCorner(aSide)]; + Rect rect(mOuterRect.TopLeft() + offset, mOuterRect.Size() - sideCornerSum); + + if (IsHorizontalSide(aSide)) + rect.height = mBorderWidths[aSide]; + else + rect.width = mBorderWidths[aSide]; + + return rect; +} + +// The side border type and the adjacent border types are +// examined and one of the different types of clipping (listed +// below) is selected. + +typedef enum { + // clip to the trapezoid formed by the corners of the + // inner and outer rectangles for the given side + // + // +--------------- + // |\%%%%%%%%%%%%%% + // | \%%%%%%%%%%%% + // | \%%%%%%%%%%% + // | \%%%%%%%%% + // | +-------- + // | | + // | | + SIDE_CLIP_TRAPEZOID, + + // clip to the trapezoid formed by the outer rectangle + // corners and the center of the region, making sure + // that diagonal lines all go directly from the outside + // corner to the inside corner, but that they then continue on + // to the middle. + // + // This is needed for correctly clipping rounded borders, + // which might extend past the SIDE_CLIP_TRAPEZOID trap. + // + // +-------__--+--- + // \%%%%_-%%%%%%%% + // \+-%%%%%%%%%% + // / \%%%%%%%%%% + // / \%%%%%%%%% + // | +%%_-+--- + // | +%%%%%% + // | / \%%%%% + // + + \%%% + // | | +- + SIDE_CLIP_TRAPEZOID_FULL, + + // clip to the rectangle formed by the given side including corner. + // This is used by the non-dotted side next to dotted side. + // + // +--------------- + // |%%%%%%%%%%%%%%% + // |%%%%%%%%%%%%%%% + // |%%%%%%%%%%%%%%% + // |%%%%%%%%%%%%%%% + // +------+-------- + // | | + // | | + SIDE_CLIP_RECTANGLE_CORNER, + + // clip to the rectangle formed by the given side excluding corner. + // This is used by the dotted side next to non-dotted side. + // + // +------+-------- + // | |%%%%%%%% + // | |%%%%%%%% + // | |%%%%%%%% + // | |%%%%%%%% + // | +-------- + // | | + // | | + SIDE_CLIP_RECTANGLE_NO_CORNER, +} SideClipType; + +// Given three points, p0, p1, and midPoint, move p1 further in to the +// rectangle (of which aMidPoint is the center) so that it reaches the +// closer of the horizontal or vertical lines intersecting the midpoint, +// while maintaing the slope of the line. If p0 and p1 are the same, +// just move p1 to midPoint (since there's no slope to maintain). +// FIXME: Extending only to the midpoint isn't actually sufficient for +// boxes with asymmetric radii. +static void MaybeMoveToMidPoint(Point& aP0, Point& aP1, + const Point& aMidPoint) { + Point ps = aP1 - aP0; + + if (ps.x == 0.0) { + if (ps.y == 0.0) { + aP1 = aMidPoint; + } else { + aP1.y = aMidPoint.y; + } + } else { + if (ps.y == 0.0) { + aP1.x = aMidPoint.x; + } else { + Float k = + std::min((aMidPoint.x - aP0.x) / ps.x, (aMidPoint.y - aP0.y) / ps.y); + aP1 = aP0 + ps * k; + } + } +} + +already_AddRefed<Path> nsCSSBorderRenderer::GetSideClipSubPath( + mozilla::Side aSide) { + // the clip proceeds clockwise from the top left corner; + // so "start" in each case is the start of the region from that side. + // + // the final path will be formed like: + // s0 ------- e0 + // | / + // s1 ----- e1 + // + // that is, the second point will always be on the inside + + Point start[2]; + Point end[2]; + +#define IS_DOTTED(_s) ((_s) == StyleBorderStyle::Dotted) + bool isDotted = IS_DOTTED(mBorderStyles[aSide]); + bool startIsDotted = IS_DOTTED(mBorderStyles[PREV_SIDE(aSide)]); + bool endIsDotted = IS_DOTTED(mBorderStyles[NEXT_SIDE(aSide)]); +#undef IS_DOTTED + + SideClipType startType = SIDE_CLIP_TRAPEZOID; + SideClipType endType = SIDE_CLIP_TRAPEZOID; + + if (!IsZeroSize(mBorderRadii[GetCCWCorner(aSide)])) { + startType = SIDE_CLIP_TRAPEZOID_FULL; + } else if (startIsDotted && !isDotted) { + startType = SIDE_CLIP_RECTANGLE_CORNER; + } else if (!startIsDotted && isDotted) { + startType = SIDE_CLIP_RECTANGLE_NO_CORNER; + } + + if (!IsZeroSize(mBorderRadii[GetCWCorner(aSide)])) { + endType = SIDE_CLIP_TRAPEZOID_FULL; + } else if (endIsDotted && !isDotted) { + endType = SIDE_CLIP_RECTANGLE_CORNER; + } else if (!endIsDotted && isDotted) { + endType = SIDE_CLIP_RECTANGLE_NO_CORNER; + } + + Point midPoint = mInnerRect.Center(); + + start[0] = mOuterRect.CCWCorner(aSide); + start[1] = mInnerRect.CCWCorner(aSide); + + end[0] = mOuterRect.CWCorner(aSide); + end[1] = mInnerRect.CWCorner(aSide); + + if (startType == SIDE_CLIP_TRAPEZOID_FULL) { + MaybeMoveToMidPoint(start[0], start[1], midPoint); + } else if (startType == SIDE_CLIP_RECTANGLE_CORNER) { + if (IsHorizontalSide(aSide)) { + start[1] = + Point(mOuterRect.CCWCorner(aSide).x, mInnerRect.CCWCorner(aSide).y); + } else { + start[1] = + Point(mInnerRect.CCWCorner(aSide).x, mOuterRect.CCWCorner(aSide).y); + } + } else if (startType == SIDE_CLIP_RECTANGLE_NO_CORNER) { + if (IsHorizontalSide(aSide)) { + start[0] = + Point(mInnerRect.CCWCorner(aSide).x, mOuterRect.CCWCorner(aSide).y); + } else { + start[0] = + Point(mOuterRect.CCWCorner(aSide).x, mInnerRect.CCWCorner(aSide).y); + } + } + + if (endType == SIDE_CLIP_TRAPEZOID_FULL) { + MaybeMoveToMidPoint(end[0], end[1], midPoint); + } else if (endType == SIDE_CLIP_RECTANGLE_CORNER) { + if (IsHorizontalSide(aSide)) { + end[1] = + Point(mOuterRect.CWCorner(aSide).x, mInnerRect.CWCorner(aSide).y); + } else { + end[1] = + Point(mInnerRect.CWCorner(aSide).x, mOuterRect.CWCorner(aSide).y); + } + } else if (endType == SIDE_CLIP_RECTANGLE_NO_CORNER) { + if (IsHorizontalSide(aSide)) { + end[0] = + Point(mInnerRect.CWCorner(aSide).x, mOuterRect.CWCorner(aSide).y); + } else { + end[0] = + Point(mOuterRect.CWCorner(aSide).x, mInnerRect.CWCorner(aSide).y); + } + } + + RefPtr<PathBuilder> builder = mDrawTarget->CreatePathBuilder(); + builder->MoveTo(start[0]); + builder->LineTo(end[0]); + builder->LineTo(end[1]); + builder->LineTo(start[1]); + builder->Close(); + return builder->Finish(); +} + +Point nsCSSBorderRenderer::GetStraightBorderPoint(mozilla::Side aSide, + Corner aCorner, + bool* aIsUnfilled, + Float aDotOffset) { + // Calculate the end point of the side for dashed/dotted border, that is also + // the end point of the corner curve. The point is specified by aSide and + // aCorner. (e.g. eSideTop and C_TL means the left end of border-top) + // + // + // aCorner aSide + // +-------------------- + // | + // | + // | +---------- + // | the end point + // | + // | +---------- + // | | + // | | + // | | + // + // The position of the point depends on the border-style, border-width, and + // border-radius of the side, corner, and the adjacent side beyond the corner, + // to make those sides (and corner) interact well. + // + // If the style of aSide is dotted and the dot at the point should be + // unfilled, true is stored to *aIsUnfilled, otherwise false is stored. + + const Float signsList[4][2] = { + {+1.0f, +1.0f}, {-1.0f, +1.0f}, {-1.0f, -1.0f}, {+1.0f, -1.0f}}; + const Float(&signs)[2] = signsList[aCorner]; + + *aIsUnfilled = false; + + Point P = mOuterRect.AtCorner(aCorner); + StyleBorderStyle style = mBorderStyles[aSide]; + Float borderWidth = mBorderWidths[aSide]; + Size dim = mBorderCornerDimensions[aCorner]; + bool isHorizontal = IsHorizontalSide(aSide); + // + // aCorner aSide + // +-------------- + // | + // | +---------- + // | | + // otherSide | | + // | | + mozilla::Side otherSide = ((uint8_t)aSide == (uint8_t)aCorner) + ? PREV_SIDE(aSide) + : NEXT_SIDE(aSide); + StyleBorderStyle otherStyle = mBorderStyles[otherSide]; + Float otherBorderWidth = mBorderWidths[otherSide]; + Size radius = mBorderRadii[aCorner]; + if (IsZeroSize(radius)) { + radius.width = 0.0f; + radius.height = 0.0f; + } + if (style == StyleBorderStyle::Dotted) { + // Offset the dot's location along the side toward the corner by a + // multiple of its width. + if (isHorizontal) { + P.x -= signs[0] * aDotOffset * borderWidth; + } else { + P.y -= signs[1] * aDotOffset * borderWidth; + } + } + if (style == StyleBorderStyle::Dotted && + otherStyle == StyleBorderStyle::Dotted) { + if (borderWidth == otherBorderWidth) { + if (radius.width < borderWidth / 2.0f && + radius.height < borderWidth / 2.0f) { + // Two dots are merged into one and placed at the corner. + // + // borderWidth / 2.0 + // |<---------->| + // | | + // |radius.width| + // |<--->| | + // | | | + // | _+------+------------+----- + // | / ###|### | + // |/ #######|####### | + // + #########|######### | + // | ##########|########## | + // | ###########|########### | + // | ###########|########### | + // |############|############| + // +------------+############| + // |########### P ###########| + // | ####################### | + // | ####################### | + // | ##################### | + // | ################### | + // | ############### | + // | ####### | + // +-------------------------+---- + // | | + // | | + P.x += signs[0] * borderWidth / 2.0f; + P.y += signs[1] * borderWidth / 2.0f; + } else { + // Two dots are drawn separately. + // + // borderWidth * 1.5 + // |<------------>| + // | | + // |radius.width | + // |<----->| | + // | | | + // | _--+-+----+--- + // | _- | ##|## + // | / | ###|### + // |/ |####|#### + // | |####+#### + // | |### P ### + // + | ###|### + // | | ##|## + // +---------+----+--- + // | ##### | + // | ####### | + // |#########| + // +----+----+ + // |#########| + // | ####### | + // | ##### | + // | | + // + // There should be enough gap between 2 dots even if radius.width is + // small but larger than borderWidth / 2.0. borderWidth * 1.5 is the + // value that there's imaginally unfilled dot at the corner. The + // unfilled dot may overflow from the outer curve, but filled dots + // doesn't, so this could be acceptable solution at least for now. + // We may have to find better model/value. + // + // imaginally unfilled dot at the corner + // | + // v +----+--- + // ***** | ##|## + // ******* | ###|### + // *********|####|#### + // *********|####+#### + // *********|### P ### + // ******* | ###|### + // ***** | ##|## + // +---------+----+--- + // | ##### | + // | ####### | + // |#########| + // +----+----+ + // |#########| + // | ####### | + // | ##### | + // | | + Float minimum = borderWidth * 1.5f; + if (isHorizontal) { + P.x += signs[0] * std::max(radius.width, minimum); + P.y += signs[1] * borderWidth / 2.0f; + } else { + P.x += signs[0] * borderWidth / 2.0f; + P.y += signs[1] * std::max(radius.height, minimum); + } + } + + return P; + } + + if (borderWidth < otherBorderWidth) { + // This side is smaller than other side, other side draws the corner. + // + // otherBorderWidth + borderWidth / 2.0 + // |<---------->| + // | | + // +---------+--+-------- + // | ##### | *|* ### + // | ####### |**|**##### + // |#########|**+**##+## + // |####+####|* P *##### + // |#########| *** ### + // | ####### +----------- + // | ##### | ^ + // | | | + // | | first dot is not filled + // | | + // + // radius.width + // |<----------------->| + // | | + // | ___---+------------- + // | __-- #|# ### + // | _- ##|## ##### + // | / ##+## ##+## + // | / # P # ##### + // | | #|# ### + // | | __--+------------- + // || _- ^ + // || / | + // | / first dot is filled + // | | + // | | + // | ##### | + // | ####### | + // |#########| + // +----+----+ + // |#########| + // | ####### | + // | ##### | + Float minimum = otherBorderWidth + borderWidth / 2.0f; + if (isHorizontal) { + if (radius.width < minimum) { + *aIsUnfilled = true; + P.x += signs[0] * minimum; + } else { + P.x += signs[0] * radius.width; + } + P.y += signs[1] * borderWidth / 2.0f; + } else { + P.x += signs[0] * borderWidth / 2.0f; + if (radius.height < minimum) { + *aIsUnfilled = true; + P.y += signs[1] * minimum; + } else { + P.y += signs[1] * radius.height; + } + } + + return P; + } + + // This side is larger than other side, this side draws the corner. + // + // borderWidth / 2.0 + // |<-->| + // | | + // +----+--------------------- + // | ##|## ##### + // | ###|### ####### + // |####|#### ######### + // |####+#### ####+#### + // |### P ### ######### + // | ####### ####### + // | ##### ##### + // +-----+--------------------- + // | *** | + // |*****| + // |**+**| <-- first dot in other side is not filled + // |*****| + // | *** | + // | ### | + // |#####| + // |##+##| + // |#####| + // | ### | + // | | + if (isHorizontal) { + P.x += signs[0] * std::max(radius.width, borderWidth / 2.0f); + P.y += signs[1] * borderWidth / 2.0f; + } else { + P.x += signs[0] * borderWidth / 2.0f; + P.y += signs[1] * std::max(radius.height, borderWidth / 2.0f); + } + return P; + } + + if (style == StyleBorderStyle::Dotted) { + // If only this side is dotted, other side draws the corner. + // + // otherBorderWidth + borderWidth / 2.0 + // |<------->| + // | | + // +------+--+-------- + // |## ##| *|* ### + // |## ##|**|**##### + // |## ##|**+**##+## + // |## ##|* P *##### + // |## ##| *** ### + // |## ##+----------- + // |## ##| ^ + // |## ##| | + // |## ##| first dot is not filled + // |## ##| + // + // radius.width + // |<----------------->| + // | | + // | ___---+------------- + // | __-- #|# ### + // | _- ##|## ##### + // | / ##+## ##+## + // | / # P # ##### + // | | #|# ### + // | | __--+------------- + // || _- ^ + // || / | + // | / first dot is filled + // | | + // | | + // | | + // | | + // | | + // +------+ + // |## ##| + // |## ##| + // |## ##| + Float minimum = otherBorderWidth + borderWidth / 2.0f; + if (isHorizontal) { + if (radius.width < minimum) { + *aIsUnfilled = true; + P.x += signs[0] * minimum; + } else { + P.x += signs[0] * radius.width; + } + P.y += signs[1] * borderWidth / 2.0f; + } else { + P.x += signs[0] * borderWidth / 2.0f; + if (radius.height < minimum) { + *aIsUnfilled = true; + P.y += signs[1] * minimum; + } else { + P.y += signs[1] * radius.height; + } + } + return P; + } + + if (otherStyle == StyleBorderStyle::Dotted && IsZeroSize(radius)) { + // If other side is dotted and radius=0, draw side to the end of corner. + // + // +------------------------------- + // |########## ########## + // P +########## ########## + // |########## ########## + // +-----+------------------------- + // | *** | + // |*****| + // |**+**| <-- first dot in other side is not filled + // |*****| + // | *** | + // | ### | + // |#####| + // |##+##| + // |#####| + // | ### | + // | | + if (isHorizontal) { + P.y += signs[1] * borderWidth / 2.0f; + } else { + P.x += signs[0] * borderWidth / 2.0f; + } + return P; + } + + // Other cases. + // + // dim.width + // |<----------------->| + // | | + // | ___---+------------------ + // | __-- |####### ### + // | _- P +####### ### + // | / |####### ### + // | / __---+------------------ + // | | __-- + // | | / + // || / + // || | + // | | + // | | + // | | + // | | + // +-+-+ + // |###| + // |###| + // |###| + // |###| + // |###| + // | | + // | | + if (isHorizontal) { + P.x += signs[0] * dim.width; + P.y += signs[1] * borderWidth / 2.0f; + } else { + P.x += signs[0] * borderWidth / 2.0f; + P.y += signs[1] * dim.height; + } + + return P; +} + +void nsCSSBorderRenderer::GetOuterAndInnerBezier(Bezier* aOuterBezier, + Bezier* aInnerBezier, + Corner aCorner) { + // Return bezier control points for outer and inner curve for given corner. + // + // ___---+ outer curve + // __-- | + // _- | + // / | + // / | + // | | + // | __--+ inner curve + // | _- + // | / + // | / + // | | + // | | + // | | + // | | + // | | + // +---------+ + + mozilla::Side sideH(GetHorizontalSide(aCorner)); + mozilla::Side sideV(GetVerticalSide(aCorner)); + + Size outerCornerSize(ceil(mBorderRadii[aCorner].width), + ceil(mBorderRadii[aCorner].height)); + Size innerCornerSize( + ceil(std::max(0.0f, mBorderRadii[aCorner].width - mBorderWidths[sideV])), + ceil( + std::max(0.0f, mBorderRadii[aCorner].height - mBorderWidths[sideH]))); + + GetBezierPointsForCorner(aOuterBezier, aCorner, mOuterRect.AtCorner(aCorner), + outerCornerSize); + + GetBezierPointsForCorner(aInnerBezier, aCorner, mInnerRect.AtCorner(aCorner), + innerCornerSize); +} + +void nsCSSBorderRenderer::FillSolidBorder(const Rect& aOuterRect, + const Rect& aInnerRect, + const RectCornerRadii& aBorderRadii, + const Float* aBorderSizes, + SideBits aSides, + const ColorPattern& aColor) { + // Note that this function is allowed to draw more than just the + // requested sides. + + // If we have a border radius, do full rounded rectangles + // and fill, regardless of what sides we're asked to draw. + if (!AllCornersZeroSize(aBorderRadii)) { + RefPtr<PathBuilder> builder = mDrawTarget->CreatePathBuilder(); + + RectCornerRadii innerRadii; + ComputeInnerRadii(aBorderRadii, aBorderSizes, &innerRadii); + + // do the outer border + AppendRoundedRectToPath(builder, aOuterRect, aBorderRadii, true); + + // then do the inner border CCW + AppendRoundedRectToPath(builder, aInnerRect, innerRadii, false); + + RefPtr<Path> path = builder->Finish(); + + mDrawTarget->Fill(path, aColor); + return; + } + + // If we're asked to draw all sides of an equal-sized border, + // stroking is fastest. This is a fairly common path, but partial + // sides is probably second in the list -- there are a bunch of + // common border styles, such as inset and outset, that are + // top-left/bottom-right split. + if (aSides == SideBits::eAll && + CheckFourFloatsEqual(aBorderSizes, aBorderSizes[0]) && !mAvoidStroke) { + Float strokeWidth = aBorderSizes[0]; + Rect r(aOuterRect); + r.Deflate(strokeWidth / 2.f); + mDrawTarget->StrokeRect(r, aColor, StrokeOptions(strokeWidth)); + return; + } + + // Otherwise, we have unequal sized borders or we're only + // drawing some sides; create rectangles for each side + // and fill them. + + Rect r[4]; + + // compute base rects for each side + if (aSides & SideBits::eTop) { + r[eSideTop] = Rect(aOuterRect.X(), aOuterRect.Y(), aOuterRect.Width(), + aBorderSizes[eSideTop]); + } + + if (aSides & SideBits::eBottom) { + r[eSideBottom] = + Rect(aOuterRect.X(), aOuterRect.YMost() - aBorderSizes[eSideBottom], + aOuterRect.Width(), aBorderSizes[eSideBottom]); + } + + if (aSides & SideBits::eLeft) { + r[eSideLeft] = Rect(aOuterRect.X(), aOuterRect.Y(), aBorderSizes[eSideLeft], + aOuterRect.Height()); + } + + if (aSides & SideBits::eRight) { + r[eSideRight] = + Rect(aOuterRect.XMost() - aBorderSizes[eSideRight], aOuterRect.Y(), + aBorderSizes[eSideRight], aOuterRect.Height()); + } + + // If two sides meet at a corner that we're rendering, then + // make sure that we adjust one of the sides to avoid overlap. + // This is especially important in the case of colors with + // an alpha channel. + + if ((aSides & (SideBits::eTop | SideBits::eLeft)) == + (SideBits::eTop | SideBits::eLeft)) { + // adjust the left's top down a bit + r[eSideLeft].y += aBorderSizes[eSideTop]; + r[eSideLeft].height -= aBorderSizes[eSideTop]; + } + + if ((aSides & (SideBits::eTop | SideBits::eRight)) == + (SideBits::eTop | SideBits::eRight)) { + // adjust the top's left a bit + r[eSideTop].width -= aBorderSizes[eSideRight]; + } + + if ((aSides & (SideBits::eBottom | SideBits::eRight)) == + (SideBits::eBottom | SideBits::eRight)) { + // adjust the right's bottom a bit + r[eSideRight].height -= aBorderSizes[eSideBottom]; + } + + if ((aSides & (SideBits::eBottom | SideBits::eLeft)) == + (SideBits::eBottom | SideBits::eLeft)) { + // adjust the bottom's left a bit + r[eSideBottom].x += aBorderSizes[eSideLeft]; + r[eSideBottom].width -= aBorderSizes[eSideLeft]; + } + + // Filling these one by one is faster than filling them all at once. + for (uint32_t i = 0; i < 4; i++) { + if (aSides & static_cast<mozilla::SideBits>(1 << i)) { + MaybeSnapToDevicePixels(r[i], *mDrawTarget, true); + mDrawTarget->FillRect(r[i], aColor); + } + } +} + +sRGBColor MakeBorderColor(nscolor aColor, BorderColorStyle aBorderColorStyle) { + nscolor colors[2]; + int k = 0; + + switch (aBorderColorStyle) { + case BorderColorStyleNone: + return sRGBColor(0.f, 0.f, 0.f, 0.f); // transparent black + + case BorderColorStyleLight: + k = 1; + [[fallthrough]]; + case BorderColorStyleDark: + NS_GetSpecial3DColors(colors, aColor); + return sRGBColor::FromABGR(colors[k]); + + case BorderColorStyleSolid: + default: + return sRGBColor::FromABGR(aColor); + } +} + +sRGBColor ComputeColorForLine(uint32_t aLineIndex, + const BorderColorStyle* aBorderColorStyle, + uint32_t aBorderColorStyleCount, + nscolor aBorderColor) { + NS_ASSERTION(aLineIndex < aBorderColorStyleCount, "Invalid lineIndex given"); + + return MakeBorderColor(aBorderColor, aBorderColorStyle[aLineIndex]); +} + +void nsCSSBorderRenderer::DrawBorderSides(mozilla::SideBits aSides) { + if (aSides == SideBits::eNone || + (aSides & ~SideBits::eAll) != SideBits::eNone) { + NS_WARNING("DrawBorderSides: invalid sides!"); + return; + } + + StyleBorderStyle borderRenderStyle = StyleBorderStyle::None; + nscolor borderRenderColor; + + uint32_t borderColorStyleCount = 0; + BorderColorStyle borderColorStyleTopLeft[3], borderColorStyleBottomRight[3]; + BorderColorStyle* borderColorStyle = nullptr; + + for (const auto i : mozilla::AllPhysicalSides()) { + if ((aSides & static_cast<mozilla::SideBits>(1 << i)) == SideBits::eNone) { + continue; + } + borderRenderStyle = mBorderStyles[i]; + borderRenderColor = mBorderColors[i]; + break; + } + + if (borderRenderStyle == StyleBorderStyle::None || + borderRenderStyle == StyleBorderStyle::Hidden) { + return; + } + + if (borderRenderStyle == StyleBorderStyle::Dashed || + borderRenderStyle == StyleBorderStyle::Dotted) { + // Draw each corner separately, with the given side's color. + if (aSides & SideBits::eTop) { + DrawDashedOrDottedCorner(eSideTop, C_TL); + } else if (aSides & SideBits::eLeft) { + DrawDashedOrDottedCorner(eSideLeft, C_TL); + } + + if (aSides & SideBits::eTop) { + DrawDashedOrDottedCorner(eSideTop, C_TR); + } else if (aSides & SideBits::eRight) { + DrawDashedOrDottedCorner(eSideRight, C_TR); + } + + if (aSides & SideBits::eBottom) { + DrawDashedOrDottedCorner(eSideBottom, C_BL); + } else if (aSides & SideBits::eLeft) { + DrawDashedOrDottedCorner(eSideLeft, C_BL); + } + + if (aSides & SideBits::eBottom) { + DrawDashedOrDottedCorner(eSideBottom, C_BR); + } else if (aSides & SideBits::eRight) { + DrawDashedOrDottedCorner(eSideRight, C_BR); + } + return; + } + + // The borderColorStyle array goes from the outer to the inner style. + // + // If the border width is 1, we need to change the borderRenderStyle + // a bit to make sure that we get the right colors -- e.g. 'ridge' + // with a 1px border needs to look like solid, not like 'outset'. + if (mOneUnitBorder && (borderRenderStyle == StyleBorderStyle::Ridge || + borderRenderStyle == StyleBorderStyle::Groove || + borderRenderStyle == StyleBorderStyle::Double)) { + borderRenderStyle = StyleBorderStyle::Solid; + } + + switch (borderRenderStyle) { + case StyleBorderStyle::Solid: + borderColorStyleTopLeft[0] = BorderColorStyleSolid; + + borderColorStyleBottomRight[0] = BorderColorStyleSolid; + + borderColorStyleCount = 1; + break; + + case StyleBorderStyle::Groove: + borderColorStyleTopLeft[0] = BorderColorStyleDark; + borderColorStyleTopLeft[1] = BorderColorStyleLight; + + borderColorStyleBottomRight[0] = BorderColorStyleLight; + borderColorStyleBottomRight[1] = BorderColorStyleDark; + + borderColorStyleCount = 2; + break; + + case StyleBorderStyle::Ridge: + borderColorStyleTopLeft[0] = BorderColorStyleLight; + borderColorStyleTopLeft[1] = BorderColorStyleDark; + + borderColorStyleBottomRight[0] = BorderColorStyleDark; + borderColorStyleBottomRight[1] = BorderColorStyleLight; + + borderColorStyleCount = 2; + break; + + case StyleBorderStyle::Double: + borderColorStyleTopLeft[0] = BorderColorStyleSolid; + borderColorStyleTopLeft[1] = BorderColorStyleNone; + borderColorStyleTopLeft[2] = BorderColorStyleSolid; + + borderColorStyleBottomRight[0] = BorderColorStyleSolid; + borderColorStyleBottomRight[1] = BorderColorStyleNone; + borderColorStyleBottomRight[2] = BorderColorStyleSolid; + + borderColorStyleCount = 3; + break; + + case StyleBorderStyle::Inset: + borderColorStyleTopLeft[0] = BorderColorStyleDark; + borderColorStyleBottomRight[0] = BorderColorStyleLight; + + borderColorStyleCount = 1; + break; + + case StyleBorderStyle::Outset: + borderColorStyleTopLeft[0] = BorderColorStyleLight; + borderColorStyleBottomRight[0] = BorderColorStyleDark; + + borderColorStyleCount = 1; + break; + + default: + MOZ_ASSERT_UNREACHABLE("Unhandled border style!!"); + break; + } + + // The only way to get to here is by having a + // borderColorStyleCount < 1 or > 3; this should never happen, + // since -moz-border-colors doesn't get handled here. + NS_ASSERTION(borderColorStyleCount > 0 && borderColorStyleCount < 4, + "Non-border-colors case with borderColorStyleCount < 1 or > 3; " + "what happened?"); + + // The caller should never give us anything with a mix + // of TL/BR if the border style would require a + // TL/BR split. + if (aSides & (SideBits::eBottom | SideBits::eRight)) { + borderColorStyle = borderColorStyleBottomRight; + } else { + borderColorStyle = borderColorStyleTopLeft; + } + + // Distribute the border across the available space. + Float borderWidths[3][4]; + + if (borderColorStyleCount == 1) { + for (const auto i : mozilla::AllPhysicalSides()) { + borderWidths[0][i] = mBorderWidths[i]; + } + } else if (borderColorStyleCount == 2) { + // with 2 color styles, any extra pixel goes to the outside + for (const auto i : mozilla::AllPhysicalSides()) { + borderWidths[0][i] = + int32_t(mBorderWidths[i]) / 2 + int32_t(mBorderWidths[i]) % 2; + borderWidths[1][i] = int32_t(mBorderWidths[i]) / 2; + } + } else if (borderColorStyleCount == 3) { + // with 3 color styles, any extra pixel (or lack of extra pixel) + // goes to the middle + for (const auto i : mozilla::AllPhysicalSides()) { + if (mBorderWidths[i] == 1.0) { + borderWidths[0][i] = 1.f; + borderWidths[1][i] = borderWidths[2][i] = 0.f; + } else { + int32_t rest = int32_t(mBorderWidths[i]) % 3; + borderWidths[0][i] = borderWidths[2][i] = borderWidths[1][i] = + (int32_t(mBorderWidths[i]) - rest) / 3; + + if (rest == 1) { + borderWidths[1][i] += 1.f; + } else if (rest == 2) { + borderWidths[0][i] += 1.f; + borderWidths[2][i] += 1.f; + } + } + } + } + + // make a copy that we can modify + RectCornerRadii radii = mBorderRadii; + + Rect soRect(mOuterRect); + Rect siRect(mOuterRect); + + // If adjacent side is dotted and radius=0, draw side to the end of corner. + // + // +-------------------------------- + // |################################ + // | + // |################################ + // +-----+-------------------------- + // | | + // | | + // | | + // | | + // | | + // | ### | + // |#####| + // |#####| + // |#####| + // | ### | + // | | + bool noMarginTop = false; + bool noMarginRight = false; + bool noMarginBottom = false; + bool noMarginLeft = false; + + // If there is at least one dotted side, every side is rendered separately. + if (IsSingleSide(aSides)) { + if (aSides == SideBits::eTop) { + if (mBorderStyles[eSideRight] == StyleBorderStyle::Dotted && + IsZeroSize(mBorderRadii[C_TR])) { + noMarginRight = true; + } + if (mBorderStyles[eSideLeft] == StyleBorderStyle::Dotted && + IsZeroSize(mBorderRadii[C_TL])) { + noMarginLeft = true; + } + } else if (aSides == SideBits::eRight) { + if (mBorderStyles[eSideTop] == StyleBorderStyle::Dotted && + IsZeroSize(mBorderRadii[C_TR])) { + noMarginTop = true; + } + if (mBorderStyles[eSideBottom] == StyleBorderStyle::Dotted && + IsZeroSize(mBorderRadii[C_BR])) { + noMarginBottom = true; + } + } else if (aSides == SideBits::eBottom) { + if (mBorderStyles[eSideRight] == StyleBorderStyle::Dotted && + IsZeroSize(mBorderRadii[C_BR])) { + noMarginRight = true; + } + if (mBorderStyles[eSideLeft] == StyleBorderStyle::Dotted && + IsZeroSize(mBorderRadii[C_BL])) { + noMarginLeft = true; + } + } else { + if (mBorderStyles[eSideTop] == StyleBorderStyle::Dotted && + IsZeroSize(mBorderRadii[C_TL])) { + noMarginTop = true; + } + if (mBorderStyles[eSideBottom] == StyleBorderStyle::Dotted && + IsZeroSize(mBorderRadii[C_BL])) { + noMarginBottom = true; + } + } + } + + for (unsigned int i = 0; i < borderColorStyleCount; i++) { + // walk siRect inwards at the start of the loop to get the + // correct inner rect. + // + // If noMarginTop is false: + // --------------------+ + // /| + // / | + // L | + // ----------------+ | + // | | + // | | + // + // If noMarginTop is true: + // ----------------+<--+ + // | | + // | | + // | | + // | | + // | | + // | | + siRect.Deflate(Margin(noMarginTop ? 0 : borderWidths[i][0], + noMarginRight ? 0 : borderWidths[i][1], + noMarginBottom ? 0 : borderWidths[i][2], + noMarginLeft ? 0 : borderWidths[i][3])); + + if (borderColorStyle[i] != BorderColorStyleNone) { + sRGBColor c = ComputeColorForLine( + i, borderColorStyle, borderColorStyleCount, borderRenderColor); + ColorPattern color(ToDeviceColor(c)); + + FillSolidBorder(soRect, siRect, radii, borderWidths[i], aSides, color); + } + + ComputeInnerRadii(radii, borderWidths[i], &radii); + + // And now soRect is the same as siRect, for the next line in. + soRect = siRect; + } +} + +void nsCSSBorderRenderer::SetupDashedOptions(StrokeOptions* aStrokeOptions, + Float aDash[2], + mozilla::Side aSide, + Float aBorderLength, + bool isCorner) { + MOZ_ASSERT(mBorderStyles[aSide] == StyleBorderStyle::Dashed || + mBorderStyles[aSide] == StyleBorderStyle::Dotted, + "Style should be dashed or dotted."); + + StyleBorderStyle style = mBorderStyles[aSide]; + Float borderWidth = mBorderWidths[aSide]; + + // Dashed line starts and ends with half segment in most case. + // + // __--+---+---+---+---+---+---+---+---+--__ + // |###| | |###|###| | |###| + // |###| | |###|###| | |###| + // |###| | |###|###| | |###| + // __--+---+---+---+---+---+---+---+---+--__ + // + // If radius=0 and other side is either dotted or 0-width, it starts or ends + // with full segment. + // + // +---+---+---+---+---+---+---+---+---+---+ + // |###|###| | |###|###| | |###|###| + // |###|###| | |###|###| | |###|###| + // |###|###| | |###|###| | |###|###| + // +---++--+---+---+---+---+---+---+--++---+ + // | | | | + // | | | | + // | | | | + // | | | | + // | ## | | ## | + // |####| |####| + // |####| |####| + // | ## | | ## | + // | | | | + bool fullStart = false, fullEnd = false; + Float halfDash; + if (style == StyleBorderStyle::Dashed) { + // If either end of the side is not connecting onto a corner then we want a + // full dash at that end. + // + // Note that in the case that a corner is empty, either the adjacent side + // has zero width, or else DrawBorders() set the corner to be empty + // (it does that if the adjacent side has zero length and the border widths + // of this and the adjacent sides are thin enough that the corner will be + // insignificantly small). + + if (mBorderRadii[GetCCWCorner(aSide)].IsEmpty() && + (mBorderCornerDimensions[GetCCWCorner(aSide)].IsEmpty() || + mBorderStyles[PREV_SIDE(aSide)] == StyleBorderStyle::Dotted || + // XXX why this <=1 check? + borderWidth <= 1.0f)) { + fullStart = true; + } + + if (mBorderRadii[GetCWCorner(aSide)].IsEmpty() && + (mBorderCornerDimensions[GetCWCorner(aSide)].IsEmpty() || + mBorderStyles[NEXT_SIDE(aSide)] == StyleBorderStyle::Dotted)) { + fullEnd = true; + } + + halfDash = borderWidth * DOT_LENGTH * DASH_LENGTH / 2.0f; + } else { + halfDash = borderWidth * DOT_LENGTH / 2.0f; + } + + if (style == StyleBorderStyle::Dashed && aBorderLength > 0.0f) { + // The number of half segments, with maximum dash length. + int32_t count = floor(aBorderLength / halfDash); + Float minHalfDash = borderWidth * DOT_LENGTH / 2.0f; + + if (fullStart && fullEnd) { + // count should be 4n + 2 + // + // 1 + 4 + 4 + 1 + // + // | | | | | + // +---+---+---+---+---+---+---+---+---+---+ + // |###|###| | |###|###| | |###|###| + // |###|###| | |###|###| | |###|###| + // |###|###| | |###|###| | |###|###| + // +---+---+---+---+---+---+---+---+---+---+ + + // If border is too short, draw solid line. + if (aBorderLength < 6.0f * minHalfDash) { + return; + } + + if (count % 4 == 0) { + count += 2; + } else if (count % 4 == 1) { + count += 1; + } else if (count % 4 == 3) { + count += 3; + } + } else if (fullStart || fullEnd) { + // count should be 4n + 1 + // + // 1 + 4 + 4 + // + // | | | | + // +---+---+---+---+---+---+---+---+---+ + // |###|###| | |###|###| | |###| + // |###|###| | |###|###| | |###| + // |###|###| | |###|###| | |###| + // +---+---+---+---+---+---+---+---+---+ + // + // 4 + 4 + 1 + // + // | | | | + // +---+---+---+---+---+---+---+---+---+ + // |###| | |###|###| | |###|###| + // |###| | |###|###| | |###|###| + // |###| | |###|###| | |###|###| + // +---+---+---+---+---+---+---+---+---+ + + // If border is too short, draw solid line. + if (aBorderLength < 5.0f * minHalfDash) { + return; + } + + if (count % 4 == 0) { + count += 1; + } else if (count % 4 == 2) { + count += 3; + } else if (count % 4 == 3) { + count += 2; + } + } else { + // count should be 4n + // + // 4 + 4 + // + // | | | + // +---+---+---+---+---+---+---+---+ + // |###| | |###|###| | |###| + // |###| | |###|###| | |###| + // |###| | |###|###| | |###| + // +---+---+---+---+---+---+---+---+ + + // If border is too short, draw solid line. + if (aBorderLength < 4.0f * minHalfDash) { + return; + } + + if (count % 4 == 1) { + count += 3; + } else if (count % 4 == 2) { + count += 2; + } else if (count % 4 == 3) { + count += 1; + } + } + halfDash = aBorderLength / count; + } + + Float fullDash = halfDash * 2.0f; + + aDash[0] = fullDash; + aDash[1] = fullDash; + + if (style == StyleBorderStyle::Dashed && fullDash > 1.0f) { + if (!fullStart) { + // Draw half segments on both ends. + aStrokeOptions->mDashOffset = halfDash; + } + } else if (style != StyleBorderStyle::Dotted && isCorner) { + // If side ends with filled full segment, corner should start with unfilled + // full segment. Not needed for dotted corners, as they overlap one dot with + // the side's end. + // + // corner side + // ------------>|<--------------------------- + // | + // __+---+---+---+---+---+---+---+---+ + // _+- | |###|###| | |###|###| | + // /##| | |###|###| | |###|###| | + // +####| | |###|###| | |###|###| | + // /#\####| _+--+---+---+---+---+---+---+---+ + // |####\##+- + // |#####+- + // +--###/ + // | --+ + aStrokeOptions->mDashOffset = fullDash; + } + + aStrokeOptions->mDashPattern = aDash; + aStrokeOptions->mDashLength = 2; + + PrintAsFormatString("dash: %f %f\n", aDash[0], aDash[1]); +} + +static Float GetBorderLength(mozilla::Side aSide, const Point& aStart, + const Point& aEnd) { + if (aSide == eSideTop) { + return aEnd.x - aStart.x; + } + if (aSide == eSideRight) { + return aEnd.y - aStart.y; + } + if (aSide == eSideBottom) { + return aStart.x - aEnd.x; + } + return aStart.y - aEnd.y; +} + +void nsCSSBorderRenderer::DrawDashedOrDottedSide(mozilla::Side aSide) { + // Draw dashed/dotted side with following approach. + // + // dashed side + // Draw dashed line along the side, with appropriate dash length and gap + // to make the side symmetric as far as possible. Dash length equals to + // the gap, and the ratio of the dash length to border-width is the maximum + // value in in [1, 3] range. + // In most case, line ends with half segment, to joint with corner easily. + // If adjacent side is dotted or 0px and border-radius for the corner + // between them is 0, the line ends with full segment. + // (see comment for GetStraightBorderPoint for more detail) + // + // dotted side + // If border-width <= 2.0, draw 1:1 dashed line. + // Otherwise, draw circles along the side, with appropriate gap that makes + // the side symmetric as far as possible. The ratio of the gap to + // border-width is the maximum value in [0.5, 1] range in most case. + // if the side is too short and there's only 2 dots, it can be more smaller. + // If there's no space to place 2 dots at the side, draw single dot at the + // middle of the side. + // In most case, line ends with filled dot, to joint with corner easily, + // If adjacent side is dotted with larger border-width, or other style, + // the line ends with unfilled dot. + // (see comment for GetStraightBorderPoint for more detail) + + NS_ASSERTION(mBorderStyles[aSide] == StyleBorderStyle::Dashed || + mBorderStyles[aSide] == StyleBorderStyle::Dotted, + "Style should be dashed or dotted."); + + Float borderWidth = mBorderWidths[aSide]; + if (borderWidth == 0.0f) { + return; + } + + if (mBorderStyles[aSide] == StyleBorderStyle::Dotted && borderWidth > 2.0f) { + DrawDottedSideSlow(aSide); + return; + } + + nscolor borderColor = mBorderColors[aSide]; + bool ignored; + // Get the start and end points of the side, ensuring that any dot origins get + // pushed outward to account for stroking. + Point start = + GetStraightBorderPoint(aSide, GetCCWCorner(aSide), &ignored, 0.5f); + Point end = GetStraightBorderPoint(aSide, GetCWCorner(aSide), &ignored, 0.5f); + if (borderWidth < 2.0f) { + // Round start to draw dot on each pixel. + if (IsHorizontalSide(aSide)) { + start.x = round(start.x); + } else { + start.y = round(start.y); + } + } + + Float borderLength = GetBorderLength(aSide, start, end); + if (borderLength < 0.0f) { + return; + } + + StrokeOptions strokeOptions(borderWidth); + Float dash[2]; + SetupDashedOptions(&strokeOptions, dash, aSide, borderLength, false); + + // For dotted sides that can merge with their prior dotted sides, advance the + // dash offset to measure the distance around the combined path. This prevents + // two dots from bunching together at a corner. + mozilla::Side mergeSide = aSide; + while (IsCornerMergeable(GetCCWCorner(mergeSide))) { + mergeSide = PREV_SIDE(mergeSide); + // If we looped all the way around, measure starting at the top side, since + // we need to pick a fixed location to start measuring distance from still. + if (mergeSide == aSide) { + mergeSide = eSideTop; + break; + } + } + while (mergeSide != aSide) { + // Measure the length of the merged side starting from a possibly + // unmergeable corner up to the merged corner. A merged corner effectively + // has no border radius, so we can just use the cheaper AtCorner to find the + // end point. + Float mergeLength = + GetBorderLength(mergeSide, + GetStraightBorderPoint( + mergeSide, GetCCWCorner(mergeSide), &ignored, 0.5f), + mOuterRect.AtCorner(GetCWCorner(mergeSide))); + // Add in the merged side length. Also offset the dash progress by an extra + // dot's width to avoid drawing a dot that would overdraw where the merged + // side would have ended in a gap, i.e. O_O_ + // O + strokeOptions.mDashOffset += mergeLength + borderWidth; + mergeSide = NEXT_SIDE(mergeSide); + } + + DrawOptions drawOptions; + if (mBorderStyles[aSide] == StyleBorderStyle::Dotted) { + drawOptions.mAntialiasMode = AntialiasMode::NONE; + } + + mDrawTarget->StrokeLine(start, end, ColorPattern(ToDeviceColor(borderColor)), + strokeOptions, drawOptions); +} + +void nsCSSBorderRenderer::DrawDottedSideSlow(mozilla::Side aSide) { + // Draw each circles separately for dotted with borderWidth > 2.0. + // Dashed line with CapStyle::ROUND doesn't render perfect circles. + + NS_ASSERTION(mBorderStyles[aSide] == StyleBorderStyle::Dotted, + "Style should be dotted."); + + Float borderWidth = mBorderWidths[aSide]; + if (borderWidth == 0.0f) { + return; + } + + nscolor borderColor = mBorderColors[aSide]; + bool isStartUnfilled, isEndUnfilled; + Point start = + GetStraightBorderPoint(aSide, GetCCWCorner(aSide), &isStartUnfilled); + Point end = GetStraightBorderPoint(aSide, GetCWCorner(aSide), &isEndUnfilled); + enum { + // Corner is not mergeable. + NO_MERGE, + + // Corner between different colors. + // Two dots are merged into one, and both side draw half dot. + MERGE_HALF, + + // Corner between same colors, CCW corner of the side. + // Two dots are merged into one, and this side draw entire dot. + // + // MERGE_ALL MERGE_NONE + // | | + // v v + // +-----------------------+----+ + // | ## ## ## | ## | + // |#### #### #### |####| + // |#### #### #### |####| + // | ## ## ## | ## | + // +----+------------------+ | + // | | | | + // | | | | + // | | | | + // | ## | | ## | + // |####| |####| + MERGE_ALL, + + // Corner between same colors, CW corner of the side. + // Two dots are merged into one, and this side doesn't draw dot. + MERGE_NONE + } mergeStart = NO_MERGE, + mergeEnd = NO_MERGE; + + if (IsCornerMergeable(GetCCWCorner(aSide))) { + if (borderColor == mBorderColors[PREV_SIDE(aSide)]) { + mergeStart = MERGE_ALL; + } else { + mergeStart = MERGE_HALF; + } + } + + if (IsCornerMergeable(GetCWCorner(aSide))) { + if (borderColor == mBorderColors[NEXT_SIDE(aSide)]) { + mergeEnd = MERGE_NONE; + } else { + mergeEnd = MERGE_HALF; + } + } + + Float borderLength = GetBorderLength(aSide, start, end); + if (borderLength < 0.0f) { + if (isStartUnfilled || isEndUnfilled) { + return; + } + borderLength = 0.0f; + start = end = (start + end) / 2.0f; + } + + Float dotWidth = borderWidth * DOT_LENGTH; + Float radius = borderWidth / 2.0f; + if (borderLength < dotWidth) { + // If dots on start and end may overlap, draw a dot at the middle of them. + // + // ___---+-------+---___ + // __-- | ##### | --__ + // #|#######|# + // ##|#######|## + // ###|#######|### + // ###+###+###+### + // start ## end # + // ##|#######|## + // #|#######|# + // | ##### | + // __--+-------+--__ + // _- -_ + // + // If that circle overflows from outer rect, do not draw it. + // + // +-------+ + // | ##### | + // #|#######|# + // ##|#######|## + // ###|#######|### + // ###|###+###|### + // ###|#######|### + // ##|#######|## + // #|#######|# + // | ##### | + // +--+-+--+ + // | | | | + // | | | | + if (!mOuterRect.Contains(Rect(start.x - radius, start.y - radius, + borderWidth, borderWidth))) { + return; + } + + if (isStartUnfilled || isEndUnfilled) { + return; + } + + Point P = (start + end) / 2; + RefPtr<PathBuilder> builder = mDrawTarget->CreatePathBuilder(); + builder->MoveTo(Point(P.x + radius, P.y)); + builder->Arc(P, radius, 0.0f, Float(2.0 * M_PI)); + RefPtr<Path> path = builder->Finish(); + mDrawTarget->Fill(path, ColorPattern(ToDeviceColor(borderColor))); + return; + } + + if (mergeStart == MERGE_HALF || mergeEnd == MERGE_HALF) { + // MERGE_HALF + // Eo + // -------+----+ + // ##### / + // ######/ + // ######/ + // ####+ + // ##/ end + // / + // / + // --+ + // Ei + // + // other (NO_MERGE, MERGE_ALL, MERGE_NONE) + // Eo + // ------------+ + // ##### | + // ####### | + // #########| + // ####+####| + // ## end ##| + // ####### | + // ##### | + // ------------+ + // Ei + + Point I(0.0f, 0.0f), J(0.0f, 0.0f); + if (aSide == eSideTop) { + I.x = 1.0f; + J.y = 1.0f; + } else if (aSide == eSideRight) { + I.y = 1.0f; + J.x = -1.0f; + } else if (aSide == eSideBottom) { + I.x = -1.0f; + J.y = -1.0f; + } else if (aSide == eSideLeft) { + I.y = -1.0f; + J.x = 1.0f; + } + + Point So, Si, Eo, Ei; + + So = (start + (-I + -J) * borderWidth / 2.0f); + Si = (mergeStart == MERGE_HALF) ? (start + (I + J) * borderWidth / 2.0f) + : (start + (-I + J) * borderWidth / 2.0f); + Eo = (end + (I - J) * borderWidth / 2.0f); + Ei = (mergeEnd == MERGE_HALF) ? (end + (-I + J) * borderWidth / 2.0f) + : (end + (I + J) * borderWidth / 2.0f); + + RefPtr<PathBuilder> builder = mDrawTarget->CreatePathBuilder(); + builder->MoveTo(So); + builder->LineTo(Eo); + builder->LineTo(Ei); + builder->LineTo(Si); + builder->Close(); + RefPtr<Path> path = builder->Finish(); + + mDrawTarget->PushClip(path); + } + + size_t count = round(borderLength / dotWidth); + if (isStartUnfilled == isEndUnfilled) { + // Split into 2n segments. + if (count % 2) { + count++; + } + } else { + // Split into 2n+1 segments. + if (count % 2 == 0) { + count++; + } + } + + // A: radius == borderWidth / 2.0 + // B: borderLength / count == borderWidth * (1 - overlap) + // + // A B B B B A + // |<-->|<------>|<------>|<------>|<------>|<-->| + // | | | | | | | + // +----+--------+--------+--------+--------+----+ + // | ##|## **|** ##|## **|** ##|## | + // | ###|### ***|*** ###|### ***|*** ###|### | + // |####|####****|****####|####****|****####|####| + // |####+####****+****####+####****+****####+####| + // |# start #****|****####|####****|****## end ##| + // | ###|### ***|*** ###|### ***|*** ###|### | + // | ##|## **|** ##|## **|** ##|## | + // +----+----+---+--------+--------+---+----+----+ + // | | | | + // | | | | + + // If isStartUnfilled is true, draw dots on 2j+1 points, if not, draw dots on + // 2j points. + size_t from = isStartUnfilled ? 1 : 0; + + // If mergeEnd == MERGE_NONE, last dot is drawn by next side. + size_t to = count; + if (mergeEnd == MERGE_NONE) { + if (to > 2) { + to -= 2; + } else { + to = 0; + } + } + + Point fromP = (start * (count - from) + end * from) / count; + Point toP = (start * (count - to) + end * to) / count; + // Extend dirty rect to avoid clipping pixel for anti-aliasing. + const Float AA_MARGIN = 2.0f; + + if (aSide == eSideTop) { + // Tweak |from| and |to| to fit into |mDirtyRect + radius margin|, + // to render only paths that may overlap mDirtyRect. + // + // mDirtyRect + radius margin + // +--+---------------------+--+ + // | | + // | mDirtyRect | + // + +---------------------+ + + // from ===> |from to | <=== to + // +-----+-----+-----+-----+-----+-----+-----+-----+ + // ### |### ### ###| ### + // ##### ##### ##### ##### ##### + // ##### ##### ##### ##### ##### + // ##### ##### ##### ##### ##### + // ### |### ### ###| ### + // | | | | + // + +---------------------+ + + // | | + // | | + // +--+---------------------+--+ + + Float left = mDirtyRect.x - radius - AA_MARGIN; + if (fromP.x < left) { + size_t tmp = ceil(count * (left - start.x) / (end.x - start.x)); + if (tmp > from) { + // We increment by 2, so odd/even should match between before/after. + if ((tmp & 1) != (from & 1)) { + from = tmp - 1; + } else { + from = tmp; + } + } + } + Float right = mDirtyRect.x + mDirtyRect.width + radius + AA_MARGIN; + if (toP.x > right) { + size_t tmp = floor(count * (right - start.x) / (end.x - start.x)); + if (tmp < to) { + if ((tmp & 1) != (to & 1)) { + to = tmp + 1; + } else { + to = tmp; + } + } + } + } else if (aSide == eSideRight) { + Float top = mDirtyRect.y - radius - AA_MARGIN; + if (fromP.y < top) { + size_t tmp = ceil(count * (top - start.y) / (end.y - start.y)); + if (tmp > from) { + if ((tmp & 1) != (from & 1)) { + from = tmp - 1; + } else { + from = tmp; + } + } + } + Float bottom = mDirtyRect.y + mDirtyRect.height + radius + AA_MARGIN; + if (toP.y > bottom) { + size_t tmp = floor(count * (bottom - start.y) / (end.y - start.y)); + if (tmp < to) { + if ((tmp & 1) != (to & 1)) { + to = tmp + 1; + } else { + to = tmp; + } + } + } + } else if (aSide == eSideBottom) { + Float right = mDirtyRect.x + mDirtyRect.width + radius + AA_MARGIN; + if (fromP.x > right) { + size_t tmp = ceil(count * (right - start.x) / (end.x - start.x)); + if (tmp > from) { + if ((tmp & 1) != (from & 1)) { + from = tmp - 1; + } else { + from = tmp; + } + } + } + Float left = mDirtyRect.x - radius - AA_MARGIN; + if (toP.x < left) { + size_t tmp = floor(count * (left - start.x) / (end.x - start.x)); + if (tmp < to) { + if ((tmp & 1) != (to & 1)) { + to = tmp + 1; + } else { + to = tmp; + } + } + } + } else if (aSide == eSideLeft) { + Float bottom = mDirtyRect.y + mDirtyRect.height + radius + AA_MARGIN; + if (fromP.y > bottom) { + size_t tmp = ceil(count * (bottom - start.y) / (end.y - start.y)); + if (tmp > from) { + if ((tmp & 1) != (from & 1)) { + from = tmp - 1; + } else { + from = tmp; + } + } + } + Float top = mDirtyRect.y - radius - AA_MARGIN; + if (toP.y < top) { + size_t tmp = floor(count * (top - start.y) / (end.y - start.y)); + if (tmp < to) { + if ((tmp & 1) != (to & 1)) { + to = tmp + 1; + } else { + to = tmp; + } + } + } + } + + RefPtr<PathBuilder> builder = mDrawTarget->CreatePathBuilder(); + size_t segmentCount = 0; + for (size_t i = from; i <= to; i += 2) { + if (segmentCount > BORDER_SEGMENT_COUNT_MAX) { + RefPtr<Path> path = builder->Finish(); + mDrawTarget->Fill(path, ColorPattern(ToDeviceColor(borderColor))); + builder = mDrawTarget->CreatePathBuilder(); + segmentCount = 0; + } + + Point P = (start * (count - i) + end * i) / count; + builder->MoveTo(Point(P.x + radius, P.y)); + builder->Arc(P, radius, 0.0f, Float(2.0 * M_PI)); + segmentCount++; + } + RefPtr<Path> path = builder->Finish(); + mDrawTarget->Fill(path, ColorPattern(ToDeviceColor(borderColor))); + + if (mergeStart == MERGE_HALF || mergeEnd == MERGE_HALF) { + mDrawTarget->PopClip(); + } +} + +void nsCSSBorderRenderer::DrawDashedOrDottedCorner(mozilla::Side aSide, + Corner aCorner) { + // Draw dashed/dotted corner with following approach. + // + // dashed corner + // If both side has same border-width and border-width <= 2.0, draw dashed + // line along the corner, with appropriate dash length and gap to make the + // corner symmetric as far as possible. Dash length equals to the gap, and + // the ratio of the dash length to border-width is the maximum value in in + // [1, 3] range. + // Otherwise, draw dashed segments along the corner, keeping same dash + // length ratio to border-width at that point. + // (see DashedCornerFinder.h for more detail) + // Line ends with half segments, to joint with both side easily. + // + // dotted corner + // If both side has same border-width and border-width <= 2.0, draw 1:1 + // dashed line along the corner. + // Otherwise Draw circles along the corner, with appropriate gap that makes + // the corner symmetric as far as possible. The size of the circle may + // change along the corner, that is tangent to the outer curver and the + // inner curve. The ratio of the gap to circle diameter is the maximum + // value in [0.5, 1] range. + // (see DottedCornerFinder.h for more detail) + // Corner ends with filled dots but those dots are drawn by + // DrawDashedOrDottedSide. So this may draw no circles if there's no space + // between 2 dots at both ends. + + NS_ASSERTION(mBorderStyles[aSide] == StyleBorderStyle::Dashed || + mBorderStyles[aSide] == StyleBorderStyle::Dotted, + "Style should be dashed or dotted."); + + if (IsCornerMergeable(aCorner)) { + // DrawDashedOrDottedSide will draw corner. + return; + } + + mozilla::Side sideH(GetHorizontalSide(aCorner)); + mozilla::Side sideV(GetVerticalSide(aCorner)); + Float borderWidthH = mBorderWidths[sideH]; + Float borderWidthV = mBorderWidths[sideV]; + if (borderWidthH == 0.0f && borderWidthV == 0.0f) { + return; + } + + StyleBorderStyle styleH = mBorderStyles[sideH]; + StyleBorderStyle styleV = mBorderStyles[sideV]; + + // Corner between dotted and others with radius=0 is drawn by side. + if (IsZeroSize(mBorderRadii[aCorner]) && + (styleV == StyleBorderStyle::Dotted || + styleH == StyleBorderStyle::Dotted)) { + return; + } + + Float maxRadius = + std::max(mBorderRadii[aCorner].width, mBorderRadii[aCorner].height); + if (maxRadius > BORDER_DOTTED_CORNER_MAX_RADIUS) { + DrawFallbackSolidCorner(aSide, aCorner); + return; + } + + if (borderWidthH != borderWidthV || borderWidthH > 2.0f) { + StyleBorderStyle style = mBorderStyles[aSide]; + if (style == StyleBorderStyle::Dotted) { + DrawDottedCornerSlow(aSide, aCorner); + } else { + DrawDashedCornerSlow(aSide, aCorner); + } + return; + } + + nscolor borderColor = mBorderColors[aSide]; + Point points[4]; + bool ignored; + // Get the start and end points of the corner arc, ensuring that any dot + // origins get pushed backwards towards the edges of the corner rect to + // account for stroking. + points[0] = GetStraightBorderPoint(sideH, aCorner, &ignored, -0.5f); + points[3] = GetStraightBorderPoint(sideV, aCorner, &ignored, -0.5f); + // Round points to draw dot on each pixel. + if (borderWidthH < 2.0f) { + points[0].x = round(points[0].x); + } + if (borderWidthV < 2.0f) { + points[3].y = round(points[3].y); + } + points[1] = points[0]; + points[1].x += kKappaFactor * (points[3].x - points[0].x); + points[2] = points[3]; + points[2].y += kKappaFactor * (points[0].y - points[3].y); + + Float len = GetQuarterEllipticArcLength(fabs(points[0].x - points[3].x), + fabs(points[0].y - points[3].y)); + + Float dash[2]; + StrokeOptions strokeOptions(borderWidthH); + SetupDashedOptions(&strokeOptions, dash, aSide, len, true); + + RefPtr<PathBuilder> builder = mDrawTarget->CreatePathBuilder(); + builder->MoveTo(points[0]); + builder->BezierTo(points[1], points[2], points[3]); + RefPtr<Path> path = builder->Finish(); + mDrawTarget->Stroke(path, ColorPattern(ToDeviceColor(borderColor)), + strokeOptions); +} + +void nsCSSBorderRenderer::DrawDottedCornerSlow(mozilla::Side aSide, + Corner aCorner) { + NS_ASSERTION(mBorderStyles[aSide] == StyleBorderStyle::Dotted, + "Style should be dotted."); + + mozilla::Side sideH(GetHorizontalSide(aCorner)); + mozilla::Side sideV(GetVerticalSide(aCorner)); + Float R0 = mBorderWidths[sideH] / 2.0f; + Float Rn = mBorderWidths[sideV] / 2.0f; + if (R0 == 0.0f && Rn == 0.0f) { + return; + } + + nscolor borderColor = mBorderColors[aSide]; + Bezier outerBezier; + Bezier innerBezier; + GetOuterAndInnerBezier(&outerBezier, &innerBezier, aCorner); + + bool ignored; + Point C0 = GetStraightBorderPoint(sideH, aCorner, &ignored); + Point Cn = GetStraightBorderPoint(sideV, aCorner, &ignored); + DottedCornerFinder finder(outerBezier, innerBezier, aCorner, + mBorderRadii[aCorner].width, + mBorderRadii[aCorner].height, C0, R0, Cn, Rn, + mBorderCornerDimensions[aCorner]); + + RefPtr<PathBuilder> builder = mDrawTarget->CreatePathBuilder(); + size_t segmentCount = 0; + const Float AA_MARGIN = 2.0f; + Rect marginedDirtyRect = mDirtyRect; + marginedDirtyRect.Inflate(std::max(R0, Rn) + AA_MARGIN); + bool entered = false; + while (finder.HasMore()) { + if (segmentCount > BORDER_SEGMENT_COUNT_MAX) { + RefPtr<Path> path = builder->Finish(); + mDrawTarget->Fill(path, ColorPattern(ToDeviceColor(borderColor))); + builder = mDrawTarget->CreatePathBuilder(); + segmentCount = 0; + } + + DottedCornerFinder::Result result = finder.Next(); + + if (marginedDirtyRect.Contains(result.C) && result.r > 0) { + entered = true; + builder->MoveTo(Point(result.C.x + result.r, result.C.y)); + builder->Arc(result.C, result.r, 0, Float(2.0 * M_PI)); + segmentCount++; + } else if (entered) { + break; + } + } + RefPtr<Path> path = builder->Finish(); + mDrawTarget->Fill(path, ColorPattern(ToDeviceColor(borderColor))); +} + +static inline bool DashedPathOverlapsRect(Rect& pathRect, + const Rect& marginedDirtyRect, + DashedCornerFinder::Result& result) { + // Calculate a rect that contains all control points of the |result| path, + // and check if it intersects with |marginedDirtyRect|. + pathRect.SetRect(result.outerSectionBezier.mPoints[0].x, + result.outerSectionBezier.mPoints[0].y, 0, 0); + pathRect.ExpandToEnclose(result.outerSectionBezier.mPoints[1]); + pathRect.ExpandToEnclose(result.outerSectionBezier.mPoints[2]); + pathRect.ExpandToEnclose(result.outerSectionBezier.mPoints[3]); + pathRect.ExpandToEnclose(result.innerSectionBezier.mPoints[0]); + pathRect.ExpandToEnclose(result.innerSectionBezier.mPoints[1]); + pathRect.ExpandToEnclose(result.innerSectionBezier.mPoints[2]); + pathRect.ExpandToEnclose(result.innerSectionBezier.mPoints[3]); + + return pathRect.Intersects(marginedDirtyRect); +} + +void nsCSSBorderRenderer::DrawDashedCornerSlow(mozilla::Side aSide, + Corner aCorner) { + NS_ASSERTION(mBorderStyles[aSide] == StyleBorderStyle::Dashed, + "Style should be dashed."); + + mozilla::Side sideH(GetHorizontalSide(aCorner)); + mozilla::Side sideV(GetVerticalSide(aCorner)); + Float borderWidthH = mBorderWidths[sideH]; + Float borderWidthV = mBorderWidths[sideV]; + if (borderWidthH == 0.0f && borderWidthV == 0.0f) { + return; + } + + nscolor borderColor = mBorderColors[aSide]; + Bezier outerBezier; + Bezier innerBezier; + GetOuterAndInnerBezier(&outerBezier, &innerBezier, aCorner); + + DashedCornerFinder finder(outerBezier, innerBezier, borderWidthH, + borderWidthV, mBorderCornerDimensions[aCorner]); + + RefPtr<PathBuilder> builder = mDrawTarget->CreatePathBuilder(); + size_t segmentCount = 0; + const Float AA_MARGIN = 2.0f; + Rect marginedDirtyRect = mDirtyRect; + marginedDirtyRect.Inflate(AA_MARGIN); + Rect pathRect; + bool entered = false; + while (finder.HasMore()) { + if (segmentCount > BORDER_SEGMENT_COUNT_MAX) { + RefPtr<Path> path = builder->Finish(); + mDrawTarget->Fill(path, ColorPattern(ToDeviceColor(borderColor))); + builder = mDrawTarget->CreatePathBuilder(); + segmentCount = 0; + } + + DashedCornerFinder::Result result = finder.Next(); + + if (DashedPathOverlapsRect(pathRect, marginedDirtyRect, result)) { + entered = true; + builder->MoveTo(result.outerSectionBezier.mPoints[0]); + builder->BezierTo(result.outerSectionBezier.mPoints[1], + result.outerSectionBezier.mPoints[2], + result.outerSectionBezier.mPoints[3]); + builder->LineTo(result.innerSectionBezier.mPoints[3]); + builder->BezierTo(result.innerSectionBezier.mPoints[2], + result.innerSectionBezier.mPoints[1], + result.innerSectionBezier.mPoints[0]); + builder->LineTo(result.outerSectionBezier.mPoints[0]); + segmentCount++; + } else if (entered) { + break; + } + } + + if (outerBezier.mPoints[0].x != innerBezier.mPoints[0].x) { + // Fill gap before the first section. + // + // outnerPoint[0] + // | + // v + // _+-----------+-- + // / \##########| + // / \#########| + // + \########| + // |\ \######| + // | \ \#####| + // | \ \####| + // | \ \##| + // | \ \#| + // | \ \| + // | \ _-+-- + // +--------------+ ^ + // | | | + // | | innerPoint[0] + // | | + builder->MoveTo(outerBezier.mPoints[0]); + builder->LineTo(innerBezier.mPoints[0]); + builder->LineTo(Point(innerBezier.mPoints[0].x, outerBezier.mPoints[0].y)); + builder->LineTo(outerBezier.mPoints[0]); + } + + if (outerBezier.mPoints[3].y != innerBezier.mPoints[3].y) { + // Fill gap after the last section. + // + // outnerPoint[3] + // | + // | + // | _+-----------+-- + // | / \ | + // v/ \ | + // + \ | + // |\ \ | + // |##\ \ | + // |####\ \ | + // |######\ \ | + // |########\ \ | + // |##########\ \| + // |############\ _-+-- + // +--------------+<-- innerPoint[3] + // | | + // | | + // | | + builder->MoveTo(outerBezier.mPoints[3]); + builder->LineTo(innerBezier.mPoints[3]); + builder->LineTo(Point(outerBezier.mPoints[3].x, innerBezier.mPoints[3].y)); + builder->LineTo(outerBezier.mPoints[3]); + } + + RefPtr<Path> path = builder->Finish(); + mDrawTarget->Fill(path, ColorPattern(ToDeviceColor(borderColor))); +} + +void nsCSSBorderRenderer::DrawFallbackSolidCorner(mozilla::Side aSide, + Corner aCorner) { + // Render too large dashed or dotted corner with solid style, to avoid hangup + // inside DashedCornerFinder and DottedCornerFinder. + + NS_ASSERTION(mBorderStyles[aSide] == StyleBorderStyle::Dashed || + mBorderStyles[aSide] == StyleBorderStyle::Dotted, + "Style should be dashed or dotted."); + + nscolor borderColor = mBorderColors[aSide]; + Bezier outerBezier; + Bezier innerBezier; + GetOuterAndInnerBezier(&outerBezier, &innerBezier, aCorner); + + RefPtr<PathBuilder> builder = mDrawTarget->CreatePathBuilder(); + + builder->MoveTo(outerBezier.mPoints[0]); + builder->BezierTo(outerBezier.mPoints[1], outerBezier.mPoints[2], + outerBezier.mPoints[3]); + builder->LineTo(innerBezier.mPoints[3]); + builder->BezierTo(innerBezier.mPoints[2], innerBezier.mPoints[1], + innerBezier.mPoints[0]); + builder->LineTo(outerBezier.mPoints[0]); + + RefPtr<Path> path = builder->Finish(); + mDrawTarget->Fill(path, ColorPattern(ToDeviceColor(borderColor))); + + if (mDocument) { + if (!mPresContext->HasWarnedAboutTooLargeDashedOrDottedRadius()) { + mPresContext->SetHasWarnedAboutTooLargeDashedOrDottedRadius(); + nsContentUtils::ReportToConsole( + nsIScriptError::warningFlag, "CSS"_ns, mDocument, + nsContentUtils::eCSS_PROPERTIES, + mBorderStyles[aSide] == StyleBorderStyle::Dashed + ? "TooLargeDashedRadius" + : "TooLargeDottedRadius"); + } + } +} + +bool nsCSSBorderRenderer::AllBordersSameWidth() { + if (mBorderWidths[0] == mBorderWidths[1] && + mBorderWidths[0] == mBorderWidths[2] && + mBorderWidths[0] == mBorderWidths[3]) { + return true; + } + + return false; +} + +bool nsCSSBorderRenderer::AllBordersSolid() { + for (const auto i : mozilla::AllPhysicalSides()) { + if (mBorderStyles[i] == StyleBorderStyle::Solid || + mBorderStyles[i] == StyleBorderStyle::None || + mBorderStyles[i] == StyleBorderStyle::Hidden) { + continue; + } + return false; + } + + return true; +} + +static bool IsVisible(StyleBorderStyle aStyle) { + if (aStyle != StyleBorderStyle::None && aStyle != StyleBorderStyle::Hidden) { + return true; + } + return false; +} + +struct twoFloats { + Float a, b; + + twoFloats operator*(const Size& aSize) const { + return {a * aSize.width, b * aSize.height}; + } + + twoFloats operator*(Float aScale) const { return {a * aScale, b * aScale}; } + + twoFloats operator+(const Point& aPoint) const { + return {a + aPoint.x, b + aPoint.y}; + } + + operator Point() const { return Point(a, b); } +}; + +void nsCSSBorderRenderer::DrawSingleWidthSolidBorder() { + // Easy enough to deal with. + Rect rect = mOuterRect; + rect.Deflate(0.5); + + const twoFloats cornerAdjusts[4] = { + {+0.5, 0}, {0, +0.5}, {-0.5, 0}, {0, -0.5}}; + for (const auto side : mozilla::AllPhysicalSides()) { + Point firstCorner = rect.CCWCorner(side) + cornerAdjusts[side]; + Point secondCorner = rect.CWCorner(side) + cornerAdjusts[side]; + + ColorPattern color(ToDeviceColor(mBorderColors[side])); + + mDrawTarget->StrokeLine(firstCorner, secondCorner, color); + } +} + +// Intersect a ray from the inner corner to the outer corner +// with the border radius, yielding the intersection point. +static Point IntersectBorderRadius(const Point& aCenter, const Size& aRadius, + const Point& aInnerCorner, + const Point& aCornerDirection) { + Point toCorner = aCornerDirection; + // transform to-corner ray to unit-circle space + toCorner.x /= aRadius.width; + toCorner.y /= aRadius.height; + // normalize to-corner ray + Float cornerDist = toCorner.Length(); + if (cornerDist < 1.0e-6f) { + return aInnerCorner; + } + toCorner = toCorner / cornerDist; + // ray from inner corner to border radius center + Point toCenter = aCenter - aInnerCorner; + // transform to-center ray to unit-circle space + toCenter.x /= aRadius.width; + toCenter.y /= aRadius.height; + // compute offset of intersection with border radius unit circle + Float offset = toCenter.DotProduct(toCorner); + // compute discriminant to check for intersections + Float discrim = 1.0f - toCenter.DotProduct(toCenter) + offset * offset; + // choose farthest intersection + offset += sqrtf(std::max(discrim, 0.0f)); + // transform to-corner ray back out of unit-circle space + toCorner.x *= aRadius.width; + toCorner.y *= aRadius.height; + return aInnerCorner + toCorner * offset; +} + +// Calculate the split point and split angle for a border radius with +// differing sides. +static inline void SplitBorderRadius(const Point& aCenter, const Size& aRadius, + const Point& aOuterCorner, + const Point& aInnerCorner, + const twoFloats& aCornerMults, + Float aStartAngle, Point& aSplit, + Float& aSplitAngle) { + Point cornerDir = aOuterCorner - aInnerCorner; + if (cornerDir.x == cornerDir.y && aRadius.IsSquare()) { + // optimize 45-degree intersection with circle since we can assume + // the circle center lies along the intersection edge + aSplit = aCenter - aCornerMults * (aRadius * Float(1.0f / M_SQRT2)); + aSplitAngle = aStartAngle + 0.5f * M_PI / 2.0f; + } else { + aSplit = IntersectBorderRadius(aCenter, aRadius, aInnerCorner, cornerDir); + aSplitAngle = atan2f((aSplit.y - aCenter.y) / aRadius.height, + (aSplit.x - aCenter.x) / aRadius.width); + } +} + +// Compute the size of the skirt needed, given the color alphas +// of each corner side and the slope between them. +static void ComputeCornerSkirtSize(Float aAlpha1, Float aAlpha2, Float aSlopeY, + Float aSlopeX, Float& aSizeResult, + Float& aSlopeResult) { + // If either side is (almost) invisible or there is no diagonal edge, + // then don't try to render a skirt. + if (aAlpha1 < 0.01f || aAlpha2 < 0.01f) { + return; + } + aSlopeX = fabs(aSlopeX); + aSlopeY = fabs(aSlopeY); + if (aSlopeX < 1.0e-6f || aSlopeY < 1.0e-6f) { + return; + } + + // If first and second color don't match, we need to split the corner in + // half. The diagonal edges created may not have full pixel coverage given + // anti-aliasing, so we need to compute a small subpixel skirt edge. This + // assumes each half has half coverage to start with, and that coverage + // increases as the skirt is pushed over, with the end result that we want + // to roughly preserve the alpha value along this edge. + // Given slope m, alphas a and A, use quadratic formula to solve for S in: + // a*(1 - 0.5*(1-S)*(1-mS))*(1 - 0.5*A) + 0.5*A = A + // yielding: + // S = ((1+m) - sqrt((1+m)*(1+m) + 4*m*(1 - A/(a*(1-0.5*A))))) / (2*m) + // and substitute k = (1+m)/(2*m): + // S = k - sqrt(k*k + (1 - A/(a*(1-0.5*A)))/m) + Float slope = aSlopeY / aSlopeX; + Float slopeScale = (1.0f + slope) / (2.0f * slope); + Float discrim = slopeScale * slopeScale + + (1 - aAlpha2 / (aAlpha1 * (1.0f - 0.49f * aAlpha2))) / slope; + if (discrim >= 0) { + aSizeResult = slopeScale - sqrtf(discrim); + aSlopeResult = slope; + } +} + +// Draws a border radius with possibly different sides. +// A skirt is drawn underneath the corner intersection to hide possible +// seams when anti-aliased drawing is used. +static void DrawBorderRadius( + DrawTarget* aDrawTarget, Corner c, const Point& aOuterCorner, + const Point& aInnerCorner, const twoFloats& aCornerMultPrev, + const twoFloats& aCornerMultNext, const Size& aCornerDims, + const Size& aOuterRadius, const Size& aInnerRadius, + const DeviceColor& aFirstColor, const DeviceColor& aSecondColor, + Float aSkirtSize, Float aSkirtSlope) { + // Connect edge to outer arc start point + Point outerCornerStart = aOuterCorner + aCornerMultPrev * aCornerDims; + // Connect edge to outer arc end point + Point outerCornerEnd = aOuterCorner + aCornerMultNext * aCornerDims; + // Connect edge to inner arc start point + Point innerCornerStart = + outerCornerStart + aCornerMultNext * (aCornerDims - aInnerRadius); + // Connect edge to inner arc end point + Point innerCornerEnd = + outerCornerEnd + aCornerMultPrev * (aCornerDims - aInnerRadius); + + // Outer arc start point + Point outerArcStart = aOuterCorner + aCornerMultPrev * aOuterRadius; + // Outer arc end point + Point outerArcEnd = aOuterCorner + aCornerMultNext * aOuterRadius; + // Inner arc start point + Point innerArcStart = aInnerCorner + aCornerMultPrev * aInnerRadius; + // Inner arc end point + Point innerArcEnd = aInnerCorner + aCornerMultNext * aInnerRadius; + + // Outer radius center + Point outerCenter = + aOuterCorner + (aCornerMultPrev + aCornerMultNext) * aOuterRadius; + // Inner radius center + Point innerCenter = + aInnerCorner + (aCornerMultPrev + aCornerMultNext) * aInnerRadius; + + RefPtr<PathBuilder> builder; + RefPtr<Path> path; + + if (aFirstColor.a > 0) { + builder = aDrawTarget->CreatePathBuilder(); + builder->MoveTo(outerCornerStart); + } + + if (aFirstColor != aSecondColor) { + // Start and end angles of corner quadrant + Float startAngle = (c * M_PI) / 2.0f - M_PI, + endAngle = startAngle + M_PI / 2.0f, outerSplitAngle, innerSplitAngle; + Point outerSplit, innerSplit; + + // Outer half-way point + SplitBorderRadius(outerCenter, aOuterRadius, aOuterCorner, aInnerCorner, + aCornerMultPrev + aCornerMultNext, startAngle, outerSplit, + outerSplitAngle); + // Inner half-way point + if (aInnerRadius.IsEmpty()) { + innerSplit = aInnerCorner; + innerSplitAngle = endAngle; + } else { + SplitBorderRadius(innerCenter, aInnerRadius, aOuterCorner, aInnerCorner, + aCornerMultPrev + aCornerMultNext, startAngle, + innerSplit, innerSplitAngle); + } + + // Draw first half with first color + if (aFirstColor.a > 0) { + AcuteArcToBezier(builder.get(), outerCenter, aOuterRadius, outerArcStart, + outerSplit, startAngle, outerSplitAngle); + // Draw skirt as part of first half + if (aSkirtSize > 0) { + builder->LineTo(outerSplit + aCornerMultNext * aSkirtSize); + builder->LineTo(innerSplit - + aCornerMultPrev * (aSkirtSize * aSkirtSlope)); + } + AcuteArcToBezier(builder.get(), innerCenter, aInnerRadius, innerSplit, + innerArcStart, innerSplitAngle, startAngle); + if ((innerCornerStart - innerArcStart).DotProduct(aCornerMultPrev) > 0) { + builder->LineTo(innerCornerStart); + } + builder->Close(); + path = builder->Finish(); + aDrawTarget->Fill(path, ColorPattern(aFirstColor)); + } + + // Draw second half with second color + if (aSecondColor.a > 0) { + builder = aDrawTarget->CreatePathBuilder(); + builder->MoveTo(outerCornerEnd); + if ((innerArcEnd - innerCornerEnd).DotProduct(aCornerMultNext) < 0) { + builder->LineTo(innerCornerEnd); + } + AcuteArcToBezier(builder.get(), innerCenter, aInnerRadius, innerArcEnd, + innerSplit, endAngle, innerSplitAngle); + AcuteArcToBezier(builder.get(), outerCenter, aOuterRadius, outerSplit, + outerArcEnd, outerSplitAngle, endAngle); + builder->Close(); + path = builder->Finish(); + aDrawTarget->Fill(path, ColorPattern(aSecondColor)); + } + } else if (aFirstColor.a > 0) { + // Draw corner with single color + AcuteArcToBezier(builder.get(), outerCenter, aOuterRadius, outerArcStart, + outerArcEnd); + builder->LineTo(outerCornerEnd); + if ((innerArcEnd - innerCornerEnd).DotProduct(aCornerMultNext) < 0) { + builder->LineTo(innerCornerEnd); + } + AcuteArcToBezier(builder.get(), innerCenter, aInnerRadius, innerArcEnd, + innerArcStart, -kKappaFactor); + if ((innerCornerStart - innerArcStart).DotProduct(aCornerMultPrev) > 0) { + builder->LineTo(innerCornerStart); + } + builder->Close(); + path = builder->Finish(); + aDrawTarget->Fill(path, ColorPattern(aFirstColor)); + } +} + +// Draw a corner with possibly different sides. +// A skirt is drawn underneath the corner intersection to hide possible +// seams when anti-aliased drawing is used. +static void DrawCorner(DrawTarget* aDrawTarget, const Point& aOuterCorner, + const Point& aInnerCorner, + const twoFloats& aCornerMultPrev, + const twoFloats& aCornerMultNext, + const Size& aCornerDims, const DeviceColor& aFirstColor, + const DeviceColor& aSecondColor, Float aSkirtSize, + Float aSkirtSlope) { + // Corner box start point + Point cornerStart = aOuterCorner + aCornerMultPrev * aCornerDims; + // Corner box end point + Point cornerEnd = aOuterCorner + aCornerMultNext * aCornerDims; + + RefPtr<PathBuilder> builder; + RefPtr<Path> path; + + if (aFirstColor.a > 0) { + builder = aDrawTarget->CreatePathBuilder(); + builder->MoveTo(cornerStart); + } + + if (aFirstColor != aSecondColor) { + // Draw first half with first color + if (aFirstColor.a > 0) { + builder->LineTo(aOuterCorner); + // Draw skirt as part of first half + if (aSkirtSize > 0) { + builder->LineTo(aOuterCorner + aCornerMultNext * aSkirtSize); + builder->LineTo(aInnerCorner - + aCornerMultPrev * (aSkirtSize * aSkirtSlope)); + } + builder->LineTo(aInnerCorner); + builder->Close(); + path = builder->Finish(); + aDrawTarget->Fill(path, ColorPattern(aFirstColor)); + } + + // Draw second half with second color + if (aSecondColor.a > 0) { + builder = aDrawTarget->CreatePathBuilder(); + builder->MoveTo(cornerEnd); + builder->LineTo(aInnerCorner); + builder->LineTo(aOuterCorner); + builder->Close(); + path = builder->Finish(); + aDrawTarget->Fill(path, ColorPattern(aSecondColor)); + } + } else if (aFirstColor.a > 0) { + // Draw corner with single color + builder->LineTo(aOuterCorner); + builder->LineTo(cornerEnd); + builder->LineTo(aInnerCorner); + builder->Close(); + path = builder->Finish(); + aDrawTarget->Fill(path, ColorPattern(aFirstColor)); + } +} + +void nsCSSBorderRenderer::DrawSolidBorder() { + const twoFloats cornerMults[4] = {{-1, 0}, {0, -1}, {+1, 0}, {0, +1}}; + + const twoFloats centerAdjusts[4] = { + {0, +0.5}, {-0.5, 0}, {0, -0.5}, {+0.5, 0}}; + + RectCornerRadii innerRadii; + ComputeInnerRadii(mBorderRadii, mBorderWidths, &innerRadii); + + Rect strokeRect = mOuterRect; + strokeRect.Deflate(Margin(mBorderWidths[0] / 2.0, mBorderWidths[1] / 2.0, + mBorderWidths[2] / 2.0, mBorderWidths[3] / 2.0)); + + for (const auto i : mozilla::AllPhysicalSides()) { + // We now draw the current side and the CW corner following it. + // The CCW corner of this side was already drawn in the previous iteration. + // The side will be drawn as an explicit stroke, and the CW corner will be + // filled separately. + // If the next side does not have a matching color, then we split the + // corner into two halves, one of each side's color and draw both. + // Thus, the CCW corner of the next side will end up drawn here. + + // the corner index -- either 1 2 3 0 (cw) or 0 3 2 1 (ccw) + Corner c = Corner((i + 1) % 4); + Corner prevCorner = Corner(i); + + // i+2 and i+3 respectively. These are used to index into the corner + // multiplier table, and were deduced by calculating out the long form + // of each corner and finding a pattern in the signs and values. + int i1 = (i + 1) % 4; + int i2 = (i + 2) % 4; + int i3 = (i + 3) % 4; + + Float sideWidth = 0.0f; + DeviceColor firstColor, secondColor; + if (IsVisible(mBorderStyles[i]) && mBorderWidths[i]) { + // draw the side since it is visible + sideWidth = mBorderWidths[i]; + firstColor = ToDeviceColor(mBorderColors[i]); + // if the next side is visible, use its color for corner + secondColor = IsVisible(mBorderStyles[i1]) && mBorderWidths[i1] + ? ToDeviceColor(mBorderColors[i1]) + : firstColor; + } else if (IsVisible(mBorderStyles[i1]) && mBorderWidths[i1]) { + // assign next side's color to both corner sides + firstColor = ToDeviceColor(mBorderColors[i1]); + secondColor = firstColor; + } else { + // neither side is visible, so nothing to do + continue; + } + + Point outerCorner = mOuterRect.AtCorner(c); + Point innerCorner = mInnerRect.AtCorner(c); + + // start and end points of border side stroke between corners + Point sideStart = mOuterRect.AtCorner(prevCorner) + + cornerMults[i2] * mBorderCornerDimensions[prevCorner]; + Point sideEnd = outerCorner + cornerMults[i] * mBorderCornerDimensions[c]; + // check if the side is visible and not inverted + if (sideWidth > 0 && firstColor.a > 0 && + -(sideEnd - sideStart).DotProduct(cornerMults[i]) > 0) { + mDrawTarget->StrokeLine(sideStart + centerAdjusts[i] * sideWidth, + sideEnd + centerAdjusts[i] * sideWidth, + ColorPattern(firstColor), + StrokeOptions(sideWidth)); + } + + Float skirtSize = 0.0f, skirtSlope = 0.0f; + // the sides don't match, so compute a skirt + if (firstColor != secondColor && + mPresContext->Type() != nsPresContext::eContext_Print) { + Point cornerDir = outerCorner - innerCorner; + ComputeCornerSkirtSize( + firstColor.a, secondColor.a, cornerDir.DotProduct(cornerMults[i]), + cornerDir.DotProduct(cornerMults[i3]), skirtSize, skirtSlope); + } + + if (!mBorderRadii[c].IsEmpty()) { + // the corner has a border radius + DrawBorderRadius(mDrawTarget, c, outerCorner, innerCorner, cornerMults[i], + cornerMults[i3], mBorderCornerDimensions[c], + mBorderRadii[c], innerRadii[c], firstColor, secondColor, + skirtSize, skirtSlope); + } else if (!mBorderCornerDimensions[c].IsEmpty()) { + // a corner with no border radius + DrawCorner(mDrawTarget, outerCorner, innerCorner, cornerMults[i], + cornerMults[i3], mBorderCornerDimensions[c], firstColor, + secondColor, skirtSize, skirtSlope); + } + } +} + +void nsCSSBorderRenderer::DrawBorders() { + if (mAllBordersSameStyle && (mBorderStyles[0] == StyleBorderStyle::None || + mBorderStyles[0] == StyleBorderStyle::Hidden || + mBorderColors[0] == NS_RGBA(0, 0, 0, 0))) { + // All borders are the same style, and the style is either none or hidden, + // or the color is transparent. + return; + } + + if (mAllBordersSameWidth && mBorderWidths[0] == 0.0) { + // Some of the mAllBordersSameWidth codepaths depend on the border + // width being greater than zero. + return; + } + + AutoRestoreTransform autoRestoreTransform; + Matrix mat = mDrawTarget->GetTransform(); + + // Clamp the CTM to be pixel-aligned; we do this only + // for translation-only matrices now, but we could do it + // if the matrix has just a scale as well. We should not + // do it if there's a rotation. + if (mat.HasNonTranslation()) { + if (!mat.HasNonAxisAlignedTransform()) { + // Scale + transform. Avoid stroke fast-paths so that we have a chance + // of snapping to pixel boundaries. + mAvoidStroke = true; + } + } else { + mat._31 = floor(mat._31 + 0.5); + mat._32 = floor(mat._32 + 0.5); + autoRestoreTransform.Init(mDrawTarget); + mDrawTarget->SetTransform(mat); + + // round mOuterRect and mInnerRect; they're already an integer + // number of pixels apart and should stay that way after + // rounding. We don't do this if there's a scale in the current transform + // since this loses information that might be relevant when we're scaling. + mOuterRect.Round(); + mInnerRect.Round(); + } + + // Initial values only used when the border colors/widths are all the same: + ColorPattern color(ToDeviceColor(mBorderColors[eSideTop])); + StrokeOptions strokeOptions(mBorderWidths[eSideTop]); // stroke width + + // First there's a couple of 'special cases' that have specifically optimized + // drawing paths, when none of these can be used we move on to the generalized + // border drawing code. + if (mAllBordersSameStyle && mAllBordersSameWidth && + mBorderStyles[0] == StyleBorderStyle::Solid && mNoBorderRadius && + !mAvoidStroke) { + // Very simple case. + Rect rect = mOuterRect; + rect.Deflate(mBorderWidths[0] / 2.0); + mDrawTarget->StrokeRect(rect, color, strokeOptions); + return; + } + + if (mAllBordersSameStyle && mBorderStyles[0] == StyleBorderStyle::Solid && + !mAvoidStroke && !mNoBorderRadius) { + // Relatively simple case. + RoundedRect borderInnerRect(mOuterRect, mBorderRadii); + borderInnerRect.Deflate(mBorderWidths[eSideTop], mBorderWidths[eSideBottom], + mBorderWidths[eSideLeft], + mBorderWidths[eSideRight]); + + // Instead of stroking we just use two paths: an inner and an outer. + // This allows us to draw borders that we couldn't when stroking. For + // example, borders with a border width >= the border radius. (i.e. when + // there are square corners on the inside) + // + // Further, this approach can be more efficient because the backend + // doesn't need to compute an offset curve to stroke the path. We know that + // the rounded parts are elipses we can offset exactly and can just compute + // a new cubic approximation. + RefPtr<PathBuilder> builder = mDrawTarget->CreatePathBuilder(); + AppendRoundedRectToPath(builder, mOuterRect, mBorderRadii, true); + AppendRoundedRectToPath(builder, borderInnerRect.rect, + borderInnerRect.corners, false); + RefPtr<Path> path = builder->Finish(); + mDrawTarget->Fill(path, color); + return; + } + + const bool allBordersSolid = AllBordersSolid(); + + // This leaves the border corners non-interpolated for single width borders. + // Doing this is slightly faster and shouldn't be a problem visually. + if (allBordersSolid && mAllBordersSameWidth && mBorderWidths[0] == 1 && + mNoBorderRadius && !mAvoidStroke) { + DrawSingleWidthSolidBorder(); + return; + } + + if (allBordersSolid && !mAvoidStroke) { + DrawSolidBorder(); + return; + } + + PrintAsString(" mOuterRect: "); + PrintAsString(mOuterRect); + PrintAsStringNewline(); + PrintAsString(" mInnerRect: "); + PrintAsString(mInnerRect); + PrintAsStringNewline(); + PrintAsFormatString(" mBorderColors: 0x%08x 0x%08x 0x%08x 0x%08x\n", + mBorderColors[0], mBorderColors[1], mBorderColors[2], + mBorderColors[3]); + + // if conditioning the outside rect failed, then bail -- the outside + // rect is supposed to enclose the entire border + { + gfxRect outerRect = ThebesRect(mOuterRect); + gfxUtils::ConditionRect(outerRect); + if (outerRect.IsEmpty()) { + return; + } + mOuterRect = ToRect(outerRect); + + gfxRect innerRect = ThebesRect(mInnerRect); + gfxUtils::ConditionRect(innerRect); + mInnerRect = ToRect(innerRect); + } + + SideBits dashedSides = SideBits::eNone; + bool forceSeparateCorners = false; + + for (const auto i : mozilla::AllPhysicalSides()) { + StyleBorderStyle style = mBorderStyles[i]; + if (style == StyleBorderStyle::Dashed || + style == StyleBorderStyle::Dotted) { + // we need to draw things separately for dashed/dotting + forceSeparateCorners = true; + dashedSides |= static_cast<mozilla::SideBits>(1 << i); + } + } + + PrintAsFormatString(" mAllBordersSameStyle: %d dashedSides: 0x%02x\n", + mAllBordersSameStyle, + static_cast<unsigned int>(dashedSides)); + + if (mAllBordersSameStyle && !forceSeparateCorners) { + /* Draw everything in one go */ + DrawBorderSides(SideBits::eAll); + PrintAsStringNewline("---------------- (1)"); + } else { + AUTO_PROFILER_LABEL("nsCSSBorderRenderer::DrawBorders:multipass", GRAPHICS); + + /* We have more than one pass to go. Draw the corners separately from the + * sides. */ + + // The corner is going to have negligible size if its two adjacent border + // sides are only 1px wide and there is no border radius. In that case we + // skip the overhead of painting the corner by setting the width or height + // of the corner to zero, which effectively extends one of the corner's + // adjacent border sides. We extend the longer adjacent side so that + // opposite sides will be the same length, which is necessary for opposite + // dashed/dotted sides to be symmetrical. + // + // if width > height + // +--+--------------+--+ +--------------------+ + // | | | | | | + // +--+--------------+--+ +--+--------------+--+ + // | | | | | | | | + // | | | | => | | | | + // | | | | | | | | + // +--+--------------+--+ +--+--------------+--+ + // | | | | | | + // +--+--------------+--+ +--------------------+ + // + // if width <= height + // +--+--------+--+ +--+--------+--+ + // | | | | | | | | + // +--+--------+--+ | +--------+ | + // | | | | | | | | + // | | | | | | | | + // | | | | | | | | + // | | | | => | | | | + // | | | | | | | | + // | | | | | | | | + // | | | | | | | | + // +--+--------+--+ | +--------+ | + // | | | | | | | | + // +--+--------+--+ +--+--------+--+ + // + // Note that if we have different border widths we could end up with + // opposite sides of different length. For example, if the left and + // bottom borders are 2px wide instead of 1px, we will end up doing + // something like: + // + // +----+------------+--+ +----+---------------+ + // | | | | | | | + // +----+------------+--+ +----+------------+--+ + // | | | | | | | | + // | | | | => | | | | + // | | | | | | | | + // +----+------------+--+ +----+------------+--+ + // | | | | | | | | + // | | | | | | | | + // +----+------------+--+ +----+------------+--+ + // + // XXX Should we only do this optimization if |mAllBordersSameWidth| is + // true? + // + // XXX In fact is this optimization even worth the complexity it adds to + // the code? 1px wide dashed borders are not overly common, and drawing + // corners for them is not that expensive. + for (const auto corner : mozilla::AllPhysicalCorners()) { + const mozilla::Side sides[2] = {mozilla::Side(corner), PREV_SIDE(corner)}; + + if (!IsZeroSize(mBorderRadii[corner])) { + continue; + } + + if (mBorderWidths[sides[0]] == 1.0 && mBorderWidths[sides[1]] == 1.0) { + if (mOuterRect.Width() > mOuterRect.Height()) { + mBorderCornerDimensions[corner].width = 0.0; + } else { + mBorderCornerDimensions[corner].height = 0.0; + } + } + } + + // First, the corners + for (const auto corner : mozilla::AllPhysicalCorners()) { + // if there's no corner, don't do all this work for it + if (IsZeroSize(mBorderCornerDimensions[corner])) { + continue; + } + + const int sides[2] = {corner, PREV_SIDE(corner)}; + SideBits sideBits = + static_cast<SideBits>((1 << sides[0]) | (1 << sides[1])); + + bool simpleCornerStyle = AreBorderSideFinalStylesSame(sideBits); + + // If we don't have anything complex going on in this corner, + // then we can just fill the corner with a solid color, and avoid + // the potentially expensive clip. + if (simpleCornerStyle && IsZeroSize(mBorderRadii[corner]) && + IsSolidCornerStyle(mBorderStyles[sides[0]], corner)) { + sRGBColor color = MakeBorderColor( + mBorderColors[sides[0]], + BorderColorStyleForSolidCorner(mBorderStyles[sides[0]], corner)); + mDrawTarget->FillRect(GetCornerRect(corner), + ColorPattern(ToDeviceColor(color))); + continue; + } + + // clip to the corner + mDrawTarget->PushClipRect(GetCornerRect(corner)); + + if (simpleCornerStyle) { + // we don't need a group for this corner, the sides are the same, + // but we weren't able to render just a solid block for the corner. + DrawBorderSides(sideBits); + } else { + // Sides are different. We could draw using OP_ADD to + // get correct color blending behaviour at the seam. We'd need + // to do it in an offscreen surface to ensure that we're + // always compositing on transparent black. If the colors + // don't have transparency and the current destination surface + // has an alpha channel, we could just clear the region and + // avoid the temporary, but that situation doesn't happen all + // that often in practice (we double buffer to no-alpha + // surfaces). We choose just to seam though, as the performance + // advantages outway the modest easthetic improvement. + + for (int cornerSide = 0; cornerSide < 2; cornerSide++) { + mozilla::Side side = mozilla::Side(sides[cornerSide]); + StyleBorderStyle style = mBorderStyles[side]; + + PrintAsFormatString("corner: %d cornerSide: %d side: %d style: %d\n", + corner, cornerSide, side, + static_cast<int>(style)); + + RefPtr<Path> path = GetSideClipSubPath(side); + mDrawTarget->PushClip(path); + + DrawBorderSides(static_cast<mozilla::SideBits>(1 << side)); + + mDrawTarget->PopClip(); + } + } + + mDrawTarget->PopClip(); + + PrintAsStringNewline(); + } + + // in the case of a single-unit border, we already munged the + // corners up above; so we can just draw the top left and bottom + // right sides separately, if they're the same. + // + // We need to check for mNoBorderRadius, because when there is + // one, FillSolidBorder always draws the full rounded rectangle + // and expects there to be a clip in place. + SideBits alreadyDrawnSides = SideBits::eNone; + if (mOneUnitBorder && mNoBorderRadius && + (dashedSides & (SideBits::eTop | SideBits::eLeft)) == SideBits::eNone) { + bool tlBordersSameStyle = + AreBorderSideFinalStylesSame(SideBits::eTop | SideBits::eLeft); + bool brBordersSameStyle = + AreBorderSideFinalStylesSame(SideBits::eBottom | SideBits::eRight); + + if (tlBordersSameStyle) { + DrawBorderSides(SideBits::eTop | SideBits::eLeft); + alreadyDrawnSides |= (SideBits::eTop | SideBits::eLeft); + } + + if (brBordersSameStyle && + (dashedSides & (SideBits::eBottom | SideBits::eRight)) == + SideBits::eNone) { + DrawBorderSides(SideBits::eBottom | SideBits::eRight); + alreadyDrawnSides |= (SideBits::eBottom | SideBits::eRight); + } + } + + // We're done with the corners, now draw the sides. + for (const auto side : mozilla::AllPhysicalSides()) { + // if we drew it above, skip it + if (alreadyDrawnSides & static_cast<mozilla::SideBits>(1 << side)) { + continue; + } + + // If there's no border on this side, skip it + if (mBorderWidths[side] == 0.0 || + mBorderStyles[side] == StyleBorderStyle::Hidden || + mBorderStyles[side] == StyleBorderStyle::None) { + continue; + } + + if (dashedSides & static_cast<mozilla::SideBits>(1 << side)) { + // Dashed sides will always draw just the part ignoring the + // corners for the side, so no need to clip. + DrawDashedOrDottedSide(side); + + PrintAsStringNewline("---------------- (d)"); + continue; + } + + // Undashed sides will currently draw the entire side, + // including parts that would normally be covered by a corner, + // so we need to clip. + // + // XXX Optimization -- it would be good to make this work like + // DrawDashedOrDottedSide, and have a DrawOneSide function that just + // draws one side and not the corners, because then we can + // avoid the potentially expensive clip. + mDrawTarget->PushClipRect(GetSideClipWithoutCornersRect(side)); + + DrawBorderSides(static_cast<mozilla::SideBits>(1 << side)); + + mDrawTarget->PopClip(); + + PrintAsStringNewline("---------------- (*)"); + } + } +} + +void nsCSSBorderRenderer::CreateWebRenderCommands( + nsDisplayItem* aItem, wr::DisplayListBuilder& aBuilder, + wr::IpcResourceUpdateQueue& aResources, + const layers::StackingContextHelper& aSc) { + LayoutDeviceRect outerRect = LayoutDeviceRect::FromUnknownRect(mOuterRect); + wr::LayoutRect roundedRect = wr::ToLayoutRect(outerRect); + wr::LayoutRect clipRect = roundedRect; + wr::BorderSide side[4]; + for (const auto i : mozilla::AllPhysicalSides()) { + side[i] = + wr::ToBorderSide(ToDeviceColor(mBorderColors[i]), mBorderStyles[i]); + } + + wr::BorderRadius borderRadius = + wr::ToBorderRadius(LayoutDeviceSize::FromUnknownSize(mBorderRadii[0]), + LayoutDeviceSize::FromUnknownSize(mBorderRadii[1]), + LayoutDeviceSize::FromUnknownSize(mBorderRadii[3]), + LayoutDeviceSize::FromUnknownSize(mBorderRadii[2])); + + if (mLocalClip) { + LayoutDeviceRect localClip = + LayoutDeviceRect::FromUnknownRect(mLocalClip.value()); + clipRect = wr::ToLayoutRect(localClip.Intersect(outerRect)); + } + + Range<const wr::BorderSide> wrsides(side, 4); + aBuilder.PushBorder(roundedRect, clipRect, mBackfaceIsVisible, + wr::ToBorderWidths(mBorderWidths[0], mBorderWidths[1], + mBorderWidths[2], mBorderWidths[3]), + wrsides, borderRadius); +} + +/* static */ +Maybe<nsCSSBorderImageRenderer> +nsCSSBorderImageRenderer::CreateBorderImageRenderer( + nsPresContext* aPresContext, nsIFrame* aForFrame, const nsRect& aBorderArea, + const nsStyleBorder& aStyleBorder, const nsRect& aDirtyRect, + Sides aSkipSides, uint32_t aFlags, ImgDrawResult* aDrawResult) { + MOZ_ASSERT(aDrawResult); + + if (aDirtyRect.IsEmpty()) { + *aDrawResult = ImgDrawResult::SUCCESS; + return Nothing(); + } + + nsImageRenderer imgRenderer(aForFrame, &aStyleBorder.mBorderImageSource, + aFlags); + if (!imgRenderer.PrepareImage()) { + *aDrawResult = imgRenderer.PrepareResult(); + return Nothing(); + } + + // We should always get here with the frame's border, but we may construct an + // nsStyleBorder om the stack to deal with :visited and other shenaningans. + // + // We always copy the border image and such from the non-visited one, so + // there's no need to do anything with it. + MOZ_ASSERT(aStyleBorder.GetBorderImageRequest() == + aForFrame->StyleBorder()->GetBorderImageRequest()); + + nsCSSBorderImageRenderer renderer(aForFrame, aBorderArea, aStyleBorder, + aSkipSides, imgRenderer); + *aDrawResult = ImgDrawResult::SUCCESS; + return Some(renderer); +} + +ImgDrawResult nsCSSBorderImageRenderer::DrawBorderImage( + nsPresContext* aPresContext, gfxContext& aRenderingContext, + nsIFrame* aForFrame, const nsRect& aDirtyRect) { + // NOTE: no Save() yet, we do that later by calling autoSR.EnsureSaved() + // in case we need it. + gfxContextAutoSaveRestore autoSR; + + if (!mClip.IsEmpty()) { + autoSR.EnsureSaved(&aRenderingContext); + aRenderingContext.Clip(NSRectToSnappedRect( + mClip, aForFrame->PresContext()->AppUnitsPerDevPixel(), + *aRenderingContext.GetDrawTarget())); + } + + // intrinsicSize.CanComputeConcreteSize() return false means we can not + // read intrinsic size from aStyleBorder.mBorderImageSource. + // In this condition, we pass imageSize(a resolved size comes from + // default sizing algorithm) to renderer as the viewport size. + CSSSizeOrRatio intrinsicSize = mImageRenderer.ComputeIntrinsicSize(); + Maybe<nsSize> svgViewportSize = + intrinsicSize.CanComputeConcreteSize() ? Nothing() : Some(mImageSize); + bool hasIntrinsicRatio = intrinsicSize.HasRatio(); + mImageRenderer.PurgeCacheForViewportChange(svgViewportSize, + hasIntrinsicRatio); + + // These helper tables recharacterize the 'slice' and 'width' margins + // in a more convenient form: they are the x/y/width/height coords + // required for various bands of the border, and they have been transformed + // to be relative to the innerRect (for 'slice') or the page (for 'border'). + enum { LEFT, MIDDLE, RIGHT, TOP = LEFT, BOTTOM = RIGHT }; + const nscoord borderX[3] = { + mArea.x + 0, + mArea.x + mWidths.left, + mArea.x + mArea.width - mWidths.right, + }; + const nscoord borderY[3] = { + mArea.y + 0, + mArea.y + mWidths.top, + mArea.y + mArea.height - mWidths.bottom, + }; + const nscoord borderWidth[3] = { + mWidths.left, + mArea.width - mWidths.left - mWidths.right, + mWidths.right, + }; + const nscoord borderHeight[3] = { + mWidths.top, + mArea.height - mWidths.top - mWidths.bottom, + mWidths.bottom, + }; + const int32_t sliceX[3] = { + 0, + mSlice.left, + mImageSize.width - mSlice.right, + }; + const int32_t sliceY[3] = { + 0, + mSlice.top, + mImageSize.height - mSlice.bottom, + }; + const int32_t sliceWidth[3] = { + mSlice.left, + std::max(mImageSize.width - mSlice.left - mSlice.right, 0), + mSlice.right, + }; + const int32_t sliceHeight[3] = { + mSlice.top, + std::max(mImageSize.height - mSlice.top - mSlice.bottom, 0), + mSlice.bottom, + }; + + ImgDrawResult result = ImgDrawResult::SUCCESS; + + for (int i = LEFT; i <= RIGHT; i++) { + for (int j = TOP; j <= BOTTOM; j++) { + StyleBorderImageRepeat fillStyleH, fillStyleV; + nsSize unitSize; + + if (i == MIDDLE && j == MIDDLE) { + // Discard the middle portion unless set to fill. + if (!mFill) { + continue; + } + + // css-background: + // The middle image's width is scaled by the same factor as the + // top image unless that factor is zero or infinity, in which + // case the scaling factor of the bottom is substituted, and + // failing that, the width is not scaled. The height of the + // middle image is scaled by the same factor as the left image + // unless that factor is zero or infinity, in which case the + // scaling factor of the right image is substituted, and failing + // that, the height is not scaled. + gfxFloat hFactor, vFactor; + + if (0 < mWidths.left && 0 < mSlice.left) { + vFactor = gfxFloat(mWidths.left) / mSlice.left; + } else if (0 < mWidths.right && 0 < mSlice.right) { + vFactor = gfxFloat(mWidths.right) / mSlice.right; + } else { + vFactor = 1; + } + + if (0 < mWidths.top && 0 < mSlice.top) { + hFactor = gfxFloat(mWidths.top) / mSlice.top; + } else if (0 < mWidths.bottom && 0 < mSlice.bottom) { + hFactor = gfxFloat(mWidths.bottom) / mSlice.bottom; + } else { + hFactor = 1; + } + + unitSize.width = sliceWidth[i] * hFactor; + unitSize.height = sliceHeight[j] * vFactor; + fillStyleH = mRepeatModeHorizontal; + fillStyleV = mRepeatModeVertical; + + } else if (i == MIDDLE) { // top, bottom + // Sides are always stretched to the thickness of their border, + // and stretched proportionately on the other axis. + gfxFloat factor; + if (0 < borderHeight[j] && 0 < sliceHeight[j]) { + factor = gfxFloat(borderHeight[j]) / sliceHeight[j]; + } else { + factor = 1; + } + + unitSize.width = sliceWidth[i] * factor; + unitSize.height = borderHeight[j]; + fillStyleH = mRepeatModeHorizontal; + fillStyleV = StyleBorderImageRepeat::Stretch; + + } else if (j == MIDDLE) { // left, right + gfxFloat factor; + if (0 < borderWidth[i] && 0 < sliceWidth[i]) { + factor = gfxFloat(borderWidth[i]) / sliceWidth[i]; + } else { + factor = 1; + } + + unitSize.width = borderWidth[i]; + unitSize.height = sliceHeight[j] * factor; + fillStyleH = StyleBorderImageRepeat::Stretch; + fillStyleV = mRepeatModeVertical; + + } else { + // Corners are always stretched to fit the corner. + unitSize.width = borderWidth[i]; + unitSize.height = borderHeight[j]; + fillStyleH = StyleBorderImageRepeat::Stretch; + fillStyleV = StyleBorderImageRepeat::Stretch; + } + + nsRect destArea(borderX[i], borderY[j], borderWidth[i], borderHeight[j]); + nsRect subArea(sliceX[i], sliceY[j], sliceWidth[i], sliceHeight[j]); + if (subArea.IsEmpty()) continue; + + nsIntRect intSubArea = subArea.ToOutsidePixels(AppUnitsPerCSSPixel()); + result &= mImageRenderer.DrawBorderImageComponent( + aPresContext, aRenderingContext, aDirtyRect, destArea, + CSSIntRect(intSubArea.x, intSubArea.y, intSubArea.width, + intSubArea.height), + fillStyleH, fillStyleV, unitSize, j * (RIGHT + 1) + i, + svgViewportSize, hasIntrinsicRatio); + } + } + + return result; +} + +ImgDrawResult nsCSSBorderImageRenderer::CreateWebRenderCommands( + nsDisplayItem* aItem, nsIFrame* aForFrame, + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const mozilla::layers::StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + if (!mImageRenderer.IsReady()) { + return ImgDrawResult::NOT_READY; + } + + float widths[4]; + float slice[4]; + float outset[4]; + const int32_t appUnitsPerDevPixel = + aForFrame->PresContext()->AppUnitsPerDevPixel(); + for (const auto i : mozilla::AllPhysicalSides()) { + slice[i] = (float)(mSlice.Side(i)) / appUnitsPerDevPixel; + widths[i] = (float)(mWidths.Side(i)) / appUnitsPerDevPixel; + + // The outset is already taken into account by the adjustments to mArea + // in our constructor. We use mArea as our dest rect so we can just supply + // zero outsets to WebRender. + outset[i] = 0.0f; + } + + LayoutDeviceRect destRect = + LayoutDeviceRect::FromAppUnits(mArea, appUnitsPerDevPixel); + destRect.Round(); + wr::LayoutRect dest = wr::ToLayoutRect(destRect); + + wr::LayoutRect clip = dest; + if (!mClip.IsEmpty()) { + LayoutDeviceRect clipRect = + LayoutDeviceRect::FromAppUnits(mClip, appUnitsPerDevPixel); + clip = wr::ToLayoutRect(clipRect); + } + + ImgDrawResult drawResult = ImgDrawResult::SUCCESS; + switch (mImageRenderer.GetType()) { + case StyleImage::Tag::Rect: + case StyleImage::Tag::Url: { + RefPtr<imgIContainer> img = mImageRenderer.GetImage(); + if (!img || img->GetType() == imgIContainer::TYPE_VECTOR) { + // Vector images will redraw each segment of the border up to 8 times. + // We draw using a restricted region derived from the segment's clip and + // scale the image accordingly (see ClippedImage::Draw). If we follow + // this convention as is for WebRender, we will need to rasterize the + // entire vector image scaled up without the restriction region, which + // means our main thread CPU and memory footprints will be much higher. + // Ideally we would be able to provide a raster image for each segment + // of the border. For now we use fallback. + return ImgDrawResult::NOT_SUPPORTED; + } + + uint32_t flags = aDisplayListBuilder->GetImageDecodeFlags(); + + LayoutDeviceRect imageRect = LayoutDeviceRect::FromAppUnits( + nsRect(nsPoint(), mImageRenderer.GetSize()), appUnitsPerDevPixel); + + Maybe<SVGImageContext> svgContext; + gfx::IntSize decodeSize = + nsLayoutUtils::ComputeImageContainerDrawingParameters( + img, aForFrame, imageRect, aSc, flags, svgContext); + + RefPtr<layers::ImageContainer> container; + drawResult = img->GetImageContainerAtSize(aManager->LayerManager(), + decodeSize, svgContext, flags, + getter_AddRefs(container)); + if (!container) { + break; + } + + mozilla::wr::ImageRendering rendering = wr::ToImageRendering( + nsLayoutUtils::GetSamplingFilterForFrame(aItem->Frame())); + gfx::IntSize size; + Maybe<wr::ImageKey> key = aManager->CommandBuilder().CreateImageKey( + aItem, container, aBuilder, aResources, rendering, aSc, size, + Nothing()); + if (key.isNothing()) { + break; + } + + if (mFill) { + float epsilon = 0.0001; + bool noVerticalBorders = widths[0] <= epsilon && widths[2] < epsilon; + bool noHorizontalBorders = widths[1] <= epsilon && widths[3] < epsilon; + + // Border image with no border. It's a little silly but WebRender + // currently does not handle this. We could fall back to a blob image + // but there are reftests that are sensible to the test going through a + // blob while the reference doesn't. + if (noVerticalBorders && noHorizontalBorders) { + aBuilder.PushImage(dest, clip, !aItem->BackfaceIsHidden(), rendering, + key.value()); + break; + } + + // Fall-back if we want to fill the middle area and opposite edges are + // both empty. + // TODO(bug 1609893): moving some of the repetition handling code out + // of the image shader will make it easier to handle these cases + // properly. + if (noHorizontalBorders || noVerticalBorders) { + return ImgDrawResult::NOT_SUPPORTED; + } + } + + wr::WrBorderImage params{ + wr::ToBorderWidths(widths[0], widths[1], widths[2], widths[3]), + key.value(), + mImageSize.width / appUnitsPerDevPixel, + mImageSize.height / appUnitsPerDevPixel, + mFill, + wr::ToDeviceIntSideOffsets(slice[0], slice[1], slice[2], slice[3]), + wr::ToLayoutSideOffsets(outset[0], outset[1], outset[2], outset[3]), + wr::ToRepeatMode(mRepeatModeHorizontal), + wr::ToRepeatMode(mRepeatModeVertical)}; + + aBuilder.PushBorderImage(dest, clip, !aItem->BackfaceIsHidden(), params); + break; + } + case StyleImage::Tag::Gradient: { + const StyleGradient& gradient = *mImageRenderer.GetGradientData(); + nsCSSGradientRenderer renderer = nsCSSGradientRenderer::Create( + aForFrame->PresContext(), aForFrame->Style(), gradient, mImageSize); + + wr::ExtendMode extendMode; + nsTArray<wr::GradientStop> stops; + LayoutDevicePoint lineStart; + LayoutDevicePoint lineEnd; + LayoutDeviceSize gradientRadius; + LayoutDevicePoint gradientCenter; + float gradientAngle; + renderer.BuildWebRenderParameters(1.0, extendMode, stops, lineStart, + lineEnd, gradientRadius, gradientCenter, + gradientAngle); + + if (gradient.IsLinear()) { + LayoutDevicePoint startPoint = + LayoutDevicePoint(dest.origin.x, dest.origin.y) + lineStart; + LayoutDevicePoint endPoint = + LayoutDevicePoint(dest.origin.x, dest.origin.y) + lineEnd; + + aBuilder.PushBorderGradient( + dest, clip, !aItem->BackfaceIsHidden(), + wr::ToBorderWidths(widths[0], widths[1], widths[2], widths[3]), + (float)(mImageSize.width) / appUnitsPerDevPixel, + (float)(mImageSize.height) / appUnitsPerDevPixel, mFill, + wr::ToDeviceIntSideOffsets(slice[0], slice[1], slice[2], slice[3]), + wr::ToLayoutPoint(startPoint), wr::ToLayoutPoint(endPoint), stops, + extendMode, + wr::ToLayoutSideOffsets(outset[0], outset[1], outset[2], + outset[3])); + } else if (gradient.IsRadial()) { + aBuilder.PushBorderRadialGradient( + dest, clip, !aItem->BackfaceIsHidden(), + wr::ToBorderWidths(widths[0], widths[1], widths[2], widths[3]), + mFill, wr::ToLayoutPoint(lineStart), + wr::ToLayoutSize(gradientRadius), stops, extendMode, + wr::ToLayoutSideOffsets(outset[0], outset[1], outset[2], + outset[3])); + } else { + MOZ_ASSERT(gradient.IsConic()); + aBuilder.PushBorderConicGradient( + dest, clip, !aItem->BackfaceIsHidden(), + wr::ToBorderWidths(widths[0], widths[1], widths[2], widths[3]), + mFill, wr::ToLayoutPoint(gradientCenter), gradientAngle, stops, + extendMode, + wr::ToLayoutSideOffsets(outset[0], outset[1], outset[2], + outset[3])); + } + break; + } + default: + MOZ_ASSERT_UNREACHABLE("Unsupport border image type"); + drawResult = ImgDrawResult::NOT_SUPPORTED; + } + + return drawResult; +} + +nsCSSBorderImageRenderer::nsCSSBorderImageRenderer( + const nsCSSBorderImageRenderer& aRhs) + : mImageRenderer(aRhs.mImageRenderer), + mImageSize(aRhs.mImageSize), + mSlice(aRhs.mSlice), + mWidths(aRhs.mWidths), + mImageOutset(aRhs.mImageOutset), + mArea(aRhs.mArea), + mClip(aRhs.mClip), + mRepeatModeHorizontal(aRhs.mRepeatModeHorizontal), + mRepeatModeVertical(aRhs.mRepeatModeVertical), + mFill(aRhs.mFill) { + Unused << mImageRenderer.PrepareResult(); +} + +nsCSSBorderImageRenderer& nsCSSBorderImageRenderer::operator=( + const nsCSSBorderImageRenderer& aRhs) { + mImageRenderer = aRhs.mImageRenderer; + mImageSize = aRhs.mImageSize; + mSlice = aRhs.mSlice; + mWidths = aRhs.mWidths; + mImageOutset = aRhs.mImageOutset; + mArea = aRhs.mArea; + mClip = aRhs.mClip; + mRepeatModeHorizontal = aRhs.mRepeatModeHorizontal; + mRepeatModeVertical = aRhs.mRepeatModeVertical; + mFill = aRhs.mFill; + Unused << mImageRenderer.PrepareResult(); + + return *this; +} + +nsCSSBorderImageRenderer::nsCSSBorderImageRenderer( + nsIFrame* aForFrame, const nsRect& aBorderArea, + const nsStyleBorder& aStyleBorder, Sides aSkipSides, + const nsImageRenderer& aImageRenderer) + : mImageRenderer(aImageRenderer) { + // Determine the border image area, which by default corresponds to the + // border box but can be modified by 'border-image-outset'. + // Note that 'border-radius' do not apply to 'border-image' borders per + // <http://dev.w3.org/csswg/css-backgrounds/#corner-clipping>. + nsMargin borderWidths(aStyleBorder.GetComputedBorder()); + mImageOutset = aStyleBorder.GetImageOutset(); + if (nsCSSRendering::IsBoxDecorationSlice(aStyleBorder) && + !aSkipSides.IsEmpty()) { + mArea = nsCSSRendering::BoxDecorationRectForBorder( + aForFrame, aBorderArea, aSkipSides, &aStyleBorder); + if (mArea.IsEqualEdges(aBorderArea)) { + // No need for a clip, just skip the sides we don't want. + borderWidths.ApplySkipSides(aSkipSides); + mImageOutset.ApplySkipSides(aSkipSides); + mArea.Inflate(mImageOutset); + } else { + // We're drawing borders around the joined continuation boxes so we need + // to clip that to the slice that we want for this frame. + mArea.Inflate(mImageOutset); + mImageOutset.ApplySkipSides(aSkipSides); + mClip = aBorderArea; + mClip.Inflate(mImageOutset); + } + } else { + mArea = aBorderArea; + mArea.Inflate(mImageOutset); + } + + // Calculate the image size used to compute slice points. + CSSSizeOrRatio intrinsicSize = mImageRenderer.ComputeIntrinsicSize(); + mImageSize = nsImageRenderer::ComputeConcreteSize( + CSSSizeOrRatio(), intrinsicSize, mArea.Size()); + mImageRenderer.SetPreferredSize(intrinsicSize, mImageSize); + + // Compute the used values of 'border-image-slice' and 'border-image-width'; + // we do them together because the latter can depend on the former. + nsMargin slice; + nsMargin border; + for (const auto s : mozilla::AllPhysicalSides()) { + const auto& slice = aStyleBorder.mBorderImageSlice.offsets.Get(s); + int32_t imgDimension = + SideIsVertical(s) ? mImageSize.width : mImageSize.height; + nscoord borderDimension = SideIsVertical(s) ? mArea.width : mArea.height; + double value; + if (slice.IsNumber()) { + value = nsPresContext::CSSPixelsToAppUnits(NS_lround(slice.AsNumber())); + } else { + MOZ_ASSERT(slice.IsPercentage()); + value = slice.AsPercentage()._0 * imgDimension; + } + if (value < 0) { + value = 0; + } + if (value > imgDimension) { + value = imgDimension; + } + mSlice.Side(s) = value; + + const auto& width = aStyleBorder.mBorderImageWidth.Get(s); + switch (width.tag) { + case StyleBorderImageSideWidth::Tag::LengthPercentage: + value = + std::max(0, width.AsLengthPercentage().Resolve(borderDimension)); + break; + case StyleBorderImageSideWidth::Tag::Number: + value = width.AsNumber() * borderWidths.Side(s); + break; + case StyleBorderImageSideWidth::Tag::Auto: + value = mSlice.Side(s); + break; + default: + MOZ_ASSERT_UNREACHABLE("unexpected CSS unit for border image area"); + value = 0; + break; + } + // NSToCoordRoundWithClamp rounds towards infinity, but that's OK + // because we expect value to be non-negative. + MOZ_ASSERT(value >= 0); + mWidths.Side(s) = NSToCoordRoundWithClamp(value); + MOZ_ASSERT(mWidths.Side(s) >= 0); + } + + // "If two opposite border-image-width offsets are large enough that they + // overlap, their used values are proportionately reduced until they no + // longer overlap." + uint32_t combinedBorderWidth = + uint32_t(mWidths.left) + uint32_t(mWidths.right); + double scaleX = combinedBorderWidth > uint32_t(mArea.width) + ? mArea.width / double(combinedBorderWidth) + : 1.0; + uint32_t combinedBorderHeight = + uint32_t(mWidths.top) + uint32_t(mWidths.bottom); + double scaleY = combinedBorderHeight > uint32_t(mArea.height) + ? mArea.height / double(combinedBorderHeight) + : 1.0; + double scale = std::min(scaleX, scaleY); + if (scale < 1.0) { + mWidths.left *= scale; + mWidths.right *= scale; + mWidths.top *= scale; + mWidths.bottom *= scale; + NS_ASSERTION(mWidths.left + mWidths.right <= mArea.width && + mWidths.top + mWidths.bottom <= mArea.height, + "rounding error in width reduction???"); + } + + mRepeatModeHorizontal = aStyleBorder.mBorderImageRepeatH; + mRepeatModeVertical = aStyleBorder.mBorderImageRepeatV; + mFill = aStyleBorder.mBorderImageSlice.fill; +} diff --git a/layout/painting/nsCSSRenderingBorders.h b/layout/painting/nsCSSRenderingBorders.h new file mode 100644 index 0000000000..35801a1773 --- /dev/null +++ b/layout/painting/nsCSSRenderingBorders.h @@ -0,0 +1,355 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef NS_CSS_RENDERING_BORDERS_H +#define NS_CSS_RENDERING_BORDERS_H + +#include "gfxRect.h" +#include "mozilla/Attributes.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/BezierUtils.h" +#include "mozilla/gfx/PathHelpers.h" +#include "mozilla/RefPtr.h" +#include "nsColor.h" +#include "nsCOMPtr.h" +#include "nsIFrame.h" +#include "nsImageRenderer.h" +#include "gfxUtils.h" + +struct nsBorderColors; +class nsDisplayBorder; + +namespace mozilla { + +enum class StyleBorderStyle : uint8_t; +enum class StyleBorderImageRepeat : uint8_t; + +namespace gfx { +class GradientStops; +} // namespace gfx +namespace layers { +class StackingContextHelper; +} // namespace layers +} // namespace mozilla + +// define this to enable a bunch of debug dump info +#undef DEBUG_NEW_BORDERS + +/* + * Helper class that handles border rendering. + * + * aDrawTarget -- the DrawTarget to which the border should be rendered + * outsideRect -- the rectangle on the outer edge of the border + * + * For any parameter where an array of side values is passed in, + * they are in top, right, bottom, left order. + * + * borderStyles -- one border style enum per side + * borderWidths -- one border width per side + * borderRadii -- a RectCornerRadii struct describing the w/h for each rounded + * corner. If the corner doesn't have a border radius, 0,0 should be given for + * it. borderColors -- one nscolor per side + * + * skipSides -- a bit mask specifying which sides, if any, to skip + * backgroundColor -- the background color of the element. + * Used in calculating colors for 2-tone borders, such as inset and outset + * gapRect - a rectangle that should be clipped out to leave a gap in a border, + * or nullptr if none. + */ + +typedef enum { + BorderColorStyleNone, + BorderColorStyleSolid, + BorderColorStyleLight, + BorderColorStyleDark +} BorderColorStyle; + +class nsPresContext; + +class nsCSSBorderRenderer final { + typedef mozilla::gfx::Bezier Bezier; + typedef mozilla::gfx::ColorPattern ColorPattern; + typedef mozilla::gfx::DrawTarget DrawTarget; + typedef mozilla::gfx::Float Float; + typedef mozilla::gfx::Path Path; + typedef mozilla::gfx::Point Point; + typedef mozilla::gfx::Rect Rect; + typedef mozilla::gfx::RectCornerRadii RectCornerRadii; + typedef mozilla::gfx::StrokeOptions StrokeOptions; + + friend class nsDisplayBorder; + friend class nsDisplayOutline; + friend class nsDisplayButtonBorder; + friend class nsDisplayButtonForeground; + + public: + nsCSSBorderRenderer(nsPresContext* aPresContext, + const mozilla::dom::Document* aDocument, + DrawTarget* aDrawTarget, const Rect& aDirtyRect, + Rect& aOuterRect, + const mozilla::StyleBorderStyle* aBorderStyles, + const Float* aBorderWidths, RectCornerRadii& aBorderRadii, + const nscolor* aBorderColors, bool aBackfaceIsVisible, + const mozilla::Maybe<Rect>& aClipRect); + + // draw the entire border + void DrawBorders(); + + void CreateWebRenderCommands( + nsDisplayItem* aItem, mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const mozilla::layers::StackingContextHelper& aSc); + + // utility function used for background painting as well as borders + static void ComputeInnerRadii(const RectCornerRadii& aRadii, + const Float* aBorderSizes, + RectCornerRadii* aInnerRadiiRet); + + // Given aRadii as the border radii for a rectangle, compute the + // appropriate radii for another rectangle *outside* that rectangle + // by increasing the radii, except keeping sharp corners sharp. + // Used for spread box-shadows + static void ComputeOuterRadii(const RectCornerRadii& aRadii, + const Float* aBorderSizes, + RectCornerRadii* aOuterRadiiRet); + + static bool AllCornersZeroSize(const RectCornerRadii& corners); + + private: + RectCornerRadii mBorderCornerDimensions; + + // Target document to report warning + nsPresContext* mPresContext; + const mozilla::dom::Document* mDocument; + + // destination DrawTarget and dirty rect + DrawTarget* mDrawTarget; + Rect mDirtyRect; + + // the rectangle of the outside and the inside of the border + Rect mOuterRect; + Rect mInnerRect; + + // the style and size of the border + mozilla::StyleBorderStyle mBorderStyles[4]; + Float mBorderWidths[4]; + RectCornerRadii mBorderRadii; + + // the colors for 'border-top-color' et. al. + nscolor mBorderColors[4]; + + // calculated values + bool mAllBordersSameStyle; + bool mAllBordersSameWidth; + bool mOneUnitBorder; + bool mNoBorderRadius; + bool mAvoidStroke; + bool mBackfaceIsVisible; + mozilla::Maybe<Rect> mLocalClip; + + // For all the sides in the bitmask, would they be rendered + // in an identical color and style? + bool AreBorderSideFinalStylesSame(mozilla::SideBits aSides); + + // For the given style, is the given corner a solid color? + bool IsSolidCornerStyle(mozilla::StyleBorderStyle aStyle, + mozilla::Corner aCorner); + + // For the given corner, is the given corner mergeable into one dot? + bool IsCornerMergeable(mozilla::Corner aCorner); + + // For the given solid corner, what color style should be used? + BorderColorStyle BorderColorStyleForSolidCorner( + mozilla::StyleBorderStyle aStyle, mozilla::Corner aCorner); + + // + // Path generation functions + // + + // Get the Rect for drawing the given corner + Rect GetCornerRect(mozilla::Corner aCorner); + // add the path for drawing the given side without any adjacent corners to the + // context + Rect GetSideClipWithoutCornersRect(mozilla::Side aSide); + + // Create a clip path for the wedge that this side of + // the border should take up. This is only called + // when we're drawing separate border sides, so we know + // that ADD compositing is taking place. + // + // This code needs to make sure that the individual pieces + // don't ever (mathematically) overlap; the pixel overlap + // is taken care of by the ADD compositing. + already_AddRefed<Path> GetSideClipSubPath(mozilla::Side aSide); + + // Return start or end point for dashed/dotted side + Point GetStraightBorderPoint(mozilla::Side aSide, mozilla::Corner aCorner, + bool* aIsUnfilled, Float aDotOffset = 0.0f); + + // Return bezier control points for the outer and the inner curve for given + // corner + void GetOuterAndInnerBezier(Bezier* aOuterBezier, Bezier* aInnerBezier, + mozilla::Corner aCorner); + + // Given a set of sides to fill and a color, do so in the fastest way. + // + // Stroke tends to be faster for smaller borders because it doesn't go + // through the tessellator, which has initialization overhead. If + // we're rendering all sides, we can use stroke at any thickness; we + // also do TL/BR pairs at 1px thickness using stroke. + // + // If we can't stroke, then if it's a TL/BR pair, we use the specific + // TL/BR paths. Otherwise, we do the full path and fill. + // + // Calling code is expected to only set up a clip as necessary; no + // clip is needed if we can render the entire border in 1 or 2 passes. + void FillSolidBorder(const Rect& aOuterRect, const Rect& aInnerRect, + const RectCornerRadii& aBorderRadii, + const Float* aBorderSizes, mozilla::SideBits aSides, + const ColorPattern& aColor); + + // + // core rendering + // + + // draw the border for the given sides, using the style of the first side + // present in the bitmask + void DrawBorderSides(mozilla::SideBits aSides); + + // Setup the stroke options for the given dashed/dotted side + void SetupDashedOptions(StrokeOptions* aStrokeOptions, Float aDash[2], + mozilla::Side aSide, Float aBorderLength, + bool isCorner); + + // Draw the given dashed/dotte side + void DrawDashedOrDottedSide(mozilla::Side aSide); + + // Draw the given dotted side, each dot separately + void DrawDottedSideSlow(mozilla::Side aSide); + + // Draw the given dashed/dotted corner + void DrawDashedOrDottedCorner(mozilla::Side aSide, mozilla::Corner aCorner); + + // Draw the given dotted corner, each segment separately + void DrawDottedCornerSlow(mozilla::Side aSide, mozilla::Corner aCorner); + + // Draw the given dashed corner, each dot separately + void DrawDashedCornerSlow(mozilla::Side aSide, mozilla::Corner aCorner); + + // Draw the given dashed/dotted corner with solid style + void DrawFallbackSolidCorner(mozilla::Side aSide, mozilla::Corner aCorner); + + // Analyze if all border sides have the same width. + bool AllBordersSameWidth(); + + // Analyze if all borders are 'solid' this also considers hidden or 'none' + // borders because they can be considered 'solid' borders of 0 width and + // with no color effect. + bool AllBordersSolid(); + + // Draw a solid color border that is uniformly the same width. + void DrawSingleWidthSolidBorder(); + + // Draw any border which is solid on all sides. + void DrawSolidBorder(); +}; + +class nsCSSBorderImageRenderer final { + typedef mozilla::nsImageRenderer nsImageRenderer; + + public: + static mozilla::Maybe<nsCSSBorderImageRenderer> CreateBorderImageRenderer( + nsPresContext* aPresContext, nsIFrame* aForFrame, + const nsRect& aBorderArea, const nsStyleBorder& aStyleBorder, + const nsRect& aDirtyRect, nsIFrame::Sides aSkipSides, uint32_t aFlags, + mozilla::image::ImgDrawResult* aDrawResult); + + mozilla::image::ImgDrawResult DrawBorderImage(nsPresContext* aPresContext, + gfxContext& aRenderingContext, + nsIFrame* aForFrame, + const nsRect& aDirtyRect); + mozilla::image::ImgDrawResult CreateWebRenderCommands( + nsDisplayItem* aItem, nsIFrame* aForFrame, + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const mozilla::layers::StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder); + + nsCSSBorderImageRenderer(const nsCSSBorderImageRenderer& aRhs); + nsCSSBorderImageRenderer& operator=(const nsCSSBorderImageRenderer& aRhs); + + private: + nsCSSBorderImageRenderer(nsIFrame* aForFrame, const nsRect& aBorderArea, + const nsStyleBorder& aStyleBorder, + nsIFrame::Sides aSkipSides, + const nsImageRenderer& aImageRenderer); + + nsImageRenderer mImageRenderer; + nsSize mImageSize; + nsMargin mSlice; + nsMargin mWidths; + nsMargin mImageOutset; + nsRect mArea; + nsRect mClip; + mozilla::StyleBorderImageRepeat mRepeatModeHorizontal; + mozilla::StyleBorderImageRepeat mRepeatModeVertical; + bool mFill; + + friend class nsDisplayBorder; + friend struct nsCSSRendering; +}; + +namespace mozilla { +#ifdef DEBUG_NEW_BORDERS +# include <stdarg.h> + +static inline void PrintAsString(const mozilla::gfx::Point& p) { + fprintf(stderr, "[%f,%f]", p.x, p.y); +} + +static inline void PrintAsString(const mozilla::gfx::Size& s) { + fprintf(stderr, "[%f %f]", s.width, s.height); +} + +static inline void PrintAsString(const mozilla::gfx::Rect& r) { + fprintf(stderr, "[%f %f %f %f]", r.X(), r.Y(), r.Width(), r.Height()); +} + +static inline void PrintAsString(const mozilla::gfx::Float f) { + fprintf(stderr, "%f", f); +} + +static inline void PrintAsString(const char* s) { fprintf(stderr, "%s", s); } + +static inline void PrintAsStringNewline(const char* s = nullptr) { + if (s) fprintf(stderr, "%s", s); + fprintf(stderr, "\n"); + fflush(stderr); +} + +static inline MOZ_FORMAT_PRINTF(1, 2) void PrintAsFormatString(const char* fmt, + ...) { + va_list vl; + va_start(vl, fmt); + vfprintf(stderr, fmt, vl); + va_end(vl); +} + +#else +static inline void PrintAsString(const mozilla::gfx::Point& p) {} +static inline void PrintAsString(const mozilla::gfx::Size& s) {} +static inline void PrintAsString(const mozilla::gfx::Rect& r) {} +static inline void PrintAsString(const mozilla::gfx::Float f) {} +static inline void PrintAsString(const char* s) {} +static inline void PrintAsStringNewline(const char* s = nullptr) {} +static inline MOZ_FORMAT_PRINTF(1, 2) void PrintAsFormatString(const char* fmt, + ...) {} +#endif + +} // namespace mozilla + +#endif /* NS_CSS_RENDERING_BORDERS_H */ diff --git a/layout/painting/nsCSSRenderingGradients.cpp b/layout/painting/nsCSSRenderingGradients.cpp new file mode 100644 index 0000000000..7deee60fce --- /dev/null +++ b/layout/painting/nsCSSRenderingGradients.cpp @@ -0,0 +1,1297 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* utility functions for drawing borders and backgrounds */ + +#include "nsCSSRenderingGradients.h" + +#include <tuple> + +#include "gfx2DGlue.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/ComputedStyle.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/Helpers.h" +#include "mozilla/MathAlgorithms.h" + +#include "nsStyleConsts.h" +#include "nsPresContext.h" +#include "nsPoint.h" +#include "nsRect.h" +#include "nsCSSColorUtils.h" +#include "gfxContext.h" +#include "nsStyleStructInlines.h" +#include "nsCSSProps.h" +#include "gfxUtils.h" +#include "gfxGradientCache.h" + +#include "mozilla/layers/StackingContextHelper.h" +#include "mozilla/layers/WebRenderLayerManager.h" +#include "mozilla/webrender/WebRenderTypes.h" +#include "mozilla/webrender/WebRenderAPI.h" +#include "Units.h" + +using namespace mozilla; +using namespace mozilla::gfx; + +static CSSPoint ResolvePosition(const Position& aPos, const CSSSize& aSize) { + CSSCoord h = aPos.horizontal.ResolveToCSSPixels(aSize.width); + CSSCoord v = aPos.vertical.ResolveToCSSPixels(aSize.height); + return CSSPoint(h, v); +} + +// Given a box with size aBoxSize and origin (0,0), and an angle aAngle, +// and a starting point for the gradient line aStart, find the endpoint of +// the gradient line --- the intersection of the gradient line with a line +// perpendicular to aAngle that passes through the farthest corner in the +// direction aAngle. +static CSSPoint ComputeGradientLineEndFromAngle(const CSSPoint& aStart, + double aAngle, + const CSSSize& aBoxSize) { + double dx = cos(-aAngle); + double dy = sin(-aAngle); + CSSPoint farthestCorner(dx > 0 ? aBoxSize.width : 0, + dy > 0 ? aBoxSize.height : 0); + CSSPoint delta = farthestCorner - aStart; + double u = delta.x * dy - delta.y * dx; + return farthestCorner + CSSPoint(-u * dy, u * dx); +} + +// Compute the start and end points of the gradient line for a linear gradient. +static std::tuple<CSSPoint, CSSPoint> ComputeLinearGradientLine( + nsPresContext* aPresContext, const StyleGradient& aGradient, + const CSSSize& aBoxSize) { + using X = StyleHorizontalPositionKeyword; + using Y = StyleVerticalPositionKeyword; + + const StyleLineDirection& direction = aGradient.AsLinear().direction; + const bool isModern = + aGradient.AsLinear().compat_mode == StyleGradientCompatMode::Modern; + + CSSPoint center(aBoxSize.width / 2, aBoxSize.height / 2); + switch (direction.tag) { + case StyleLineDirection::Tag::Angle: { + double angle = direction.AsAngle().ToRadians(); + if (isModern) { + angle = M_PI_2 - angle; + } + CSSPoint end = ComputeGradientLineEndFromAngle(center, angle, aBoxSize); + CSSPoint start = CSSPoint(aBoxSize.width, aBoxSize.height) - end; + return {start, end}; + } + case StyleLineDirection::Tag::Vertical: { + CSSPoint start(center.x, 0); + CSSPoint end(center.x, aBoxSize.height); + if (isModern == (direction.AsVertical() == Y::Top)) { + std::swap(start.y, end.y); + } + return {start, end}; + } + case StyleLineDirection::Tag::Horizontal: { + CSSPoint start(0, center.y); + CSSPoint end(aBoxSize.width, center.y); + if (isModern == (direction.AsHorizontal() == X::Left)) { + std::swap(start.x, end.x); + } + return {start, end}; + } + case StyleLineDirection::Tag::Corner: { + const auto& corner = direction.AsCorner(); + const X& h = corner._0; + const Y& v = corner._1; + + if (isModern) { + float xSign = h == X::Right ? 1.0 : -1.0; + float ySign = v == Y::Top ? 1.0 : -1.0; + double angle = atan2(ySign * aBoxSize.width, xSign * aBoxSize.height); + CSSPoint end = ComputeGradientLineEndFromAngle(center, angle, aBoxSize); + CSSPoint start = CSSPoint(aBoxSize.width, aBoxSize.height) - end; + return {start, end}; + } + + CSSCoord startX = h == X::Left ? 0.0 : aBoxSize.width; + CSSCoord startY = v == Y::Top ? 0.0 : aBoxSize.height; + + CSSPoint start(startX, startY); + CSSPoint end = CSSPoint(aBoxSize.width, aBoxSize.height) - start; + return {start, end}; + } + default: + break; + } + MOZ_ASSERT_UNREACHABLE("Unknown line direction"); + return {CSSPoint(), CSSPoint()}; +} + +using EndingShape = StyleGenericEndingShape<Length, LengthPercentage>; +using RadialGradientRadii = + Variant<StyleShapeExtent, std::pair<CSSCoord, CSSCoord>>; + +static RadialGradientRadii ComputeRadialGradientRadii(const EndingShape& aShape, + const CSSSize& aSize) { + if (aShape.IsCircle()) { + auto& circle = aShape.AsCircle(); + if (circle.IsExtent()) { + return RadialGradientRadii(circle.AsExtent()); + } + CSSCoord radius = circle.AsRadius().ToCSSPixels(); + return RadialGradientRadii(std::make_pair(radius, radius)); + } + auto& ellipse = aShape.AsEllipse(); + if (ellipse.IsExtent()) { + return RadialGradientRadii(ellipse.AsExtent()); + } + + auto& radii = ellipse.AsRadii(); + return RadialGradientRadii( + std::make_pair(radii._0.ResolveToCSSPixels(aSize.width), + radii._1.ResolveToCSSPixels(aSize.height))); +} + +// Compute the start and end points of the gradient line for a radial gradient. +// Also returns the horizontal and vertical radii defining the circle or +// ellipse to use. +static std::tuple<CSSPoint, CSSPoint, CSSCoord, CSSCoord> +ComputeRadialGradientLine(const StyleGradient& aGradient, + const CSSSize& aBoxSize) { + const auto& radial = aGradient.AsRadial(); + const EndingShape& endingShape = radial.shape; + const Position& position = radial.position; + CSSPoint start = ResolvePosition(position, aBoxSize); + + // Compute gradient shape: the x and y radii of an ellipse. + CSSCoord radiusX, radiusY; + CSSCoord leftDistance = Abs(start.x); + CSSCoord rightDistance = Abs(aBoxSize.width - start.x); + CSSCoord topDistance = Abs(start.y); + CSSCoord bottomDistance = Abs(aBoxSize.height - start.y); + + auto radii = ComputeRadialGradientRadii(endingShape, aBoxSize); + if (radii.is<StyleShapeExtent>()) { + switch (radii.as<StyleShapeExtent>()) { + case StyleShapeExtent::ClosestSide: + radiusX = std::min(leftDistance, rightDistance); + radiusY = std::min(topDistance, bottomDistance); + if (endingShape.IsCircle()) { + radiusX = radiusY = std::min(radiusX, radiusY); + } + break; + case StyleShapeExtent::ClosestCorner: { + // Compute x and y distances to nearest corner + CSSCoord offsetX = std::min(leftDistance, rightDistance); + CSSCoord offsetY = std::min(topDistance, bottomDistance); + if (endingShape.IsCircle()) { + radiusX = radiusY = NS_hypot(offsetX, offsetY); + } else { + // maintain aspect ratio + radiusX = offsetX * M_SQRT2; + radiusY = offsetY * M_SQRT2; + } + break; + } + case StyleShapeExtent::FarthestSide: + radiusX = std::max(leftDistance, rightDistance); + radiusY = std::max(topDistance, bottomDistance); + if (endingShape.IsCircle()) { + radiusX = radiusY = std::max(radiusX, radiusY); + } + break; + case StyleShapeExtent::FarthestCorner: { + // Compute x and y distances to nearest corner + CSSCoord offsetX = std::max(leftDistance, rightDistance); + CSSCoord offsetY = std::max(topDistance, bottomDistance); + if (endingShape.IsCircle()) { + radiusX = radiusY = NS_hypot(offsetX, offsetY); + } else { + // maintain aspect ratio + radiusX = offsetX * M_SQRT2; + radiusY = offsetY * M_SQRT2; + } + break; + } + default: + MOZ_ASSERT_UNREACHABLE("Unknown shape extent keyword?"); + radiusX = radiusY = 0; + } + } else { + auto pair = radii.as<std::pair<CSSCoord, CSSCoord>>(); + radiusX = pair.first; + radiusY = pair.second; + } + + // The gradient line end point is where the gradient line intersects + // the ellipse. + CSSPoint end = start + CSSPoint(radiusX, 0); + return {start, end, radiusX, radiusY}; +} + +// Compute the center and the start angle of the conic gradient. +static std::tuple<CSSPoint, float> ComputeConicGradientProperties( + const StyleGradient& aGradient, const CSSSize& aBoxSize) { + const auto& conic = aGradient.AsConic(); + const Position& position = conic.position; + float angle = static_cast<float>(conic.angle.ToRadians()); + CSSPoint center = ResolvePosition(position, aBoxSize); + + return {center, angle}; +} + +static float Interpolate(float aF1, float aF2, float aFrac) { + return aF1 + aFrac * (aF2 - aF1); +} + +// Returns aFrac*aC2 + (1 - aFrac)*C1. The interpolation is done +// in unpremultiplied space, which is what SVG gradients and cairo +// gradients expect. +static sRGBColor InterpolateColor(const sRGBColor& aC1, const sRGBColor& aC2, + float aFrac) { + double other = 1 - aFrac; + return sRGBColor(aC2.r * aFrac + aC1.r * other, aC2.g * aFrac + aC1.g * other, + aC2.b * aFrac + aC1.b * other, + aC2.a * aFrac + aC1.a * other); +} + +static nscoord FindTileStart(nscoord aDirtyCoord, nscoord aTilePos, + nscoord aTileDim) { + NS_ASSERTION(aTileDim > 0, "Non-positive tile dimension"); + double multiples = floor(double(aDirtyCoord - aTilePos) / aTileDim); + return NSToCoordRound(multiples * aTileDim + aTilePos); +} + +static gfxFloat LinearGradientStopPositionForPoint( + const gfxPoint& aGradientStart, const gfxPoint& aGradientEnd, + const gfxPoint& aPoint) { + gfxPoint d = aGradientEnd - aGradientStart; + gfxPoint p = aPoint - aGradientStart; + /** + * Compute a parameter t such that a line perpendicular to the + * d vector, passing through aGradientStart + d*t, also + * passes through aPoint. + * + * t is given by + * (p.x - d.x*t)*d.x + (p.y - d.y*t)*d.y = 0 + * + * Solving for t we get + * numerator = d.x*p.x + d.y*p.y + * denominator = d.x^2 + d.y^2 + * t = numerator/denominator + * + * In nsCSSRendering::PaintGradient we know the length of d + * is not zero. + */ + double numerator = d.x * p.x + d.y * p.y; + double denominator = d.x * d.x + d.y * d.y; + return numerator / denominator; +} + +static bool RectIsBeyondLinearGradientEdge(const gfxRect& aRect, + const gfxMatrix& aPatternMatrix, + const nsTArray<ColorStop>& aStops, + const gfxPoint& aGradientStart, + const gfxPoint& aGradientEnd, + sRGBColor* aOutEdgeColor) { + gfxFloat topLeft = LinearGradientStopPositionForPoint( + aGradientStart, aGradientEnd, + aPatternMatrix.TransformPoint(aRect.TopLeft())); + gfxFloat topRight = LinearGradientStopPositionForPoint( + aGradientStart, aGradientEnd, + aPatternMatrix.TransformPoint(aRect.TopRight())); + gfxFloat bottomLeft = LinearGradientStopPositionForPoint( + aGradientStart, aGradientEnd, + aPatternMatrix.TransformPoint(aRect.BottomLeft())); + gfxFloat bottomRight = LinearGradientStopPositionForPoint( + aGradientStart, aGradientEnd, + aPatternMatrix.TransformPoint(aRect.BottomRight())); + + const ColorStop& firstStop = aStops[0]; + if (topLeft < firstStop.mPosition && topRight < firstStop.mPosition && + bottomLeft < firstStop.mPosition && bottomRight < firstStop.mPosition) { + *aOutEdgeColor = firstStop.mColor; + return true; + } + + const ColorStop& lastStop = aStops.LastElement(); + if (topLeft >= lastStop.mPosition && topRight >= lastStop.mPosition && + bottomLeft >= lastStop.mPosition && bottomRight >= lastStop.mPosition) { + *aOutEdgeColor = lastStop.mColor; + return true; + } + + return false; +} + +static void ResolveMidpoints(nsTArray<ColorStop>& stops) { + for (size_t x = 1; x < stops.Length() - 1;) { + if (!stops[x].mIsMidpoint) { + x++; + continue; + } + + sRGBColor color1 = stops[x - 1].mColor; + sRGBColor color2 = stops[x + 1].mColor; + float offset1 = stops[x - 1].mPosition; + float offset2 = stops[x + 1].mPosition; + float offset = stops[x].mPosition; + // check if everything coincides. If so, ignore the midpoint. + if (offset - offset1 == offset2 - offset) { + stops.RemoveElementAt(x); + continue; + } + + // Check if we coincide with the left colorstop. + if (offset1 == offset) { + // Morph the midpoint to a regular stop with the color of the next + // color stop. + stops[x].mColor = color2; + stops[x].mIsMidpoint = false; + continue; + } + + // Check if we coincide with the right colorstop. + if (offset2 == offset) { + // Morph the midpoint to a regular stop with the color of the previous + // color stop. + stops[x].mColor = color1; + stops[x].mIsMidpoint = false; + continue; + } + + float midpoint = (offset - offset1) / (offset2 - offset1); + ColorStop newStops[9]; + if (midpoint > .5f) { + for (size_t y = 0; y < 7; y++) { + newStops[y].mPosition = offset1 + (offset - offset1) * (7 + y) / 13; + } + + newStops[7].mPosition = offset + (offset2 - offset) / 3; + newStops[8].mPosition = offset + (offset2 - offset) * 2 / 3; + } else { + newStops[0].mPosition = offset1 + (offset - offset1) / 3; + newStops[1].mPosition = offset1 + (offset - offset1) * 2 / 3; + + for (size_t y = 0; y < 7; y++) { + newStops[y + 2].mPosition = offset + (offset2 - offset) * y / 13; + } + } + // calculate colors + + for (auto& newStop : newStops) { + // Calculate the intermediate color stops per the formula of the CSS + // images spec. http://dev.w3.org/csswg/css-images/#color-stop-syntax 9 + // points were chosen since it is the minimum number of stops that always + // give the smoothest appearace regardless of midpoint position and + // difference in luminance of the end points. + float relativeOffset = + (newStop.mPosition - offset1) / (offset2 - offset1); + float multiplier = powf(relativeOffset, logf(.5f) / logf(midpoint)); + + gfx::Float red = color1.r + multiplier * (color2.r - color1.r); + gfx::Float green = color1.g + multiplier * (color2.g - color1.g); + gfx::Float blue = color1.b + multiplier * (color2.b - color1.b); + gfx::Float alpha = color1.a + multiplier * (color2.a - color1.a); + + newStop.mColor = sRGBColor(red, green, blue, alpha); + } + + stops.ReplaceElementsAt(x, 1, newStops, 9); + x += 9; + } +} + +static sRGBColor Premultiply(const sRGBColor& aColor) { + gfx::Float a = aColor.a; + return sRGBColor(aColor.r * a, aColor.g * a, aColor.b * a, a); +} + +static sRGBColor Unpremultiply(const sRGBColor& aColor) { + gfx::Float a = aColor.a; + return (a > 0.f) ? sRGBColor(aColor.r / a, aColor.g / a, aColor.b / a, a) + : aColor; +} + +static sRGBColor TransparentColor(sRGBColor aColor) { + aColor.a = 0; + return aColor; +} + +// Adjusts and adds color stops in such a way that drawing the gradient with +// unpremultiplied interpolation looks nearly the same as if it were drawn with +// premultiplied interpolation. +static const float kAlphaIncrementPerGradientStep = 0.1f; +static void ResolvePremultipliedAlpha(nsTArray<ColorStop>& aStops) { + for (size_t x = 1; x < aStops.Length(); x++) { + const ColorStop leftStop = aStops[x - 1]; + const ColorStop rightStop = aStops[x]; + + // if the left and right stop have the same alpha value, we don't need + // to do anything. Hardstops should be instant, and also should never + // require dealing with interpolation. + if (leftStop.mColor.a == rightStop.mColor.a || + leftStop.mPosition == rightStop.mPosition) { + continue; + } + + // Is the stop on the left 100% transparent? If so, have it adopt the color + // of the right stop + if (leftStop.mColor.a == 0) { + aStops[x - 1].mColor = TransparentColor(rightStop.mColor); + continue; + } + + // Is the stop on the right completely transparent? + // If so, duplicate it and assign it the color on the left. + if (rightStop.mColor.a == 0) { + ColorStop newStop = rightStop; + newStop.mColor = TransparentColor(leftStop.mColor); + aStops.InsertElementAt(x, newStop); + x++; + continue; + } + + // Now handle cases where one or both of the stops are partially + // transparent. + if (leftStop.mColor.a != 1.0f || rightStop.mColor.a != 1.0f) { + sRGBColor premulLeftColor = Premultiply(leftStop.mColor); + sRGBColor premulRightColor = Premultiply(rightStop.mColor); + // Calculate how many extra steps. We do a step per 10% transparency. + size_t stepCount = + NSToIntFloor(fabsf(leftStop.mColor.a - rightStop.mColor.a) / + kAlphaIncrementPerGradientStep); + for (size_t y = 1; y < stepCount; y++) { + float frac = static_cast<float>(y) / stepCount; + ColorStop newStop( + Interpolate(leftStop.mPosition, rightStop.mPosition, frac), false, + Unpremultiply( + InterpolateColor(premulLeftColor, premulRightColor, frac))); + aStops.InsertElementAt(x, newStop); + x++; + } + } + } +} + +static ColorStop InterpolateColorStop(const ColorStop& aFirst, + const ColorStop& aSecond, + double aPosition, + const sRGBColor& aDefault) { + MOZ_ASSERT(aFirst.mPosition <= aPosition); + MOZ_ASSERT(aPosition <= aSecond.mPosition); + + double delta = aSecond.mPosition - aFirst.mPosition; + if (delta < 1e-6) { + return ColorStop(aPosition, false, aDefault); + } + + return ColorStop(aPosition, false, + Unpremultiply(InterpolateColor( + Premultiply(aFirst.mColor), Premultiply(aSecond.mColor), + (aPosition - aFirst.mPosition) / delta))); +} + +// Clamp and extend the given ColorStop array in-place to fit exactly into the +// range [0, 1]. +static void ClampColorStops(nsTArray<ColorStop>& aStops) { + MOZ_ASSERT(aStops.Length() > 0); + + // If all stops are outside the range, then get rid of everything and replace + // with a single colour. + if (aStops.Length() < 2 || aStops[0].mPosition > 1 || + aStops.LastElement().mPosition < 0) { + sRGBColor c = aStops[0].mPosition > 1 ? aStops[0].mColor + : aStops.LastElement().mColor; + aStops.Clear(); + aStops.AppendElement(ColorStop(0, false, c)); + return; + } + + // Create the 0 and 1 points if they fall in the range of |aStops|, and + // discard all stops outside the range [0, 1]. + // XXX: If we have stops positioned at 0 or 1, we only keep the innermost of + // those stops. This should be fine for the current user(s) of this function. + for (size_t i = aStops.Length() - 1; i > 0; i--) { + if (aStops[i - 1].mPosition < 1 && aStops[i].mPosition >= 1) { + // Add a point to position 1. + aStops[i] = + InterpolateColorStop(aStops[i - 1], aStops[i], + /* aPosition = */ 1, aStops[i - 1].mColor); + // Remove all the elements whose position is greater than 1. + aStops.RemoveLastElements(aStops.Length() - (i + 1)); + } + if (aStops[i - 1].mPosition <= 0 && aStops[i].mPosition > 0) { + // Add a point to position 0. + aStops[i - 1] = + InterpolateColorStop(aStops[i - 1], aStops[i], + /* aPosition = */ 0, aStops[i].mColor); + // Remove all of the preceding stops -- they are all negative. + aStops.RemoveElementsAt(0, i - 1); + break; + } + } + + MOZ_ASSERT(aStops[0].mPosition >= -1e6); + MOZ_ASSERT(aStops.LastElement().mPosition - 1 <= 1e6); + + // The end points won't exist yet if they don't fall in the original range of + // |aStops|. Create them if needed. + if (aStops[0].mPosition > 0) { + aStops.InsertElementAt(0, ColorStop(0, false, aStops[0].mColor)); + } + if (aStops.LastElement().mPosition < 1) { + aStops.AppendElement(ColorStop(1, false, aStops.LastElement().mColor)); + } +} + +namespace mozilla { + +template <typename T> +static sRGBColor GetSpecifiedColor( + const StyleGenericGradientItem<StyleColor, T>& aItem, + const ComputedStyle& aStyle) { + if (aItem.IsInterpolationHint()) { + return sRGBColor(); + } + const StyleColor& color = aItem.IsSimpleColorStop() + ? aItem.AsSimpleColorStop() + : aItem.AsComplexColorStop().color; + return sRGBColor::FromABGR(color.CalcColor(aStyle)); +} + +static Maybe<double> GetSpecifiedGradientPosition( + const StyleGenericGradientItem<StyleColor, StyleLengthPercentage>& aItem, + CSSCoord aLineLength) { + if (aItem.IsSimpleColorStop()) { + return Nothing(); + } + + const LengthPercentage& pos = aItem.IsComplexColorStop() + ? aItem.AsComplexColorStop().position + : aItem.AsInterpolationHint(); + + if (pos.ConvertsToPercentage()) { + return Some(pos.ToPercentage()); + } + + if (aLineLength < 1e-6) { + return Some(0.0); + } + return Some(pos.ResolveToCSSPixels(aLineLength) / aLineLength); +} + +// aLineLength argument is unused for conic-gradients. +static Maybe<double> GetSpecifiedGradientPosition( + const StyleGenericGradientItem<StyleColor, StyleAngleOrPercentage>& aItem, + CSSCoord aLineLength) { + if (aItem.IsSimpleColorStop()) { + return Nothing(); + } + + const StyleAngleOrPercentage& pos = aItem.IsComplexColorStop() + ? aItem.AsComplexColorStop().position + : aItem.AsInterpolationHint(); + + if (pos.IsPercentage()) { + return Some(pos.AsPercentage()._0); + } + + return Some(pos.AsAngle().ToRadians() / (2 * M_PI)); +} + +template <typename T> +static nsTArray<ColorStop> ComputeColorStopsForItems( + ComputedStyle* aComputedStyle, + Span<const StyleGenericGradientItem<StyleColor, T>> aItems, + CSSCoord aLineLength) { + MOZ_ASSERT(aItems.Length() >= 2, + "The parser should reject gradients with less than two stops"); + + nsTArray<ColorStop> stops(aItems.Length()); + + // If there is a run of stops before stop i that did not have specified + // positions, then this is the index of the first stop in that run. + Maybe<size_t> firstUnsetPosition; + for (size_t i = 0; i < aItems.Length(); ++i) { + const auto& stop = aItems[i]; + double position; + + Maybe<double> specifiedPosition = + GetSpecifiedGradientPosition(stop, aLineLength); + + if (specifiedPosition) { + position = *specifiedPosition; + } else if (i == 0) { + // First stop defaults to position 0.0 + position = 0.0; + } else if (i == aItems.Length() - 1) { + // Last stop defaults to position 1.0 + position = 1.0; + } else { + // Other stops with no specified position get their position assigned + // later by interpolation, see below. + // Remember where the run of stops with no specified position starts, + // if it starts here. + if (firstUnsetPosition.isNothing()) { + firstUnsetPosition.emplace(i); + } + MOZ_ASSERT(!stop.IsInterpolationHint(), + "Interpolation hints always specify position"); + auto color = GetSpecifiedColor(stop, *aComputedStyle); + stops.AppendElement(ColorStop(0, false, color)); + continue; + } + + if (i > 0) { + // Prevent decreasing stop positions by advancing this position + // to the previous stop position, if necessary + double previousPosition = firstUnsetPosition + ? stops[*firstUnsetPosition - 1].mPosition + : stops[i - 1].mPosition; + position = std::max(position, previousPosition); + } + auto stopColor = GetSpecifiedColor(stop, *aComputedStyle); + stops.AppendElement( + ColorStop(position, stop.IsInterpolationHint(), stopColor)); + if (firstUnsetPosition) { + // Interpolate positions for all stops that didn't have a specified + // position + double p = stops[*firstUnsetPosition - 1].mPosition; + double d = (stops[i].mPosition - p) / (i - *firstUnsetPosition + 1); + for (size_t j = *firstUnsetPosition; j < i; ++j) { + p += d; + stops[j].mPosition = p; + } + firstUnsetPosition.reset(); + } + } + + return stops; +} + +static nsTArray<ColorStop> ComputeColorStops(ComputedStyle* aComputedStyle, + const StyleGradient& aGradient, + CSSCoord aLineLength) { + if (aGradient.IsLinear()) { + return ComputeColorStopsForItems( + aComputedStyle, aGradient.AsLinear().items.AsSpan(), aLineLength); + } + if (aGradient.IsRadial()) { + return ComputeColorStopsForItems( + aComputedStyle, aGradient.AsRadial().items.AsSpan(), aLineLength); + } + return ComputeColorStopsForItems( + aComputedStyle, aGradient.AsConic().items.AsSpan(), aLineLength); +} + +nsCSSGradientRenderer nsCSSGradientRenderer::Create( + nsPresContext* aPresContext, ComputedStyle* aComputedStyle, + const StyleGradient& aGradient, const nsSize& aIntrinsicSize) { + auto srcSize = CSSSize::FromAppUnits(aIntrinsicSize); + + // Compute "gradient line" start and end relative to the intrinsic size of + // the gradient. + CSSPoint lineStart, lineEnd, center; // center is for conic gradients only + CSSCoord radiusX = 0, radiusY = 0; // for radial gradients only + float angle = 0.0; // for conic gradients only + if (aGradient.IsLinear()) { + std::tie(lineStart, lineEnd) = + ComputeLinearGradientLine(aPresContext, aGradient, srcSize); + } else if (aGradient.IsRadial()) { + std::tie(lineStart, lineEnd, radiusX, radiusY) = + ComputeRadialGradientLine(aGradient, srcSize); + } else { + MOZ_ASSERT(aGradient.IsConic()); + std::tie(center, angle) = + ComputeConicGradientProperties(aGradient, srcSize); + } + // Avoid sending Infs or Nans to downwind draw targets. + if (!lineStart.IsFinite() || !lineEnd.IsFinite()) { + lineStart = lineEnd = CSSPoint(0, 0); + } + if (!center.IsFinite()) { + center = CSSPoint(0, 0); + } + CSSCoord lineLength = + NS_hypot(lineEnd.x - lineStart.x, lineEnd.y - lineStart.y); + + // Build color stop array and compute stop positions + nsTArray<ColorStop> stops = + ComputeColorStops(aComputedStyle, aGradient, lineLength); + + ResolveMidpoints(stops); + + nsCSSGradientRenderer renderer; + renderer.mPresContext = aPresContext; + renderer.mGradient = &aGradient; + renderer.mStops = std::move(stops); + renderer.mLineStart = { + aPresContext->CSSPixelsToDevPixels(lineStart.x), + aPresContext->CSSPixelsToDevPixels(lineStart.y), + }; + renderer.mLineEnd = { + aPresContext->CSSPixelsToDevPixels(lineEnd.x), + aPresContext->CSSPixelsToDevPixels(lineEnd.y), + }; + renderer.mRadiusX = aPresContext->CSSPixelsToDevPixels(radiusX); + renderer.mRadiusY = aPresContext->CSSPixelsToDevPixels(radiusY); + renderer.mCenter = { + aPresContext->CSSPixelsToDevPixels(center.x), + aPresContext->CSSPixelsToDevPixels(center.y), + }; + renderer.mAngle = angle; + return renderer; +} + +void nsCSSGradientRenderer::Paint(gfxContext& aContext, const nsRect& aDest, + const nsRect& aFillArea, + const nsSize& aRepeatSize, + const CSSIntRect& aSrc, + const nsRect& aDirtyRect, float aOpacity) { + AUTO_PROFILER_LABEL("nsCSSGradientRenderer::Paint", GRAPHICS); + + if (aDest.IsEmpty() || aFillArea.IsEmpty()) { + return; + } + + nscoord appUnitsPerDevPixel = mPresContext->AppUnitsPerDevPixel(); + + gfxFloat lineLength = + NS_hypot(mLineEnd.x - mLineStart.x, mLineEnd.y - mLineStart.y); + bool cellContainsFill = aDest.Contains(aFillArea); + + // If a non-repeating linear gradient is axis-aligned and there are no gaps + // between tiles, we can optimise away most of the work by converting to a + // repeating linear gradient and filling the whole destination rect at once. + bool forceRepeatToCoverTiles = + mGradient->IsLinear() && + (mLineStart.x == mLineEnd.x) != (mLineStart.y == mLineEnd.y) && + aRepeatSize.width == aDest.width && aRepeatSize.height == aDest.height && + !mGradient->AsLinear().repeating && !aSrc.IsEmpty() && !cellContainsFill; + + gfxMatrix matrix; + if (forceRepeatToCoverTiles) { + // Length of the source rectangle along the gradient axis. + double rectLen; + // The position of the start of the rectangle along the gradient. + double offset; + + // The gradient line is "backwards". Flip the line upside down to make + // things easier, and then rotate the matrix to turn everything back the + // right way up. + if (mLineStart.x > mLineEnd.x || mLineStart.y > mLineEnd.y) { + std::swap(mLineStart, mLineEnd); + matrix.PreScale(-1, -1); + } + + // Fit the gradient line exactly into the source rect. + // aSrc is relative to aIntrinsincSize. + // srcRectDev will be relative to srcSize, so in the same coordinate space + // as lineStart / lineEnd. + gfxRect srcRectDev = nsLayoutUtils::RectToGfxRect( + CSSPixel::ToAppUnits(aSrc), appUnitsPerDevPixel); + if (mLineStart.x != mLineEnd.x) { + rectLen = srcRectDev.width; + offset = (srcRectDev.x - mLineStart.x) / lineLength; + mLineStart.x = srcRectDev.x; + mLineEnd.x = srcRectDev.XMost(); + } else { + rectLen = srcRectDev.height; + offset = (srcRectDev.y - mLineStart.y) / lineLength; + mLineStart.y = srcRectDev.y; + mLineEnd.y = srcRectDev.YMost(); + } + + // Adjust gradient stop positions for the new gradient line. + double scale = lineLength / rectLen; + for (size_t i = 0; i < mStops.Length(); i++) { + mStops[i].mPosition = (mStops[i].mPosition - offset) * fabs(scale); + } + + // Clamp or extrapolate gradient stops to exactly [0, 1]. + ClampColorStops(mStops); + + lineLength = rectLen; + } + + // Eliminate negative-position stops if the gradient is radial. + double firstStop = mStops[0].mPosition; + if (mGradient->IsRadial() && firstStop < 0.0) { + if (mGradient->AsRadial().repeating) { + // Choose an instance of the repeated pattern that gives us all positive + // stop-offsets. + double lastStop = mStops[mStops.Length() - 1].mPosition; + double stopDelta = lastStop - firstStop; + // If all the stops are in approximately the same place then logic below + // will kick in that makes us draw just the last stop color, so don't + // try to do anything in that case. We certainly need to avoid + // dividing by zero. + if (stopDelta >= 1e-6) { + double instanceCount = ceil(-firstStop / stopDelta); + // Advance stops by instanceCount multiples of the period of the + // repeating gradient. + double offset = instanceCount * stopDelta; + for (uint32_t i = 0; i < mStops.Length(); i++) { + mStops[i].mPosition += offset; + } + } + } else { + // Move negative-position stops to position 0.0. We may also need + // to set the color of the stop to the color the gradient should have + // at the center of the ellipse. + for (uint32_t i = 0; i < mStops.Length(); i++) { + double pos = mStops[i].mPosition; + if (pos < 0.0) { + mStops[i].mPosition = 0.0; + // If this is the last stop, we don't need to adjust the color, + // it will fill the entire area. + if (i < mStops.Length() - 1) { + double nextPos = mStops[i + 1].mPosition; + // If nextPos is approximately equal to pos, then we don't + // need to adjust the color of this stop because it's + // not going to be displayed. + // If nextPos is negative, we don't need to adjust the color of + // this stop since it's not going to be displayed because + // nextPos will also be moved to 0.0. + if (nextPos >= 0.0 && nextPos - pos >= 1e-6) { + // Compute how far the new position 0.0 is along the interval + // between pos and nextPos. + // XXX Color interpolation (in cairo, too) should use the + // CSS 'color-interpolation' property! + float frac = float((0.0 - pos) / (nextPos - pos)); + mStops[i].mColor = InterpolateColor(mStops[i].mColor, + mStops[i + 1].mColor, frac); + } + } + } + } + } + firstStop = mStops[0].mPosition; + MOZ_ASSERT(firstStop >= 0.0, "Failed to fix stop offsets"); + } + + if (mGradient->IsRadial() && !mGradient->AsRadial().repeating) { + // Direct2D can only handle a particular class of radial gradients because + // of the way the it specifies gradients. Setting firstStop to 0, when we + // can, will help us stay on the fast path. Currently we don't do this + // for repeating gradients but we could by adjusting the stop collection + // to start at 0 + firstStop = 0; + } + + double lastStop = mStops[mStops.Length() - 1].mPosition; + // Cairo gradients must have stop positions in the range [0, 1]. So, + // stop positions will be normalized below by subtracting firstStop and then + // multiplying by stopScale. + double stopScale; + double stopOrigin = firstStop; + double stopEnd = lastStop; + double stopDelta = lastStop - firstStop; + bool zeroRadius = + mGradient->IsRadial() && (mRadiusX < 1e-6 || mRadiusY < 1e-6); + if (stopDelta < 1e-6 || (!mGradient->IsConic() && lineLength < 1e-6) || + zeroRadius) { + // Stops are all at the same place. Map all stops to 0.0. + // For repeating radial gradients, or for any radial gradients with + // a zero radius, we need to fill with the last stop color, so just set + // both radii to 0. + if (mGradient->Repeating() || zeroRadius) { + mRadiusX = mRadiusY = 0.0; + } + stopDelta = 0.0; + } + + // Don't normalize non-repeating or degenerate gradients below 0..1 + // This keeps the gradient line as large as the box and doesn't + // lets us avoiding having to get padding correct for stops + // at 0 and 1 + if (!mGradient->Repeating() || stopDelta == 0.0) { + stopOrigin = std::min(stopOrigin, 0.0); + stopEnd = std::max(stopEnd, 1.0); + } + stopScale = 1.0 / (stopEnd - stopOrigin); + + // Create the gradient pattern. + RefPtr<gfxPattern> gradientPattern; + gfxPoint gradientStart; + gfxPoint gradientEnd; + if (mGradient->IsLinear()) { + // Compute the actual gradient line ends we need to pass to cairo after + // stops have been normalized. + gradientStart = mLineStart + (mLineEnd - mLineStart) * stopOrigin; + gradientEnd = mLineStart + (mLineEnd - mLineStart) * stopEnd; + + if (stopDelta == 0.0) { + // Stops are all at the same place. For repeating gradients, this will + // just paint the last stop color. We don't need to do anything. + // For non-repeating gradients, this should render as two colors, one + // on each "side" of the gradient line segment, which is a point. All + // our stops will be at 0.0; we just need to set the direction vector + // correctly. + gradientEnd = gradientStart + (mLineEnd - mLineStart); + } + + gradientPattern = new gfxPattern(gradientStart.x, gradientStart.y, + gradientEnd.x, gradientEnd.y); + } else if (mGradient->IsRadial()) { + NS_ASSERTION(firstStop >= 0.0, + "Negative stops not allowed for radial gradients"); + + // To form an ellipse, we'll stretch a circle vertically, if necessary. + // So our radii are based on radiusX. + double innerRadius = mRadiusX * stopOrigin; + double outerRadius = mRadiusX * stopEnd; + if (stopDelta == 0.0) { + // Stops are all at the same place. See above (except we now have + // the inside vs. outside of an ellipse). + outerRadius = innerRadius + 1; + } + gradientPattern = new gfxPattern(mLineStart.x, mLineStart.y, innerRadius, + mLineStart.x, mLineStart.y, outerRadius); + if (mRadiusX != mRadiusY) { + // Stretch the circles into ellipses vertically by setting a transform + // in the pattern. + // Recall that this is the transform from user space to pattern space. + // So to stretch the ellipse by factor of P vertically, we scale + // user coordinates by 1/P. + matrix.PreTranslate(mLineStart); + matrix.PreScale(1.0, mRadiusX / mRadiusY); + matrix.PreTranslate(-mLineStart); + } + } else { + gradientPattern = + new gfxPattern(mCenter.x, mCenter.y, mAngle, stopOrigin, stopEnd); + } + // Use a pattern transform to take account of source and dest rects + matrix.PreTranslate(gfxPoint(mPresContext->CSSPixelsToDevPixels(aSrc.x), + mPresContext->CSSPixelsToDevPixels(aSrc.y))); + matrix.PreScale( + gfxFloat(nsPresContext::CSSPixelsToAppUnits(aSrc.width)) / aDest.width, + gfxFloat(nsPresContext::CSSPixelsToAppUnits(aSrc.height)) / aDest.height); + gradientPattern->SetMatrix(matrix); + + if (stopDelta == 0.0) { + // Non-repeating gradient with all stops in same place -> just add + // first stop and last stop, both at position 0. + // Repeating gradient with all stops in the same place, or radial + // gradient with radius of 0 -> just paint the last stop color. + // We use firstStop offset to keep |stops| with same units (will later + // normalize to 0). + sRGBColor firstColor(mStops[0].mColor); + sRGBColor lastColor(mStops.LastElement().mColor); + mStops.Clear(); + + if (!mGradient->Repeating() && !zeroRadius) { + mStops.AppendElement(ColorStop(firstStop, false, firstColor)); + } + mStops.AppendElement(ColorStop(firstStop, false, lastColor)); + } + + ResolvePremultipliedAlpha(mStops); + + bool isRepeat = mGradient->Repeating() || forceRepeatToCoverTiles; + + // Now set normalized color stops in pattern. + // Offscreen gradient surface cache (not a tile): + // On some backends (e.g. D2D), the GradientStops object holds an offscreen + // surface which is a lookup table used to evaluate the gradient. This surface + // can use much memory (ram and/or GPU ram) and can be expensive to create. So + // we cache it. The cache key correlates 1:1 with the arguments for + // CreateGradientStops (also the implied backend type) Note that GradientStop + // is a simple struct with a stop value (while GradientStops has the surface). + nsTArray<gfx::GradientStop> rawStops(mStops.Length()); + rawStops.SetLength(mStops.Length()); + for (uint32_t i = 0; i < mStops.Length(); i++) { + rawStops[i].color = ToDeviceColor(mStops[i].mColor); + rawStops[i].color.a *= aOpacity; + rawStops[i].offset = stopScale * (mStops[i].mPosition - stopOrigin); + } + RefPtr<mozilla::gfx::GradientStops> gs = + gfxGradientCache::GetOrCreateGradientStops( + aContext.GetDrawTarget(), rawStops, + isRepeat ? gfx::ExtendMode::REPEAT : gfx::ExtendMode::CLAMP); + gradientPattern->SetColorStops(gs); + + // Paint gradient tiles. This isn't terribly efficient, but doing it this + // way is simple and sure to get pixel-snapping right. We could speed things + // up by drawing tiles into temporary surfaces and copying those to the + // destination, but after pixel-snapping tiles may not all be the same size. + nsRect dirty; + if (!dirty.IntersectRect(aDirtyRect, aFillArea)) return; + + gfxRect areaToFill = + nsLayoutUtils::RectToGfxRect(aFillArea, appUnitsPerDevPixel); + gfxRect dirtyAreaToFill = + nsLayoutUtils::RectToGfxRect(dirty, appUnitsPerDevPixel); + dirtyAreaToFill.RoundOut(); + + Matrix ctm = aContext.CurrentMatrix(); + bool isCTMPreservingAxisAlignedRectangles = + ctm.PreservesAxisAlignedRectangles(); + + // xStart/yStart are the top-left corner of the top-left tile. + nscoord xStart = FindTileStart(dirty.x, aDest.x, aRepeatSize.width); + nscoord yStart = FindTileStart(dirty.y, aDest.y, aRepeatSize.height); + nscoord xEnd = forceRepeatToCoverTiles ? xStart + aDest.width : dirty.XMost(); + nscoord yEnd = + forceRepeatToCoverTiles ? yStart + aDest.height : dirty.YMost(); + + if (TryPaintTilesWithExtendMode(aContext, gradientPattern, xStart, yStart, + dirtyAreaToFill, aDest, aRepeatSize, + forceRepeatToCoverTiles)) { + return; + } + + // x and y are the top-left corner of the tile to draw + for (nscoord y = yStart; y < yEnd; y += aRepeatSize.height) { + for (nscoord x = xStart; x < xEnd; x += aRepeatSize.width) { + // The coordinates of the tile + gfxRect tileRect = nsLayoutUtils::RectToGfxRect( + nsRect(x, y, aDest.width, aDest.height), appUnitsPerDevPixel); + // The actual area to fill with this tile is the intersection of this + // tile with the overall area we're supposed to be filling + gfxRect fillRect = + forceRepeatToCoverTiles ? areaToFill : tileRect.Intersect(areaToFill); + // Try snapping the fill rect. Snap its top-left and bottom-right + // independently to preserve the orientation. + gfxPoint snappedFillRectTopLeft = fillRect.TopLeft(); + gfxPoint snappedFillRectTopRight = fillRect.TopRight(); + gfxPoint snappedFillRectBottomRight = fillRect.BottomRight(); + // Snap three points instead of just two to ensure we choose the + // correct orientation if there's a reflection. + if (isCTMPreservingAxisAlignedRectangles && + aContext.UserToDevicePixelSnapped(snappedFillRectTopLeft, true) && + aContext.UserToDevicePixelSnapped(snappedFillRectBottomRight, true) && + aContext.UserToDevicePixelSnapped(snappedFillRectTopRight, true)) { + if (snappedFillRectTopLeft.x == snappedFillRectBottomRight.x || + snappedFillRectTopLeft.y == snappedFillRectBottomRight.y) { + // Nothing to draw; avoid scaling by zero and other weirdness that + // could put the context in an error state. + continue; + } + // Set the context's transform to the transform that maps fillRect to + // snappedFillRect. The part of the gradient that was going to + // exactly fill fillRect will fill snappedFillRect instead. + gfxMatrix transform = gfxUtils::TransformRectToRect( + fillRect, snappedFillRectTopLeft, snappedFillRectTopRight, + snappedFillRectBottomRight); + aContext.SetMatrixDouble(transform); + } + aContext.NewPath(); + aContext.Rectangle(fillRect); + + gfxRect dirtyFillRect = fillRect.Intersect(dirtyAreaToFill); + gfxRect fillRectRelativeToTile = dirtyFillRect - tileRect.TopLeft(); + sRGBColor edgeColor; + if (mGradient->IsLinear() && !isRepeat && + RectIsBeyondLinearGradientEdge(fillRectRelativeToTile, matrix, mStops, + gradientStart, gradientEnd, + &edgeColor)) { + edgeColor.a *= aOpacity; + aContext.SetColor(edgeColor); + } else { + aContext.SetMatrixDouble( + aContext.CurrentMatrixDouble().Copy().PreTranslate( + tileRect.TopLeft())); + aContext.SetPattern(gradientPattern); + } + aContext.Fill(); + aContext.SetMatrix(ctm); + } + } +} + +bool nsCSSGradientRenderer::TryPaintTilesWithExtendMode( + gfxContext& aContext, gfxPattern* aGradientPattern, nscoord aXStart, + nscoord aYStart, const gfxRect& aDirtyAreaToFill, const nsRect& aDest, + const nsSize& aRepeatSize, bool aForceRepeatToCoverTiles) { + // If we have forced a non-repeating gradient to repeat to cover tiles, + // then it will be faster to just paint it once using that optimization + if (aForceRepeatToCoverTiles) { + return false; + } + + nscoord appUnitsPerDevPixel = mPresContext->AppUnitsPerDevPixel(); + + // We can only use this fast path if we don't have to worry about pixel + // snapping, and there is no spacing between tiles. We could handle spacing + // by increasing the size of tileSurface and leaving it transparent, but I'm + // not sure it's worth it + bool canUseExtendModeForTiling = (aXStart % appUnitsPerDevPixel == 0) && + (aYStart % appUnitsPerDevPixel == 0) && + (aDest.width % appUnitsPerDevPixel == 0) && + (aDest.height % appUnitsPerDevPixel == 0) && + (aRepeatSize.width == aDest.width) && + (aRepeatSize.height == aDest.height); + + if (!canUseExtendModeForTiling) { + return false; + } + + IntSize tileSize{ + NSAppUnitsToIntPixels(aDest.width, appUnitsPerDevPixel), + NSAppUnitsToIntPixels(aDest.height, appUnitsPerDevPixel), + }; + + // Check whether this is a reasonable surface size and doesn't overflow + // before doing calculations with the tile size + if (!Factory::ReasonableSurfaceSize(tileSize)) { + return false; + } + + // We only want to do this when there are enough tiles to justify the + // overhead of painting to an offscreen surface. The heuristic here + // is when we will be painting at least 16 tiles or more, this is kind + // of arbitrary + bool shouldUseExtendModeForTiling = + aDirtyAreaToFill.Area() > (tileSize.width * tileSize.height) * 16.0; + + if (!shouldUseExtendModeForTiling) { + return false; + } + + // Draw the gradient pattern into a surface for our single tile + RefPtr<gfx::SourceSurface> tileSurface; + { + RefPtr<gfx::DrawTarget> tileTarget = + aContext.GetDrawTarget()->CreateSimilarDrawTarget( + tileSize, gfx::SurfaceFormat::B8G8R8A8); + if (!tileTarget || !tileTarget->IsValid()) { + return false; + } + + RefPtr<gfxContext> tileContext = gfxContext::CreateOrNull(tileTarget); + + tileContext->SetPattern(aGradientPattern); + tileContext->Paint(); + + tileContext = nullptr; + tileSurface = tileTarget->Snapshot(); + tileTarget = nullptr; + } + + // Draw the gradient using tileSurface as a repeating pattern masked by + // the dirtyRect + Matrix tileTransform = Matrix::Translation( + NSAppUnitsToFloatPixels(aXStart, appUnitsPerDevPixel), + NSAppUnitsToFloatPixels(aYStart, appUnitsPerDevPixel)); + + aContext.NewPath(); + aContext.Rectangle(aDirtyAreaToFill); + aContext.Fill(SurfacePattern(tileSurface, ExtendMode::REPEAT, tileTransform)); + + return true; +} + +void nsCSSGradientRenderer::BuildWebRenderParameters( + float aOpacity, wr::ExtendMode& aMode, nsTArray<wr::GradientStop>& aStops, + LayoutDevicePoint& aLineStart, LayoutDevicePoint& aLineEnd, + LayoutDeviceSize& aGradientRadius, LayoutDevicePoint& aGradientCenter, + float& aGradientAngle) { + aMode = + mGradient->Repeating() ? wr::ExtendMode::Repeat : wr::ExtendMode::Clamp; + + aStops.SetLength(mStops.Length()); + for (uint32_t i = 0; i < mStops.Length(); i++) { + aStops[i].color = wr::ToColorF(ToDeviceColor(mStops[i].mColor)); + aStops[i].color.a *= aOpacity; + aStops[i].offset = mStops[i].mPosition; + } + + aLineStart = LayoutDevicePoint(mLineStart.x, mLineStart.y); + aLineEnd = LayoutDevicePoint(mLineEnd.x, mLineEnd.y); + aGradientRadius = LayoutDeviceSize(mRadiusX, mRadiusY); + aGradientCenter = LayoutDevicePoint(mCenter.x, mCenter.y); + aGradientAngle = mAngle; +} + +void nsCSSGradientRenderer::BuildWebRenderDisplayItems( + wr::DisplayListBuilder& aBuilder, const layers::StackingContextHelper& aSc, + const nsRect& aDest, const nsRect& aFillArea, const nsSize& aRepeatSize, + const CSSIntRect& aSrc, bool aIsBackfaceVisible, float aOpacity) { + if (aDest.IsEmpty() || aFillArea.IsEmpty()) { + return; + } + + wr::ExtendMode extendMode; + nsTArray<wr::GradientStop> stops; + LayoutDevicePoint lineStart; + LayoutDevicePoint lineEnd; + LayoutDeviceSize gradientRadius; + LayoutDevicePoint gradientCenter; + float gradientAngle; + BuildWebRenderParameters(aOpacity, extendMode, stops, lineStart, lineEnd, + gradientRadius, gradientCenter, gradientAngle); + + nscoord appUnitsPerDevPixel = mPresContext->AppUnitsPerDevPixel(); + + nsPoint firstTile = + nsPoint(FindTileStart(aFillArea.x, aDest.x, aRepeatSize.width), + FindTileStart(aFillArea.y, aDest.y, aRepeatSize.height)); + + // Translate the parameters into device coordinates + LayoutDeviceRect clipBounds = + LayoutDevicePixel::FromAppUnits(aFillArea, appUnitsPerDevPixel); + LayoutDeviceRect firstTileBounds = LayoutDevicePixel::FromAppUnits( + nsRect(firstTile, aDest.Size()), appUnitsPerDevPixel); + LayoutDeviceSize tileRepeat = + LayoutDevicePixel::FromAppUnits(aRepeatSize, appUnitsPerDevPixel); + + // Calculate the bounds of the gradient display item, which starts at the + // first tile and extends to the end of clip bounds + LayoutDevicePoint tileToClip = + clipBounds.BottomRight() - firstTileBounds.TopLeft(); + LayoutDeviceRect gradientBounds = LayoutDeviceRect( + firstTileBounds.TopLeft(), LayoutDeviceSize(tileToClip.x, tileToClip.y)); + + // Calculate the tile spacing, which is the repeat size minus the tile size + LayoutDeviceSize tileSpacing = tileRepeat - firstTileBounds.Size(); + + // srcTransform is used for scaling the gradient to match aSrc + LayoutDeviceRect srcTransform = LayoutDeviceRect( + nsPresContext::CSSPixelsToAppUnits(aSrc.x), + nsPresContext::CSSPixelsToAppUnits(aSrc.y), + aDest.width / ((float)nsPresContext::CSSPixelsToAppUnits(aSrc.width)), + aDest.height / ((float)nsPresContext::CSSPixelsToAppUnits(aSrc.height))); + + lineStart.x = (lineStart.x - srcTransform.x) * srcTransform.width; + lineStart.y = (lineStart.y - srcTransform.y) * srcTransform.height; + + gradientCenter.x = (gradientCenter.x - srcTransform.x) * srcTransform.width; + gradientCenter.y = (gradientCenter.y - srcTransform.y) * srcTransform.height; + + if (mGradient->IsLinear()) { + lineEnd.x = (lineEnd.x - srcTransform.x) * srcTransform.width; + lineEnd.y = (lineEnd.y - srcTransform.y) * srcTransform.height; + + aBuilder.PushLinearGradient( + mozilla::wr::ToLayoutRect(gradientBounds), + mozilla::wr::ToLayoutRect(clipBounds), aIsBackfaceVisible, + mozilla::wr::ToLayoutPoint(lineStart), + mozilla::wr::ToLayoutPoint(lineEnd), stops, extendMode, + mozilla::wr::ToLayoutSize(firstTileBounds.Size()), + mozilla::wr::ToLayoutSize(tileSpacing)); + } else if (mGradient->IsRadial()) { + gradientRadius.width *= srcTransform.width; + gradientRadius.height *= srcTransform.height; + + aBuilder.PushRadialGradient( + mozilla::wr::ToLayoutRect(gradientBounds), + mozilla::wr::ToLayoutRect(clipBounds), aIsBackfaceVisible, + mozilla::wr::ToLayoutPoint(lineStart), + mozilla::wr::ToLayoutSize(gradientRadius), stops, extendMode, + mozilla::wr::ToLayoutSize(firstTileBounds.Size()), + mozilla::wr::ToLayoutSize(tileSpacing)); + } else { + MOZ_ASSERT(mGradient->IsConic()); + aBuilder.PushConicGradient( + mozilla::wr::ToLayoutRect(gradientBounds), + mozilla::wr::ToLayoutRect(clipBounds), aIsBackfaceVisible, + mozilla::wr::ToLayoutPoint(gradientCenter), gradientAngle, stops, + extendMode, mozilla::wr::ToLayoutSize(firstTileBounds.Size()), + mozilla::wr::ToLayoutSize(tileSpacing)); + } +} + +} // namespace mozilla diff --git a/layout/painting/nsCSSRenderingGradients.h b/layout/painting/nsCSSRenderingGradients.h new file mode 100644 index 0000000000..bde769e5e5 --- /dev/null +++ b/layout/painting/nsCSSRenderingGradients.h @@ -0,0 +1,121 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsCSSRenderingGradients_h__ +#define nsCSSRenderingGradients_h__ + +#include "nsStyleStruct.h" +#include "Units.h" +#include "mozilla/Maybe.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/webrender/webrender_ffi.h" + +namespace mozilla { + +namespace layers { +class StackingContextHelper; +} // namespace layers + +namespace wr { +class DisplayListBuilder; +} // namespace wr + +// A resolved color stop, with a specific position along the gradient line and +// a color. +struct ColorStop { + ColorStop() : mPosition(0), mIsMidpoint(false) {} + ColorStop(double aPosition, bool aIsMidPoint, const gfx::sRGBColor& aColor) + : mPosition(aPosition), mIsMidpoint(aIsMidPoint), mColor(aColor) {} + double mPosition; // along the gradient line; 0=start, 1=end + bool mIsMidpoint; + gfx::sRGBColor mColor; +}; + +class nsCSSGradientRenderer final { + public: + /** + * Prepare a nsCSSGradientRenderer for a gradient for an element. + * aIntrinsicSize - the size of the source gradient. + */ + static nsCSSGradientRenderer Create(nsPresContext* aPresContext, + ComputedStyle* aComputedStyle, + const StyleGradient& aGradient, + const nsSize& aIntrinsiceSize); + + /** + * Draw the gradient to aContext + * aDest - where the first tile of gradient is + * aFill - the area to be filled with tiles of aDest + * aSrc - the area of the gradient that will fill aDest + * aRepeatSize - the distance from the origin of a tile + * to the next origin of a tile + * aDirtyRect - pixels outside of this area may be skipped + */ + void Paint(gfxContext& aContext, const nsRect& aDest, const nsRect& aFill, + const nsSize& aRepeatSize, const mozilla::CSSIntRect& aSrc, + const nsRect& aDirtyRect, float aOpacity = 1.0); + + /** + * Collect the gradient parameters + */ + void BuildWebRenderParameters(float aOpacity, wr::ExtendMode& aMode, + nsTArray<wr::GradientStop>& aStops, + LayoutDevicePoint& aLineStart, + LayoutDevicePoint& aLineEnd, + LayoutDeviceSize& aGradientRadius, + LayoutDevicePoint& aGradientCenter, + float& aGradientAngle); + + /** + * Build display items for the gradient + * aLayer - the layer to make this display item relative to + * aDest - where the first tile of gradient is + * aFill - the area to be filled with tiles of aDest + * aRepeatSize - the distance from the origin of a tile + * to the next origin of a tile + * aSrc - the area of the gradient that will fill aDest + */ + void BuildWebRenderDisplayItems(wr::DisplayListBuilder& aBuilder, + const layers::StackingContextHelper& aSc, + const nsRect& aDest, const nsRect& aFill, + const nsSize& aRepeatSize, + const mozilla::CSSIntRect& aSrc, + bool aIsBackfaceVisible, + float aOpacity = 1.0); + + private: + nsCSSGradientRenderer() + : mPresContext(nullptr), + mGradient(nullptr), + mRadiusX(0.0), + mRadiusY(0.0), + mAngle(0.0) {} + + /** + * Attempts to paint the tiles for a gradient by painting it once to an + * offscreen surface and then painting that offscreen surface with + * ExtendMode::Repeat to cover all tiles. + * + * Returns false if the optimization wasn't able to be used, in which case + * a fallback should be used. + */ + bool TryPaintTilesWithExtendMode( + gfxContext& aContext, gfxPattern* aGradientPattern, nscoord aXStart, + nscoord aYStart, const gfxRect& aDirtyAreaToFill, const nsRect& aDest, + const nsSize& aRepeatSize, bool aForceRepeatToCoverTiles); + + nsPresContext* mPresContext; + const StyleGradient* mGradient; + nsTArray<ColorStop> mStops; + gfxPoint mLineStart, mLineEnd; // only for linear/radial gradients + double mRadiusX, mRadiusY; // only for radial gradients + gfxPoint mCenter; // only for conic gradients + float mAngle; // only for conic gradients +}; + +} // namespace mozilla + +#endif /* nsCSSRenderingGradients_h__ */ diff --git a/layout/painting/nsDisplayItemTypes.h b/layout/painting/nsDisplayItemTypes.h new file mode 100644 index 0000000000..f0c7c333e0 --- /dev/null +++ b/layout/painting/nsDisplayItemTypes.h @@ -0,0 +1,73 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// IWYU pragma: private, include "nsDisplayList.h" + +/** + * It's useful to be able to dynamically check the type of certain items. + * Every subclass of nsDisplayItem must have a new type added here for the + * purposes of easy comparison and matching of items in different display lists. + */ + +#ifndef NSDISPLAYITEMTYPES_H_ +#define NSDISPLAYITEMTYPES_H_ + +enum class DisplayItemType : uint8_t { + TYPE_ZERO = 0, /** Spacer so that the first item starts at 1 */ + +#define DECLARE_DISPLAY_ITEM_TYPE(name, flags) TYPE_##name, +#include "nsDisplayItemTypesList.h" +#undef DECLARE_DISPLAY_ITEM_TYPE + + TYPE_MAX +}; + +enum { + // Number of bits needed to represent all types + TYPE_BITS = 8 +}; + +enum DisplayItemFlags { + TYPE_RENDERS_NO_IMAGES = 1 << 0, + TYPE_IS_CONTENTFUL = 1 << 1, + TYPE_IS_CONTAINER = 1 << 2 +}; + +inline const char* DisplayItemTypeName(DisplayItemType aType) { + switch (aType) { +#define DECLARE_DISPLAY_ITEM_TYPE(name, flags) \ + case DisplayItemType::TYPE_##name: \ + return #name; +#include "nsDisplayItemTypesList.h" +#undef DECLARE_DISPLAY_ITEM_TYPE + + default: + return "TYPE_UNKNOWN"; + } +} + +inline uint8_t GetDisplayItemFlagsForType(DisplayItemType aType) { + static const uint8_t flags[static_cast<uint32_t>(DisplayItemType::TYPE_MAX)] = + {0 +#define DECLARE_DISPLAY_ITEM_TYPE(name, flags) , flags +#include "nsDisplayItemTypesList.h" +#undef DECLARE_DISPLAY_ITEM_TYPE + }; + + return flags[static_cast<uint32_t>(aType)]; +} + +inline DisplayItemType GetDisplayItemTypeFromKey(uint32_t aDisplayItemKey) { + static const uint32_t typeMask = (1 << TYPE_BITS) - 1; + DisplayItemType type = + static_cast<DisplayItemType>(aDisplayItemKey & typeMask); + NS_ASSERTION( + type >= DisplayItemType::TYPE_ZERO && type < DisplayItemType::TYPE_MAX, + "Invalid display item type!"); + return type; +} + +#endif /*NSDISPLAYITEMTYPES_H_*/ diff --git a/layout/painting/nsDisplayItemTypesList.h b/layout/painting/nsDisplayItemTypesList.h new file mode 100644 index 0000000000..74c3197804 --- /dev/null +++ b/layout/painting/nsDisplayItemTypesList.h @@ -0,0 +1,133 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +// IWYU pragma: private, include "nsDisplayList.h" +DECLARE_DISPLAY_ITEM_TYPE(ALT_FEEDBACK, 0) +DECLARE_DISPLAY_ITEM_TYPE(ASYNC_ZOOM, + TYPE_RENDERS_NO_IMAGES | TYPE_IS_CONTAINER) +DECLARE_DISPLAY_ITEM_TYPE(BACKDROP_FILTER, TYPE_IS_CONTAINER) +DECLARE_DISPLAY_ITEM_TYPE(BACKDROP_ROOT_CONTAINER, TYPE_IS_CONTAINER) +DECLARE_DISPLAY_ITEM_TYPE(BACKGROUND, TYPE_IS_CONTENTFUL) +DECLARE_DISPLAY_ITEM_TYPE(BACKGROUND_COLOR, TYPE_RENDERS_NO_IMAGES) +DECLARE_DISPLAY_ITEM_TYPE(BLEND_CONTAINER, + TYPE_RENDERS_NO_IMAGES | TYPE_IS_CONTAINER) +DECLARE_DISPLAY_ITEM_TYPE(BLEND_MODE, + TYPE_RENDERS_NO_IMAGES | TYPE_IS_CONTAINER) +DECLARE_DISPLAY_ITEM_TYPE(BORDER, 0) +DECLARE_DISPLAY_ITEM_TYPE(BOX_SHADOW_INNER, TYPE_RENDERS_NO_IMAGES) +DECLARE_DISPLAY_ITEM_TYPE(BOX_SHADOW_OUTER, TYPE_RENDERS_NO_IMAGES) +DECLARE_DISPLAY_ITEM_TYPE(BULLET, TYPE_IS_CONTENTFUL) +DECLARE_DISPLAY_ITEM_TYPE(BUTTON_BORDER_BACKGROUND, TYPE_IS_CONTENTFUL) +DECLARE_DISPLAY_ITEM_TYPE(BUTTON_BOX_SHADOW_OUTER, + TYPE_RENDERS_NO_IMAGES | TYPE_IS_CONTENTFUL) +DECLARE_DISPLAY_ITEM_TYPE(BUTTON_FOREGROUND, TYPE_IS_CONTENTFUL) +DECLARE_DISPLAY_ITEM_TYPE(CANVAS, TYPE_RENDERS_NO_IMAGES) +DECLARE_DISPLAY_ITEM_TYPE(CANVAS_BACKGROUND_COLOR, TYPE_RENDERS_NO_IMAGES) +DECLARE_DISPLAY_ITEM_TYPE(CANVAS_BACKGROUND_IMAGE, TYPE_IS_CONTENTFUL) +DECLARE_DISPLAY_ITEM_TYPE(CANVAS_FOCUS, TYPE_RENDERS_NO_IMAGES) +DECLARE_DISPLAY_ITEM_TYPE(CANVAS_THEMED_BACKGROUND, + TYPE_RENDERS_NO_IMAGES | TYPE_IS_CONTENTFUL) +DECLARE_DISPLAY_ITEM_TYPE(CARET, TYPE_RENDERS_NO_IMAGES | TYPE_IS_CONTENTFUL) +DECLARE_DISPLAY_ITEM_TYPE(CHECKED_CHECKBOX, + TYPE_RENDERS_NO_IMAGES | TYPE_IS_CONTENTFUL) +DECLARE_DISPLAY_ITEM_TYPE(CHECKED_RADIOBUTTON, + TYPE_RENDERS_NO_IMAGES | TYPE_IS_CONTENTFUL) +DECLARE_DISPLAY_ITEM_TYPE(COLUMN_RULE, TYPE_RENDERS_NO_IMAGES) +DECLARE_DISPLAY_ITEM_TYPE(COMBOBOX_FOCUS, TYPE_RENDERS_NO_IMAGES) +DECLARE_DISPLAY_ITEM_TYPE(COMPOSITOR_HITTEST_INFO, TYPE_RENDERS_NO_IMAGES) +DECLARE_DISPLAY_ITEM_TYPE(CONTAINER, TYPE_RENDERS_NO_IMAGES | TYPE_IS_CONTAINER) +DECLARE_DISPLAY_ITEM_TYPE(EVENT_RECEIVER, TYPE_RENDERS_NO_IMAGES) +DECLARE_DISPLAY_ITEM_TYPE(FIELDSET_BORDER_BACKGROUND, 0) +DECLARE_DISPLAY_ITEM_TYPE(FILTER, TYPE_RENDERS_NO_IMAGES | TYPE_IS_CONTAINER) +DECLARE_DISPLAY_ITEM_TYPE(FIXED_POSITION, + TYPE_RENDERS_NO_IMAGES | TYPE_IS_CONTAINER) +DECLARE_DISPLAY_ITEM_TYPE(FOREIGN_OBJECT, + TYPE_IS_CONTENTFUL | TYPE_IS_CONTAINER) +DECLARE_DISPLAY_ITEM_TYPE(FRAMESET_BLANK, TYPE_RENDERS_NO_IMAGES) +DECLARE_DISPLAY_ITEM_TYPE(FRAMESET_BORDER, TYPE_RENDERS_NO_IMAGES) +DECLARE_DISPLAY_ITEM_TYPE(GENERIC, TYPE_RENDERS_NO_IMAGES) +DECLARE_DISPLAY_ITEM_TYPE(HEADER_FOOTER, TYPE_RENDERS_NO_IMAGES) +DECLARE_DISPLAY_ITEM_TYPE(IMAGE, TYPE_IS_CONTENTFUL) +DECLARE_DISPLAY_ITEM_TYPE(LIST_FOCUS, TYPE_RENDERS_NO_IMAGES) +DECLARE_DISPLAY_ITEM_TYPE(MARGIN_GUIDES, TYPE_RENDERS_NO_IMAGES) +DECLARE_DISPLAY_ITEM_TYPE(MASK, TYPE_IS_CONTAINER) +DECLARE_DISPLAY_ITEM_TYPE(OPACITY, TYPE_RENDERS_NO_IMAGES | TYPE_IS_CONTAINER) +DECLARE_DISPLAY_ITEM_TYPE(OPTION_EVENT_GRABBER, + TYPE_RENDERS_NO_IMAGES | TYPE_IS_CONTAINER) +DECLARE_DISPLAY_ITEM_TYPE(OUTLINE, TYPE_RENDERS_NO_IMAGES) +DECLARE_DISPLAY_ITEM_TYPE(OWN_LAYER, TYPE_RENDERS_NO_IMAGES | TYPE_IS_CONTAINER) +DECLARE_DISPLAY_ITEM_TYPE(PERSPECTIVE, + TYPE_RENDERS_NO_IMAGES | TYPE_IS_CONTAINER) +DECLARE_DISPLAY_ITEM_TYPE(PLUGIN, 0) +DECLARE_DISPLAY_ITEM_TYPE(PLUGIN_READBACK, 0) +DECLARE_DISPLAY_ITEM_TYPE(PRINT_PLUGIN, 0) +DECLARE_DISPLAY_ITEM_TYPE(RANGE_FOCUS_RING, 0) +DECLARE_DISPLAY_ITEM_TYPE(REMOTE, TYPE_RENDERS_NO_IMAGES) +DECLARE_DISPLAY_ITEM_TYPE(SCROLL_INFO_LAYER, + TYPE_RENDERS_NO_IMAGES | TYPE_IS_CONTAINER) +DECLARE_DISPLAY_ITEM_TYPE(SELECTION_OVERLAY, TYPE_RENDERS_NO_IMAGES) +DECLARE_DISPLAY_ITEM_TYPE(SOLID_COLOR, TYPE_RENDERS_NO_IMAGES) +DECLARE_DISPLAY_ITEM_TYPE(SOLID_COLOR_REGION, TYPE_RENDERS_NO_IMAGES) +DECLARE_DISPLAY_ITEM_TYPE(SUBDOCUMENT, + TYPE_RENDERS_NO_IMAGES | TYPE_IS_CONTAINER) +DECLARE_DISPLAY_ITEM_TYPE(STICKY_POSITION, + TYPE_RENDERS_NO_IMAGES | TYPE_IS_CONTAINER) +DECLARE_DISPLAY_ITEM_TYPE(SVG_OUTER_SVG, TYPE_RENDERS_NO_IMAGES) +DECLARE_DISPLAY_ITEM_TYPE(SVG_GEOMETRY, TYPE_IS_CONTENTFUL) +DECLARE_DISPLAY_ITEM_TYPE(SVG_TEXT, TYPE_IS_CONTENTFUL) +DECLARE_DISPLAY_ITEM_TYPE(SVG_WRAPPER, TYPE_IS_CONTAINER) +DECLARE_DISPLAY_ITEM_TYPE(TABLE_BACKGROUND_COLOR, TYPE_RENDERS_NO_IMAGES) +DECLARE_DISPLAY_ITEM_TYPE(TABLE_BACKGROUND_IMAGE, TYPE_IS_CONTENTFUL) +DECLARE_DISPLAY_ITEM_TYPE(TABLE_BLEND_CONTAINER, + TYPE_RENDERS_NO_IMAGES | TYPE_IS_CONTAINER) +DECLARE_DISPLAY_ITEM_TYPE(TABLE_BLEND_MODE, + TYPE_RENDERS_NO_IMAGES | TYPE_IS_CONTAINER) +DECLARE_DISPLAY_ITEM_TYPE(TABLE_BORDER_COLLAPSE, 0) +DECLARE_DISPLAY_ITEM_TYPE(TABLE_CELL_BACKGROUND, 0) +DECLARE_DISPLAY_ITEM_TYPE(TABLE_CELL_SELECTION, TYPE_RENDERS_NO_IMAGES) +DECLARE_DISPLAY_ITEM_TYPE(TABLE_THEMED_BACKGROUND_IMAGE, TYPE_IS_CONTENTFUL) +DECLARE_DISPLAY_ITEM_TYPE(TABLE_FIXED_POSITION, + TYPE_RENDERS_NO_IMAGES | TYPE_IS_CONTAINER) +DECLARE_DISPLAY_ITEM_TYPE(TEXT, TYPE_RENDERS_NO_IMAGES | TYPE_IS_CONTENTFUL) +DECLARE_DISPLAY_ITEM_TYPE(TEXT_OVERFLOW, + TYPE_RENDERS_NO_IMAGES | TYPE_IS_CONTENTFUL) +DECLARE_DISPLAY_ITEM_TYPE(THEMED_BACKGROUND, TYPE_RENDERS_NO_IMAGES) +DECLARE_DISPLAY_ITEM_TYPE(TRANSFORM, TYPE_RENDERS_NO_IMAGES | TYPE_IS_CONTAINER) +DECLARE_DISPLAY_ITEM_TYPE(VIDEO, TYPE_RENDERS_NO_IMAGES | TYPE_IS_CONTENTFUL) +DECLARE_DISPLAY_ITEM_TYPE(WRAP_LIST, TYPE_RENDERS_NO_IMAGES | TYPE_IS_CONTAINER) +DECLARE_DISPLAY_ITEM_TYPE(ZOOM, TYPE_RENDERS_NO_IMAGES | TYPE_IS_CONTAINER) + +#if defined(MOZ_REFLOW_PERF_DSP) && defined(MOZ_REFLOW_PERF) +DECLARE_DISPLAY_ITEM_TYPE(REFLOW_COUNT, TYPE_RENDERS_NO_IMAGES) +#endif + +#ifdef MOZ_XUL +DECLARE_DISPLAY_ITEM_TYPE(XUL_EVENT_REDIRECTOR, + TYPE_RENDERS_NO_IMAGES | TYPE_IS_CONTAINER) +DECLARE_DISPLAY_ITEM_TYPE(XUL_GROUP_BACKGROUND, 0) +DECLARE_DISPLAY_ITEM_TYPE(XUL_IMAGE, 0) +DECLARE_DISPLAY_ITEM_TYPE(XUL_TEXT_BOX, TYPE_RENDERS_NO_IMAGES) +DECLARE_DISPLAY_ITEM_TYPE(XUL_TREE_BODY, 0) +DECLARE_DISPLAY_ITEM_TYPE(XUL_TREE_COL_SPLITTER_TARGET, TYPE_RENDERS_NO_IMAGES) +# ifdef DEBUG_LAYOUT +DECLARE_DISPLAY_ITEM_TYPE(XUL_DEBUG, TYPE_RENDERS_NO_IMAGES) +# endif +#endif + +DECLARE_DISPLAY_ITEM_TYPE(MATHML_BAR, TYPE_RENDERS_NO_IMAGES) +DECLARE_DISPLAY_ITEM_TYPE(MATHML_CHAR_FOREGROUND, TYPE_RENDERS_NO_IMAGES) +DECLARE_DISPLAY_ITEM_TYPE(MATHML_ERROR, TYPE_RENDERS_NO_IMAGES) +DECLARE_DISPLAY_ITEM_TYPE(MATHML_MENCLOSE_NOTATION, TYPE_RENDERS_NO_IMAGES) +DECLARE_DISPLAY_ITEM_TYPE(MATHML_SELECTION_RECT, TYPE_RENDERS_NO_IMAGES) +DECLARE_DISPLAY_ITEM_TYPE(MATHML_SLASH, TYPE_RENDERS_NO_IMAGES) +#ifdef DEBUG +DECLARE_DISPLAY_ITEM_TYPE(MATHML_BOUNDING_METRICS, TYPE_RENDERS_NO_IMAGES) +DECLARE_DISPLAY_ITEM_TYPE(MATHML_CHAR_DEBUG, TYPE_RENDERS_NO_IMAGES) + +DECLARE_DISPLAY_ITEM_TYPE(DEBUG_BORDER, TYPE_RENDERS_NO_IMAGES) +DECLARE_DISPLAY_ITEM_TYPE(DEBUG_IMAGE_MAP, TYPE_RENDERS_NO_IMAGES) +DECLARE_DISPLAY_ITEM_TYPE(DEBUG_PLACEHOLDER, TYPE_RENDERS_NO_IMAGES) +DECLARE_DISPLAY_ITEM_TYPE(EVENT_TARGET_BORDER, TYPE_RENDERS_NO_IMAGES) +#endif diff --git a/layout/painting/nsDisplayList.cpp b/layout/painting/nsDisplayList.cpp new file mode 100644 index 0000000000..dfe894b670 --- /dev/null +++ b/layout/painting/nsDisplayList.cpp @@ -0,0 +1,10324 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +/* + * structures that represent things to be painted (ordered in z-order), + * used during painting and hit testing + */ + +#include "nsDisplayList.h" + +#include <stdint.h> +#include <algorithm> +#include <limits> + +#include "gfxContext.h" +#include "gfxUtils.h" +#include "mozilla/DisplayPortUtils.h" +#include "mozilla/dom/BrowserChild.h" +#include "mozilla/dom/HTMLCanvasElement.h" +#include "mozilla/dom/Selection.h" +#include "mozilla/dom/ServiceWorkerRegistrar.h" +#include "mozilla/dom/ServiceWorkerRegistration.h" +#include "mozilla/dom/SVGElement.h" +#include "mozilla/dom/TouchEvent.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/layers/PLayerTransaction.h" +#include "mozilla/PresShell.h" +#include "mozilla/ShapeUtils.h" +#include "mozilla/StaticPrefs_apz.h" +#include "mozilla/StaticPrefs_gfx.h" +#include "mozilla/StaticPrefs_layers.h" +#include "mozilla/StaticPrefs_layout.h" +#include "mozilla/SVGIntegrationUtils.h" +#include "mozilla/SVGUtils.h" +#include "mozilla/ViewportUtils.h" +#include "nsCSSRendering.h" +#include "nsCSSRenderingGradients.h" +#include "nsRefreshDriver.h" +#include "nsRegion.h" +#include "nsStyleStructInlines.h" +#include "nsStyleTransformMatrix.h" +#include "nsTransitionManager.h" +#include "gfxMatrix.h" +#include "nsLayoutUtils.h" +#include "nsIScrollableFrame.h" +#include "nsIFrameInlines.h" +#include "nsStyleConsts.h" +#include "BorderConsts.h" +#include "LayerTreeInvalidation.h" +#include "mozilla/MathAlgorithms.h" + +#include "imgIContainer.h" +#include "BasicLayers.h" +#include "nsBoxFrame.h" +#include "nsImageFrame.h" +#include "nsSubDocumentFrame.h" +#include "GeckoProfiler.h" +#include "nsViewManager.h" +#include "ImageLayers.h" +#include "ImageContainer.h" +#include "nsCanvasFrame.h" +#include "nsSubDocumentFrame.h" +#include "StickyScrollContainer.h" +#include "mozilla/AnimationPerformanceWarning.h" +#include "mozilla/AnimationUtils.h" +#include "mozilla/AutoRestore.h" +#include "mozilla/EffectCompositor.h" +#include "mozilla/EffectSet.h" +#include "mozilla/EventStates.h" +#include "mozilla/HashTable.h" +#include "mozilla/LookAndFeel.h" +#include "mozilla/OperatorNewExtensions.h" +#include "mozilla/PendingAnimationTracker.h" +#include "mozilla/Preferences.h" +#include "mozilla/StyleAnimationValue.h" +#include "mozilla/ServoBindings.h" +#include "mozilla/SVGClipPathFrame.h" +#include "mozilla/SVGMaskFrame.h" +#include "mozilla/SVGObserverUtils.h" +#include "mozilla/Telemetry.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/Unused.h" +#include "mozilla/ViewportFrame.h" +#include "mozilla/gfx/gfxVars.h" +#include "ActiveLayerTracker.h" +#include "nsPrintfCString.h" +#include "UnitTransforms.h" +#include "LayerAnimationInfo.h" +#include "FrameLayerBuilder.h" +#include "mozilla/EventStateManager.h" +#include "nsCaret.h" +#include "nsDOMTokenList.h" +#include "nsCSSProps.h" +#include "nsTableCellFrame.h" +#include "nsTableColFrame.h" +#include "nsTextFrame.h" +#include "nsSliderFrame.h" +#include "nsFocusManager.h" +#include "ClientLayerManager.h" +#include "TextDrawTarget.h" +#include "mozilla/layers/AnimationHelper.h" +#include "mozilla/layers/CompositorThread.h" +#include "mozilla/layers/InputAPZContext.h" +#include "mozilla/layers/RenderRootStateManager.h" +#include "mozilla/layers/StackingContextHelper.h" +#include "mozilla/layers/TreeTraversal.h" +#include "mozilla/layers/WebRenderBridgeChild.h" +#include "mozilla/layers/WebRenderLayerManager.h" +#include "mozilla/layers/WebRenderMessages.h" +#include "mozilla/layers/WebRenderScrollData.h" + +using namespace mozilla; +using namespace mozilla::layers; +using namespace mozilla::dom; +using namespace mozilla::layout; +using namespace mozilla::gfx; + +typedef ScrollableLayerGuid::ViewID ViewID; +typedef nsStyleTransformMatrix::TransformReferenceBox TransformReferenceBox; + +#ifdef DEBUG +static bool SpammyLayoutWarningsEnabled() { + static bool sValue = false; + static bool sValueInitialized = false; + + if (!sValueInitialized) { + Preferences::GetBool("layout.spammy_warnings.enabled", &sValue); + sValueInitialized = true; + } + + return sValue; +} +#endif + +#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED +void AssertUniqueItem(nsDisplayItem* aItem) { + nsIFrame::DisplayItemArray* items = + aItem->Frame()->GetProperty(nsIFrame::DisplayItems()); + if (!items) { + return; + } + for (nsDisplayItemBase* i : *items) { + if (i != aItem && !i->HasDeletedFrame() && i->Frame() == aItem->Frame() && + i->GetPerFrameKey() == aItem->GetPerFrameKey()) { + if (i->IsPreProcessedItem()) { + continue; + } + MOZ_DIAGNOSTIC_ASSERT(false, "Duplicate display item!"); + } + } +} +#endif + +bool ShouldBuildItemForEventsOrPlugins(const DisplayItemType aType) { + return aType == DisplayItemType::TYPE_COMPOSITOR_HITTEST_INFO || + aType == DisplayItemType::TYPE_PLUGIN || + (GetDisplayItemFlagsForType(aType) & TYPE_IS_CONTAINER); +} + +void UpdateDisplayItemData(nsPaintedDisplayItem* aItem) { + for (mozilla::DisplayItemData* did : aItem->Frame()->DisplayItemData()) { + if (did->GetDisplayItemKey() == aItem->GetPerFrameKey() && + did->GetLayer()->AsPaintedLayer()) { + if (!did->HasMergedFrames()) { + aItem->SetDisplayItemData(did, did->GetLayer()->Manager()); + } + + return; + } + } +} + +/* static */ +already_AddRefed<ActiveScrolledRoot> ActiveScrolledRoot::CreateASRForFrame( + const ActiveScrolledRoot* aParent, nsIScrollableFrame* aScrollableFrame, + bool aIsRetained) { + nsIFrame* f = do_QueryFrame(aScrollableFrame); + + RefPtr<ActiveScrolledRoot> asr; + if (aIsRetained) { + asr = f->GetProperty(ActiveScrolledRootCache()); + } + + if (!asr) { + asr = new ActiveScrolledRoot(); + + if (aIsRetained) { + RefPtr<ActiveScrolledRoot> ref = asr; + f->SetProperty(ActiveScrolledRootCache(), ref.forget().take()); + } + } + asr->mParent = aParent; + asr->mScrollableFrame = aScrollableFrame; + asr->mViewId = Nothing(); + asr->mDepth = aParent ? aParent->mDepth + 1 : 1; + asr->mRetained = aIsRetained; + + return asr.forget(); +} + +/* static */ +bool ActiveScrolledRoot::IsAncestor(const ActiveScrolledRoot* aAncestor, + const ActiveScrolledRoot* aDescendant) { + if (!aAncestor) { + // nullptr is the root + return true; + } + if (Depth(aAncestor) > Depth(aDescendant)) { + return false; + } + const ActiveScrolledRoot* asr = aDescendant; + while (asr) { + if (asr == aAncestor) { + return true; + } + asr = asr->mParent; + } + return false; +} + +/* static */ +nsCString ActiveScrolledRoot::ToString( + const ActiveScrolledRoot* aActiveScrolledRoot) { + nsAutoCString str; + for (auto* asr = aActiveScrolledRoot; asr; asr = asr->mParent) { + str.AppendPrintf("<0x%p>", asr->mScrollableFrame); + if (asr->mParent) { + str.AppendLiteral(", "); + } + } + return std::move(str); +} + +mozilla::layers::ScrollableLayerGuid::ViewID ActiveScrolledRoot::ComputeViewId() + const { + nsIContent* content = mScrollableFrame->GetScrolledFrame()->GetContent(); + return nsLayoutUtils::FindOrCreateIDFor(content); +} + +ActiveScrolledRoot::~ActiveScrolledRoot() { + if (mScrollableFrame && mRetained) { + nsIFrame* f = do_QueryFrame(mScrollableFrame); + f->RemoveProperty(ActiveScrolledRootCache()); + } +} + +static uint64_t AddAnimationsForWebRender( + nsDisplayItem* aItem, mozilla::layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder, + const Maybe<LayoutDevicePoint>& aPosition = Nothing()) { + EffectSet* effects = + EffectSet::GetEffectSetForFrame(aItem->Frame(), aItem->GetType()); + if (!effects || effects->IsEmpty()) { + // If there is no animation on the nsIFrame, that means + // 1) we've never created any animations on this frame or + // 2) the frame was reconstruced or + // 3) all animations on the frame have finished + // in such cases we don't need do anything here. + // + // Even if there is a WebRenderAnimationData for the display item type on + // this frame, it's going to be discarded since it's not marked as being + // used. + return 0; + } + + RefPtr<WebRenderAnimationData> animationData = + aManager->CommandBuilder() + .CreateOrRecycleWebRenderUserData<WebRenderAnimationData>(aItem); + AnimationInfo& animationInfo = animationData->GetAnimationInfo(); + animationInfo.AddAnimationsForDisplayItem( + aItem->Frame(), aDisplayListBuilder, aItem, aItem->GetType(), + aManager->LayerManager(), aPosition); + animationInfo.StartPendingAnimations( + aManager->LayerManager()->GetAnimationReadyTime()); + + // Note that animationsId can be 0 (uninitialized in AnimationInfo) if there + // are no active animations. + uint64_t animationsId = animationInfo.GetCompositorAnimationsId(); + if (!animationInfo.GetAnimations().IsEmpty()) { + OpAddCompositorAnimations anim( + CompositorAnimations(animationInfo.GetAnimations(), animationsId)); + aManager->WrBridge()->AddWebRenderParentCommand(anim); + aManager->AddActiveCompositorAnimationId(animationsId); + } else if (animationsId) { + aManager->AddCompositorAnimationsIdForDiscard(animationsId); + animationsId = 0; + } + + return animationsId; +} + +static bool GenerateAndPushTextMask(nsIFrame* aFrame, gfxContext* aContext, + const nsRect& aFillRect, + nsDisplayListBuilder* aBuilder) { + if (aBuilder->IsForGenerateGlyphMask()) { + return false; + } + + SVGObserverUtils::GetAndObserveBackgroundClip(aFrame); + + // The main function of enabling background-clip:text property value. + // When a nsDisplayBackgroundImage detects "text" bg-clip style, it will call + // this function to + // 1. Generate a mask by all descendant text frames + // 2. Push the generated mask into aContext. + + gfxContext* sourceCtx = aContext; + LayoutDeviceRect bounds = LayoutDeviceRect::FromAppUnits( + aFillRect, aFrame->PresContext()->AppUnitsPerDevPixel()); + + // Create a mask surface. + RefPtr<DrawTarget> sourceTarget = sourceCtx->GetDrawTarget(); + RefPtr<DrawTarget> maskDT = sourceTarget->CreateClippedDrawTarget( + bounds.ToUnknownRect(), SurfaceFormat::A8); + if (!maskDT || !maskDT->IsValid()) { + return false; + } + RefPtr<gfxContext> maskCtx = + gfxContext::CreatePreservingTransformOrNull(maskDT); + MOZ_ASSERT(maskCtx); + maskCtx->Multiply(Matrix::Translation(bounds.TopLeft().ToUnknownPoint())); + + // Shade text shape into mask A8 surface. + nsLayoutUtils::PaintFrame( + maskCtx, aFrame, nsRect(nsPoint(0, 0), aFrame->GetSize()), + NS_RGB(255, 255, 255), nsDisplayListBuilderMode::GenerateGlyph); + + // Push the generated mask into aContext, so that the caller can pop and + // blend with it. + + Matrix currentMatrix = sourceCtx->CurrentMatrix(); + Matrix invCurrentMatrix = currentMatrix; + invCurrentMatrix.Invert(); + + RefPtr<SourceSurface> maskSurface = maskDT->Snapshot(); + sourceCtx->PushGroupForBlendBack(gfxContentType::COLOR_ALPHA, 1.0, + maskSurface, invCurrentMatrix); + + return true; +} + +/* static */ +void nsDisplayListBuilder::AddAnimationsAndTransitionsToLayer( + Layer* aLayer, nsDisplayListBuilder* aBuilder, nsDisplayItem* aItem, + nsIFrame* aFrame, DisplayItemType aType) { + // This function can be called in two ways: from + // nsDisplay*::BuildLayer while constructing a layer (with all + // pointers non-null), or from RestyleManager's handling of + // UpdateOpacityLayer/UpdateTransformLayer hints. + MOZ_ASSERT(!aBuilder == !aItem, + "should only be called in two configurations, with both " + "aBuilder and aItem, or with neither"); + MOZ_ASSERT(!aItem || aFrame == aItem->Frame(), "frame mismatch"); + + // Only send animations to a layer that is actually using + // off-main-thread compositing. + LayersBackend backend = aLayer->Manager()->GetBackendType(); + if (!(backend == layers::LayersBackend::LAYERS_CLIENT || + backend == layers::LayersBackend::LAYERS_WR)) { + return; + } + + AnimationInfo& animationInfo = aLayer->GetAnimationInfo(); + animationInfo.AddAnimationsForDisplayItem(aFrame, aBuilder, aItem, aType, + aLayer->Manager()); + animationInfo.TransferMutatedFlagToLayer(aLayer); +} + +nsDisplayWrapList* nsDisplayListBuilder::MergeItems( + nsTArray<nsDisplayWrapList*>& aItems) { + // For merging, we create a temporary item by cloning the last item of the + // mergeable items list. This ensures that the temporary item will have the + // correct frame and bounds. + nsDisplayWrapList* merged = nullptr; + + for (nsDisplayWrapList* item : Reversed(aItems)) { + MOZ_ASSERT(item); + + if (!merged) { + // Create the temporary item. + merged = item->Clone(this); + MOZ_ASSERT(merged); + + AddTemporaryItem(merged); + } else { + // Merge the item properties (frame/bounds/etc) with the previously + // created temporary item. + MOZ_ASSERT(merged->CanMerge(item)); + merged->Merge(item); + } + + // Create nsDisplayWrapList that points to the internal display list of the + // item we are merging. This nsDisplayWrapList is added to the display list + // of the temporary item. + merged->MergeDisplayListFromItem(this, item); + } + + return merged; +} + +void nsDisplayListBuilder::AutoCurrentActiveScrolledRootSetter:: + SetCurrentActiveScrolledRoot( + const ActiveScrolledRoot* aActiveScrolledRoot) { + MOZ_ASSERT(!mUsed); + + // Set the builder's mCurrentActiveScrolledRoot. + mBuilder->mCurrentActiveScrolledRoot = aActiveScrolledRoot; + + // We also need to adjust the builder's mCurrentContainerASR. + // mCurrentContainerASR needs to be an ASR that all the container's + // contents have finite bounds with respect to. If aActiveScrolledRoot + // is an ancestor ASR of mCurrentContainerASR, that means we need to + // set mCurrentContainerASR to aActiveScrolledRoot, because otherwise + // the items that will be created with aActiveScrolledRoot wouldn't + // have finite bounds with respect to mCurrentContainerASR. There's one + // exception, in the case where there's a content clip on the builder + // that is scrolled by a descendant ASR of aActiveScrolledRoot. This + // content clip will clip all items that are created while this + // AutoCurrentActiveScrolledRootSetter exists. This means that the items + // created during our lifetime will have finite bounds with respect to + // the content clip's ASR, even if the items' actual ASR is an ancestor + // of that. And it also means that mCurrentContainerASR only needs to be + // set to the content clip's ASR and not all the way to aActiveScrolledRoot. + // This case is tested by fixed-pos-scrolled-clip-opacity-layerize.html + // and fixed-pos-scrolled-clip-opacity-inside-layerize.html. + + // finiteBoundsASR is the leafmost ASR that all items created during + // object's lifetime have finite bounds with respect to. + const ActiveScrolledRoot* finiteBoundsASR = + ActiveScrolledRoot::PickDescendant(mContentClipASR, aActiveScrolledRoot); + + // mCurrentContainerASR is adjusted so that it's still an ancestor of + // finiteBoundsASR. + mBuilder->mCurrentContainerASR = ActiveScrolledRoot::PickAncestor( + mBuilder->mCurrentContainerASR, finiteBoundsASR); + + // If we are entering out-of-flow content inside a CSS filter, mark + // scroll frames wrt. which the content is fixed as containing such content. + if (mBuilder->mFilterASR && ActiveScrolledRoot::IsAncestor( + aActiveScrolledRoot, mBuilder->mFilterASR)) { + for (const ActiveScrolledRoot* asr = mBuilder->mFilterASR; + asr && asr != aActiveScrolledRoot; asr = asr->mParent) { + asr->mScrollableFrame->SetHasOutOfFlowContentInsideFilter(); + } + } + + mUsed = true; +} + +void nsDisplayListBuilder::AutoCurrentActiveScrolledRootSetter:: + InsertScrollFrame(nsIScrollableFrame* aScrollableFrame) { + MOZ_ASSERT(!mUsed); + size_t descendantsEndIndex = mBuilder->mActiveScrolledRoots.Length(); + const ActiveScrolledRoot* parentASR = mBuilder->mCurrentActiveScrolledRoot; + const ActiveScrolledRoot* asr = + mBuilder->AllocateActiveScrolledRoot(parentASR, aScrollableFrame); + mBuilder->mCurrentActiveScrolledRoot = asr; + + // All child ASRs of parentASR that were created while this + // AutoCurrentActiveScrolledRootSetter object was on the stack belong to us + // now. Reparent them to asr. + for (size_t i = mDescendantsStartIndex; i < descendantsEndIndex; i++) { + ActiveScrolledRoot* descendantASR = mBuilder->mActiveScrolledRoots[i]; + if (ActiveScrolledRoot::IsAncestor(parentASR, descendantASR)) { + descendantASR->IncrementDepth(); + if (descendantASR->mParent == parentASR) { + descendantASR->mParent = asr; + } + } + } + + mUsed = true; +} + +nsPresContext* nsDisplayListBuilder::CurrentPresContext() { + return CurrentPresShellState()->mPresShell->GetPresContext(); +} + +/* static */ +nsRect nsDisplayListBuilder::OutOfFlowDisplayData::ComputeVisibleRectForFrame( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + const nsRect& aVisibleRect, const nsRect& aDirtyRect, + nsRect* aOutDirtyRect) { + nsRect visible = aVisibleRect; + nsRect dirtyRectRelativeToDirtyFrame = aDirtyRect; + + bool inPartialUpdate = + aBuilder->IsRetainingDisplayList() && aBuilder->IsPartialUpdate(); + if (StaticPrefs::apz_allow_zooming() && + DisplayPortUtils::IsFixedPosFrameInDisplayPort(aFrame) && + aBuilder->IsPaintingToWindow() && !inPartialUpdate) { + dirtyRectRelativeToDirtyFrame = + nsRect(nsPoint(0, 0), aFrame->GetParent()->GetSize()); + + // If there's a visual viewport size set, restrict the amount of the + // fixed-position element we paint to the visual viewport. (In general + // the fixed-position element can be as large as the layout viewport, + // which at a high zoom level can cause us to paint too large of an + // area.) + PresShell* presShell = aFrame->PresShell(); + if (presShell->IsVisualViewportSizeSet()) { + dirtyRectRelativeToDirtyFrame = + nsRect(presShell->GetVisualViewportOffsetRelativeToLayoutViewport(), + presShell->GetVisualViewportSize()); + // But if we have a displayport, expand it to the displayport, so + // that async-scrolling the visual viewport within the layout viewport + // will not checkerboard. + if (nsIFrame* rootScrollFrame = presShell->GetRootScrollFrame()) { + nsRect displayport; + // Note that the displayport here is already in the right coordinate + // space: it's relative to the scroll port (= layout viewport), but + // covers the visual viewport with some margins around it, which is + // exactly what we want. + if (DisplayPortUtils::GetHighResolutionDisplayPort( + rootScrollFrame->GetContent(), &displayport, + DisplayPortOptions().With(ContentGeometryType::Fixed))) { + dirtyRectRelativeToDirtyFrame = displayport; + } + } + } + visible = dirtyRectRelativeToDirtyFrame; + if (StaticPrefs::apz_test_logging_enabled() && + presShell->GetDocument()->IsContentDocument()) { + nsLayoutUtils::LogAdditionalTestData( + aBuilder, "fixedPosDisplayport", + ToString(CSSSize::FromAppUnits(visible))); + } + } + + *aOutDirtyRect = dirtyRectRelativeToDirtyFrame - aFrame->GetPosition(); + visible -= aFrame->GetPosition(); + + nsRect overflowRect = aFrame->InkOverflowRect(); + + if (aFrame->IsTransformed() && + mozilla::EffectCompositor::HasAnimationsForCompositor( + aFrame, DisplayItemType::TYPE_TRANSFORM)) { + /** + * Add a fuzz factor to the overflow rectangle so that elements only + * just out of view are pulled into the display list, so they can be + * prerendered if necessary. + */ + overflowRect.Inflate(nsPresContext::CSSPixelsToAppUnits(32)); + } + + visible.IntersectRect(visible, overflowRect); + aOutDirtyRect->IntersectRect(*aOutDirtyRect, overflowRect); + + return visible; +} + +nsDisplayListBuilder::nsDisplayListBuilder(nsIFrame* aReferenceFrame, + nsDisplayListBuilderMode aMode, + bool aBuildCaret, + bool aRetainingDisplayList) + : mReferenceFrame(aReferenceFrame), + mIgnoreScrollFrame(nullptr), + mCurrentActiveScrolledRoot(nullptr), + mCurrentContainerASR(nullptr), + mCurrentFrame(aReferenceFrame), + mCurrentReferenceFrame(aReferenceFrame), + mRootAGR(AnimatedGeometryRoot::CreateAGRForFrame( + aReferenceFrame, nullptr, true, aRetainingDisplayList)), + mCurrentAGR(mRootAGR), + mBuildingExtraPagesForPageNum(0), + mUsedAGRBudget(0), + mDirtyRect(-1, -1, -1, -1), + mGlassDisplayItem(nullptr), + mCaretFrame(nullptr), + mScrollInfoItemsForHoisting(nullptr), + mFirstClipChainToDestroy(nullptr), + mMode(aMode), + mTableBackgroundSet(nullptr), + mCurrentScrollParentId(ScrollableLayerGuid::NULL_SCROLL_ID), + mCurrentScrollbarTarget(ScrollableLayerGuid::NULL_SCROLL_ID), + mFilterASR(nullptr), + mContainsBlendMode(false), + mIsBuildingScrollbar(false), + mCurrentScrollbarWillHaveLayer(false), + mBuildCaret(aBuildCaret), + mRetainingDisplayList(aRetainingDisplayList), + mPartialUpdate(false), + mIgnoreSuppression(false), + mIncludeAllOutOfFlows(false), + mDescendIntoSubdocuments(true), + mSelectedFramesOnly(false), + mAllowMergingAndFlattening(true), + mWillComputePluginGeometry(false), + mInTransform(false), + mInEventsAndPluginsOnly(false), + mInFilter(false), + mInPageSequence(false), + mIsInChromePresContext(false), + mSyncDecodeImages(false), + mIsPaintingToWindow(false), + mUseHighQualityScaling(false), + mIsPaintingForWebRender(false), + mIsCompositingCheap(false), + mContainsPluginItem(false), + mAncestorHasApzAwareEventHandler(false), + mHaveScrollableDisplayPort(false), + mWindowDraggingAllowed(false), + mIsBuildingForPopup(nsLayoutUtils::IsPopup(aReferenceFrame)), + mForceLayerForScrollParent(false), + mAsyncPanZoomEnabled(nsLayoutUtils::AsyncPanZoomEnabled(aReferenceFrame)), + mBuildingInvisibleItems(false), + mIsBuilding(false), + mInInvalidSubtree(false), + mDisablePartialUpdates(false), + mPartialBuildFailed(false), + mIsInActiveDocShell(false), + mBuildAsyncZoomContainer(false), + mContainsBackdropFilter(false), + mIsRelativeToLayoutViewport(false), + mUseOverlayScrollbars(false), + mHitTestArea(), + mHitTestInfo(CompositorHitTestInvisibleToHit) { + MOZ_COUNT_CTOR(nsDisplayListBuilder); + + mBuildCompositorHitTestInfo = mAsyncPanZoomEnabled && IsForPainting(); + + ShouldRebuildDisplayListDueToPrefChange(); + + mUseOverlayScrollbars = + (LookAndFeel::GetInt(LookAndFeel::IntID::UseOverlayScrollbars) != 0); + + static_assert( + static_cast<uint32_t>(DisplayItemType::TYPE_MAX) < (1 << TYPE_BITS), + "Check TYPE_MAX should not overflow"); +} + +static PresShell* GetFocusedPresShell() { + nsPIDOMWindowOuter* focusedWnd = + nsFocusManager::GetFocusManager()->GetFocusedWindow(); + if (!focusedWnd) { + return nullptr; + } + + nsCOMPtr<nsIDocShell> focusedDocShell = focusedWnd->GetDocShell(); + if (!focusedDocShell) { + return nullptr; + } + + return focusedDocShell->GetPresShell(); +} + +void nsDisplayListBuilder::BeginFrame() { + nsCSSRendering::BeginFrameTreesLocked(); + mCurrentAGR = mRootAGR; + mFrameToAnimatedGeometryRootMap.Put(mReferenceFrame, mRootAGR); + + mIsPaintingToWindow = false; + mUseHighQualityScaling = false; + mIgnoreSuppression = false; + mInTransform = false; + mInFilter = false; + mSyncDecodeImages = false; + + if (!mBuildCaret) { + return; + } + + RefPtr<PresShell> presShell = GetFocusedPresShell(); + if (presShell) { + RefPtr<nsCaret> caret = presShell->GetCaret(); + mCaretFrame = caret->GetPaintGeometry(&mCaretRect); + + // The focused pres shell may not be in the document that we're + // painting, or be in a popup. Check if the display root for + // the caret matches the display root that we're painting, and + // only use it if it matches. + if (mCaretFrame && + nsLayoutUtils::GetDisplayRootFrame(mCaretFrame) != + nsLayoutUtils::GetDisplayRootFrame(mReferenceFrame)) { + mCaretFrame = nullptr; + } + } +} + +void nsDisplayListBuilder::EndFrame() { + NS_ASSERTION(!mInInvalidSubtree, + "Someone forgot to cleanup mInInvalidSubtree!"); + mFrameToAnimatedGeometryRootMap.Clear(); + mAGRBudgetSet.Clear(); + mActiveScrolledRoots.Clear(); + mEffectsUpdates.Clear(); + FreeClipChains(); + FreeTemporaryItems(); + nsCSSRendering::EndFrameTreesLocked(); + mCaretFrame = nullptr; +} + +void nsDisplayListBuilder::MarkFrameForDisplay(nsIFrame* aFrame, + const nsIFrame* aStopAtFrame) { + mFramesMarkedForDisplay.AppendElement(aFrame); + for (nsIFrame* f = aFrame; f; + f = nsLayoutUtils::GetParentOrPlaceholderForCrossDoc(f)) { + if (f->HasAnyStateBits(NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO)) { + return; + } + f->AddStateBits(NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO); + if (f == aStopAtFrame) { + // we've reached a frame that we know will be painted, so we can stop. + break; + } + } +} + +void nsDisplayListBuilder::AddFrameMarkedForDisplayIfVisible(nsIFrame* aFrame) { + mFramesMarkedForDisplayIfVisible.AppendElement(aFrame); +} + +void nsDisplayListBuilder::MarkFrameForDisplayIfVisible( + nsIFrame* aFrame, const nsIFrame* aStopAtFrame) { + AddFrameMarkedForDisplayIfVisible(aFrame); + for (nsIFrame* f = aFrame; f; f = nsLayoutUtils::GetDisplayListParent(f)) { + if (f->ForceDescendIntoIfVisible()) { + return; + } + f->SetForceDescendIntoIfVisible(true); + if (f == aStopAtFrame) { + // we've reached a frame that we know will be painted, so we can stop. + break; + } + } +} + +void nsDisplayListBuilder::SetGlassDisplayItem(nsDisplayItem* aItem) { + // Web pages or extensions could trigger the "Multiple glass backgrounds + // found?" warning by using -moz-appearance:win-borderless-glass etc on their + // own elements (as long as they are DocElementBoxFrames, which is rare as + // each xul doc only gets one near the root). We only care about first one, + // since that will be the background of the root window. + + if (IsPartialUpdate()) { + if (aItem->Frame()->IsDocElementBoxFrame()) { +#ifdef DEBUG + if (mHasGlassItemDuringPartial) { + NS_WARNING("Multiple glass backgrounds found?"); + } else +#endif + if (!mHasGlassItemDuringPartial) { + mHasGlassItemDuringPartial = true; + aItem->SetIsGlassItem(); + } + } + return; + } + + if (aItem->Frame()->IsDocElementBoxFrame()) { +#ifdef DEBUG + if (mGlassDisplayItem) { + NS_WARNING("Multiple glass backgrounds found?"); + } else +#endif + if (!mGlassDisplayItem) { + mGlassDisplayItem = aItem; + mGlassDisplayItem->SetIsGlassItem(); + } + } +} + +bool nsDisplayListBuilder::NeedToForceTransparentSurfaceForItem( + nsDisplayItem* aItem) { + return aItem == mGlassDisplayItem; +} + +AnimatedGeometryRoot* nsDisplayListBuilder::WrapAGRForFrame( + nsIFrame* aAnimatedGeometryRoot, bool aIsAsync, + AnimatedGeometryRoot* aParent /* = nullptr */) { + DebugOnly<bool> dummy; + MOZ_ASSERT(IsAnimatedGeometryRoot(aAnimatedGeometryRoot, dummy) == AGR_YES); + + RefPtr<AnimatedGeometryRoot> result; + if (!mFrameToAnimatedGeometryRootMap.Get(aAnimatedGeometryRoot, &result)) { + MOZ_ASSERT(nsLayoutUtils::IsAncestorFrameCrossDoc(RootReferenceFrame(), + aAnimatedGeometryRoot)); + RefPtr<AnimatedGeometryRoot> parent = aParent; + if (!parent) { + nsIFrame* parentFrame = + nsLayoutUtils::GetCrossDocParentFrame(aAnimatedGeometryRoot); + if (parentFrame) { + bool isAsync; + nsIFrame* parentAGRFrame = + FindAnimatedGeometryRootFrameFor(parentFrame, isAsync); + parent = WrapAGRForFrame(parentAGRFrame, isAsync); + } + } + result = AnimatedGeometryRoot::CreateAGRForFrame( + aAnimatedGeometryRoot, parent, aIsAsync, IsRetainingDisplayList()); + mFrameToAnimatedGeometryRootMap.Put(aAnimatedGeometryRoot, result); + } + MOZ_ASSERT(!aParent || result->mParentAGR == aParent); + return result; +} + +AnimatedGeometryRoot* nsDisplayListBuilder::AnimatedGeometryRootForASR( + const ActiveScrolledRoot* aASR) { + if (!aASR) { + return GetRootAnimatedGeometryRoot(); + } + nsIFrame* scrolledFrame = aASR->mScrollableFrame->GetScrolledFrame(); + return FindAnimatedGeometryRootFor(scrolledFrame); +} + +AnimatedGeometryRoot* nsDisplayListBuilder::FindAnimatedGeometryRootFor( + nsIFrame* aFrame) { + if (!IsPaintingToWindow()) { + return mRootAGR; + } + if (aFrame == mCurrentFrame) { + return mCurrentAGR; + } + RefPtr<AnimatedGeometryRoot> result; + if (mFrameToAnimatedGeometryRootMap.Get(aFrame, &result)) { + return result; + } + + bool isAsync; + nsIFrame* agrFrame = FindAnimatedGeometryRootFrameFor(aFrame, isAsync); + result = WrapAGRForFrame(agrFrame, isAsync); + mFrameToAnimatedGeometryRootMap.Put(aFrame, result); + return result; +} + +AnimatedGeometryRoot* nsDisplayListBuilder::FindAnimatedGeometryRootFor( + nsDisplayItem* aItem) { + if (aItem->ShouldFixToViewport(this)) { + // Make its active scrolled root be the active scrolled root of + // the enclosing viewport, since it shouldn't be scrolled by scrolled + // frames in its document. InvalidateFixedBackgroundFramesFromList in + // nsGfxScrollFrame will not repaint this item when scrolling occurs. + nsIFrame* viewportFrame = nsLayoutUtils::GetClosestFrameOfType( + aItem->Frame(), LayoutFrameType::Viewport, RootReferenceFrame()); + if (viewportFrame) { + return FindAnimatedGeometryRootFor(viewportFrame); + } + } + return FindAnimatedGeometryRootFor(aItem->Frame()); +} + +void nsDisplayListBuilder::SetIsRelativeToLayoutViewport() { + mIsRelativeToLayoutViewport = true; + UpdateShouldBuildAsyncZoomContainer(); +} + +void nsDisplayListBuilder::UpdateShouldBuildAsyncZoomContainer() { + Document* document = mReferenceFrame->PresContext()->Document(); + // On desktop, we want to disable zooming in fullscreen mode (bug 1650488). + // On mobile (and RDM), we need zooming even in fullscreen mode to respect + // mobile viewport sizing (bug 1659761). + bool disableZoomingForFullscreen = + document->Fullscreen() && + !document->GetPresShell()->UsesMobileViewportSizing(); + mBuildAsyncZoomContainer = !mIsRelativeToLayoutViewport && + !disableZoomingForFullscreen && + nsLayoutUtils::AllowZoomingForDocument(document); +} + +// Certain prefs may cause display list items to be added or removed when they +// are toggled. In those cases, we need to fully rebuild the display list. +bool nsDisplayListBuilder::ShouldRebuildDisplayListDueToPrefChange() { + // If we transition between wrapping the RCD-RSF contents into an async + // zoom container vs. not, we need to rebuild the display list. This only + // happens when the zooming or container scrolling prefs are toggled + // (manually by the user, or during test setup). + bool didBuildAsyncZoomContainer = mBuildAsyncZoomContainer; + UpdateShouldBuildAsyncZoomContainer(); + + bool hadOverlayScrollbarsLastTime = mUseOverlayScrollbars; + mUseOverlayScrollbars = + (LookAndFeel::GetInt(LookAndFeel::IntID::UseOverlayScrollbars) != 0); + + if (didBuildAsyncZoomContainer != mBuildAsyncZoomContainer) { + return true; + } + + if (hadOverlayScrollbarsLastTime != mUseOverlayScrollbars) { + return true; + } + + return false; +} + +void nsDisplayListBuilder::AddScrollFrameToNotify( + nsIScrollableFrame* aScrollFrame) { + mScrollFramesToNotify.insert(aScrollFrame); +} + +void nsDisplayListBuilder::NotifyAndClearScrollFrames() { + for (const auto& it : mScrollFramesToNotify) { + it->NotifyApzTransaction(); + } + mScrollFramesToNotify.clear(); +} + +bool nsDisplayListBuilder::MarkOutOfFlowFrameForDisplay( + nsIFrame* aDirtyFrame, nsIFrame* aFrame, const nsRect& aVisibleRect, + const nsRect& aDirtyRect) { + MOZ_ASSERT(aFrame->GetParent() == aDirtyFrame); + nsRect dirty; + nsRect visible = OutOfFlowDisplayData::ComputeVisibleRectForFrame( + this, aFrame, aVisibleRect, aDirtyRect, &dirty); + if (!aFrame->HasAnyStateBits(NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO) && + visible.IsEmpty()) { + return false; + } + + // Only MarkFrameForDisplay if we're dirty. If this is a nested out-of-flow + // frame, then it will also mark any outer frames to ensure that building + // reaches the dirty feame. + if (!dirty.IsEmpty() || aFrame->ForceDescendIntoIfVisible()) { + MarkFrameForDisplay(aFrame, aDirtyFrame); + } + + return true; +} + +static void UnmarkFrameForDisplay(nsIFrame* aFrame, + const nsIFrame* aStopAtFrame) { + for (nsIFrame* f = aFrame; f; + f = nsLayoutUtils::GetParentOrPlaceholderForCrossDoc(f)) { + if (!f->HasAnyStateBits(NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO)) { + return; + } + f->RemoveStateBits(NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO); + if (f == aStopAtFrame) { + // we've reached a frame that we know will be painted, so we can stop. + break; + } + } +} + +static void UnmarkFrameForDisplayIfVisible(nsIFrame* aFrame) { + for (nsIFrame* f = aFrame; f; f = nsLayoutUtils::GetDisplayListParent(f)) { + if (!f->ForceDescendIntoIfVisible()) { + return; + } + f->SetForceDescendIntoIfVisible(false); + } +} + +nsDisplayListBuilder::~nsDisplayListBuilder() { + NS_ASSERTION(mFramesMarkedForDisplay.Length() == 0, + "All frames should have been unmarked"); + NS_ASSERTION(mFramesWithOOFData.Length() == 0, + "All OOF data should have been removed"); + NS_ASSERTION(mPresShellStates.Length() == 0, + "All presshells should have been exited"); + + DisplayItemClipChain* c = mFirstClipChainToDestroy; + while (c) { + DisplayItemClipChain* next = c->mNextClipChainToDestroy; + c->DisplayItemClipChain::~DisplayItemClipChain(); + c = next; + } + + MOZ_COUNT_DTOR(nsDisplayListBuilder); +} + +uint32_t nsDisplayListBuilder::GetBackgroundPaintFlags() { + uint32_t flags = 0; + if (mSyncDecodeImages) { + flags |= nsCSSRendering::PAINTBG_SYNC_DECODE_IMAGES; + } + if (mIsPaintingToWindow) { + flags |= nsCSSRendering::PAINTBG_TO_WINDOW; + } + if (mUseHighQualityScaling) { + flags |= nsCSSRendering::PAINTBG_HIGH_QUALITY_SCALING; + } + return flags; +} + +uint32_t nsDisplayListBuilder::GetImageDecodeFlags() const { + uint32_t flags = imgIContainer::FLAG_ASYNC_NOTIFY; + if (mSyncDecodeImages) { + flags |= imgIContainer::FLAG_SYNC_DECODE; + } else { + flags |= imgIContainer::FLAG_SYNC_DECODE_IF_FAST; + } + if (mIsPaintingToWindow || mUseHighQualityScaling) { + flags |= imgIContainer::FLAG_HIGH_QUALITY_SCALING; + } + return flags; +} + +void nsDisplayListBuilder::SubtractFromVisibleRegion(nsRegion* aVisibleRegion, + const nsRegion& aRegion) { + if (aRegion.IsEmpty()) { + return; + } + + nsRegion tmp; + tmp.Sub(*aVisibleRegion, aRegion); + // Don't let *aVisibleRegion get too complex, but don't let it fluff out + // to its bounds either, which can be very bad (see bug 516740). + // Do let aVisibleRegion get more complex if by doing so we reduce its + // area by at least half. + if (GetAccurateVisibleRegions() || tmp.GetNumRects() <= 15 || + tmp.Area() <= aVisibleRegion->Area() / 2) { + *aVisibleRegion = tmp; + } +} + +nsCaret* nsDisplayListBuilder::GetCaret() { + RefPtr<nsCaret> caret = CurrentPresShellState()->mPresShell->GetCaret(); + return caret; +} + +void nsDisplayListBuilder::IncrementPresShellPaintCount(PresShell* aPresShell) { + if (mIsPaintingToWindow) { + mReferenceFrame->AddPaintedPresShell(aPresShell); + aPresShell->IncrementPaintCount(); + } +} + +void nsDisplayListBuilder::EnterPresShell(const nsIFrame* aReferenceFrame, + bool aPointerEventsNoneDoc) { + PresShellState* state = mPresShellStates.AppendElement(); + state->mPresShell = aReferenceFrame->PresShell(); + state->mFirstFrameMarkedForDisplay = mFramesMarkedForDisplay.Length(); + state->mFirstFrameWithOOFData = mFramesWithOOFData.Length(); + + nsIScrollableFrame* sf = state->mPresShell->GetRootScrollFrameAsScrollable(); + if (sf && IsInSubdocument()) { + // We are forcing a rebuild of nsDisplayCanvasBackgroundColor to make sure + // that the canvas background color will be set correctly, and that only one + // unscrollable item will be created. + // This is done to avoid, for example, a case where only scrollbar frames + // are invalidated - we would skip creating nsDisplayCanvasBackgroundColor + // and possibly end up with an extra nsDisplaySolidColor item. + // We skip this for the root document, since we don't want to use + // MarkFrameForDisplayIfVisible before ComputeRebuildRegion. We'll + // do it manually there. + nsCanvasFrame* canvasFrame = do_QueryFrame(sf->GetScrolledFrame()); + if (canvasFrame) { + MarkFrameForDisplayIfVisible(canvasFrame, aReferenceFrame); + } + } + +#ifdef DEBUG + state->mAutoLayoutPhase.emplace(aReferenceFrame->PresContext(), + nsLayoutPhase::DisplayListBuilding); +#endif + + state->mPresShell->UpdateCanvasBackground(); + + bool buildCaret = mBuildCaret; + if (mIgnoreSuppression || !state->mPresShell->IsPaintingSuppressed()) { + state->mIsBackgroundOnly = false; + } else { + state->mIsBackgroundOnly = true; + buildCaret = false; + } + + bool pointerEventsNone = aPointerEventsNoneDoc; + if (IsInSubdocument()) { + pointerEventsNone |= mPresShellStates[mPresShellStates.Length() - 2] + .mInsidePointerEventsNoneDoc; + } + state->mInsidePointerEventsNoneDoc = pointerEventsNone; + + state->mPresShellIgnoreScrollFrame = + state->mPresShell->IgnoringViewportScrolling() + ? state->mPresShell->GetRootScrollFrame() + : nullptr; + + nsPresContext* pc = aReferenceFrame->PresContext(); + mIsInChromePresContext = pc->IsChrome(); + nsIDocShell* docShell = pc->GetDocShell(); + + if (docShell) { + docShell->GetWindowDraggingAllowed(&mWindowDraggingAllowed); + } + + state->mTouchEventPrefEnabledDoc = dom::TouchEvent::PrefEnabled(docShell); + + if (!buildCaret) { + return; + } + + // Caret frames add visual area to their frame, but we don't update the + // overflow area. Use flags to make sure we build display items for that frame + // instead. + if (mCaretFrame && mCaretFrame->PresShell() == state->mPresShell) { + MarkFrameForDisplay(mCaretFrame, aReferenceFrame); + } +} + +// A non-blank paint is a paint that does not just contain the canvas +// background. +static bool DisplayListIsNonBlank(nsDisplayList* aList) { + for (nsDisplayItem* i : *aList) { + switch (i->GetType()) { + case DisplayItemType::TYPE_COMPOSITOR_HITTEST_INFO: + case DisplayItemType::TYPE_CANVAS_BACKGROUND_COLOR: + case DisplayItemType::TYPE_CANVAS_BACKGROUND_IMAGE: + continue; + case DisplayItemType::TYPE_SOLID_COLOR: + case DisplayItemType::TYPE_BACKGROUND: + case DisplayItemType::TYPE_BACKGROUND_COLOR: + if (i->Frame()->IsCanvasFrame()) { + continue; + } + return true; + default: + return true; + } + } + return false; +} + +// A contentful paint is a paint that does contains DOM content (text, +// images, non-blank canvases, SVG): "First Contentful Paint entry +// contains a DOMHighResTimeStamp reporting the time when the browser +// first rendered any text, image (including background images), +// non-white canvas or SVG. This excludes any content of iframes, but +// includes text with pending webfonts. This is the first time users +// could start consuming page content." +static bool DisplayListIsContentful(nsDisplayListBuilder* aBuilder, + nsDisplayList* aList) { + for (nsDisplayItem* i : *aList) { + DisplayItemType type = i->GetType(); + nsDisplayList* children = i->GetChildren(); + + switch (type) { + case DisplayItemType::TYPE_SUBDOCUMENT: // iframes are ignored + break; + // CANVASes check if they may have been modified (as a stand-in + // actually tracking all modifications) + default: + if (i->IsContentful()) { + bool dummy; + nsRect bound = i->GetBounds(aBuilder, &dummy); + if (!bound.IsEmpty()) { + return true; + } + } + if (children) { + if (DisplayListIsContentful(aBuilder, children)) { + return true; + } + } + break; + } + } + return false; +} + +void nsDisplayListBuilder::LeavePresShell(const nsIFrame* aReferenceFrame, + nsDisplayList* aPaintedContents) { + NS_ASSERTION( + CurrentPresShellState()->mPresShell == aReferenceFrame->PresShell(), + "Presshell mismatch"); + + if (mIsPaintingToWindow && aPaintedContents) { + nsPresContext* pc = aReferenceFrame->PresContext(); + if (!pc->HadNonBlankPaint()) { + if (!CurrentPresShellState()->mIsBackgroundOnly && + DisplayListIsNonBlank(aPaintedContents)) { + pc->NotifyNonBlankPaint(); + } + } + nsRootPresContext* rootPresContext = pc->GetRootPresContext(); + if (!pc->HadContentfulPaint() && rootPresContext && + rootPresContext->RefreshDriver()->IsInRefresh()) { + if (!CurrentPresShellState()->mIsBackgroundOnly) { + if (pc->HasEverBuiltInvisibleText() || + DisplayListIsContentful(this, aPaintedContents)) { + pc->NotifyContentfulPaint(); + } + } + } + } + + ResetMarkedFramesForDisplayList(aReferenceFrame); + mPresShellStates.RemoveLastElement(); + + if (!mPresShellStates.IsEmpty()) { + nsPresContext* pc = CurrentPresContext(); + nsIDocShell* docShell = pc->GetDocShell(); + if (docShell) { + docShell->GetWindowDraggingAllowed(&mWindowDraggingAllowed); + } + mIsInChromePresContext = pc->IsChrome(); + } else { + mCurrentAGR = mRootAGR; + + for (uint32_t i = 0; i < mFramesMarkedForDisplayIfVisible.Length(); ++i) { + UnmarkFrameForDisplayIfVisible(mFramesMarkedForDisplayIfVisible[i]); + } + mFramesMarkedForDisplayIfVisible.SetLength(0); + } +} + +void nsDisplayListBuilder::FreeClipChains() { + // Iterate the clip chains from newest to oldest (forward + // iteration), so that we destroy descendants first which + // will drop the ref count on their ancestors. + DisplayItemClipChain** indirect = &mFirstClipChainToDestroy; + + while (*indirect) { + if (!(*indirect)->mRefCount) { + DisplayItemClipChain* next = (*indirect)->mNextClipChainToDestroy; + + mClipDeduplicator.erase(*indirect); + (*indirect)->DisplayItemClipChain::~DisplayItemClipChain(); + Destroy(DisplayListArenaObjectId::CLIPCHAIN, *indirect); + + *indirect = next; + } else { + indirect = &(*indirect)->mNextClipChainToDestroy; + } + } +} + +void nsDisplayListBuilder::FreeTemporaryItems() { + for (nsDisplayItem* i : mTemporaryItems) { + // Temporary display items are not added to the frames. + MOZ_ASSERT(i->Frame()); + i->RemoveFrame(i->Frame()); + i->Destroy(this); + } + + mTemporaryItems.Clear(); +} + +void nsDisplayListBuilder::ResetMarkedFramesForDisplayList( + const nsIFrame* aReferenceFrame) { + // Unmark and pop off the frames marked for display in this pres shell. + uint32_t firstFrameForShell = + CurrentPresShellState()->mFirstFrameMarkedForDisplay; + for (uint32_t i = firstFrameForShell; i < mFramesMarkedForDisplay.Length(); + ++i) { + UnmarkFrameForDisplay(mFramesMarkedForDisplay[i], aReferenceFrame); + } + mFramesMarkedForDisplay.SetLength(firstFrameForShell); + + firstFrameForShell = CurrentPresShellState()->mFirstFrameWithOOFData; + for (uint32_t i = firstFrameForShell; i < mFramesWithOOFData.Length(); ++i) { + mFramesWithOOFData[i]->RemoveProperty(OutOfFlowDisplayDataProperty()); + } + mFramesWithOOFData.SetLength(firstFrameForShell); +} + +void nsDisplayListBuilder::ClearFixedBackgroundDisplayData() { + CurrentPresShellState()->mFixedBackgroundDisplayData = Nothing(); +} + +void nsDisplayListBuilder::MarkFramesForDisplayList( + nsIFrame* aDirtyFrame, const nsFrameList& aFrames) { + nsRect visibleRect = GetVisibleRect(); + nsRect dirtyRect = GetDirtyRect(); + + // If we are entering content that is fixed to the RCD-RSF, we are + // crossing the async zoom container boundary, and need to convert from + // visual to layout coordinates. + if (ViewportFrame* viewportFrame = do_QueryFrame(aDirtyFrame)) { + if (IsForEventDelivery() && ShouldBuildAsyncZoomContainer() && + viewportFrame->PresContext()->IsRootContentDocumentCrossProcess()) { + if (viewportFrame->PresShell()->GetRootScrollFrame()) { +#ifdef DEBUG + for (nsIFrame* f : aFrames) { + MOZ_ASSERT(ViewportUtils::IsZoomedContentRoot(f)); + } +#endif + visibleRect = ViewportUtils::VisualToLayout(visibleRect, + viewportFrame->PresShell()); + dirtyRect = ViewportUtils::VisualToLayout(dirtyRect, + viewportFrame->PresShell()); + } +#ifdef DEBUG + else { + // This is an edge case that should only happen if we are in a + // document with a XUL root element so that it does not have a root + // scroll frame but it has fixed pos content and all of the frames in + // aFrames are that fixed pos content. + for (nsIFrame* f : aFrames) { + MOZ_ASSERT(!ViewportUtils::IsZoomedContentRoot(f) && + f->GetParent() == aDirtyFrame && + f->StyleDisplay()->mPosition == + StylePositionProperty::Fixed); + } + // There's no root scroll frame so there can't be any zooming or async + // panning so we don't need to adjust the visible and dirty rects. + } +#endif + } + } + + bool markedFrames = false; + for (nsIFrame* e : aFrames) { + // Skip the AccessibleCaret frame when building no caret. + if (!IsBuildingCaret()) { + nsIContent* content = e->GetContent(); + if (content && content->IsInNativeAnonymousSubtree() && + content->IsElement()) { + auto classList = content->AsElement()->ClassList(); + if (classList->Contains(u"moz-accessiblecaret"_ns)) { + continue; + } + } + } + if (MarkOutOfFlowFrameForDisplay(aDirtyFrame, e, visibleRect, dirtyRect)) { + markedFrames = true; + } + } + + if (markedFrames) { + // mClipState.GetClipChainForContainingBlockDescendants can return pointers + // to objects on the stack, so we need to clone the chain. + const DisplayItemClipChain* clipChain = + CopyWholeChain(mClipState.GetClipChainForContainingBlockDescendants()); + const DisplayItemClipChain* combinedClipChain = + mClipState.GetCurrentCombinedClipChain(this); + const ActiveScrolledRoot* asr = mCurrentActiveScrolledRoot; + + OutOfFlowDisplayData* data = new OutOfFlowDisplayData( + clipChain, combinedClipChain, asr, visibleRect, dirtyRect); + aDirtyFrame->SetProperty( + nsDisplayListBuilder::OutOfFlowDisplayDataProperty(), data); + mFramesWithOOFData.AppendElement(aDirtyFrame); + } + + if (!aDirtyFrame->GetParent()) { + // This is the viewport frame of aDirtyFrame's presshell. + // Store the current display data so that it can be used for fixed + // background images. + NS_ASSERTION( + CurrentPresShellState()->mPresShell == aDirtyFrame->PresShell(), + "Presshell mismatch"); + MOZ_ASSERT(!CurrentPresShellState()->mFixedBackgroundDisplayData, + "already traversed this presshell's root frame?"); + + const DisplayItemClipChain* clipChain = + CopyWholeChain(mClipState.GetClipChainForContainingBlockDescendants()); + const DisplayItemClipChain* combinedClipChain = + mClipState.GetCurrentCombinedClipChain(this); + const ActiveScrolledRoot* asr = mCurrentActiveScrolledRoot; + CurrentPresShellState()->mFixedBackgroundDisplayData.emplace( + clipChain, combinedClipChain, asr, GetVisibleRect(), GetDirtyRect()); + } +} + +/** + * Mark all preserve-3d children with + * NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO to make sure + * nsIFrame::BuildDisplayListForChild() would visit them. Also compute + * dirty rect for preserve-3d children. + * + * @param aDirtyFrame is the frame to mark children extending context. + */ +void nsDisplayListBuilder::MarkPreserve3DFramesForDisplayList( + nsIFrame* aDirtyFrame) { + for (const auto& childList : aDirtyFrame->ChildLists()) { + for (nsIFrame* child : childList.mList) { + if (child->Combines3DTransformWithAncestors()) { + MarkFrameForDisplay(child, aDirtyFrame); + } + + if (child->IsBlockWrapper()) { + // Mark preserve-3d frames inside the block wrapper. + MarkPreserve3DFramesForDisplayList(child); + } + } + } +} + +ActiveScrolledRoot* nsDisplayListBuilder::AllocateActiveScrolledRoot( + const ActiveScrolledRoot* aParent, nsIScrollableFrame* aScrollableFrame) { + RefPtr<ActiveScrolledRoot> asr = ActiveScrolledRoot::CreateASRForFrame( + aParent, aScrollableFrame, IsRetainingDisplayList()); + mActiveScrolledRoots.AppendElement(asr); + return asr; +} + +const DisplayItemClipChain* nsDisplayListBuilder::AllocateDisplayItemClipChain( + const DisplayItemClip& aClip, const ActiveScrolledRoot* aASR, + const DisplayItemClipChain* aParent) { + MOZ_ASSERT(!(aParent && aParent->mOnStack)); + void* p = Allocate(sizeof(DisplayItemClipChain), + DisplayListArenaObjectId::CLIPCHAIN); + DisplayItemClipChain* c = new (KnownNotNull, p) + DisplayItemClipChain(aClip, aASR, aParent, mFirstClipChainToDestroy); +#ifdef DEBUG + c->mOnStack = false; +#endif + auto result = mClipDeduplicator.insert(c); + if (!result.second) { + // An equivalent clip chain item was already created, so let's return that + // instead. Destroy the one we just created. + // Note that this can cause clip chains from different coordinate systems to + // collapse into the same clip chain object, because clip chains do not keep + // track of the reference frame that they were created in. + c->DisplayItemClipChain::~DisplayItemClipChain(); + Destroy(DisplayListArenaObjectId::CLIPCHAIN, c); + return *(result.first); + } + mFirstClipChainToDestroy = c; + return c; +} + +struct ClipChainItem { + DisplayItemClip clip; + const ActiveScrolledRoot* asr; +}; + +const DisplayItemClipChain* nsDisplayListBuilder::CreateClipChainIntersection( + const DisplayItemClipChain* aAncestor, + const DisplayItemClipChain* aLeafClip1, + const DisplayItemClipChain* aLeafClip2) { + AutoTArray<ClipChainItem, 8> intersectedClips; + + const DisplayItemClipChain* clip1 = aLeafClip1; + const DisplayItemClipChain* clip2 = aLeafClip2; + + const ActiveScrolledRoot* asr = ActiveScrolledRoot::PickDescendant( + clip1 ? clip1->mASR : nullptr, clip2 ? clip2->mASR : nullptr); + + // Build up the intersection from the leaf to the root and put it into + // intersectedClips. The loop below will convert intersectedClips into an + // actual DisplayItemClipChain. + // (We need to do this in two passes because we need the parent clip in order + // to create the DisplayItemClipChain object, but the parent clip has not + // been created at that point.) + while (!aAncestor || asr != aAncestor->mASR) { + if (clip1 && clip1->mASR == asr) { + if (clip2 && clip2->mASR == asr) { + DisplayItemClip intersection = clip1->mClip; + intersection.IntersectWith(clip2->mClip); + intersectedClips.AppendElement(ClipChainItem{intersection, asr}); + clip2 = clip2->mParent; + } else { + intersectedClips.AppendElement(ClipChainItem{clip1->mClip, asr}); + } + clip1 = clip1->mParent; + } else if (clip2 && clip2->mASR == asr) { + intersectedClips.AppendElement(ClipChainItem{clip2->mClip, asr}); + clip2 = clip2->mParent; + } + if (!asr) { + MOZ_ASSERT(!aAncestor, "We should have exited this loop earlier"); + break; + } + asr = asr->mParent; + } + + // Convert intersectedClips into a DisplayItemClipChain. + const DisplayItemClipChain* parentSC = aAncestor; + for (auto& sc : Reversed(intersectedClips)) { + parentSC = AllocateDisplayItemClipChain(sc.clip, sc.asr, parentSC); + } + return parentSC; +} + +const DisplayItemClipChain* nsDisplayListBuilder::CopyWholeChain( + const DisplayItemClipChain* aClipChain) { + return CreateClipChainIntersection(nullptr, aClipChain, nullptr); +} + +const DisplayItemClipChain* nsDisplayListBuilder::FuseClipChainUpTo( + const DisplayItemClipChain* aClipChain, const ActiveScrolledRoot* aASR) { + if (!aClipChain) { + return nullptr; + } + + const DisplayItemClipChain* sc = aClipChain; + DisplayItemClip mergedClip; + while (sc && ActiveScrolledRoot::PickDescendant(aASR, sc->mASR) == sc->mASR) { + mergedClip.IntersectWith(sc->mClip); + sc = sc->mParent; + } + + if (!mergedClip.HasClip()) { + return nullptr; + } + + return AllocateDisplayItemClipChain(mergedClip, aASR, sc); +} + +const nsIFrame* nsDisplayListBuilder::FindReferenceFrameFor( + const nsIFrame* aFrame, nsPoint* aOffset) const { + auto MaybeApplyAdditionalOffset = [&]() { + if (AdditionalOffset()) { + // The additional reference frame offset should only affect descendants + // of |mAdditionalOffsetFrame|. + MOZ_ASSERT(nsLayoutUtils::IsAncestorFrameCrossDoc(mAdditionalOffsetFrame, + aFrame)); + *aOffset += *AdditionalOffset(); + } + }; + + if (aFrame == mCurrentFrame) { + if (aOffset) { + *aOffset = mCurrentOffsetToReferenceFrame; + } + return mCurrentReferenceFrame; + } + + for (const nsIFrame* f = aFrame; f; + f = nsLayoutUtils::GetCrossDocParentFrame(f)) { + if (f == mReferenceFrame || f->IsTransformed()) { + if (aOffset) { + *aOffset = aFrame->GetOffsetToCrossDoc(f); + MaybeApplyAdditionalOffset(); + } + return f; + } + } + + if (aOffset) { + *aOffset = aFrame->GetOffsetToCrossDoc(mReferenceFrame); + } + + return mReferenceFrame; +} + +// Sticky frames are active if their nearest scrollable frame is also active. +static bool IsStickyFrameActive(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame, nsIFrame* aParent) { + MOZ_ASSERT(aFrame->StyleDisplay()->mPosition == + StylePositionProperty::Sticky); + + // Find the nearest scrollframe. + nsIScrollableFrame* sf = nsLayoutUtils::GetNearestScrollableFrame( + aFrame->GetParent(), nsLayoutUtils::SCROLLABLE_SAME_DOC | + nsLayoutUtils::SCROLLABLE_INCLUDE_HIDDEN); + if (!sf) { + return false; + } + + return sf->IsScrollingActive(aBuilder); +} + +nsDisplayListBuilder::AGRState nsDisplayListBuilder::IsAnimatedGeometryRoot( + nsIFrame* aFrame, bool& aIsAsync, nsIFrame** aParent) { + // We can return once we know that this frame is an AGR, and we're either + // async, or sure that none of the later conditions might make us async. + // The exception to this is when IsPaintingToWindow() == false. + aIsAsync = false; + if (aFrame == mReferenceFrame) { + aIsAsync = true; + return AGR_YES; + } + + if (!IsPaintingToWindow()) { + if (aParent) { + *aParent = nsLayoutUtils::GetCrossDocParentFrame(aFrame); + } + return AGR_NO; + } + + nsIFrame* parent = nsLayoutUtils::GetCrossDocParentFrame(aFrame); + if (!parent) { + aIsAsync = true; + return AGR_YES; + } + + if (aFrame->StyleDisplay()->mPosition == StylePositionProperty::Sticky && + IsStickyFrameActive(this, aFrame, parent)) { + aIsAsync = true; + return AGR_YES; + } + + if (aFrame->IsTransformed()) { + aIsAsync = EffectCompositor::HasAnimationsForCompositor( + aFrame, DisplayItemType::TYPE_TRANSFORM); + return AGR_YES; + } + + LayoutFrameType parentType = parent->Type(); + if (parentType == LayoutFrameType::Scroll || + parentType == LayoutFrameType::ListControl) { + nsIScrollableFrame* sf = do_QueryFrame(parent); + if (sf->GetScrolledFrame() == aFrame && sf->IsScrollingActive(this)) { + MOZ_ASSERT(!aFrame->IsTransformed()); + aIsAsync = sf->IsMaybeAsynchronouslyScrolled(); + return AGR_YES; + } + } + + // Treat the slider thumb as being as an active scrolled root when it wants + // its own layer so that it can move without repainting. + if (parentType == LayoutFrameType::Slider) { + auto* sf = static_cast<nsSliderFrame*>(parent)->GetScrollFrame(); + // The word "Maybe" in IsMaybeScrollingActive might be confusing but we do + // indeed need to always consider scroll thumbs as AGRs if + // IsMaybeScrollingActive is true because that is the same condition we use + // in ScrollFrameHelper::AppendScrollPartsTo to layerize scroll thumbs. + if (sf && sf->IsMaybeScrollingActive()) { + return AGR_YES; + } + } + + if (nsLayoutUtils::IsPopup(aFrame)) { + return AGR_YES; + } + + if (ActiveLayerTracker::IsOffsetStyleAnimated(aFrame)) { + const bool inBudget = AddToAGRBudget(aFrame); + if (inBudget) { + return AGR_YES; + } + } + + if (!aFrame->GetParent() && + DisplayPortUtils::ViewportHasDisplayPort(aFrame->PresContext())) { + // Viewport frames in a display port need to be animated geometry roots + // for background-attachment:fixed elements. + return AGR_YES; + } + + // Fixed-pos frames are parented by the viewport frame, which has no parent. + if (DisplayPortUtils::IsFixedPosFrameInDisplayPort(aFrame)) { + return AGR_YES; + } + + if (aParent) { + *aParent = parent; + } + + return AGR_NO; +} + +nsIFrame* nsDisplayListBuilder::FindAnimatedGeometryRootFrameFor( + nsIFrame* aFrame, bool& aIsAsync) { + MOZ_ASSERT( + nsLayoutUtils::IsAncestorFrameCrossDoc(RootReferenceFrame(), aFrame)); + nsIFrame* cursor = aFrame; + while (cursor != RootReferenceFrame()) { + nsIFrame* next; + if (IsAnimatedGeometryRoot(cursor, aIsAsync, &next) == AGR_YES) { + return cursor; + } + cursor = next; + } + // Root frame is always an async agr. + aIsAsync = true; + return cursor; +} + +void nsDisplayListBuilder::RecomputeCurrentAnimatedGeometryRoot() { + bool isAsync; + if (*mCurrentAGR != mCurrentFrame && + IsAnimatedGeometryRoot(const_cast<nsIFrame*>(mCurrentFrame), isAsync) == + AGR_YES) { + AnimatedGeometryRoot* oldAGR = mCurrentAGR; + mCurrentAGR = WrapAGRForFrame(const_cast<nsIFrame*>(mCurrentFrame), isAsync, + mCurrentAGR); + + // Iterate the AGR cache and look for any objects that reference the old AGR + // and check to see if they need to be updated. AGRs can be in the cache + // multiple times, so we may end up doing the work multiple times for AGRs + // that don't change. + for (auto iter = mFrameToAnimatedGeometryRootMap.Iter(); !iter.Done(); + iter.Next()) { + RefPtr<AnimatedGeometryRoot> cached = iter.UserData(); + if (cached->mParentAGR == oldAGR && cached != mCurrentAGR) { + // It's possible that this cached AGR struct that has the old AGR as a + // parent should instead have mCurrentFrame has a parent. + nsIFrame* parent = FindAnimatedGeometryRootFrameFor(*cached, isAsync); + MOZ_ASSERT(parent == mCurrentFrame || parent == *oldAGR); + if (parent == mCurrentFrame) { + cached->mParentAGR = mCurrentAGR; + } + } + } + } +} + +static nsRect ApplyAllClipNonRoundedIntersection( + const DisplayItemClipChain* aClipChain, const nsRect& aRect) { + nsRect result = aRect; + while (aClipChain) { + result = aClipChain->mClip.ApplyNonRoundedIntersection(result); + aClipChain = aClipChain->mParent; + } + return result; +} + +void nsDisplayListBuilder::AdjustWindowDraggingRegion(nsIFrame* aFrame) { + if (!mWindowDraggingAllowed || !IsForPainting()) { + return; + } + + const nsStyleUIReset* styleUI = aFrame->StyleUIReset(); + if (styleUI->mWindowDragging == StyleWindowDragging::Default) { + // This frame has the default value and doesn't influence the window + // dragging region. + return; + } + + LayoutDeviceToLayoutDeviceMatrix4x4 referenceFrameToRootReferenceFrame; + + // The const_cast is for nsLayoutUtils::GetTransformToAncestor. + nsIFrame* referenceFrame = + const_cast<nsIFrame*>(FindReferenceFrameFor(aFrame)); + + if (IsInTransform()) { + // Only support 2d rectilinear transforms. Transform support is needed for + // the horizontal flip transform that's applied to the urlbar textbox in + // RTL mode - it should be able to exclude itself from the draggable region. + referenceFrameToRootReferenceFrame = + ViewAs<LayoutDeviceToLayoutDeviceMatrix4x4>( + nsLayoutUtils::GetTransformToAncestor(RelativeTo{referenceFrame}, + RelativeTo{mReferenceFrame}) + .GetMatrix()); + Matrix referenceFrameToRootReferenceFrame2d; + if (!referenceFrameToRootReferenceFrame.Is2D( + &referenceFrameToRootReferenceFrame2d) || + !referenceFrameToRootReferenceFrame2d.IsRectilinear()) { + return; + } + } else { + MOZ_ASSERT(referenceFrame == mReferenceFrame, + "referenceFrameToRootReferenceFrame needs to be adjusted"); + } + + // We do some basic visibility checking on the frame's border box here. + // We intersect it both with the current dirty rect and with the current + // clip. Either one is just a conservative approximation on its own, but + // their intersection luckily works well enough for our purposes, so that + // we don't have to do full-blown visibility computations. + // The most important case we need to handle is the scrolled-off tab: + // If the tab bar overflows, tab parts that are clipped by the scrollbox + // should not be allowed to interfere with the window dragging region. Using + // just the current DisplayItemClip is not enough to cover this case + // completely because clips are reset while building stacking context + // contents, so for example we'd fail to clip frames that have a clip path + // applied to them. But the current dirty rect doesn't get reset in that + // case, so we use it to make this case work. + nsRect borderBox = aFrame->GetRectRelativeToSelf().Intersect(mVisibleRect); + borderBox += ToReferenceFrame(aFrame); + const DisplayItemClipChain* clip = + ClipState().GetCurrentCombinedClipChain(this); + borderBox = ApplyAllClipNonRoundedIntersection(clip, borderBox); + if (borderBox.IsEmpty()) { + return; + } + + LayoutDeviceRect devPixelBorderBox = LayoutDevicePixel::FromAppUnits( + borderBox, aFrame->PresContext()->AppUnitsPerDevPixel()); + + LayoutDeviceRect transformedDevPixelBorderBox = + TransformBy(referenceFrameToRootReferenceFrame, devPixelBorderBox); + transformedDevPixelBorderBox.Round(); + LayoutDeviceIntRect transformedDevPixelBorderBoxInt; + + if (!transformedDevPixelBorderBox.ToIntRect( + &transformedDevPixelBorderBoxInt)) { + return; + } + + LayoutDeviceIntRegion& region = + styleUI->mWindowDragging == StyleWindowDragging::Drag + ? mWindowDraggingRegion + : mWindowNoDraggingRegion; + + if (!IsRetainingDisplayList()) { + region.OrWith(transformedDevPixelBorderBoxInt); + return; + } + + mozilla::gfx::IntRect rect(transformedDevPixelBorderBoxInt.ToUnknownRect()); + if (styleUI->mWindowDragging == StyleWindowDragging::Drag) { + mRetainedWindowDraggingRegion.Add(aFrame, rect); + } else { + mRetainedWindowNoDraggingRegion.Add(aFrame, rect); + } +} + +LayoutDeviceIntRegion nsDisplayListBuilder::GetWindowDraggingRegion() const { + LayoutDeviceIntRegion result; + if (!IsRetainingDisplayList()) { + result.Sub(mWindowDraggingRegion, mWindowNoDraggingRegion); + return result; + } + + LayoutDeviceIntRegion dragRegion = + mRetainedWindowDraggingRegion.ToLayoutDeviceIntRegion(); + + LayoutDeviceIntRegion noDragRegion = + mRetainedWindowNoDraggingRegion.ToLayoutDeviceIntRegion(); + + result.Sub(dragRegion, noDragRegion); + return result; +} + +void nsDisplayHitTestInfoBase::AddSizeOfExcludingThis( + nsWindowSizes& aSizes) const { + nsPaintedDisplayItem::AddSizeOfExcludingThis(aSizes); + aSizes.mLayoutRetainedDisplayListSize += + aSizes.mState.mMallocSizeOf(mHitTestInfo.get()); +} + +void nsDisplayTransform::AddSizeOfExcludingThis(nsWindowSizes& aSizes) const { + nsDisplayHitTestInfoBase::AddSizeOfExcludingThis(aSizes); + aSizes.mLayoutRetainedDisplayListSize += + aSizes.mState.mMallocSizeOf(mTransformPreserves3D.get()); +} + +void nsDisplayListBuilder::AddSizeOfExcludingThis(nsWindowSizes& aSizes) const { + mPool.AddSizeOfExcludingThis(aSizes, Arena::ArenaKind::DisplayList); + + size_t n = 0; + MallocSizeOf mallocSizeOf = aSizes.mState.mMallocSizeOf; + n += mDocumentWillChangeBudgets.ShallowSizeOfExcludingThis(mallocSizeOf); + n += mFrameWillChangeBudgets.ShallowSizeOfExcludingThis(mallocSizeOf); + n += mAGRBudgetSet.ShallowSizeOfExcludingThis(mallocSizeOf); + n += mEffectsUpdates.ShallowSizeOfExcludingThis(mallocSizeOf); + n += mWindowExcludeGlassRegion.SizeOfExcludingThis(mallocSizeOf); + n += mRetainedWindowDraggingRegion.SizeOfExcludingThis(mallocSizeOf); + n += mRetainedWindowNoDraggingRegion.SizeOfExcludingThis(mallocSizeOf); + n += mRetainedWindowOpaqueRegion.SizeOfExcludingThis(mallocSizeOf); + // XXX can't measure mClipDeduplicator since it uses std::unordered_set. + + aSizes.mLayoutRetainedDisplayListSize += n; +} + +void RetainedDisplayList::AddSizeOfExcludingThis(nsWindowSizes& aSizes) const { + for (nsDisplayItem* item : *this) { + item->AddSizeOfExcludingThis(aSizes); + if (RetainedDisplayList* children = item->GetChildren()) { + children->AddSizeOfExcludingThis(aSizes); + } + } + + size_t n = 0; + + n += mDAG.mDirectPredecessorList.ShallowSizeOfExcludingThis( + aSizes.mState.mMallocSizeOf); + n += mDAG.mNodesInfo.ShallowSizeOfExcludingThis(aSizes.mState.mMallocSizeOf); + n += mOldItems.ShallowSizeOfExcludingThis(aSizes.mState.mMallocSizeOf); + + aSizes.mLayoutRetainedDisplayListSize += n; +} + +size_t nsDisplayListBuilder::WeakFrameRegion::SizeOfExcludingThis( + MallocSizeOf aMallocSizeOf) const { + size_t n = 0; + n += mFrames.ShallowSizeOfExcludingThis(aMallocSizeOf); + for (auto& frame : mFrames) { + const UniquePtr<WeakFrame>& weakFrame = frame.mWeakFrame; + n += aMallocSizeOf(weakFrame.get()); + } + n += mRects.ShallowSizeOfExcludingThis(aMallocSizeOf); + return n; +} + +/** + * Removes modified frames and rects from this WeakFrameRegion. + */ +void nsDisplayListBuilder::WeakFrameRegion::RemoveModifiedFramesAndRects() { + MOZ_ASSERT(mFrames.Length() == mRects.Length()); + + uint32_t i = 0; + uint32_t length = mFrames.Length(); + + while (i < length) { + auto& wrapper = mFrames[i]; + + if (!wrapper.mWeakFrame->IsAlive() || + AnyContentAncestorModified(wrapper.mWeakFrame->GetFrame())) { + // To avoid multiple O(n) shifts in the array, move the last element of + // the array to the current position and decrease the array length. + mFrameSet.RemoveEntry(wrapper.mFrame); + mFrames[i] = std::move(mFrames[length - 1]); + mRects[i] = std::move(mRects[length - 1]); + length--; + } else { + i++; + } + } + + mFrames.TruncateLength(length); + mRects.TruncateLength(length); +} + +void nsDisplayListBuilder::RemoveModifiedWindowRegions() { + mRetainedWindowDraggingRegion.RemoveModifiedFramesAndRects(); + mRetainedWindowNoDraggingRegion.RemoveModifiedFramesAndRects(); + mWindowExcludeGlassRegion.RemoveModifiedFramesAndRects(); + mRetainedWindowOpaqueRegion.RemoveModifiedFramesAndRects(); + + mHasGlassItemDuringPartial = false; +} + +void nsDisplayListBuilder::ClearRetainedWindowRegions() { + mRetainedWindowDraggingRegion.Clear(); + mRetainedWindowNoDraggingRegion.Clear(); + mWindowExcludeGlassRegion.Clear(); + mRetainedWindowOpaqueRegion.Clear(); + + mGlassDisplayItem = nullptr; +} + +const uint32_t gWillChangeAreaMultiplier = 3; +static uint32_t GetLayerizationCost(const nsSize& aSize) { + // There's significant overhead for each layer created from Gecko + // (IPC+Shared Objects) and from the backend (like an OpenGL texture). + // Therefore we set a minimum cost threshold of a 64x64 area. + const int minBudgetCost = 64 * 64; + + const uint32_t budgetCost = std::max( + minBudgetCost, nsPresContext::AppUnitsToIntCSSPixels(aSize.width) * + nsPresContext::AppUnitsToIntCSSPixels(aSize.height)); + + return budgetCost; +} + +bool nsDisplayListBuilder::AddToWillChangeBudget(nsIFrame* aFrame, + const nsSize& aSize) { + MOZ_ASSERT(IsForPainting()); + + if (aFrame->MayHaveWillChangeBudget()) { + // The frame is already in the will-change budget. + return true; + } + + const nsPresContext* presContext = aFrame->PresContext(); + const nsRect area = presContext->GetVisibleArea(); + const uint32_t budgetLimit = + nsPresContext::AppUnitsToIntCSSPixels(area.width) * + nsPresContext::AppUnitsToIntCSSPixels(area.height); + const uint32_t cost = GetLayerizationCost(aSize); + + DocumentWillChangeBudget& documentBudget = + mDocumentWillChangeBudgets.GetOrInsert(presContext); + + const bool onBudget = + (documentBudget + cost) / gWillChangeAreaMultiplier < budgetLimit; + + if (onBudget) { + documentBudget += cost; + mFrameWillChangeBudgets.Put(aFrame, + FrameWillChangeBudget(presContext, cost)); + aFrame->SetMayHaveWillChangeBudget(true); + } + + return onBudget; +} + +bool nsDisplayListBuilder::IsInWillChangeBudget(nsIFrame* aFrame, + const nsSize& aSize) { + if (!IsForPainting()) { + // If this nsDisplayListBuilder is not for painting, the layerization should + // not matter. Do the simple thing and return false. + return false; + } + + const bool onBudget = AddToWillChangeBudget(aFrame, aSize); + if (onBudget) { + return true; + } + + auto* pc = aFrame->PresContext(); + auto* doc = pc->Document(); + if (!doc->HasWarnedAbout(Document::eIgnoringWillChangeOverBudget)) { + AutoTArray<nsString, 2> params; + params.AppendElement()->AppendInt(gWillChangeAreaMultiplier); + + nsRect area = pc->GetVisibleArea(); + uint32_t budgetLimit = nsPresContext::AppUnitsToIntCSSPixels(area.width) * + nsPresContext::AppUnitsToIntCSSPixels(area.height); + params.AppendElement()->AppendInt(budgetLimit); + + doc->WarnOnceAbout(Document::eIgnoringWillChangeOverBudget, false, params); + } + + return false; +} + +void nsDisplayListBuilder::ClearWillChangeBudgetStatus(nsIFrame* aFrame) { + MOZ_ASSERT(IsForPainting()); + + if (!aFrame->MayHaveWillChangeBudget()) { + return; + } + + aFrame->SetMayHaveWillChangeBudget(false); + RemoveFromWillChangeBudgets(aFrame); +} + +void nsDisplayListBuilder::RemoveFromWillChangeBudgets(const nsIFrame* aFrame) { + if (auto entry = mFrameWillChangeBudgets.Lookup(aFrame)) { + const FrameWillChangeBudget& frameBudget = entry.Data(); + + DocumentWillChangeBudget* documentBudget = + mDocumentWillChangeBudgets.GetValue(frameBudget.mPresContext); + + if (documentBudget) { + *documentBudget -= frameBudget.mUsage; + } + + entry.Remove(); + } +} + +void nsDisplayListBuilder::ClearWillChangeBudgets() { + mFrameWillChangeBudgets.Clear(); + mDocumentWillChangeBudgets.Clear(); +} + +#ifdef MOZ_GFX_OPTIMIZE_MOBILE +const float gAGRBudgetAreaMultiplier = 0.3; +#else +const float gAGRBudgetAreaMultiplier = 3.0; +#endif + +bool nsDisplayListBuilder::AddToAGRBudget(nsIFrame* aFrame) { + if (mAGRBudgetSet.Contains(aFrame)) { + return true; + } + + const nsPresContext* presContext = + aFrame->PresContext()->GetRootPresContext(); + if (!presContext) { + return false; + } + + const nsRect area = presContext->GetVisibleArea(); + const uint32_t budgetLimit = + gAGRBudgetAreaMultiplier * + nsPresContext::AppUnitsToIntCSSPixels(area.width) * + nsPresContext::AppUnitsToIntCSSPixels(area.height); + + const uint32_t cost = GetLayerizationCost(aFrame->GetSize()); + const bool onBudget = mUsedAGRBudget + cost < budgetLimit; + + if (onBudget) { + mUsedAGRBudget += cost; + mAGRBudgetSet.PutEntry(aFrame); + } + + return onBudget; +} + +void nsDisplayListBuilder::EnterSVGEffectsContents( + nsIFrame* aEffectsFrame, nsDisplayList* aHoistedItemsStorage) { + MOZ_ASSERT(aHoistedItemsStorage); + if (mSVGEffectsFrames.IsEmpty()) { + MOZ_ASSERT(!mScrollInfoItemsForHoisting); + mScrollInfoItemsForHoisting = aHoistedItemsStorage; + } + mSVGEffectsFrames.AppendElement(aEffectsFrame); +} + +void nsDisplayListBuilder::ExitSVGEffectsContents() { + MOZ_ASSERT(!mSVGEffectsFrames.IsEmpty()); + mSVGEffectsFrames.RemoveLastElement(); + MOZ_ASSERT(mScrollInfoItemsForHoisting); + if (mSVGEffectsFrames.IsEmpty()) { + mScrollInfoItemsForHoisting = nullptr; + } +} + +bool nsDisplayListBuilder::ShouldBuildScrollInfoItemsForHoisting() const { + /* + * Note: if changing the conditions under which scroll info layers + * are created, make a corresponding change to + * ScrollFrameWillBuildScrollInfoLayer() in nsSliderFrame.cpp. + */ + for (nsIFrame* frame : mSVGEffectsFrames) { + if (SVGIntegrationUtils::UsesSVGEffectsNotSupportedInCompositor(frame)) { + return true; + } + } + return false; +} + +void nsDisplayListBuilder::AppendNewScrollInfoItemForHoisting( + nsDisplayScrollInfoLayer* aScrollInfoItem) { + MOZ_ASSERT(ShouldBuildScrollInfoItemsForHoisting()); + MOZ_ASSERT(mScrollInfoItemsForHoisting); + mScrollInfoItemsForHoisting->AppendToTop(aScrollInfoItem); +} + +void nsDisplayListBuilder::BuildCompositorHitTestInfoIfNeeded( + nsIFrame* aFrame, nsDisplayList* aList, const bool aBuildNew) { + MOZ_ASSERT(aFrame); + MOZ_ASSERT(aList); + + if (!BuildCompositorHitTestInfo()) { + return; + } + + const CompositorHitTestInfo info = aFrame->GetCompositorHitTestInfo(this); + if (info == CompositorHitTestInvisibleToHit) { + return; + } + + const nsRect area = aFrame->GetCompositorHitTestArea(this); + if (!aBuildNew && GetHitTestInfo() == info && + GetHitTestArea().Contains(area)) { + return; + } + + auto* item = MakeDisplayItem<nsDisplayCompositorHitTestInfo>( + this, aFrame, info, Some(area)); + MOZ_ASSERT(item); + + SetCompositorHitTestInfo(area, info); + aList->AppendToTop(item); +} + +void nsDisplayListSet::MoveTo(const nsDisplayListSet& aDestination) const { + aDestination.BorderBackground()->AppendToTop(BorderBackground()); + aDestination.BlockBorderBackgrounds()->AppendToTop(BlockBorderBackgrounds()); + aDestination.Floats()->AppendToTop(Floats()); + aDestination.Content()->AppendToTop(Content()); + aDestination.PositionedDescendants()->AppendToTop(PositionedDescendants()); + aDestination.Outlines()->AppendToTop(Outlines()); +} + +static void MoveListTo(nsDisplayList* aList, + nsTArray<nsDisplayItem*>* aElements) { + nsDisplayItem* item; + while ((item = aList->RemoveBottom()) != nullptr) { + aElements->AppendElement(item); + } +} + +nsRect nsDisplayList::GetClippedBounds(nsDisplayListBuilder* aBuilder) const { + nsRect bounds; + for (nsDisplayItem* i : *this) { + bounds.UnionRect(bounds, i->GetClippedBounds(aBuilder)); + } + return bounds; +} + +nsRect nsDisplayList::GetClippedBoundsWithRespectToASR( + nsDisplayListBuilder* aBuilder, const ActiveScrolledRoot* aASR, + nsRect* aBuildingRect) const { + nsRect bounds; + for (nsDisplayItem* i : *this) { + nsRect r = i->GetClippedBounds(aBuilder); + if (aASR != i->GetActiveScrolledRoot() && !r.IsEmpty()) { + if (Maybe<nsRect> clip = i->GetClipWithRespectToASR(aBuilder, aASR)) { + r = clip.ref(); + } + } + if (aBuildingRect) { + aBuildingRect->UnionRect(*aBuildingRect, i->GetBuildingRect()); + } + bounds.UnionRect(bounds, r); + } + return bounds; +} + +nsRect nsDisplayList::GetBuildingRect() const { + nsRect result; + for (nsDisplayItem* i : *this) { + result.UnionRect(result, i->GetBuildingRect()); + } + return result; +} + +bool nsDisplayList::ComputeVisibilityForRoot(nsDisplayListBuilder* aBuilder, + nsRegion* aVisibleRegion) { + AUTO_PROFILER_LABEL("nsDisplayList::ComputeVisibilityForRoot", GRAPHICS); + + nsRegion r; + const ActiveScrolledRoot* rootASR = nullptr; + r.And(*aVisibleRegion, GetClippedBoundsWithRespectToASR(aBuilder, rootASR)); + return ComputeVisibilityForSublist(aBuilder, aVisibleRegion, r.GetBounds()); +} + +static nsRegion TreatAsOpaque(nsDisplayItem* aItem, + nsDisplayListBuilder* aBuilder) { + bool snap; + nsRegion opaque = aItem->GetOpaqueRegion(aBuilder, &snap); + MOZ_ASSERT( + (aBuilder->IsForEventDelivery() && aBuilder->HitTestIsForVisibility()) || + !opaque.IsComplex()); + if (aBuilder->IsForPluginGeometry() && + aItem->GetType() != DisplayItemType::TYPE_COMPOSITOR_HITTEST_INFO) { + // Treat all leaf chrome items as opaque, unless their frames are opacity:0. + // Since opacity:0 frames generate an nsDisplayOpacity, that item will + // not be treated as opaque here, so opacity:0 chrome content will be + // effectively ignored, as it should be. + // We treat leaf chrome items as opaque to ensure that they cover + // content plugins, for security reasons. + // Non-leaf chrome items don't render contents of their own so shouldn't + // be treated as opaque (and their bounds is just the union of their + // children, which might be a large area their contents don't really cover). + nsIFrame* f = aItem->Frame(); + if (f->PresContext()->IsChrome() && !aItem->GetChildren() && + f->StyleEffects()->mOpacity != 0.0) { + opaque = aItem->GetBounds(aBuilder, &snap); + } + } + if (opaque.IsEmpty()) { + return opaque; + } + nsRegion opaqueClipped; + for (auto iter = opaque.RectIter(); !iter.Done(); iter.Next()) { + opaqueClipped.Or(opaqueClipped, + aItem->GetClip().ApproximateIntersectInward(iter.Get())); + } + return opaqueClipped; +} + +bool nsDisplayList::ComputeVisibilityForSublist( + nsDisplayListBuilder* aBuilder, nsRegion* aVisibleRegion, + const nsRect& aListVisibleBounds) { +#ifdef DEBUG + nsRegion r; + r.And(*aVisibleRegion, GetClippedBounds(aBuilder)); + // XXX this fails sometimes: + NS_WARNING_ASSERTION(r.GetBounds().IsEqualInterior(aListVisibleBounds), + "bad aListVisibleBounds"); +#endif + + bool anyVisible = false; + + AutoTArray<nsDisplayItem*, 512> elements; + MoveListTo(this, &elements); + + for (int32_t i = elements.Length() - 1; i >= 0; --i) { + nsDisplayItem* item = elements[i]; + + if (item->ForceNotVisible() && !item->GetSameCoordinateSystemChildren()) { + NS_ASSERTION(item->GetBuildingRect().IsEmpty(), + "invisible items should have empty vis rect"); + item->SetPaintRect(nsRect()); + } else { + nsRect bounds = item->GetClippedBounds(aBuilder); + + nsRegion itemVisible; + itemVisible.And(*aVisibleRegion, bounds); + item->SetPaintRect(itemVisible.GetBounds()); + } + + if (item->ComputeVisibility(aBuilder, aVisibleRegion)) { + anyVisible = true; + + nsRegion opaque = TreatAsOpaque(item, aBuilder); + // Subtract opaque item from the visible region + aBuilder->SubtractFromVisibleRegion(aVisibleRegion, opaque); + } + AppendToBottom(item); + } + + mIsOpaque = !aVisibleRegion->Intersects(aListVisibleBounds); + return anyVisible; +} + +static void TriggerPendingAnimations(Document& aDoc, + const TimeStamp& aReadyTime) { + MOZ_ASSERT(!aReadyTime.IsNull(), + "Animation ready time is not set. Perhaps we're using a layer" + " manager that doesn't update it"); + if (PendingAnimationTracker* tracker = aDoc.GetPendingAnimationTracker()) { + PresShell* presShell = aDoc.GetPresShell(); + // If paint-suppression is in effect then we haven't finished painting + // this document yet so we shouldn't start animations + if (!presShell || !presShell->IsPaintingSuppressed()) { + tracker->TriggerPendingAnimationsOnNextTick(aReadyTime); + } + } + auto recurse = [&aReadyTime](Document& aDoc) { + TriggerPendingAnimations(aDoc, aReadyTime); + return CallState::Continue; + }; + aDoc.EnumerateSubDocuments(recurse); +} + +LayerManager* nsDisplayListBuilder::GetWidgetLayerManager(nsView** aView) { + if (aView) { + *aView = RootReferenceFrame()->GetView(); + } + if (RootReferenceFrame() != + nsLayoutUtils::GetDisplayRootFrame(RootReferenceFrame())) { + return nullptr; + } + nsIWidget* window = RootReferenceFrame()->GetNearestWidget(); + if (window) { + return window->GetLayerManager(); + } + return nullptr; +} + +// Find the layer which should house the root scroll metadata for a given +// layer tree. This is the async zoom container layer if there is one, +// otherwise it's the root layer. +Layer* GetLayerForRootMetadata(Layer* aRootLayer, ViewID aRootScrollId) { + Layer* asyncZoomContainer = DepthFirstSearch<ForwardIterator>( + aRootLayer, [aRootScrollId](Layer* aLayer) { + if (auto id = aLayer->IsAsyncZoomContainer()) { + return *id == aRootScrollId; + } + return false; + }); + return asyncZoomContainer ? asyncZoomContainer : aRootLayer; +} + +FrameLayerBuilder* nsDisplayList::BuildLayers(nsDisplayListBuilder* aBuilder, + LayerManager* aLayerManager, + uint32_t aFlags, + bool aIsWidgetTransaction) { + nsIFrame* frame = aBuilder->RootReferenceFrame(); + nsPresContext* presContext = frame->PresContext(); + PresShell* presShell = presContext->PresShell(); + + FrameLayerBuilder* layerBuilder = new FrameLayerBuilder(); + layerBuilder->Init(aBuilder, aLayerManager); + + if (aFlags & PAINT_COMPRESSED) { + layerBuilder->SetLayerTreeCompressionMode(); + } + + RefPtr<ContainerLayer> root; + { + AUTO_PROFILER_LABEL_CATEGORY_PAIR(GRAPHICS_LayerBuilding); +#ifdef MOZ_GECKO_PROFILER + nsCOMPtr<nsIDocShell> docShell = presContext->GetDocShell(); + AUTO_PROFILER_TRACING_MARKER_DOCSHELL("Paint", "LayerBuilding", GRAPHICS, + docShell); +#endif + + if (XRE_IsContentProcess() && StaticPrefs::gfx_content_always_paint()) { + FrameLayerBuilder::InvalidateAllLayers(aLayerManager); + } + + if (aIsWidgetTransaction) { + layerBuilder->DidBeginRetainedLayerTransaction(aLayerManager); + } + + // Clear any ScrollMetadata that may have been set on the root layer on a + // previous paint. This paint will set new metrics if necessary, and if we + // don't clear the old one here, we may be left with extra metrics. + if (Layer* rootLayer = aLayerManager->GetRoot()) { + rootLayer->SetScrollMetadata(nsTArray<ScrollMetadata>()); + } + + float resolutionUniform = 1.0f; + float resolutionX = resolutionUniform; + float resolutionY = resolutionUniform; + + // If we are in a remote browser, then apply scaling from ancestor browsers + if (BrowserChild* browserChild = BrowserChild::GetFrom(presShell)) { + if (!browserChild->IsTopLevel()) { + resolutionX *= browserChild->GetEffectsInfo().mScaleX; + resolutionY *= browserChild->GetEffectsInfo().mScaleY; + } + } + + ContainerLayerParameters containerParameters(resolutionX, resolutionY); + + { + PaintTelemetry::AutoRecord record(PaintTelemetry::Metric::Layerization); + + root = layerBuilder->BuildContainerLayerFor(aBuilder, aLayerManager, + frame, nullptr, this, + containerParameters, nullptr); + + aBuilder->NotifyAndClearScrollFrames(); + + if (!record.GetStart().IsNull() && + StaticPrefs::layers_acceleration_draw_fps()) { + if (PaintTiming* pt = + ClientLayerManager::MaybeGetPaintTiming(aLayerManager)) { + pt->flbMs() = (TimeStamp::Now() - record.GetStart()).ToMilliseconds(); + } + } + } + + if (!root) { + return nullptr; + } + // Root is being scaled up by the X/Y resolution. Scale it back down. + root->SetPostScale(1.0f / resolutionX, 1.0f / resolutionY); + + auto callback = [root](ScrollableLayerGuid::ViewID aScrollId) -> bool { + return nsLayoutUtils::ContainsMetricsWithId(root, aScrollId); + }; + if (Maybe<ScrollMetadata> rootMetadata = nsLayoutUtils::GetRootMetadata( + aBuilder, root->Manager(), containerParameters, callback)) { + GetLayerForRootMetadata(root, rootMetadata->GetMetrics().GetScrollId()) + ->SetScrollMetadata(rootMetadata.value()); + } + + // NS_WARNING is debug-only, so don't even bother checking the conditions + // in a release build. +#ifdef DEBUG + bool usingDisplayport = false; + if (nsIFrame* rootScrollFrame = presShell->GetRootScrollFrame()) { + nsIContent* content = rootScrollFrame->GetContent(); + if (content) { + usingDisplayport = DisplayPortUtils::HasDisplayPort(content); + } + } + if (usingDisplayport && + !(root->GetContentFlags() & Layer::CONTENT_OPAQUE) && + SpammyLayoutWarningsEnabled()) { + // See bug 693938, attachment 567017 + NS_WARNING("Transparent content with displayports can be expensive."); + } +#endif + + aLayerManager->SetRoot(root); + layerBuilder->WillEndTransaction(); + } + return layerBuilder; +} + +/** + * We paint by executing a layer manager transaction, constructing a + * single layer representing the display list, and then making it the + * root of the layer manager, drawing into the PaintedLayers. + */ +already_AddRefed<LayerManager> nsDisplayList::PaintRoot( + nsDisplayListBuilder* aBuilder, gfxContext* aCtx, uint32_t aFlags) { + AUTO_PROFILER_LABEL("nsDisplayList::PaintRoot", GRAPHICS); + + RefPtr<LayerManager> layerManager; + bool widgetTransaction = false; + bool doBeginTransaction = true; + nsView* view = nullptr; + if (aFlags & PAINT_USE_WIDGET_LAYERS) { + layerManager = aBuilder->GetWidgetLayerManager(&view); + if (layerManager) { + layerManager->SetContainsSVG(false); + + doBeginTransaction = !(aFlags & PAINT_EXISTING_TRANSACTION); + widgetTransaction = true; + } + } + if (!layerManager) { + if (!aCtx) { + NS_WARNING("Nowhere to paint into"); + return nullptr; + } + layerManager = new BasicLayerManager(BasicLayerManager::BLM_OFFSCREEN); + } + + nsIFrame* frame = aBuilder->RootReferenceFrame(); + nsPresContext* presContext = frame->PresContext(); + PresShell* presShell = presContext->PresShell(); + Document* document = presShell->GetDocument(); + + if (layerManager->GetBackendType() == layers::LayersBackend::LAYERS_WR) { + if (doBeginTransaction) { + if (aCtx) { + if (!layerManager->BeginTransactionWithTarget(aCtx)) { + return nullptr; + } + } else { + if (!layerManager->BeginTransaction()) { + return nullptr; + } + } + } + + bool prevIsCompositingCheap = + aBuilder->SetIsCompositingCheap(layerManager->IsCompositingCheap()); + MaybeSetupTransactionIdAllocator(layerManager, presContext); + + bool sent = false; + if (aFlags & PAINT_IDENTICAL_DISPLAY_LIST) { + sent = layerManager->EndEmptyTransaction(); + } + + if (!sent) { + // Windowed plugins are not supported with WebRender enabled. + // But PluginGeometry needs to be updated to show plugin. + // Windowed plugins are going to be removed by Bug 1296400. + nsRootPresContext* rootPresContext = presContext->GetRootPresContext(); + if (rootPresContext && XRE_IsContentProcess()) { + if (aBuilder->WillComputePluginGeometry()) { + rootPresContext->ComputePluginGeometryUpdates( + aBuilder->RootReferenceFrame(), aBuilder, this); + } + // This must be called even if PluginGeometryUpdates were not computed. + rootPresContext->CollectPluginGeometryUpdates(layerManager); + } + + auto* wrManager = static_cast<WebRenderLayerManager*>(layerManager.get()); + + nsIDocShell* docShell = presContext->GetDocShell(); + WrFiltersHolder wrFilters; + gfx::Matrix5x4* colorMatrix = + nsDocShell::Cast(docShell)->GetColorMatrix(); + if (colorMatrix) { + wrFilters.filters.AppendElement( + wr::FilterOp::ColorMatrix(colorMatrix->components)); + } + + wrManager->EndTransactionWithoutLayer(this, aBuilder, + std::move(wrFilters)); + } + + // For layers-free mode, we check the invalidation state bits in the + // EndTransaction. So we clear the invalidation state bits after + // EndTransaction. + if (widgetTransaction || + // SVG-as-an-image docs don't paint as part of the retained layer tree, + // but they still need the invalidation state bits cleared in order for + // invalidation for CSS/SMIL animation to work properly. + (document && document->IsBeingUsedAsImage())) { + frame->ClearInvalidationStateBits(); + } + + aBuilder->SetIsCompositingCheap(prevIsCompositingCheap); + if (document && widgetTransaction) { + TriggerPendingAnimations(*document, + layerManager->GetAnimationReadyTime()); + } + + if (presContext->RefreshDriver()->HasScheduleFlush()) { + presContext->NotifyInvalidation(layerManager->GetLastTransactionId(), + frame->GetRect()); + } + + return layerManager.forget(); + } + + NotifySubDocInvalidationFunc computeInvalidFunc = + presContext->MayHavePaintEventListenerInSubDocument() + ? nsPresContext::NotifySubDocInvalidation + : nullptr; + + UniquePtr<LayerProperties> props; + + bool computeInvalidRect = + (computeInvalidFunc || (!layerManager->IsCompositingCheap() && + layerManager->NeedsWidgetInvalidation())) && + widgetTransaction; + + if (computeInvalidRect) { + props = LayerProperties::CloneFrom(layerManager->GetRoot()); + } + + if (doBeginTransaction) { + if (aCtx) { + if (!layerManager->BeginTransactionWithTarget(aCtx)) { + return nullptr; + } + } else { + if (!layerManager->BeginTransaction()) { + return nullptr; + } + } + } + + bool temp = + aBuilder->SetIsCompositingCheap(layerManager->IsCompositingCheap()); + LayerManager::EndTransactionFlags flags = LayerManager::END_DEFAULT; + if (layerManager->NeedsWidgetInvalidation()) { + if (aFlags & PAINT_NO_COMPOSITE) { + flags = LayerManager::END_NO_COMPOSITE; + } + } else { + // Client layer managers never composite directly, so + // we don't need to worry about END_NO_COMPOSITE. + if (aBuilder->WillComputePluginGeometry()) { + flags = LayerManager::END_NO_REMOTE_COMPOSITE; + } + } + + MaybeSetupTransactionIdAllocator(layerManager, presContext); + + bool sent = false; + if (aFlags & PAINT_IDENTICAL_DISPLAY_LIST) { + sent = layerManager->EndEmptyTransaction(flags); + } + + if (!sent) { + FrameLayerBuilder* layerBuilder = + BuildLayers(aBuilder, layerManager, aFlags, widgetTransaction); + + if (!layerBuilder) { + layerManager->SetUserData(&gLayerManagerLayerBuilder, nullptr); + return nullptr; + } + + // If this is the content process, we ship plugin geometry updates over with + // layer updates, so calculate that now before we call EndTransaction. + nsRootPresContext* rootPresContext = presContext->GetRootPresContext(); + if (rootPresContext && XRE_IsContentProcess()) { + if (aBuilder->WillComputePluginGeometry()) { + rootPresContext->ComputePluginGeometryUpdates( + aBuilder->RootReferenceFrame(), aBuilder, this); + } + // The layer system caches plugin configuration information for forwarding + // with layer updates which needs to get set during reflow. This must be + // called even if there are no windowed plugins in the page. + rootPresContext->CollectPluginGeometryUpdates(layerManager); + } + + layerManager->EndTransaction(FrameLayerBuilder::DrawPaintedLayer, aBuilder, + flags); + layerBuilder->DidEndTransaction(); + } + + if (widgetTransaction || + // SVG-as-an-image docs don't paint as part of the retained layer tree, + // but they still need the invalidation state bits cleared in order for + // invalidation for CSS/SMIL animation to work properly. + (document && document->IsBeingUsedAsImage())) { + frame->ClearInvalidationStateBits(); + } + + aBuilder->SetIsCompositingCheap(temp); + + if (document && widgetTransaction) { + TriggerPendingAnimations(*document, layerManager->GetAnimationReadyTime()); + } + + nsIntRegion invalid; + if (props) { + if (!props->ComputeDifferences(layerManager->GetRoot(), invalid, + computeInvalidFunc)) { + invalid = nsIntRect::MaxIntRect(); + } + } else if (widgetTransaction) { + LayerProperties::ClearInvalidations(layerManager->GetRoot()); + } + + bool shouldInvalidate = layerManager->NeedsWidgetInvalidation(); + + if (view) { + if (props) { + if (!invalid.IsEmpty()) { + nsIntRect bounds = invalid.GetBounds(); + nsRect rect(presContext->DevPixelsToAppUnits(bounds.x), + presContext->DevPixelsToAppUnits(bounds.y), + presContext->DevPixelsToAppUnits(bounds.width), + presContext->DevPixelsToAppUnits(bounds.height)); + if (shouldInvalidate) { + view->GetViewManager()->InvalidateViewNoSuppression(view, rect); + } + presContext->NotifyInvalidation(layerManager->GetLastTransactionId(), + bounds); + } + } else if (shouldInvalidate) { + view->GetViewManager()->InvalidateView(view); + } + } + + layerManager->SetUserData(&gLayerManagerLayerBuilder, nullptr); + return layerManager.forget(); +} + +nsDisplayItem* nsDisplayList::RemoveBottom() { + nsDisplayItem* item = mSentinel.mAbove; + if (!item) { + return nullptr; + } + mSentinel.mAbove = item->mAbove; + if (item == mTop) { + // must have been the only item + mTop = &mSentinel; + } + item->mAbove = nullptr; + mLength--; + return item; +} + +void nsDisplayList::DeleteAll(nsDisplayListBuilder* aBuilder) { + nsDisplayItem* item; + while ((item = RemoveBottom()) != nullptr) { + item->Destroy(aBuilder); + } +} + +static bool IsFrameReceivingPointerEvents(nsIFrame* aFrame) { + return StylePointerEvents::None != + aFrame->StyleUI()->GetEffectivePointerEvents(aFrame); +} + +// A list of frames, and their z depth. Used for sorting +// the results of hit testing. +struct FramesWithDepth { + explicit FramesWithDepth(float aDepth) : mDepth(aDepth) {} + + bool operator<(const FramesWithDepth& aOther) const { + if (!FuzzyEqual(mDepth, aOther.mDepth, 0.1f)) { + // We want to sort so that the shallowest item (highest depth value) is + // first + return mDepth > aOther.mDepth; + } + return this < &aOther; + } + bool operator==(const FramesWithDepth& aOther) const { + return this == &aOther; + } + + float mDepth; + nsTArray<nsIFrame*> mFrames; +}; + +// Sort the frames by depth and then moves all the contained frames to the +// destination +static void FlushFramesArray(nsTArray<FramesWithDepth>& aSource, + nsTArray<nsIFrame*>* aDest) { + if (aSource.IsEmpty()) { + return; + } + aSource.Sort(); + uint32_t length = aSource.Length(); + for (uint32_t i = 0; i < length; i++) { + aDest->AppendElements(std::move(aSource[i].mFrames)); + } + aSource.Clear(); +} + +void nsDisplayList::HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect, + nsDisplayItem::HitTestState* aState, + nsTArray<nsIFrame*>* aOutFrames) const { + nsDisplayItem* item; + + if (aState->mInPreserves3D) { + // Collect leaves of the current 3D rendering context. + for (nsDisplayItem* item : *this) { + auto itemType = item->GetType(); + if (itemType != DisplayItemType::TYPE_TRANSFORM || + !static_cast<nsDisplayTransform*>(item)->IsLeafOf3DContext()) { + item->HitTest(aBuilder, aRect, aState, aOutFrames); + } else { + // One of leaves in the current 3D rendering context. + aState->mItemBuffer.AppendElement(item); + } + } + return; + } + + int32_t itemBufferStart = aState->mItemBuffer.Length(); + for (nsDisplayItem* item : *this) { + aState->mItemBuffer.AppendElement(item); + } + + AutoTArray<FramesWithDepth, 16> temp; + for (int32_t i = aState->mItemBuffer.Length() - 1; i >= itemBufferStart; + --i) { + // Pop element off the end of the buffer. We want to shorten the buffer + // so that recursive calls to HitTest have more buffer space. + item = aState->mItemBuffer[i]; + aState->mItemBuffer.SetLength(i); + + bool snap; + nsRect r = item->GetBounds(aBuilder, &snap).Intersect(aRect); + auto itemType = item->GetType(); + bool same3DContext = + (itemType == DisplayItemType::TYPE_TRANSFORM && + static_cast<nsDisplayTransform*>(item)->IsParticipating3DContext()) || + (itemType == DisplayItemType::TYPE_PERSPECTIVE && + item->Frame()->Extend3DContext()); + if (same3DContext && + (itemType != DisplayItemType::TYPE_TRANSFORM || + !static_cast<nsDisplayTransform*>(item)->IsLeafOf3DContext())) { + if (!item->GetClip().MayIntersect(aRect)) { + continue; + } + AutoTArray<nsIFrame*, 1> neverUsed; + // Start gethering leaves of the 3D rendering context, and + // append leaves at the end of mItemBuffer. Leaves are + // processed at following iterations. + aState->mInPreserves3D = true; + item->HitTest(aBuilder, aRect, aState, &neverUsed); + aState->mInPreserves3D = false; + i = aState->mItemBuffer.Length(); + continue; + } + if (same3DContext || item->GetClip().MayIntersect(r)) { + AutoTArray<nsIFrame*, 16> outFrames; + item->HitTest(aBuilder, aRect, aState, &outFrames); + + // For 3d transforms with preserve-3d we add hit frames into the temp list + // so we can sort them later, otherwise we add them directly to the output + // list. + nsTArray<nsIFrame*>* writeFrames = aOutFrames; + if (item->GetType() == DisplayItemType::TYPE_TRANSFORM && + static_cast<nsDisplayTransform*>(item)->IsLeafOf3DContext()) { + if (outFrames.Length()) { + nsDisplayTransform* transform = + static_cast<nsDisplayTransform*>(item); + nsPoint point = aRect.TopLeft(); + // A 1x1 rect means a point, otherwise use the center of the rect + if (aRect.width != 1 || aRect.height != 1) { + point = aRect.Center(); + } + temp.AppendElement( + FramesWithDepth(transform->GetHitDepthAtPoint(aBuilder, point))); + writeFrames = &temp[temp.Length() - 1].mFrames; + } + } else { + // We may have just finished a run of consecutive preserve-3d + // transforms, so flush these into the destination array before + // processing our frame list. + FlushFramesArray(temp, aOutFrames); + } + + for (uint32_t j = 0; j < outFrames.Length(); j++) { + nsIFrame* f = outFrames.ElementAt(j); + // Filter out some frames depending on the type of hittest + // we are doing. For visibility tests, pass through all frames. + // For pointer tests, only pass through frames that are styled + // to receive pointer events. + if (aBuilder->HitTestIsForVisibility() || + IsFrameReceivingPointerEvents(f)) { + writeFrames->AppendElement(f); + } + } + + if (aBuilder->HitTestIsForVisibility()) { + aState->mHitOccludingItem = [&] { + if (aState->mHitOccludingItem) { + // We already hit something before. + return true; + } + if (aState->mCurrentOpacity == 1.0f && + item->GetOpaqueRegion(aBuilder, &snap).Contains(aRect)) { + // An opaque item always occludes everything. Note that we need to + // check wrapping opacity and such as well. + return true; + } + float threshold = aBuilder->VisibilityThreshold(); + if (threshold == 1.0f) { + return false; + } + float itemOpacity = [&] { + switch (item->GetType()) { + case DisplayItemType::TYPE_OPACITY: + return static_cast<nsDisplayOpacity*>(item)->GetOpacity(); + case DisplayItemType::TYPE_BACKGROUND_COLOR: + return static_cast<nsDisplayBackgroundColor*>(item) + ->GetOpacity(); + default: + // Be conservative and assume it won't occlude other items. + return 0.0f; + } + }(); + return itemOpacity * aState->mCurrentOpacity >= threshold; + }(); + + if (aState->mHitOccludingItem) { + // We're exiting early, so pop the remaining items off the buffer. + aState->mItemBuffer.TruncateLength(itemBufferStart); + break; + } + } + } + } + // Clear any remaining preserve-3d transforms. + FlushFramesArray(temp, aOutFrames); + NS_ASSERTION(aState->mItemBuffer.Length() == uint32_t(itemBufferStart), + "How did we forget to pop some elements?"); +} + +static nsIContent* FindContentInDocument(nsDisplayItem* aItem, Document* aDoc) { + nsIFrame* f = aItem->Frame(); + while (f) { + nsPresContext* pc = f->PresContext(); + if (pc->Document() == aDoc) { + return f->GetContent(); + } + f = nsLayoutUtils::GetCrossDocParentFrame(pc->PresShell()->GetRootFrame()); + } + return nullptr; +} + +struct ZSortItem { + nsDisplayItem* item; + int32_t zIndex; + + explicit ZSortItem(nsDisplayItem* aItem) + : item(aItem), zIndex(aItem->ZIndex()) {} + + operator nsDisplayItem*() { return item; } +}; + +struct ZOrderComparator { + bool operator()(const ZSortItem& aLeft, const ZSortItem& aRight) const { + // Note that we can't just take the difference of the two + // z-indices here, because that might overflow a 32-bit int. + return aLeft.zIndex < aRight.zIndex; + } +}; + +void nsDisplayList::SortByZOrder() { Sort<ZSortItem>(ZOrderComparator()); } + +struct ContentComparator { + nsIContent* mCommonAncestor; + + explicit ContentComparator(nsIContent* aCommonAncestor) + : mCommonAncestor(aCommonAncestor) {} + + bool operator()(nsDisplayItem* aLeft, nsDisplayItem* aRight) const { + // It's possible that the nsIContent for aItem1 or aItem2 is in a + // subdocument of commonAncestor, because display items for subdocuments + // have been mixed into the same list. Ensure that we're looking at content + // in commonAncestor's document. + Document* commonAncestorDoc = mCommonAncestor->OwnerDoc(); + nsIContent* content1 = FindContentInDocument(aLeft, commonAncestorDoc); + nsIContent* content2 = FindContentInDocument(aRight, commonAncestorDoc); + if (!content1 || !content2) { + NS_ERROR("Document trees are mixed up!"); + // Something weird going on + return true; + } + return nsLayoutUtils::CompareTreePosition(content1, content2, + mCommonAncestor) < 0; + } +}; + +void nsDisplayList::SortByContentOrder(nsIContent* aCommonAncestor) { + Sort<nsDisplayItem*>(ContentComparator(aCommonAncestor)); +} + +bool nsDisplayItemBase::HasModifiedFrame() const { + return mItemFlags.contains(ItemBaseFlag::ModifiedFrame); +} + +void nsDisplayItemBase::SetModifiedFrame(bool aModified) { + if (aModified) { + mItemFlags += ItemBaseFlag::ModifiedFrame; + } else { + mItemFlags -= ItemBaseFlag::ModifiedFrame; + } +} + +void nsDisplayItemBase::SetDeletedFrame() { + mItemFlags += ItemBaseFlag::DeletedFrame; +} + +bool nsDisplayItemBase::HasDeletedFrame() const { + bool retval = mItemFlags.contains(ItemBaseFlag::DeletedFrame) || + (GetType() == DisplayItemType::TYPE_REMOTE && + !static_cast<const nsDisplayRemote*>(this)->GetFrameLoader()); + MOZ_ASSERT(retval || mFrame); + return retval; +} + +#if !defined(DEBUG) && !defined(MOZ_DIAGNOSTIC_ASSERT_ENABLED) +static_assert(sizeof(nsDisplayItem) <= 176, "nsDisplayItem has grown"); +#endif + +nsDisplayItem::nsDisplayItem(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame) + : nsDisplayItem(aBuilder, aFrame, aBuilder->CurrentActiveScrolledRoot()) {} + +nsDisplayItem::nsDisplayItem(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + const ActiveScrolledRoot* aActiveScrolledRoot) + : nsDisplayItemBase(aBuilder, aFrame), + mActiveScrolledRoot(aActiveScrolledRoot), + mAnimatedGeometryRoot(nullptr) { + MOZ_COUNT_CTOR(nsDisplayItem); + + mReferenceFrame = aBuilder->FindReferenceFrameFor(aFrame, &mToReferenceFrame); + // This can return the wrong result if the item override + // ShouldFixToViewport(), the item needs to set it again in its constructor. + mAnimatedGeometryRoot = aBuilder->FindAnimatedGeometryRootFor(aFrame); + MOZ_ASSERT(nsLayoutUtils::IsAncestorFrameCrossDoc( + aBuilder->RootReferenceFrame(), *mAnimatedGeometryRoot), + "Bad"); + NS_ASSERTION( + aBuilder->GetVisibleRect().width >= 0 || !aBuilder->IsForPainting(), + "visible rect not set"); + + nsDisplayItem::SetClipChain( + aBuilder->ClipState().GetCurrentCombinedClipChain(aBuilder), true); + + // The visible rect is for mCurrentFrame, so we have to use + // mCurrentOffsetToReferenceFrame + nsRect visible = aBuilder->GetVisibleRect() + + aBuilder->GetCurrentFrameOffsetToReferenceFrame(); + SetBuildingRect(visible); + + const nsStyleDisplay* disp = mFrame->StyleDisplay(); + if (mFrame->BackfaceIsHidden(disp)) { + mItemFlags += ItemFlag::BackfaceHidden; + } + if (mFrame->Combines3DTransformWithAncestors(disp)) { + mItemFlags += ItemFlag::Combines3DTransformWithAncestors; + } +} + +/* static */ +bool nsDisplayItem::ForceActiveLayers() { + return StaticPrefs::layers_force_active(); +} + +int32_t nsDisplayItem::ZIndex() const { return mFrame->ZIndex().valueOr(0); } + +bool nsDisplayItem::ComputeVisibility(nsDisplayListBuilder* aBuilder, + nsRegion* aVisibleRegion) { + return !GetPaintRect().IsEmpty() && + !IsInvisibleInRect(aVisibleRegion->GetBounds()); +} + +bool nsDisplayItem::RecomputeVisibility(nsDisplayListBuilder* aBuilder, + nsRegion* aVisibleRegion) { + if (ForceNotVisible() && !GetSameCoordinateSystemChildren()) { + // mForceNotVisible wants to ensure that this display item doesn't render + // anything itself. If this item has contents, then we obviously want to + // render those, so we don't need this check in that case. + NS_ASSERTION(GetBuildingRect().IsEmpty(), + "invisible items without children should have empty vis rect"); + SetPaintRect(nsRect()); + } else { + bool snap; + nsRect bounds = GetBounds(aBuilder, &snap); + + nsRegion itemVisible; + itemVisible.And(*aVisibleRegion, bounds); + SetPaintRect(itemVisible.GetBounds()); + } + + // When we recompute visibility within layers we don't need to + // expand the visible region for content behind plugins (the plugin + // is not in the layer). + if (!ComputeVisibility(aBuilder, aVisibleRegion)) { + SetPaintRect(nsRect()); + return false; + } + + nsRegion opaque = TreatAsOpaque(this, aBuilder); + aBuilder->SubtractFromVisibleRegion(aVisibleRegion, opaque); + return true; +} + +void nsDisplayItem::SetClipChain(const DisplayItemClipChain* aClipChain, + bool aStore) { + mClipChain = aClipChain; + mClip = DisplayItemClipChain::ClipForASR(aClipChain, mActiveScrolledRoot); + + if (aStore) { + mState.mClipChain = mClipChain; + mState.mClip = mClip; + } +} + +Maybe<nsRect> nsDisplayItem::GetClipWithRespectToASR( + nsDisplayListBuilder* aBuilder, const ActiveScrolledRoot* aASR) const { + if (const DisplayItemClip* clip = + DisplayItemClipChain::ClipForASR(GetClipChain(), aASR)) { + return Some(clip->GetClipRect()); + } +#ifdef DEBUG + MOZ_ASSERT(false, "item should have finite clip with respect to aASR"); +#endif + return Nothing(); +} + +void nsDisplayItem::FuseClipChainUpTo(nsDisplayListBuilder* aBuilder, + const ActiveScrolledRoot* aASR) { + mClipChain = aBuilder->FuseClipChainUpTo(mClipChain, aASR); + + if (mClipChain) { + mClip = &mClipChain->mClip; + } else { + mClip = nullptr; + } +} + +bool nsDisplayItem::ShouldUseAdvancedLayer(LayerManager* aManager, + PrefFunc aFunc) const { + return CanUseAdvancedLayer(aManager) ? aFunc() : false; +} + +bool nsDisplayItem::CanUseAdvancedLayer(LayerManager* aManager) const { + return StaticPrefs::layers_advanced_basic_layer_enabled() || !aManager || + aManager->GetBackendType() == layers::LayersBackend::LAYERS_WR; +} + +static const DisplayItemClipChain* FindCommonAncestorClipForIntersection( + const DisplayItemClipChain* aOne, const DisplayItemClipChain* aTwo) { + for (const ActiveScrolledRoot* asr = + ActiveScrolledRoot::PickDescendant(aOne->mASR, aTwo->mASR); + asr; asr = asr->mParent) { + if (aOne == aTwo) { + return aOne; + } + if (aOne->mASR == asr) { + aOne = aOne->mParent; + } + if (aTwo->mASR == asr) { + aTwo = aTwo->mParent; + } + if (!aOne) { + return aTwo; + } + if (!aTwo) { + return aOne; + } + } + return nullptr; +} + +void nsDisplayItem::IntersectClip(nsDisplayListBuilder* aBuilder, + const DisplayItemClipChain* aOther, + bool aStore) { + if (!aOther || mClipChain == aOther) { + return; + } + + // aOther might be a reference to a clip on the stack. We need to make sure + // that CreateClipChainIntersection will allocate the actual intersected + // clip in the builder's arena, so for the mClipChain == nullptr case, + // we supply nullptr as the common ancestor so that + // CreateClipChainIntersection clones the whole chain. + const DisplayItemClipChain* ancestorClip = + mClipChain ? FindCommonAncestorClipForIntersection(mClipChain, aOther) + : nullptr; + + SetClipChain( + aBuilder->CreateClipChainIntersection(ancestorClip, mClipChain, aOther), + aStore); +} + +nsRect nsDisplayItem::GetClippedBounds(nsDisplayListBuilder* aBuilder) const { + bool snap; + nsRect r = GetBounds(aBuilder, &snap); + return GetClip().ApplyNonRoundedIntersection(r); +} + +nsDisplayContainer::nsDisplayContainer( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + const ActiveScrolledRoot* aActiveScrolledRoot, nsDisplayList* aList) + : nsDisplayItem(aBuilder, aFrame, aActiveScrolledRoot) { + MOZ_COUNT_CTOR(nsDisplayContainer); + mChildren.AppendToTop(aList); + UpdateBounds(aBuilder); + + // Clear and store the clip chain set by nsDisplayItem constructor. + nsDisplayItem::SetClipChain(nullptr, true); +} + +bool nsDisplayContainer::CreateWebRenderCommands( + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + aManager->CommandBuilder().CreateWebRenderCommandsFromDisplayList( + GetChildren(), this, aDisplayListBuilder, aSc, aBuilder, aResources); + return true; +} + +/** + * Like |nsDisplayList::ComputeVisibilityForSublist()|, but restricts + * |aVisibleRegion| to given |aBounds| of the list. + */ +static bool ComputeClippedVisibilityForSubList(nsDisplayListBuilder* aBuilder, + nsRegion* aVisibleRegion, + nsDisplayList* aList, + const nsRect& aBounds) { + nsRegion visibleRegion; + visibleRegion.And(*aVisibleRegion, aBounds); + nsRegion originalVisibleRegion = visibleRegion; + + const bool anyItemVisible = + aList->ComputeVisibilityForSublist(aBuilder, &visibleRegion, aBounds); + nsRegion removed; + // removed = originalVisibleRegion - visibleRegion + removed.Sub(originalVisibleRegion, visibleRegion); + // aVisibleRegion = aVisibleRegion - removed (modulo any simplifications + // SubtractFromVisibleRegion does) + aBuilder->SubtractFromVisibleRegion(aVisibleRegion, removed); + + return anyItemVisible; +} + +bool nsDisplayContainer::ComputeVisibility(nsDisplayListBuilder* aBuilder, + nsRegion* aVisibleRegion) { + return ::ComputeClippedVisibilityForSubList(aBuilder, aVisibleRegion, + GetChildren(), GetPaintRect()); +} + +nsRect nsDisplayContainer::GetBounds(nsDisplayListBuilder* aBuilder, + bool* aSnap) const { + *aSnap = false; + return mBounds; +} + +nsRect nsDisplayContainer::GetComponentAlphaBounds( + nsDisplayListBuilder* aBuilder) const { + return mChildren.GetComponentAlphaBounds(aBuilder); +} + +static nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder, + nsDisplayList* aList, + const nsRect& aListBounds) { + if (aList->IsOpaque()) { + // Everything within list bounds that's visible is opaque. This is an + // optimization to avoid calculating the opaque region. + return aListBounds; + } + + if (aBuilder->HitTestIsForVisibility()) { + // If we care about an accurate opaque region, iterate the display list + // and build up a region of opaque bounds. + return aList->GetOpaqueRegion(aBuilder); + } + + return nsRegion(); +} + +nsRegion nsDisplayContainer::GetOpaqueRegion(nsDisplayListBuilder* aBuilder, + bool* aSnap) const { + return ::GetOpaqueRegion(aBuilder, GetChildren(), GetBounds(aBuilder, aSnap)); +} + +Maybe<nsRect> nsDisplayContainer::GetClipWithRespectToASR( + nsDisplayListBuilder* aBuilder, const ActiveScrolledRoot* aASR) const { + // Our children should have finite bounds with respect to |aASR|. + if (aASR == mActiveScrolledRoot) { + return Some(mBounds); + } + + return Some(mChildren.GetClippedBoundsWithRespectToASR(aBuilder, aASR)); +} + +void nsDisplayContainer::HitTest(nsDisplayListBuilder* aBuilder, + const nsRect& aRect, HitTestState* aState, + nsTArray<nsIFrame*>* aOutFrames) { + mChildren.HitTest(aBuilder, aRect, aState, aOutFrames); +} + +void nsDisplayContainer::UpdateBounds(nsDisplayListBuilder* aBuilder) { + // Container item bounds are expected to be clipped. + mBounds = + mChildren.GetClippedBoundsWithRespectToASR(aBuilder, mActiveScrolledRoot); +} + +nsRect nsDisplaySolidColor::GetBounds(nsDisplayListBuilder* aBuilder, + bool* aSnap) const { + *aSnap = true; + return mBounds; +} + +LayerState nsDisplaySolidColor::GetLayerState( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aParameters) { + if (ForceActiveLayers()) { + return LayerState::LAYER_ACTIVE; + } + + return LayerState::LAYER_NONE; +} + +already_AddRefed<Layer> nsDisplaySolidColor::BuildLayer( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aContainerParameters) { + RefPtr<ColorLayer> layer = static_cast<ColorLayer*>( + aManager->GetLayerBuilder()->GetLeafLayerFor(aBuilder, this)); + if (!layer) { + layer = aManager->CreateColorLayer(); + if (!layer) { + return nullptr; + } + } + layer->SetColor(ToDeviceColor(mColor)); + + const int32_t appUnitsPerDevPixel = + mFrame->PresContext()->AppUnitsPerDevPixel(); + layer->SetBounds(mBounds.ToNearestPixels(appUnitsPerDevPixel)); + layer->SetBaseTransform(gfx::Matrix4x4::Translation( + aContainerParameters.mOffset.x, aContainerParameters.mOffset.y, 0)); + + return layer.forget(); +} + +void nsDisplaySolidColor::Paint(nsDisplayListBuilder* aBuilder, + gfxContext* aCtx) { + int32_t appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel(); + DrawTarget* drawTarget = aCtx->GetDrawTarget(); + Rect rect = + NSRectToSnappedRect(GetPaintRect(), appUnitsPerDevPixel, *drawTarget); + drawTarget->FillRect(rect, ColorPattern(ToDeviceColor(mColor))); +} + +void nsDisplaySolidColor::WriteDebugInfo(std::stringstream& aStream) { + aStream << " (rgba " << (int)NS_GET_R(mColor) << "," << (int)NS_GET_G(mColor) + << "," << (int)NS_GET_B(mColor) << "," << (int)NS_GET_A(mColor) + << ")"; +} + +bool nsDisplaySolidColor::CreateWebRenderCommands( + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + LayoutDeviceRect bounds = LayoutDeviceRect::FromAppUnits( + mBounds, mFrame->PresContext()->AppUnitsPerDevPixel()); + wr::LayoutRect r = wr::ToLayoutRect(bounds); + aBuilder.PushRect(r, r, !BackfaceIsHidden(), + wr::ToColorF(ToDeviceColor(mColor))); + + return true; +} + +nsRect nsDisplaySolidColorRegion::GetBounds(nsDisplayListBuilder* aBuilder, + bool* aSnap) const { + *aSnap = true; + return mRegion.GetBounds(); +} + +void nsDisplaySolidColorRegion::Paint(nsDisplayListBuilder* aBuilder, + gfxContext* aCtx) { + int32_t appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel(); + DrawTarget* drawTarget = aCtx->GetDrawTarget(); + ColorPattern color(ToDeviceColor(mColor)); + for (auto iter = mRegion.RectIter(); !iter.Done(); iter.Next()) { + Rect rect = + NSRectToSnappedRect(iter.Get(), appUnitsPerDevPixel, *drawTarget); + drawTarget->FillRect(rect, color); + } +} + +void nsDisplaySolidColorRegion::WriteDebugInfo(std::stringstream& aStream) { + aStream << " (rgba " << int(mColor.r * 255) << "," << int(mColor.g * 255) + << "," << int(mColor.b * 255) << "," << mColor.a << ")"; +} + +bool nsDisplaySolidColorRegion::CreateWebRenderCommands( + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + for (auto iter = mRegion.RectIter(); !iter.Done(); iter.Next()) { + nsRect rect = iter.Get(); + LayoutDeviceRect layerRects = LayoutDeviceRect::FromAppUnits( + rect, mFrame->PresContext()->AppUnitsPerDevPixel()); + wr::LayoutRect r = wr::ToLayoutRect(layerRects); + aBuilder.PushRect(r, r, !BackfaceIsHidden(), + wr::ToColorF(ToDeviceColor(mColor))); + } + + return true; +} + +static void RegisterThemeGeometry(nsDisplayListBuilder* aBuilder, + nsDisplayItem* aItem, nsIFrame* aFrame, + nsITheme::ThemeGeometryType aType) { + if (aBuilder->IsInChromeDocumentOrPopup() && !aBuilder->IsInTransform()) { + nsIFrame* displayRoot = nsLayoutUtils::GetDisplayRootFrame(aFrame); + nsPoint offset = aBuilder->IsInSubdocument() + ? aBuilder->ToReferenceFrame(aFrame) + : aFrame->GetOffsetTo(displayRoot); + nsRect borderBox = nsRect(offset, aFrame->GetSize()); + aBuilder->RegisterThemeGeometry( + aType, aItem, + LayoutDeviceIntRect::FromUnknownRect(borderBox.ToNearestPixels( + aFrame->PresContext()->AppUnitsPerDevPixel()))); + } +} + +// Return the bounds of the viewport relative to |aFrame|'s reference frame. +// Returns Nothing() if transforming into |aFrame|'s coordinate space fails. +static Maybe<nsRect> GetViewportRectRelativeToReferenceFrame( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame) { + nsIFrame* rootFrame = aFrame->PresShell()->GetRootFrame(); + nsRect rootRect = rootFrame->GetRectRelativeToSelf(); + if (nsLayoutUtils::TransformRect(rootFrame, aFrame, rootRect) == + nsLayoutUtils::TRANSFORM_SUCCEEDED) { + return Some(rootRect + aBuilder->ToReferenceFrame(aFrame)); + } + return Nothing(); +} + +/* static */ nsDisplayBackgroundImage::InitData +nsDisplayBackgroundImage::GetInitData(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame, uint16_t aLayer, + const nsRect& aBackgroundRect, + ComputedStyle* aBackgroundStyle) { + nsPresContext* presContext = aFrame->PresContext(); + uint32_t flags = aBuilder->GetBackgroundPaintFlags(); + const nsStyleImageLayers::Layer& layer = + aBackgroundStyle->StyleBackground()->mImage.mLayers[aLayer]; + + bool isTransformedFixed; + nsBackgroundLayerState state = nsCSSRendering::PrepareImageLayer( + presContext, aFrame, flags, aBackgroundRect, aBackgroundRect, layer, + &isTransformedFixed); + + // background-attachment:fixed is treated as background-attachment:scroll + // if it's affected by a transform. + // See https://www.w3.org/Bugs/Public/show_bug.cgi?id=17521. + bool shouldTreatAsFixed = + layer.mAttachment == StyleImageLayerAttachment::Fixed && + !isTransformedFixed; + + bool shouldFixToViewport = shouldTreatAsFixed && !layer.mImage.IsNone(); + bool isRasterImage = state.mImageRenderer.IsRasterImage(); + nsCOMPtr<imgIContainer> image; + if (isRasterImage) { + image = state.mImageRenderer.GetImage(); + } + return InitData{aBuilder, aBackgroundStyle, image, + aBackgroundRect, state.mFillArea, state.mDestArea, + aLayer, isRasterImage, shouldFixToViewport}; +} + +nsDisplayBackgroundImage::nsDisplayBackgroundImage( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, const InitData& aInitData, + nsIFrame* aFrameForBounds) + : nsDisplayImageContainer(aBuilder, aFrame), + mBackgroundStyle(aInitData.backgroundStyle), + mImage(aInitData.image), + mDependentFrame(nullptr), + mBackgroundRect(aInitData.backgroundRect), + mFillRect(aInitData.fillArea), + mDestRect(aInitData.destArea), + mLayer(aInitData.layer), + mIsRasterImage(aInitData.isRasterImage), + mShouldFixToViewport(aInitData.shouldFixToViewport), + mImageFlags(0) { + MOZ_COUNT_CTOR(nsDisplayBackgroundImage); +#ifdef DEBUG + if (mBackgroundStyle && mBackgroundStyle != mFrame->Style()) { + // If this changes, then you also need to adjust css::ImageLoader to + // invalidate mFrame as needed. + MOZ_ASSERT(mFrame->IsCanvasFrame() || + mFrame->IsFrameOfType(nsIFrame::eTablePart)); + } +#endif + + mBounds = GetBoundsInternal(aInitData.builder, aFrameForBounds); + if (mShouldFixToViewport) { + mAnimatedGeometryRoot = + aInitData.builder->FindAnimatedGeometryRootFor(this); + + // Expand the item's visible rect to cover the entire bounds, limited to the + // viewport rect. This is necessary because the background's clip can move + // asynchronously. + if (Maybe<nsRect> viewportRect = GetViewportRectRelativeToReferenceFrame( + aInitData.builder, mFrame)) { + SetBuildingRect(mBounds.Intersect(*viewportRect)); + } + } +} + +nsDisplayBackgroundImage::~nsDisplayBackgroundImage() { + MOZ_COUNT_DTOR(nsDisplayBackgroundImage); + if (mDependentFrame) { + mDependentFrame->RemoveDisplayItem(this); + } +} + +static nsIFrame* GetBackgroundComputedStyleFrame(nsIFrame* aFrame) { + nsIFrame* f; + if (!nsCSSRendering::FindBackgroundFrame(aFrame, &f)) { + // We don't want to bail out if moz-appearance is set on a root + // node. If it has a parent content node, bail because it's not + // a root, other wise keep going in order to let the theme stuff + // draw the background. The canvas really should be drawing the + // bg, but there's no way to hook that up via css. + if (!aFrame->StyleDisplay()->HasAppearance()) { + return nullptr; + } + + nsIContent* content = aFrame->GetContent(); + if (!content || content->GetParent()) { + return nullptr; + } + + f = aFrame; + } + return f; +} + +static void SetBackgroundClipRegion( + DisplayListClipState::AutoSaveRestore& aClipState, nsIFrame* aFrame, + const nsStyleImageLayers::Layer& aLayer, const nsRect& aBackgroundRect, + bool aWillPaintBorder) { + nsCSSRendering::ImageLayerClipState clip; + nsCSSRendering::GetImageLayerClip( + aLayer, aFrame, *aFrame->StyleBorder(), aBackgroundRect, aBackgroundRect, + aWillPaintBorder, aFrame->PresContext()->AppUnitsPerDevPixel(), &clip); + + if (clip.mHasAdditionalBGClipArea) { + aClipState.ClipContentDescendants( + clip.mAdditionalBGClipArea, clip.mBGClipArea, + clip.mHasRoundedCorners ? clip.mRadii : nullptr); + } else { + aClipState.ClipContentDescendants( + clip.mBGClipArea, clip.mHasRoundedCorners ? clip.mRadii : nullptr); + } +} + +/** + * This is used for the find bar highlighter overlay. It's only accessible + * through the AnonymousContent API, so it's not exposed to general web pages. + */ +static bool SpecialCutoutRegionCase(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame, + const nsRect& aBackgroundRect, + nsDisplayList* aList, nscolor aColor) { + nsIContent* content = aFrame->GetContent(); + if (!content) { + return false; + } + + void* cutoutRegion = content->GetProperty(nsGkAtoms::cutoutregion); + if (!cutoutRegion) { + return false; + } + + if (NS_GET_A(aColor) == 0) { + return true; + } + + nsRegion region; + region.Sub(aBackgroundRect, *static_cast<nsRegion*>(cutoutRegion)); + region.MoveBy(aBuilder->ToReferenceFrame(aFrame)); + aList->AppendNewToTop<nsDisplaySolidColorRegion>(aBuilder, aFrame, region, + aColor); + + return true; +} + +enum class TableType : uint8_t { + Table, + TableCol, + TableColGroup, + TableRow, + TableRowGroup, + TableCell, + + MAX, +}; + +enum class TableTypeBits : uint8_t { Count = 3 }; + +static_assert(static_cast<uint8_t>(TableType::MAX) < + (1 << (static_cast<uint8_t>(TableTypeBits::Count) + 1)), + "TableType cannot fit with TableTypeBits::Count"); +TableType GetTableTypeFromFrame(nsIFrame* aFrame); + +static uint16_t CalculateTablePerFrameKey(const uint16_t aIndex, + const TableType aType) { + const uint32_t key = (aIndex << static_cast<uint8_t>(TableTypeBits::Count)) | + static_cast<uint8_t>(aType); + + return static_cast<uint16_t>(key); +} + +static nsDisplayBackgroundImage* CreateBackgroundImage( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsIFrame* aSecondaryFrame, + const nsDisplayBackgroundImage::InitData& aBgData) { + const auto index = aBgData.layer; + + if (aSecondaryFrame) { + const auto tableType = GetTableTypeFromFrame(aFrame); + const uint16_t tableItemIndex = CalculateTablePerFrameKey(index, tableType); + + return MakeDisplayItemWithIndex<nsDisplayTableBackgroundImage>( + aBuilder, aSecondaryFrame, tableItemIndex, aBgData, aFrame); + } + + return MakeDisplayItemWithIndex<nsDisplayBackgroundImage>(aBuilder, aFrame, + index, aBgData); +} + +static nsDisplayThemedBackground* CreateThemedBackground( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsIFrame* aSecondaryFrame, + nsRect& aBgRect) { + if (aSecondaryFrame) { + const uint16_t index = static_cast<uint16_t>(GetTableTypeFromFrame(aFrame)); + return MakeDisplayItemWithIndex<nsDisplayTableThemedBackground>( + aBuilder, aSecondaryFrame, index, aBgRect, aFrame); + } + + return MakeDisplayItem<nsDisplayThemedBackground>(aBuilder, aFrame, aBgRect); +} + +static nsDisplayBackgroundColor* CreateBackgroundColor( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsIFrame* aSecondaryFrame, + nsRect& aBgRect, ComputedStyle* aBgSC, nscolor aColor) { + if (aSecondaryFrame) { + const uint16_t index = static_cast<uint16_t>(GetTableTypeFromFrame(aFrame)); + return MakeDisplayItemWithIndex<nsDisplayTableBackgroundColor>( + aBuilder, aSecondaryFrame, index, aBgRect, aBgSC, aColor, aFrame); + } + + return MakeDisplayItem<nsDisplayBackgroundColor>(aBuilder, aFrame, aBgRect, + aBgSC, aColor); +} + +/*static*/ +bool nsDisplayBackgroundImage::AppendBackgroundItemsToTop( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + const nsRect& aBackgroundRect, nsDisplayList* aList, + bool aAllowWillPaintBorderOptimization, ComputedStyle* aComputedStyle, + const nsRect& aBackgroundOriginRect, nsIFrame* aSecondaryReferenceFrame, + Maybe<nsDisplayListBuilder::AutoBuildingDisplayList>* + aAutoBuildingDisplayList) { + ComputedStyle* bgSC = aComputedStyle; + const nsStyleBackground* bg = nullptr; + nsRect bgRect = aBackgroundRect; + nsRect bgOriginRect = bgRect; + if (!aBackgroundOriginRect.IsEmpty()) { + bgOriginRect = aBackgroundOriginRect; + } + nsPresContext* presContext = aFrame->PresContext(); + bool isThemed = aFrame->IsThemed(); + nsIFrame* dependentFrame = nullptr; + if (!isThemed) { + if (!bgSC) { + dependentFrame = GetBackgroundComputedStyleFrame(aFrame); + if (dependentFrame) { + bgSC = dependentFrame->Style(); + if (dependentFrame == aFrame) { + dependentFrame = nullptr; + } + } + } + if (bgSC) { + bg = bgSC->StyleBackground(); + } + } + + bool drawBackgroundColor = false; + // XUL root frames need special handling for now even though they return true + // from nsCSSRendering::IsCanvasFrame they rely on us painting the background + // image from here, see bug 1665476. + bool drawBackgroundImage = + aFrame->IsXULRootFrame() && aFrame->ComputeShouldPaintBackground().mImage; + nscolor color = NS_RGBA(0, 0, 0, 0); + if (!nsCSSRendering::IsCanvasFrame(aFrame) && bg) { + color = nsCSSRendering::DetermineBackgroundColor( + presContext, bgSC, aFrame, drawBackgroundImage, drawBackgroundColor); + } + + if (SpecialCutoutRegionCase(aBuilder, aFrame, aBackgroundRect, aList, + color)) { + return false; + } + + const nsStyleBorder* borderStyle = aFrame->StyleBorder(); + const nsStyleEffects* effectsStyle = aFrame->StyleEffects(); + bool hasInsetShadow = effectsStyle->HasBoxShadowWithInset(true); + bool willPaintBorder = aAllowWillPaintBorderOptimization && !isThemed && + !hasInsetShadow && borderStyle->HasBorder(); + + // An auxiliary list is necessary in case we have background blending; if that + // is the case, background items need to be wrapped by a blend container to + // isolate blending to the background + nsDisplayList bgItemList; + // Even if we don't actually have a background color to paint, we may still + // need to create an item for hit testing. + if ((drawBackgroundColor && color != NS_RGBA(0, 0, 0, 0)) || + aBuilder->IsForEventDelivery()) { + if (aAutoBuildingDisplayList && !*aAutoBuildingDisplayList) { + aAutoBuildingDisplayList->emplace(aBuilder, aFrame); + } + Maybe<DisplayListClipState::AutoSaveRestore> clipState; + nsRect bgColorRect = bgRect; + if (bg && !aBuilder->IsForEventDelivery()) { + // Disable the will-paint-border optimization for background + // colors with no border-radius. Enabling it for background colors + // doesn't help much (there are no tiling issues) and clipping the + // background breaks detection of the element's border-box being + // opaque. For nonzero border-radius we still need it because we + // want to inset the background if possible to avoid antialiasing + // artifacts along the rounded corners. + bool useWillPaintBorderOptimization = + willPaintBorder && + nsLayoutUtils::HasNonZeroCorner(borderStyle->mBorderRadius); + + nsCSSRendering::ImageLayerClipState clip; + nsCSSRendering::GetImageLayerClip( + bg->BottomLayer(), aFrame, *aFrame->StyleBorder(), bgRect, bgRect, + useWillPaintBorderOptimization, + aFrame->PresContext()->AppUnitsPerDevPixel(), &clip); + + bgColorRect = bgColorRect.Intersect(clip.mBGClipArea); + if (clip.mHasAdditionalBGClipArea) { + bgColorRect = bgColorRect.Intersect(clip.mAdditionalBGClipArea); + } + if (clip.mHasRoundedCorners) { + clipState.emplace(aBuilder); + clipState->ClipContentDescendants(clip.mBGClipArea, clip.mRadii); + } + } + + nsDisplayBackgroundColor* bgItem = CreateBackgroundColor( + aBuilder, aFrame, aSecondaryReferenceFrame, bgColorRect, bgSC, + drawBackgroundColor ? color : NS_RGBA(0, 0, 0, 0)); + + if (bgItem) { + bgItem->SetDependentFrame(aBuilder, dependentFrame); + bgItemList.AppendToTop(bgItem); + } + } + + if (isThemed) { + nsDisplayThemedBackground* bgItem = CreateThemedBackground( + aBuilder, aFrame, aSecondaryReferenceFrame, bgRect); + + if (bgItem) { + bgItem->Init(aBuilder); + bgItemList.AppendToTop(bgItem); + } + + aList->AppendToTop(&bgItemList); + return true; + } + + if (!bg || !drawBackgroundImage) { + aList->AppendToTop(&bgItemList); + return false; + } + + const ActiveScrolledRoot* asr = aBuilder->CurrentActiveScrolledRoot(); + + bool needBlendContainer = false; + + // Passing bg == nullptr in this macro will result in one iteration with + // i = 0. + NS_FOR_VISIBLE_IMAGE_LAYERS_BACK_TO_FRONT(i, bg->mImage) { + if (bg->mImage.mLayers[i].mImage.IsNone()) { + continue; + } + + if (aAutoBuildingDisplayList && !*aAutoBuildingDisplayList) { + aAutoBuildingDisplayList->emplace(aBuilder, aFrame); + } + + if (bg->mImage.mLayers[i].mBlendMode != StyleBlend::Normal) { + needBlendContainer = true; + } + + DisplayListClipState::AutoSaveRestore clipState(aBuilder); + if (!aBuilder->IsForEventDelivery()) { + const nsStyleImageLayers::Layer& layer = bg->mImage.mLayers[i]; + SetBackgroundClipRegion(clipState, aFrame, layer, bgRect, + willPaintBorder); + } + + nsDisplayList thisItemList; + nsDisplayBackgroundImage::InitData bgData = + nsDisplayBackgroundImage::GetInitData(aBuilder, aFrame, i, bgOriginRect, + bgSC); + + if (bgData.shouldFixToViewport) { + auto* displayData = aBuilder->GetCurrentFixedBackgroundDisplayData(); + nsDisplayListBuilder::AutoBuildingDisplayList buildingDisplayList( + aBuilder, aFrame, aBuilder->GetVisibleRect(), + aBuilder->GetDirtyRect()); + + nsDisplayListBuilder::AutoCurrentActiveScrolledRootSetter asrSetter( + aBuilder); + if (displayData) { + asrSetter.SetCurrentActiveScrolledRoot( + displayData->mContainingBlockActiveScrolledRoot); + if (nsLayoutUtils::UsesAsyncScrolling(aFrame)) { + // Override the dirty rect on the builder to be the dirty rect of + // the viewport. + // displayData->mDirtyRect is relative to the presshell's viewport + // frame (the root frame), and we need it to be relative to aFrame. + nsIFrame* rootFrame = + aBuilder->CurrentPresShellState()->mPresShell->GetRootFrame(); + // There cannot be any transforms between aFrame and rootFrame + // because then bgData.shouldFixToViewport would have been false. + nsRect visibleRect = + displayData->mVisibleRect + aFrame->GetOffsetTo(rootFrame); + aBuilder->SetVisibleRect(visibleRect); + nsRect dirtyRect = + displayData->mDirtyRect + aFrame->GetOffsetTo(rootFrame); + aBuilder->SetDirtyRect(dirtyRect); + } + } + + nsDisplayBackgroundImage* bgItem = nullptr; + { + // The clip is captured by the nsDisplayFixedPosition, so clear the + // clip for the nsDisplayBackgroundImage inside. + DisplayListClipState::AutoSaveRestore bgImageClip(aBuilder); + bgImageClip.Clear(); + bgItem = CreateBackgroundImage(aBuilder, aFrame, + aSecondaryReferenceFrame, bgData); + } + if (bgItem) { + bgItem->SetDependentFrame(aBuilder, dependentFrame); + + thisItemList.AppendToTop( + nsDisplayFixedPosition::CreateForFixedBackground( + aBuilder, aFrame, aSecondaryReferenceFrame, bgItem, i)); + } + } else { // bgData.shouldFixToViewport == false + nsDisplayBackgroundImage* bgItem = CreateBackgroundImage( + aBuilder, aFrame, aSecondaryReferenceFrame, bgData); + if (bgItem) { + bgItem->SetDependentFrame(aBuilder, dependentFrame); + thisItemList.AppendToTop(bgItem); + } + } + + if (bg->mImage.mLayers[i].mBlendMode != StyleBlend::Normal) { + DisplayListClipState::AutoSaveRestore blendClip(aBuilder); + // asr is scrolled. Even if we wrap a fixed background layer, that's + // fine, because the item will have a scrolled clip that limits the + // item with respect to asr. + if (aSecondaryReferenceFrame) { + const auto tableType = GetTableTypeFromFrame(aFrame); + const uint16_t index = CalculateTablePerFrameKey(i + 1, tableType); + + thisItemList.AppendNewToTopWithIndex<nsDisplayTableBlendMode>( + aBuilder, aSecondaryReferenceFrame, index, &thisItemList, + bg->mImage.mLayers[i].mBlendMode, asr, aFrame, true); + } else { + thisItemList.AppendNewToTopWithIndex<nsDisplayBlendMode>( + aBuilder, aFrame, i + 1, &thisItemList, + bg->mImage.mLayers[i].mBlendMode, asr, true); + } + } + bgItemList.AppendToTop(&thisItemList); + } + + if (needBlendContainer) { + DisplayListClipState::AutoSaveRestore blendContainerClip(aBuilder); + + bgItemList.AppendToTop( + nsDisplayBlendContainer::CreateForBackgroundBlendMode( + aBuilder, aFrame, aSecondaryReferenceFrame, &bgItemList, asr)); + } + + aList->AppendToTop(&bgItemList); + return false; +} + +// Check that the rounded border of aFrame, added to aToReferenceFrame, +// intersects aRect. Assumes that the unrounded border has already +// been checked for intersection. +static bool RoundedBorderIntersectsRect(nsIFrame* aFrame, + const nsPoint& aFrameToReferenceFrame, + const nsRect& aTestRect) { + if (!nsRect(aFrameToReferenceFrame, aFrame->GetSize()).Intersects(aTestRect)) + return false; + + nscoord radii[8]; + return !aFrame->GetBorderRadii(radii) || + nsLayoutUtils::RoundedRectIntersectsRect( + nsRect(aFrameToReferenceFrame, aFrame->GetSize()), radii, + aTestRect); +} + +// Returns TRUE if aContainedRect is guaranteed to be contained in +// the rounded rect defined by aRoundedRect and aRadii. Complex cases are +// handled conservatively by returning FALSE in some situations where +// a more thorough analysis could return TRUE. +// +// See also RoundedRectIntersectsRect. +static bool RoundedRectContainsRect(const nsRect& aRoundedRect, + const nscoord aRadii[8], + const nsRect& aContainedRect) { + nsRegion rgn = nsLayoutUtils::RoundedRectIntersectRect(aRoundedRect, aRadii, + aContainedRect); + return rgn.Contains(aContainedRect); +} + +bool nsDisplayBackgroundImage::CanOptimizeToImageLayer( + LayerManager* aManager, nsDisplayListBuilder* aBuilder) { + if (!mBackgroundStyle) { + return false; + } + + // We currently can't handle tiled backgrounds. + if (!mDestRect.Contains(mFillRect)) { + return false; + } + + // For 'contain' and 'cover', we allow any pixel of the image to be sampled + // because there isn't going to be any spriting/atlasing going on. + const nsStyleImageLayers::Layer& layer = + mBackgroundStyle->StyleBackground()->mImage.mLayers[mLayer]; + bool allowPartialImages = layer.mSize.IsContain() || layer.mSize.IsCover(); + if (!allowPartialImages && !mFillRect.Contains(mDestRect)) { + return false; + } + + return nsDisplayImageContainer::CanOptimizeToImageLayer(aManager, aBuilder); +} + +nsRect nsDisplayBackgroundImage::GetDestRect() const { return mDestRect; } + +already_AddRefed<imgIContainer> nsDisplayBackgroundImage::GetImage() { + nsCOMPtr<imgIContainer> image = mImage; + return image.forget(); +} + +nsDisplayBackgroundImage::ImageLayerization +nsDisplayBackgroundImage::ShouldCreateOwnLayer(nsDisplayListBuilder* aBuilder, + LayerManager* aManager) { + if (ForceActiveLayers()) { + return WHENEVER_POSSIBLE; + } + + nsIFrame* backgroundStyleFrame = + nsCSSRendering::FindBackgroundStyleFrame(StyleFrame()); + if (ActiveLayerTracker::IsBackgroundPositionAnimated(aBuilder, + backgroundStyleFrame)) { + return WHENEVER_POSSIBLE; + } + + if (StaticPrefs::layout_animated_image_layers_enabled() && mBackgroundStyle) { + const nsStyleImageLayers::Layer& layer = + mBackgroundStyle->StyleBackground()->mImage.mLayers[mLayer]; + const auto* image = &layer.mImage; + if (auto* request = image->GetImageRequest()) { + nsCOMPtr<imgIContainer> image; + if (NS_SUCCEEDED(request->GetImage(getter_AddRefs(image))) && image) { + bool animated = false; + if (NS_SUCCEEDED(image->GetAnimated(&animated)) && animated) { + return WHENEVER_POSSIBLE; + } + } + } + } + + if (nsLayoutUtils::GPUImageScalingEnabled() && + aManager->IsCompositingCheap()) { + return ONLY_FOR_SCALING; + } + + return NO_LAYER_NEEDED; +} + +static void CheckForBorderItem(nsDisplayItem* aItem, uint32_t& aFlags) { + nsDisplayItem* nextItem = aItem->GetAbove(); + while (nextItem && nextItem->GetType() == DisplayItemType::TYPE_BACKGROUND) { + nextItem = nextItem->GetAbove(); + } + if (nextItem && nextItem->Frame() == aItem->Frame() && + nextItem->GetType() == DisplayItemType::TYPE_BORDER) { + aFlags |= nsCSSRendering::PAINTBG_WILL_PAINT_BORDER; + } +} + +LayerState nsDisplayBackgroundImage::GetLayerState( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aParameters) { + mImageFlags = aBuilder->GetBackgroundPaintFlags(); + CheckForBorderItem(this, mImageFlags); + + ImageLayerization shouldLayerize = ShouldCreateOwnLayer(aBuilder, aManager); + if (shouldLayerize == NO_LAYER_NEEDED) { + // We can skip the call to CanOptimizeToImageLayer if we don't want a + // layer anyway. + return LayerState::LAYER_NONE; + } + + if (CanOptimizeToImageLayer(aManager, aBuilder)) { + if (shouldLayerize == WHENEVER_POSSIBLE) { + return LayerState::LAYER_ACTIVE; + } + + MOZ_ASSERT(shouldLayerize == ONLY_FOR_SCALING, + "unhandled ImageLayerization value?"); + + MOZ_ASSERT(mImage); + int32_t imageWidth; + int32_t imageHeight; + mImage->GetWidth(&imageWidth); + mImage->GetHeight(&imageHeight); + NS_ASSERTION(imageWidth != 0 && imageHeight != 0, "Invalid image size!"); + + int32_t appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel(); + LayoutDeviceRect destRect = + LayoutDeviceRect::FromAppUnits(GetDestRect(), appUnitsPerDevPixel); + + const LayerRect destLayerRect = destRect * aParameters.Scale(); + + // Calculate the scaling factor for the frame. + const gfxSize scale = gfxSize(destLayerRect.width / imageWidth, + destLayerRect.height / imageHeight); + + if ((scale.width != 1.0f || scale.height != 1.0f) && + (destLayerRect.width * destLayerRect.height >= 64 * 64)) { + // Separate this image into a layer. + // There's no point in doing this if we are not scaling at all or if the + // target size is pretty small. + return LayerState::LAYER_ACTIVE; + } + } + + return LayerState::LAYER_NONE; +} + +already_AddRefed<Layer> nsDisplayBackgroundImage::BuildLayer( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aParameters) { + RefPtr<ImageLayer> layer = static_cast<ImageLayer*>( + aManager->GetLayerBuilder()->GetLeafLayerFor(aBuilder, this)); + if (!layer) { + layer = aManager->CreateImageLayer(); + if (!layer) { + return nullptr; + } + } + RefPtr<ImageContainer> imageContainer = GetContainer(aManager, aBuilder); + layer->SetContainer(imageContainer); + ConfigureLayer(layer, aParameters); + return layer.forget(); +} + +bool nsDisplayBackgroundImage::CanBuildWebRenderDisplayItems( + LayerManager* aManager, nsDisplayListBuilder* aDisplayListBuilder) { + if (aDisplayListBuilder) { + mImageFlags = aDisplayListBuilder->GetBackgroundPaintFlags(); + } + + return mBackgroundStyle->StyleBackground()->mImage.mLayers[mLayer].mClip != + StyleGeometryBox::Text && + nsCSSRendering::CanBuildWebRenderDisplayItemsForStyleImageLayer( + aManager, *StyleFrame()->PresContext(), StyleFrame(), + mBackgroundStyle->StyleBackground(), mLayer, mImageFlags); +} + +bool nsDisplayBackgroundImage::CreateWebRenderCommands( + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + if (!CanBuildWebRenderDisplayItems(aManager->LayerManager(), + aDisplayListBuilder)) { + return false; + } + + CheckForBorderItem(this, mImageFlags); + nsCSSRendering::PaintBGParams params = + nsCSSRendering::PaintBGParams::ForSingleLayer( + *StyleFrame()->PresContext(), GetPaintRect(), mBackgroundRect, + StyleFrame(), mImageFlags, mLayer, CompositionOp::OP_OVER); + params.bgClipRect = &mBounds; + ImgDrawResult result = + nsCSSRendering::BuildWebRenderDisplayItemsForStyleImageLayer( + params, aBuilder, aResources, aSc, aManager, this); + if (result == ImgDrawResult::NOT_SUPPORTED) { + return false; + } + + nsDisplayBackgroundGeometry::UpdateDrawResult(this, result); + return true; +} + +void nsDisplayBackgroundImage::HitTest(nsDisplayListBuilder* aBuilder, + const nsRect& aRect, + HitTestState* aState, + nsTArray<nsIFrame*>* aOutFrames) { + if (RoundedBorderIntersectsRect(mFrame, ToReferenceFrame(), aRect)) { + aOutFrames->AppendElement(mFrame); + } +} + +bool nsDisplayBackgroundImage::ComputeVisibility(nsDisplayListBuilder* aBuilder, + nsRegion* aVisibleRegion) { + if (!nsDisplayImageContainer::ComputeVisibility(aBuilder, aVisibleRegion)) { + return false; + } + + // Return false if the background was propagated away from this + // frame. We don't want this display item to show up and confuse + // anything. + return mBackgroundStyle; +} + +/* static */ +nsRegion nsDisplayBackgroundImage::GetInsideClipRegion( + const nsDisplayItem* aItem, StyleGeometryBox aClip, const nsRect& aRect, + const nsRect& aBackgroundRect) { + nsRegion result; + if (aRect.IsEmpty()) { + return result; + } + + nsIFrame* frame = aItem->Frame(); + + nsRect clipRect = aBackgroundRect; + if (frame->IsCanvasFrame()) { + nsCanvasFrame* canvasFrame = static_cast<nsCanvasFrame*>(frame); + clipRect = canvasFrame->CanvasArea() + aItem->ToReferenceFrame(); + } else if (aClip == StyleGeometryBox::PaddingBox || + aClip == StyleGeometryBox::ContentBox) { + nsMargin border = frame->GetUsedBorder(); + if (aClip == StyleGeometryBox::ContentBox) { + border += frame->GetUsedPadding(); + } + border.ApplySkipSides(frame->GetSkipSides()); + clipRect.Deflate(border); + } + + return clipRect.Intersect(aRect); +} + +nsRegion nsDisplayBackgroundImage::GetOpaqueRegion( + nsDisplayListBuilder* aBuilder, bool* aSnap) const { + nsRegion result; + *aSnap = false; + + if (!mBackgroundStyle) { + return result; + } + + *aSnap = true; + + // For StyleBoxDecorationBreak::Slice, don't try to optimize here, since + // this could easily lead to O(N^2) behavior inside InlineBackgroundData, + // which expects frames to be sent to it in content order, not reverse + // content order which we'll produce here. + // Of course, if there's only one frame in the flow, it doesn't matter. + if (mFrame->StyleBorder()->mBoxDecorationBreak == + StyleBoxDecorationBreak::Clone || + (!mFrame->GetPrevContinuation() && !mFrame->GetNextContinuation())) { + const nsStyleImageLayers::Layer& layer = + mBackgroundStyle->StyleBackground()->mImage.mLayers[mLayer]; + if (layer.mImage.IsOpaque() && layer.mBlendMode == StyleBlend::Normal && + layer.mRepeat.mXRepeat != StyleImageLayerRepeat::Space && + layer.mRepeat.mYRepeat != StyleImageLayerRepeat::Space && + layer.mClip != StyleGeometryBox::Text) { + result = GetInsideClipRegion(this, layer.mClip, mBounds, mBackgroundRect); + } + } + + return result; +} + +Maybe<nscolor> nsDisplayBackgroundImage::IsUniform( + nsDisplayListBuilder* aBuilder) const { + if (!mBackgroundStyle) { + return Some(NS_RGBA(0, 0, 0, 0)); + } + return Nothing(); +} + +nsRect nsDisplayBackgroundImage::GetPositioningArea() const { + if (!mBackgroundStyle) { + return nsRect(); + } + nsIFrame* attachedToFrame; + bool transformedFixed; + return nsCSSRendering::ComputeImageLayerPositioningArea( + mFrame->PresContext(), mFrame, mBackgroundRect, + mBackgroundStyle->StyleBackground()->mImage.mLayers[mLayer], + &attachedToFrame, &transformedFixed) + + ToReferenceFrame(); +} + +bool nsDisplayBackgroundImage::RenderingMightDependOnPositioningAreaSizeChange() + const { + if (!mBackgroundStyle) { + return false; + } + + nscoord radii[8]; + if (mFrame->GetBorderRadii(radii)) { + // A change in the size of the positioning area might change the position + // of the rounded corners. + return true; + } + + const nsStyleImageLayers::Layer& layer = + mBackgroundStyle->StyleBackground()->mImage.mLayers[mLayer]; + if (layer.RenderingMightDependOnPositioningAreaSizeChange()) { + return true; + } + return false; +} + +void nsDisplayBackgroundImage::Paint(nsDisplayListBuilder* aBuilder, + gfxContext* aCtx) { + PaintInternal(aBuilder, aCtx, GetPaintRect(), &mBounds); +} + +void nsDisplayBackgroundImage::PaintInternal(nsDisplayListBuilder* aBuilder, + gfxContext* aCtx, + const nsRect& aBounds, + nsRect* aClipRect) { + gfxContext* ctx = aCtx; + StyleGeometryBox clip = + mBackgroundStyle->StyleBackground()->mImage.mLayers[mLayer].mClip; + + if (clip == StyleGeometryBox::Text) { + if (!GenerateAndPushTextMask(StyleFrame(), aCtx, mBackgroundRect, + aBuilder)) { + return; + } + } + + nsCSSRendering::PaintBGParams params = + nsCSSRendering::PaintBGParams::ForSingleLayer( + *StyleFrame()->PresContext(), aBounds, mBackgroundRect, StyleFrame(), + mImageFlags, mLayer, CompositionOp::OP_OVER); + params.bgClipRect = aClipRect; + ImgDrawResult result = nsCSSRendering::PaintStyleImageLayer(params, *aCtx); + + if (clip == StyleGeometryBox::Text) { + ctx->PopGroupAndBlend(); + } + + nsDisplayBackgroundGeometry::UpdateDrawResult(this, result); +} + +void nsDisplayBackgroundImage::ComputeInvalidationRegion( + nsDisplayListBuilder* aBuilder, const nsDisplayItemGeometry* aGeometry, + nsRegion* aInvalidRegion) const { + if (!mBackgroundStyle) { + return; + } + + auto* geometry = static_cast<const nsDisplayBackgroundGeometry*>(aGeometry); + + bool snap; + nsRect bounds = GetBounds(aBuilder, &snap); + nsRect positioningArea = GetPositioningArea(); + if (positioningArea.TopLeft() != geometry->mPositioningArea.TopLeft() || + (positioningArea.Size() != geometry->mPositioningArea.Size() && + RenderingMightDependOnPositioningAreaSizeChange())) { + // Positioning area changed in a way that could cause everything to change, + // so invalidate everything (both old and new painting areas). + aInvalidRegion->Or(bounds, geometry->mBounds); + return; + } + if (!mDestRect.IsEqualInterior(geometry->mDestRect)) { + // Dest area changed in a way that could cause everything to change, + // so invalidate everything (both old and new painting areas). + aInvalidRegion->Or(bounds, geometry->mBounds); + return; + } + if (aBuilder->ShouldSyncDecodeImages()) { + const auto& image = + mBackgroundStyle->StyleBackground()->mImage.mLayers[mLayer].mImage; + if (image.IsImageRequestType() && + geometry->ShouldInvalidateToSyncDecodeImages()) { + aInvalidRegion->Or(*aInvalidRegion, bounds); + } + } + if (!bounds.IsEqualInterior(geometry->mBounds)) { + // Positioning area is unchanged, so invalidate just the change in the + // painting area. + aInvalidRegion->Xor(bounds, geometry->mBounds); + } +} + +nsRect nsDisplayBackgroundImage::GetBounds(nsDisplayListBuilder* aBuilder, + bool* aSnap) const { + *aSnap = true; + return mBounds; +} + +nsRect nsDisplayBackgroundImage::GetBoundsInternal( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrameForBounds) { + // This allows nsDisplayTableBackgroundImage to change the frame used for + // bounds calculation. + nsIFrame* frame = aFrameForBounds ? aFrameForBounds : mFrame; + + nsPresContext* presContext = frame->PresContext(); + + if (!mBackgroundStyle) { + return nsRect(); + } + + nsRect clipRect = mBackgroundRect; + if (frame->IsCanvasFrame()) { + nsCanvasFrame* canvasFrame = static_cast<nsCanvasFrame*>(frame); + clipRect = canvasFrame->CanvasArea() + ToReferenceFrame(); + } + const nsStyleImageLayers::Layer& layer = + mBackgroundStyle->StyleBackground()->mImage.mLayers[mLayer]; + return nsCSSRendering::GetBackgroundLayerRect( + presContext, frame, mBackgroundRect, clipRect, layer, + aBuilder->GetBackgroundPaintFlags()); +} + +nsDisplayTableBackgroundImage::nsDisplayTableBackgroundImage( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, const InitData& aData, + nsIFrame* aCellFrame) + : nsDisplayBackgroundImage(aBuilder, aFrame, aData, aCellFrame), + mStyleFrame(aCellFrame) { + if (aBuilder->IsRetainingDisplayList()) { + mStyleFrame->AddDisplayItem(this); + } +} + +nsDisplayTableBackgroundImage::~nsDisplayTableBackgroundImage() { + if (mStyleFrame) { + mStyleFrame->RemoveDisplayItem(this); + } +} + +bool nsDisplayTableBackgroundImage::IsInvalid(nsRect& aRect) const { + bool result = mStyleFrame ? mStyleFrame->IsInvalid(aRect) : false; + aRect += ToReferenceFrame(); + return result; +} + +nsDisplayThemedBackground::nsDisplayThemedBackground( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + const nsRect& aBackgroundRect) + : nsPaintedDisplayItem(aBuilder, aFrame), mBackgroundRect(aBackgroundRect) { + MOZ_COUNT_CTOR(nsDisplayThemedBackground); +} + +void nsDisplayThemedBackground::Init(nsDisplayListBuilder* aBuilder) { + const nsStyleDisplay* disp = StyleFrame()->StyleDisplay(); + mAppearance = disp->EffectiveAppearance(); + StyleFrame()->IsThemed(disp, &mThemeTransparency); + + // Perform necessary RegisterThemeGeometry + nsITheme* theme = StyleFrame()->PresContext()->Theme(); + nsITheme::ThemeGeometryType type = + theme->ThemeGeometryTypeForWidget(StyleFrame(), mAppearance); + if (type != nsITheme::eThemeGeometryTypeUnknown) { + RegisterThemeGeometry(aBuilder, this, StyleFrame(), type); + } + + if (mAppearance == StyleAppearance::MozWinBorderlessGlass || + mAppearance == StyleAppearance::MozWinGlass) { + aBuilder->SetGlassDisplayItem(this); + } + + mBounds = GetBoundsInternal(); +} + +void nsDisplayThemedBackground::WriteDebugInfo(std::stringstream& aStream) { + aStream << " (themed, appearance:" << (int)mAppearance << ")"; +} + +void nsDisplayThemedBackground::HitTest(nsDisplayListBuilder* aBuilder, + const nsRect& aRect, + HitTestState* aState, + nsTArray<nsIFrame*>* aOutFrames) { + // Assume that any point in our background rect is a hit. + if (mBackgroundRect.Intersects(aRect)) { + aOutFrames->AppendElement(mFrame); + } +} + +nsRegion nsDisplayThemedBackground::GetOpaqueRegion( + nsDisplayListBuilder* aBuilder, bool* aSnap) const { + nsRegion result; + *aSnap = false; + + if (mThemeTransparency == nsITheme::eOpaque) { + *aSnap = true; + result = mBackgroundRect; + } + return result; +} + +Maybe<nscolor> nsDisplayThemedBackground::IsUniform( + nsDisplayListBuilder* aBuilder) const { + if (mAppearance == StyleAppearance::MozWinBorderlessGlass || + mAppearance == StyleAppearance::MozWinGlass) { + return Some(NS_RGBA(0, 0, 0, 0)); + } + return Nothing(); +} + +nsRect nsDisplayThemedBackground::GetPositioningArea() const { + return mBackgroundRect; +} + +void nsDisplayThemedBackground::Paint(nsDisplayListBuilder* aBuilder, + gfxContext* aCtx) { + PaintInternal(aBuilder, aCtx, GetPaintRect(), nullptr); +} + +void nsDisplayThemedBackground::PaintInternal(nsDisplayListBuilder* aBuilder, + gfxContext* aCtx, + const nsRect& aBounds, + nsRect* aClipRect) { + // XXXzw this ignores aClipRect. + nsPresContext* presContext = StyleFrame()->PresContext(); + nsITheme* theme = presContext->Theme(); + nsRect drawing(mBackgroundRect); + theme->GetWidgetOverflow(presContext->DeviceContext(), StyleFrame(), + mAppearance, &drawing); + drawing.IntersectRect(drawing, aBounds); + theme->DrawWidgetBackground(aCtx, StyleFrame(), mAppearance, mBackgroundRect, + drawing); +} + +bool nsDisplayThemedBackground::CreateWebRenderCommands( + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + nsITheme* theme = StyleFrame()->PresContext()->Theme(); + return theme->CreateWebRenderCommandsForWidget(aBuilder, aResources, aSc, + aManager, StyleFrame(), + mAppearance, mBackgroundRect); +} + +bool nsDisplayThemedBackground::IsWindowActive() const { + EventStates docState = mFrame->GetContent()->OwnerDoc()->GetDocumentState(); + return !docState.HasState(NS_DOCUMENT_STATE_WINDOW_INACTIVE); +} + +void nsDisplayThemedBackground::ComputeInvalidationRegion( + nsDisplayListBuilder* aBuilder, const nsDisplayItemGeometry* aGeometry, + nsRegion* aInvalidRegion) const { + auto* geometry = + static_cast<const nsDisplayThemedBackgroundGeometry*>(aGeometry); + + bool snap; + nsRect bounds = GetBounds(aBuilder, &snap); + nsRect positioningArea = GetPositioningArea(); + if (!positioningArea.IsEqualInterior(geometry->mPositioningArea)) { + // Invalidate everything (both old and new painting areas). + aInvalidRegion->Or(bounds, geometry->mBounds); + return; + } + if (!bounds.IsEqualInterior(geometry->mBounds)) { + // Positioning area is unchanged, so invalidate just the change in the + // painting area. + aInvalidRegion->Xor(bounds, geometry->mBounds); + } + nsITheme* theme = StyleFrame()->PresContext()->Theme(); + if (theme->WidgetAppearanceDependsOnWindowFocus(mAppearance) && + IsWindowActive() != geometry->mWindowIsActive) { + aInvalidRegion->Or(*aInvalidRegion, bounds); + } +} + +nsRect nsDisplayThemedBackground::GetBounds(nsDisplayListBuilder* aBuilder, + bool* aSnap) const { + *aSnap = true; + return mBounds; +} + +nsRect nsDisplayThemedBackground::GetBoundsInternal() { + nsPresContext* presContext = mFrame->PresContext(); + + nsRect r = mBackgroundRect - ToReferenceFrame(); + presContext->Theme()->GetWidgetOverflow( + presContext->DeviceContext(), mFrame, + mFrame->StyleDisplay()->EffectiveAppearance(), &r); + return r + ToReferenceFrame(); +} + +void nsDisplayImageContainer::ConfigureLayer( + ImageLayer* aLayer, const ContainerLayerParameters& aParameters) { + aLayer->SetSamplingFilter(nsLayoutUtils::GetSamplingFilterForFrame(mFrame)); + + nsCOMPtr<imgIContainer> image = GetImage(); + MOZ_ASSERT(image); + int32_t imageWidth; + int32_t imageHeight; + image->GetWidth(&imageWidth); + image->GetHeight(&imageHeight); + NS_ASSERTION(imageWidth != 0 && imageHeight != 0, "Invalid image size!"); + + if (imageWidth > 0 && imageHeight > 0) { + // We're actually using the ImageContainer. Let our frame know that it + // should consider itself to have painted successfully. + UpdateDrawResult(ImgDrawResult::SUCCESS); + } + + // It's possible (for example, due to downscale-during-decode) that the + // ImageContainer this ImageLayer is holding has a different size from the + // intrinsic size of the image. For this reason we compute the transform using + // the ImageContainer's size rather than the image's intrinsic size. + // XXX(seth): In reality, since the size of the ImageContainer may change + // asynchronously, this is not enough. Bug 1183378 will provide a more + // complete fix, but this solution is safe in more cases than simply relying + // on the intrinsic size. + IntSize containerSize = aLayer->GetContainer() + ? aLayer->GetContainer()->GetCurrentSize() + : IntSize(imageWidth, imageHeight); + + const int32_t factor = mFrame->PresContext()->AppUnitsPerDevPixel(); + const LayoutDeviceRect destRect( + LayoutDeviceIntRect::FromAppUnitsToNearest(GetDestRect(), factor)); + + const LayoutDevicePoint p = destRect.TopLeft(); + Matrix transform = Matrix::Translation(p.x + aParameters.mOffset.x, + p.y + aParameters.mOffset.y); + transform.PreScale(destRect.width / containerSize.width, + destRect.height / containerSize.height); + aLayer->SetBaseTransform(gfx::Matrix4x4::From2D(transform)); +} + +already_AddRefed<ImageContainer> nsDisplayImageContainer::GetContainer( + LayerManager* aManager, nsDisplayListBuilder* aBuilder) { + nsCOMPtr<imgIContainer> image = GetImage(); + if (!image) { + MOZ_ASSERT_UNREACHABLE( + "Must call CanOptimizeToImage() and get true " + "before calling GetContainer()"); + return nullptr; + } + + uint32_t flags = imgIContainer::FLAG_ASYNC_NOTIFY; + if (aBuilder->ShouldSyncDecodeImages()) { + flags |= imgIContainer::FLAG_SYNC_DECODE; + } + + RefPtr<ImageContainer> container = image->GetImageContainer(aManager, flags); + if (!container || !container->HasCurrentImage()) { + return nullptr; + } + + return container.forget(); +} + +bool nsDisplayImageContainer::CanOptimizeToImageLayer( + LayerManager* aManager, nsDisplayListBuilder* aBuilder) { + uint32_t flags = aBuilder->ShouldSyncDecodeImages() + ? imgIContainer::FLAG_SYNC_DECODE + : imgIContainer::FLAG_NONE; + + nsCOMPtr<imgIContainer> image = GetImage(); + if (!image) { + return false; + } + + if (!image->IsImageContainerAvailable(aManager, flags)) { + return false; + } + + int32_t imageWidth; + int32_t imageHeight; + image->GetWidth(&imageWidth); + image->GetHeight(&imageHeight); + + if (imageWidth == 0 || imageHeight == 0) { + NS_ASSERTION(false, "invalid image size"); + return false; + } + + const int32_t factor = mFrame->PresContext()->AppUnitsPerDevPixel(); + const LayoutDeviceRect destRect( + LayoutDeviceIntRect::FromAppUnitsToNearest(GetDestRect(), factor)); + + // Calculate the scaling factor for the frame. + const gfxSize scale = + gfxSize(destRect.width / imageWidth, destRect.height / imageHeight); + + if (scale.width < 0.34 || scale.height < 0.34) { + // This would look awful as long as we can't use high-quality downscaling + // for image layers (bug 803703), so don't turn this into an image layer. + return false; + } + + if (mFrame->IsImageFrame() || mFrame->IsImageControlFrame()) { + // Image layer doesn't support draw focus ring for image map. + nsImageFrame* f = static_cast<nsImageFrame*>(mFrame); + if (f->HasImageMap()) { + return false; + } + } + + return true; +} + +#if defined(MOZ_REFLOW_PERF_DSP) && defined(MOZ_REFLOW_PERF) +void nsDisplayReflowCount::Paint(nsDisplayListBuilder* aBuilder, + gfxContext* aCtx) { + mFrame->PresShell()->PaintCount(mFrameName, aCtx, mFrame->PresContext(), + mFrame, ToReferenceFrame(), mColor); +} +#endif + +void nsDisplayBackgroundColor::ApplyOpacity(nsDisplayListBuilder* aBuilder, + float aOpacity, + const DisplayItemClipChain* aClip) { + NS_ASSERTION(CanApplyOpacity(), "ApplyOpacity should be allowed"); + mColor.a = mColor.a * aOpacity; + IntersectClip(aBuilder, aClip, false); +} + +bool nsDisplayBackgroundColor::CanApplyOpacity() const { + // Don't apply opacity if the background color is animated since the color is + // going to be changed on the compositor. + return !EffectCompositor::HasAnimationsForCompositor( + mFrame, DisplayItemType::TYPE_BACKGROUND_COLOR); +} + +LayerState nsDisplayBackgroundColor::GetLayerState( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aParameters) { + if (ForceActiveLayers() && !HasBackgroundClipText()) { + return LayerState::LAYER_ACTIVE; + } + + if (EffectCompositor::HasAnimationsForCompositor( + mFrame, DisplayItemType::TYPE_BACKGROUND_COLOR)) { + return LayerState::LAYER_ACTIVE_FORCE; + } + + return LayerState::LAYER_NONE; +} + +already_AddRefed<Layer> nsDisplayBackgroundColor::BuildLayer( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aContainerParameters) { + if (mColor == sRGBColor()) { + return nullptr; + } + + RefPtr<ColorLayer> layer = static_cast<ColorLayer*>( + aManager->GetLayerBuilder()->GetLeafLayerFor(aBuilder, this)); + if (!layer) { + layer = aManager->CreateColorLayer(); + if (!layer) { + return nullptr; + } + } + layer->SetColor(ToDeviceColor(mColor)); + + int32_t appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel(); + layer->SetBounds(mBackgroundRect.ToNearestPixels(appUnitsPerDevPixel)); + layer->SetBaseTransform(gfx::Matrix4x4::Translation( + aContainerParameters.mOffset.x, aContainerParameters.mOffset.y, 0)); + + // Both nsDisplayBackgroundColor and nsDisplayTableBackgroundColor use this + // function, but only nsDisplayBackgroundColor supports compositor animations. + if (GetType() == DisplayItemType::TYPE_BACKGROUND_COLOR) { + nsDisplayListBuilder::AddAnimationsAndTransitionsToLayer( + layer, aBuilder, this, mFrame, GetType()); + } + return layer.forget(); +} + +bool nsDisplayBackgroundColor::CreateWebRenderCommands( + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + if (mColor == sRGBColor()) { + return true; + } + + if (HasBackgroundClipText()) { + return false; + } + + uint64_t animationsId = 0; + // We don't support background-color animations on table elements yet. + if (GetType() == DisplayItemType::TYPE_BACKGROUND_COLOR) { + animationsId = + AddAnimationsForWebRender(this, aManager, aDisplayListBuilder); + } + + LayoutDeviceRect bounds = LayoutDeviceRect::FromAppUnits( + mBackgroundRect, mFrame->PresContext()->AppUnitsPerDevPixel()); + wr::LayoutRect r = wr::ToLayoutRect(bounds); + + if (animationsId) { + wr::WrAnimationProperty prop{ + wr::WrAnimationType::BackgroundColor, + animationsId, + }; + aBuilder.PushRectWithAnimation(r, r, !BackfaceIsHidden(), + wr::ToColorF(ToDeviceColor(mColor)), &prop); + } else { + aBuilder.StartGroup(this); + aBuilder.PushRect(r, r, !BackfaceIsHidden(), + wr::ToColorF(ToDeviceColor(mColor))); + aBuilder.FinishGroup(); + } + + return true; +} + +void nsDisplayBackgroundColor::PaintWithClip(nsDisplayListBuilder* aBuilder, + gfxContext* aCtx, + const DisplayItemClip& aClip) { + MOZ_ASSERT(!HasBackgroundClipText()); + + if (mColor == sRGBColor()) { + return; + } + + nsRect fillRect = mBackgroundRect; + if (aClip.HasClip()) { + fillRect.IntersectRect(fillRect, aClip.GetClipRect()); + } + + DrawTarget* dt = aCtx->GetDrawTarget(); + int32_t A2D = mFrame->PresContext()->AppUnitsPerDevPixel(); + Rect bounds = ToRect(nsLayoutUtils::RectToGfxRect(fillRect, A2D)); + MaybeSnapToDevicePixels(bounds, *dt); + ColorPattern fill(ToDeviceColor(mColor)); + + if (aClip.GetRoundedRectCount()) { + MOZ_ASSERT(aClip.GetRoundedRectCount() == 1); + + AutoTArray<DisplayItemClip::RoundedRect, 1> roundedRect; + aClip.AppendRoundedRects(&roundedRect); + + bool pushedClip = false; + if (!fillRect.Contains(roundedRect[0].mRect)) { + dt->PushClipRect(bounds); + pushedClip = true; + } + + RectCornerRadii pixelRadii; + nsCSSRendering::ComputePixelRadii(roundedRect[0].mRadii, A2D, &pixelRadii); + dt->FillRoundedRect( + RoundedRect(NSRectToSnappedRect(roundedRect[0].mRect, A2D, *dt), + pixelRadii), + fill); + if (pushedClip) { + dt->PopClip(); + } + } else { + dt->FillRect(bounds, fill); + } +} + +void nsDisplayBackgroundColor::Paint(nsDisplayListBuilder* aBuilder, + gfxContext* aCtx) { + if (mColor == sRGBColor()) { + return; + } + +#if 0 + // See https://bugzilla.mozilla.org/show_bug.cgi?id=1148418#c21 for why this + // results in a precision induced rounding issue that makes the rect one + // pixel shorter in rare cases. Disabled in favor of the old code for now. + // Note that the pref layout.css.devPixelsPerPx needs to be set to 1 to + // reproduce the bug. + // + // TODO: + // This new path does not include support for background-clip:text; need to + // be fixed if/when we switch to this new code path. + + DrawTarget& aDrawTarget = *aCtx->GetDrawTarget(); + + Rect rect = NSRectToSnappedRect(mBackgroundRect, + mFrame->PresContext()->AppUnitsPerDevPixel(), + aDrawTarget); + ColorPattern color(ToDeviceColor(mColor)); + aDrawTarget.FillRect(rect, color); +#else + gfxContext* ctx = aCtx; + gfxRect bounds = nsLayoutUtils::RectToGfxRect( + mBackgroundRect, mFrame->PresContext()->AppUnitsPerDevPixel()); + + if (HasBackgroundClipText()) { + if (!GenerateAndPushTextMask(mFrame, aCtx, mBackgroundRect, aBuilder)) { + return; + } + + ctx->SetColor(mColor); + ctx->NewPath(); + ctx->SnappedRectangle(bounds); + ctx->Fill(); + ctx->PopGroupAndBlend(); + return; + } + + ctx->SetColor(mColor); + ctx->NewPath(); + ctx->SnappedRectangle(bounds); + ctx->Fill(); +#endif +} + +nsRegion nsDisplayBackgroundColor::GetOpaqueRegion( + nsDisplayListBuilder* aBuilder, bool* aSnap) const { + *aSnap = false; + + if (mColor.a != 1 || + // Even if the current alpha channel is 1, we treat this item as if it's + // non-opaque if there is a background-color animation since the animation + // might change the alpha channel. + EffectCompositor::HasAnimationsForCompositor( + mFrame, DisplayItemType::TYPE_BACKGROUND_COLOR)) { + return nsRegion(); + } + + if (!mHasStyle || HasBackgroundClipText()) { + return nsRegion(); + } + + *aSnap = true; + return nsDisplayBackgroundImage::GetInsideClipRegion( + this, mBottomLayerClip, mBackgroundRect, mBackgroundRect); +} + +Maybe<nscolor> nsDisplayBackgroundColor::IsUniform( + nsDisplayListBuilder* aBuilder) const { + return Some(mColor.ToABGR()); +} + +void nsDisplayBackgroundColor::HitTest(nsDisplayListBuilder* aBuilder, + const nsRect& aRect, + HitTestState* aState, + nsTArray<nsIFrame*>* aOutFrames) { + if (!RoundedBorderIntersectsRect(mFrame, ToReferenceFrame(), aRect)) { + // aRect doesn't intersect our border-radius curve. + return; + } + + aOutFrames->AppendElement(mFrame); +} + +void nsDisplayBackgroundColor::WriteDebugInfo(std::stringstream& aStream) { + aStream << " (rgba " << mColor.r << "," << mColor.g << "," << mColor.b << "," + << mColor.a << ")"; + aStream << " backgroundRect" << mBackgroundRect; +} + +nsRect nsDisplayOutline::GetBounds(nsDisplayListBuilder* aBuilder, + bool* aSnap) const { + *aSnap = false; + return mFrame->InkOverflowRectRelativeToSelf() + ToReferenceFrame(); +} + +void nsDisplayOutline::Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) { + // TODO join outlines together + MOZ_ASSERT(mFrame->StyleOutline()->ShouldPaintOutline(), + "Should have not created a nsDisplayOutline!"); + + nsPoint offset = ToReferenceFrame(); + nsCSSRendering::PaintOutline( + mFrame->PresContext(), *aCtx, mFrame, GetPaintRect(), + nsRect(offset, mFrame->GetSize()), mFrame->Style()); +} + +bool nsDisplayOutline::IsThemedOutline() const { + const auto& outlineStyle = mFrame->StyleOutline()->mOutlineStyle; + if (!outlineStyle.IsAuto() || + !StaticPrefs::layout_css_outline_style_auto_enabled()) { + return false; + } + + nsPresContext* pc = mFrame->PresContext(); + nsITheme* theme = pc->Theme(); + return theme->ThemeSupportsWidget(pc, mFrame, StyleAppearance::FocusOutline); +} + +bool nsDisplayOutline::CreateWebRenderCommands( + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + if (IsThemedOutline()) { + return false; + } + + nsPoint offset = ToReferenceFrame(); + + mozilla::Maybe<nsCSSBorderRenderer> borderRenderer = + nsCSSRendering::CreateBorderRendererForOutline( + mFrame->PresContext(), nullptr, mFrame, GetPaintRect(), + nsRect(offset, mFrame->GetSize()), mFrame->Style()); + + if (!borderRenderer) { + // No border renderer means "there is no outline". + // Paint nothing and return success. + return true; + } + + borderRenderer->CreateWebRenderCommands(this, aBuilder, aResources, aSc); + return true; +} + +bool nsDisplayOutline::IsInvisibleInRect(const nsRect& aRect) const { + const nsStyleOutline* outline = mFrame->StyleOutline(); + nsRect borderBox(ToReferenceFrame(), mFrame->GetSize()); + if (borderBox.Contains(aRect) && + !nsLayoutUtils::HasNonZeroCorner(outline->mOutlineRadius)) { + if (outline->mOutlineOffset._0 >= 0.0f) { + // aRect is entirely inside the border-rect, and the outline isn't + // rendered inside the border-rect, so the outline is not visible. + return true; + } + } + + return false; +} + +void nsDisplayEventReceiver::HitTest(nsDisplayListBuilder* aBuilder, + const nsRect& aRect, HitTestState* aState, + nsTArray<nsIFrame*>* aOutFrames) { + if (!RoundedBorderIntersectsRect(mFrame, ToReferenceFrame(), aRect)) { + // aRect doesn't intersect our border-radius curve. + return; + } + + aOutFrames->AppendElement(mFrame); +} + +nsDisplayCompositorHitTestInfo::nsDisplayCompositorHitTestInfo( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + const mozilla::gfx::CompositorHitTestInfo& aHitTestFlags, + const mozilla::Maybe<nsRect>& aArea) + : nsDisplayHitTestInfoBase(aBuilder, aFrame), + mAppUnitsPerDevPixel(mFrame->PresContext()->AppUnitsPerDevPixel()) { + MOZ_COUNT_CTOR(nsDisplayCompositorHitTestInfo); + // We should never even create this display item if we're not building + // compositor hit-test info or if the computed hit info indicated the + // frame is invisible to hit-testing + MOZ_ASSERT(aBuilder->BuildCompositorHitTestInfo()); + MOZ_ASSERT(aHitTestFlags != CompositorHitTestInvisibleToHit); + + const nsRect& area = + aArea.isSome() ? *aArea : aFrame->GetCompositorHitTestArea(aBuilder); + + SetHitTestInfo(area, aHitTestFlags); + InitializeScrollTarget(aBuilder); +} + +nsDisplayCompositorHitTestInfo::nsDisplayCompositorHitTestInfo( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + mozilla::UniquePtr<HitTestInfo>&& aHitTestInfo) + : nsDisplayHitTestInfoBase(aBuilder, aFrame), + mAppUnitsPerDevPixel(mFrame->PresContext()->AppUnitsPerDevPixel()) { + MOZ_COUNT_CTOR(nsDisplayCompositorHitTestInfo); + SetHitTestInfo(std::move(aHitTestInfo)); + InitializeScrollTarget(aBuilder); +} + +void nsDisplayCompositorHitTestInfo::InitializeScrollTarget( + nsDisplayListBuilder* aBuilder) { + if (aBuilder->GetCurrentScrollbarDirection().isSome()) { + // In the case of scrollbar frames, we use the scrollbar's target + // scrollframe instead of the scrollframe with which the scrollbar actually + // moves. + MOZ_ASSERT(HitTestFlags().contains(CompositorHitTestFlags::eScrollbar)); + mScrollTarget = mozilla::Some(aBuilder->GetCurrentScrollbarTarget()); + } +} + +bool nsDisplayCompositorHitTestInfo::CreateWebRenderCommands( + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + if (HitTestArea().IsEmpty()) { + return true; + } + + // XXX: eventually this scrollId computation and the SetHitTestInfo + // call will get moved out into the WR display item iteration code so that + // we don't need to do it as often, and so that we can do it for other + // display item types as well (reducing the need for as many instances of + // this display item). + ScrollableLayerGuid::ViewID scrollId = + mScrollTarget.valueOrFrom([&]() -> ScrollableLayerGuid::ViewID { + const ActiveScrolledRoot* asr = GetActiveScrolledRoot(); + Maybe<ScrollableLayerGuid::ViewID> fixedTarget = + aBuilder.GetContainingFixedPosScrollTarget(asr); + if (fixedTarget) { + return *fixedTarget; + } + if (asr) { + return asr->GetViewId(); + } + return ScrollableLayerGuid::NULL_SCROLL_ID; + }); + + Maybe<SideBits> sideBits = + aBuilder.GetContainingFixedPosSideBits(GetActiveScrolledRoot()); + + // Insert a transparent rectangle with the hit-test info + const LayoutDeviceRect devRect = + LayoutDeviceRect::FromAppUnits(HitTestArea(), mAppUnitsPerDevPixel); + + const wr::LayoutRect rect = wr::ToLayoutRect(devRect); + + aBuilder.StartGroup(this); + aBuilder.PushHitTest(rect, rect, !BackfaceIsHidden(), scrollId, + HitTestFlags(), sideBits.valueOr(SideBits::eNone)); + aBuilder.FinishGroup(); + + return true; +} + +int32_t nsDisplayCompositorHitTestInfo::ZIndex() const { + return mOverrideZIndex ? *mOverrideZIndex + : nsDisplayHitTestInfoBase::ZIndex(); +} + +void nsDisplayCompositorHitTestInfo::SetOverrideZIndex(int32_t aZIndex) { + mOverrideZIndex = Some(aZIndex); +} + +nsDisplayCaret::nsDisplayCaret(nsDisplayListBuilder* aBuilder, + nsIFrame* aCaretFrame) + : nsPaintedDisplayItem(aBuilder, aCaretFrame), + mCaret(aBuilder->GetCaret()), + mBounds(aBuilder->GetCaretRect() + ToReferenceFrame()) { + MOZ_COUNT_CTOR(nsDisplayCaret); +} + +#ifdef NS_BUILD_REFCNT_LOGGING +nsDisplayCaret::~nsDisplayCaret() { MOZ_COUNT_DTOR(nsDisplayCaret); } +#endif + +nsRect nsDisplayCaret::GetBounds(nsDisplayListBuilder* aBuilder, + bool* aSnap) const { + *aSnap = true; + // The caret returns a rect in the coordinates of mFrame. + return mBounds; +} + +void nsDisplayCaret::Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) { + // Note: Because we exist, we know that the caret is visible, so we don't + // need to check for the caret's visibility. + mCaret->PaintCaret(*aCtx->GetDrawTarget(), mFrame, ToReferenceFrame()); +} + +bool nsDisplayCaret::CreateWebRenderCommands( + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + using namespace mozilla::layers; + int32_t contentOffset; + nsIFrame* frame = mCaret->GetFrame(&contentOffset); + if (!frame) { + return true; + } + NS_ASSERTION(frame == mFrame, "We're referring different frame"); + + int32_t appUnitsPerDevPixel = frame->PresContext()->AppUnitsPerDevPixel(); + + nsRect caretRect; + nsRect hookRect; + mCaret->ComputeCaretRects(frame, contentOffset, &caretRect, &hookRect); + + gfx::DeviceColor color = ToDeviceColor(frame->GetCaretColorAt(contentOffset)); + LayoutDeviceRect devCaretRect = LayoutDeviceRect::FromAppUnits( + caretRect + ToReferenceFrame(), appUnitsPerDevPixel); + LayoutDeviceRect devHookRect = LayoutDeviceRect::FromAppUnits( + hookRect + ToReferenceFrame(), appUnitsPerDevPixel); + + wr::LayoutRect caret = wr::ToLayoutRect(devCaretRect); + wr::LayoutRect hook = wr::ToLayoutRect(devHookRect); + + // Note, WR will pixel snap anything that is layout aligned. + aBuilder.PushRect(caret, caret, !BackfaceIsHidden(), wr::ToColorF(color)); + + if (!devHookRect.IsEmpty()) { + aBuilder.PushRect(hook, hook, !BackfaceIsHidden(), wr::ToColorF(color)); + } + return true; +} + +nsDisplayBorder::nsDisplayBorder(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame) + : nsPaintedDisplayItem(aBuilder, aFrame) { + MOZ_COUNT_CTOR(nsDisplayBorder); + + mBounds = CalculateBounds<nsRect>(*mFrame->StyleBorder()); +} + +bool nsDisplayBorder::IsInvisibleInRect(const nsRect& aRect) const { + nsRect paddingRect = GetPaddingRect(); + const nsStyleBorder* styleBorder; + if (paddingRect.Contains(aRect) && + !(styleBorder = mFrame->StyleBorder())->IsBorderImageSizeAvailable() && + !nsLayoutUtils::HasNonZeroCorner(styleBorder->mBorderRadius)) { + // aRect is entirely inside the content rect, and no part + // of the border is rendered inside the content rect, so we are not + // visible + // Skip this if there's a border-image (which draws a background + // too) or if there is a border-radius (which makes the border draw + // further in). + return true; + } + + return false; +} + +nsDisplayItemGeometry* nsDisplayBorder::AllocateGeometry( + nsDisplayListBuilder* aBuilder) { + return new nsDisplayBorderGeometry(this, aBuilder); +} + +void nsDisplayBorder::ComputeInvalidationRegion( + nsDisplayListBuilder* aBuilder, const nsDisplayItemGeometry* aGeometry, + nsRegion* aInvalidRegion) const { + auto* geometry = static_cast<const nsDisplayBorderGeometry*>(aGeometry); + bool snap; + + if (!geometry->mBounds.IsEqualInterior(GetBounds(aBuilder, &snap))) { + // We can probably get away with only invalidating the difference + // between the border and padding rects, but the XUL ui at least + // is apparently painting a background with this? + aInvalidRegion->Or(GetBounds(aBuilder, &snap), geometry->mBounds); + } + + if (aBuilder->ShouldSyncDecodeImages() && + geometry->ShouldInvalidateToSyncDecodeImages()) { + aInvalidRegion->Or(*aInvalidRegion, GetBounds(aBuilder, &snap)); + } +} + +LayerState nsDisplayBorder::GetLayerState( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aParameters) { + return LayerState::LAYER_NONE; +} + +bool nsDisplayBorder::CreateWebRenderCommands( + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + nsRect rect = nsRect(ToReferenceFrame(), mFrame->GetSize()); + + aBuilder.StartGroup(this); + ImgDrawResult drawResult = nsCSSRendering::CreateWebRenderCommandsForBorder( + this, mFrame, rect, aBuilder, aResources, aSc, aManager, + aDisplayListBuilder); + + if (drawResult == ImgDrawResult::NOT_SUPPORTED) { + aBuilder.CancelGroup(true); + return false; + } + + aBuilder.FinishGroup(); + + nsDisplayBorderGeometry::UpdateDrawResult(this, drawResult); + return true; +}; + +void nsDisplayBorder::Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) { + nsPoint offset = ToReferenceFrame(); + + PaintBorderFlags flags = aBuilder->ShouldSyncDecodeImages() + ? PaintBorderFlags::SyncDecodeImages + : PaintBorderFlags(); + + ImgDrawResult result = nsCSSRendering::PaintBorder( + mFrame->PresContext(), *aCtx, mFrame, GetPaintRect(), + nsRect(offset, mFrame->GetSize()), mFrame->Style(), flags, + mFrame->GetSkipSides()); + + nsDisplayBorderGeometry::UpdateDrawResult(this, result); +} + +nsRect nsDisplayBorder::GetBounds(nsDisplayListBuilder* aBuilder, + bool* aSnap) const { + *aSnap = true; + return mBounds; +} + +// Given a region, compute a conservative approximation to it as a list +// of rectangles that aren't vertically adjacent (i.e., vertically +// adjacent or overlapping rectangles are combined). +// Right now this is only approximate, some vertically overlapping rectangles +// aren't guaranteed to be combined. +static void ComputeDisjointRectangles(const nsRegion& aRegion, + nsTArray<nsRect>* aRects) { + nscoord accumulationMargin = nsPresContext::CSSPixelsToAppUnits(25); + nsRect accumulated; + + for (auto iter = aRegion.RectIter(); !iter.Done(); iter.Next()) { + const nsRect& r = iter.Get(); + if (accumulated.IsEmpty()) { + accumulated = r; + continue; + } + + if (accumulated.YMost() >= r.y - accumulationMargin) { + accumulated.UnionRect(accumulated, r); + } else { + aRects->AppendElement(accumulated); + accumulated = r; + } + } + + // Finish the in-flight rectangle, if there is one. + if (!accumulated.IsEmpty()) { + aRects->AppendElement(accumulated); + } +} + +void nsDisplayBoxShadowOuter::Paint(nsDisplayListBuilder* aBuilder, + gfxContext* aCtx) { + nsPoint offset = ToReferenceFrame(); + nsRect borderRect = mFrame->VisualBorderRectRelativeToSelf() + offset; + nsPresContext* presContext = mFrame->PresContext(); + AutoTArray<nsRect, 10> rects; + ComputeDisjointRectangles(mVisibleRegion, &rects); + + AUTO_PROFILER_LABEL("nsDisplayBoxShadowOuter::Paint", GRAPHICS); + + for (uint32_t i = 0; i < rects.Length(); ++i) { + nsCSSRendering::PaintBoxShadowOuter(presContext, *aCtx, mFrame, borderRect, + rects[i], mOpacity); + } +} + +nsRect nsDisplayBoxShadowOuter::GetBounds(nsDisplayListBuilder* aBuilder, + bool* aSnap) const { + *aSnap = false; + return mBounds; +} + +nsRect nsDisplayBoxShadowOuter::GetBoundsInternal() { + return nsLayoutUtils::GetBoxShadowRectForFrame(mFrame, mFrame->GetSize()) + + ToReferenceFrame(); +} + +bool nsDisplayBoxShadowOuter::IsInvisibleInRect(const nsRect& aRect) const { + nsPoint origin = ToReferenceFrame(); + nsRect frameRect(origin, mFrame->GetSize()); + if (!frameRect.Contains(aRect)) { + return false; + } + + // the visible region is entirely inside the border-rect, and box shadows + // never render within the border-rect (unless there's a border radius). + nscoord twipsRadii[8]; + bool hasBorderRadii = mFrame->GetBorderRadii(twipsRadii); + if (!hasBorderRadii) { + return true; + } + + return RoundedRectContainsRect(frameRect, twipsRadii, aRect); +} + +bool nsDisplayBoxShadowOuter::ComputeVisibility(nsDisplayListBuilder* aBuilder, + nsRegion* aVisibleRegion) { + if (!nsPaintedDisplayItem::ComputeVisibility(aBuilder, aVisibleRegion)) { + return false; + } + + mVisibleRegion.And(*aVisibleRegion, GetPaintRect()); + return true; +} + +bool nsDisplayBoxShadowOuter::CanBuildWebRenderDisplayItems() { + auto shadows = mFrame->StyleEffects()->mBoxShadow.AsSpan(); + if (shadows.IsEmpty()) { + return false; + } + + bool hasBorderRadius; + bool nativeTheme = + nsCSSRendering::HasBoxShadowNativeTheme(mFrame, hasBorderRadius); + + // We don't support native themed things yet like box shadows around + // input buttons. + if (nativeTheme) { + return false; + } + + return true; +} + +bool nsDisplayBoxShadowOuter::CreateWebRenderCommands( + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + if (!CanBuildWebRenderDisplayItems()) { + return false; + } + + int32_t appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel(); + nsPoint offset = ToReferenceFrame(); + nsRect borderRect = mFrame->VisualBorderRectRelativeToSelf() + offset; + AutoTArray<nsRect, 10> rects; + bool snap; + nsRect bounds = GetBounds(aDisplayListBuilder, &snap); + ComputeDisjointRectangles(bounds, &rects); + + bool hasBorderRadius; + bool nativeTheme = + nsCSSRendering::HasBoxShadowNativeTheme(mFrame, hasBorderRadius); + + // Don't need the full size of the shadow rect like we do in + // nsCSSRendering since WR takes care of calculations for blur + // and spread radius. + nsRect frameRect = + nsCSSRendering::GetShadowRect(borderRect, nativeTheme, mFrame); + + RectCornerRadii borderRadii; + if (hasBorderRadius) { + hasBorderRadius = nsCSSRendering::GetBorderRadii(frameRect, borderRect, + mFrame, borderRadii); + } + + // Everything here is in app units, change to device units. + for (uint32_t i = 0; i < rects.Length(); ++i) { + LayoutDeviceRect clipRect = + LayoutDeviceRect::FromAppUnits(rects[i], appUnitsPerDevPixel); + auto shadows = mFrame->StyleEffects()->mBoxShadow.AsSpan(); + MOZ_ASSERT(!shadows.IsEmpty()); + + for (auto& shadow : Reversed(shadows)) { + if (shadow.inset) { + continue; + } + + float blurRadius = + float(shadow.base.blur.ToAppUnits()) / float(appUnitsPerDevPixel); + gfx::sRGBColor shadowColor = + nsCSSRendering::GetShadowColor(shadow.base, mFrame, mOpacity); + + // We don't move the shadow rect here since WR does it for us + // Now translate everything to device pixels. + const nsRect& shadowRect = frameRect; + LayoutDevicePoint shadowOffset = LayoutDevicePoint::FromAppUnits( + nsPoint(shadow.base.horizontal.ToAppUnits(), + shadow.base.vertical.ToAppUnits()), + appUnitsPerDevPixel); + + LayoutDeviceRect deviceBox = + LayoutDeviceRect::FromAppUnits(shadowRect, appUnitsPerDevPixel); + wr::LayoutRect deviceBoxRect = wr::ToLayoutRect(deviceBox); + wr::LayoutRect deviceClipRect = wr::ToLayoutRect(clipRect); + + LayoutDeviceSize zeroSize; + wr::BorderRadius borderRadius = + wr::ToBorderRadius(zeroSize, zeroSize, zeroSize, zeroSize); + if (hasBorderRadius) { + borderRadius = wr::ToBorderRadius( + LayoutDeviceSize::FromUnknownSize(borderRadii.TopLeft()), + LayoutDeviceSize::FromUnknownSize(borderRadii.TopRight()), + LayoutDeviceSize::FromUnknownSize(borderRadii.BottomLeft()), + LayoutDeviceSize::FromUnknownSize(borderRadii.BottomRight())); + } + + float spreadRadius = + float(shadow.spread.ToAppUnits()) / float(appUnitsPerDevPixel); + + aBuilder.PushBoxShadow(deviceBoxRect, deviceClipRect, !BackfaceIsHidden(), + deviceBoxRect, wr::ToLayoutVector2D(shadowOffset), + wr::ToColorF(ToDeviceColor(shadowColor)), + blurRadius, spreadRadius, borderRadius, + wr::BoxShadowClipMode::Outset); + } + } + + return true; +} + +void nsDisplayBoxShadowOuter::ComputeInvalidationRegion( + nsDisplayListBuilder* aBuilder, const nsDisplayItemGeometry* aGeometry, + nsRegion* aInvalidRegion) const { + auto* geometry = + static_cast<const nsDisplayBoxShadowOuterGeometry*>(aGeometry); + bool snap; + if (!geometry->mBounds.IsEqualInterior(GetBounds(aBuilder, &snap)) || + !geometry->mBorderRect.IsEqualInterior(GetBorderRect()) || + mOpacity != geometry->mOpacity) { + nsRegion oldShadow, newShadow; + nscoord dontCare[8]; + bool hasBorderRadius = mFrame->GetBorderRadii(dontCare); + if (hasBorderRadius) { + // If we have rounded corners then we need to invalidate the frame area + // too since we paint into it. + oldShadow = geometry->mBounds; + newShadow = GetBounds(aBuilder, &snap); + } else { + oldShadow.Sub(geometry->mBounds, geometry->mBorderRect); + newShadow.Sub(GetBounds(aBuilder, &snap), GetBorderRect()); + } + aInvalidRegion->Or(oldShadow, newShadow); + } +} + +void nsDisplayBoxShadowInner::Paint(nsDisplayListBuilder* aBuilder, + gfxContext* aCtx) { + nsPoint offset = ToReferenceFrame(); + nsRect borderRect = nsRect(offset, mFrame->GetSize()); + nsPresContext* presContext = mFrame->PresContext(); + AutoTArray<nsRect, 10> rects; + ComputeDisjointRectangles(mVisibleRegion, &rects); + + AUTO_PROFILER_LABEL("nsDisplayBoxShadowInner::Paint", GRAPHICS); + + DrawTarget* drawTarget = aCtx->GetDrawTarget(); + gfxContext* gfx = aCtx; + int32_t appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel(); + + for (uint32_t i = 0; i < rects.Length(); ++i) { + gfx->Save(); + gfx->Clip(NSRectToSnappedRect(rects[i], appUnitsPerDevPixel, *drawTarget)); + nsCSSRendering::PaintBoxShadowInner(presContext, *aCtx, mFrame, borderRect); + gfx->Restore(); + } +} + +bool nsDisplayBoxShadowInner::CanCreateWebRenderCommands( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + const nsPoint& aReferenceOffset) { + auto shadows = aFrame->StyleEffects()->mBoxShadow.AsSpan(); + if (shadows.IsEmpty()) { + // Means we don't have to paint anything + return true; + } + + bool hasBorderRadius; + bool nativeTheme = + nsCSSRendering::HasBoxShadowNativeTheme(aFrame, hasBorderRadius); + + // We don't support native themed things yet like box shadows around + // input buttons. + if (nativeTheme) { + return false; + } + + return true; +} + +/* static */ +void nsDisplayBoxShadowInner::CreateInsetBoxShadowWebRenderCommands( + mozilla::wr::DisplayListBuilder& aBuilder, const StackingContextHelper& aSc, + nsRegion& aVisibleRegion, nsIFrame* aFrame, const nsRect& aBorderRect) { + if (!nsCSSRendering::ShouldPaintBoxShadowInner(aFrame)) { + return; + } + + int32_t appUnitsPerDevPixel = aFrame->PresContext()->AppUnitsPerDevPixel(); + + AutoTArray<nsRect, 10> rects; + ComputeDisjointRectangles(aVisibleRegion, &rects); + + auto shadows = aFrame->StyleEffects()->mBoxShadow.AsSpan(); + + for (uint32_t i = 0; i < rects.Length(); ++i) { + LayoutDeviceRect clipRect = + LayoutDeviceRect::FromAppUnits(rects[i], appUnitsPerDevPixel); + + for (auto& shadow : Reversed(shadows)) { + if (!shadow.inset) { + continue; + } + + nsRect shadowRect = + nsCSSRendering::GetBoxShadowInnerPaddingRect(aFrame, aBorderRect); + RectCornerRadii innerRadii; + nsCSSRendering::GetShadowInnerRadii(aFrame, aBorderRect, innerRadii); + + // Now translate everything to device pixels. + LayoutDeviceRect deviceBoxRect = + LayoutDeviceRect::FromAppUnits(shadowRect, appUnitsPerDevPixel); + wr::LayoutRect deviceClipRect = wr::ToLayoutRect(clipRect); + sRGBColor shadowColor = + nsCSSRendering::GetShadowColor(shadow.base, aFrame, 1.0); + + LayoutDevicePoint shadowOffset = LayoutDevicePoint::FromAppUnits( + nsPoint(shadow.base.horizontal.ToAppUnits(), + shadow.base.vertical.ToAppUnits()), + appUnitsPerDevPixel); + + float blurRadius = + float(shadow.base.blur.ToAppUnits()) / float(appUnitsPerDevPixel); + + wr::BorderRadius borderRadius = wr::ToBorderRadius( + LayoutDeviceSize::FromUnknownSize(innerRadii.TopLeft()), + LayoutDeviceSize::FromUnknownSize(innerRadii.TopRight()), + LayoutDeviceSize::FromUnknownSize(innerRadii.BottomLeft()), + LayoutDeviceSize::FromUnknownSize(innerRadii.BottomRight())); + // NOTE: Any spread radius > 0 will render nothing. WR Bug. + float spreadRadius = + float(shadow.spread.ToAppUnits()) / float(appUnitsPerDevPixel); + + aBuilder.PushBoxShadow( + wr::ToLayoutRect(deviceBoxRect), deviceClipRect, + !aFrame->BackfaceIsHidden(), wr::ToLayoutRect(deviceBoxRect), + wr::ToLayoutVector2D(shadowOffset), + wr::ToColorF(ToDeviceColor(shadowColor)), blurRadius, spreadRadius, + borderRadius, wr::BoxShadowClipMode::Inset); + } + } +} + +bool nsDisplayBoxShadowInner::CreateWebRenderCommands( + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + if (!CanCreateWebRenderCommands(aDisplayListBuilder, mFrame, + ToReferenceFrame())) { + return false; + } + + bool snap; + nsRegion visible = GetBounds(aDisplayListBuilder, &snap); + nsPoint offset = ToReferenceFrame(); + nsRect borderRect = nsRect(offset, mFrame->GetSize()); + nsDisplayBoxShadowInner::CreateInsetBoxShadowWebRenderCommands( + aBuilder, aSc, visible, mFrame, borderRect); + + return true; +} + +bool nsDisplayBoxShadowInner::ComputeVisibility(nsDisplayListBuilder* aBuilder, + nsRegion* aVisibleRegion) { + if (!nsPaintedDisplayItem::ComputeVisibility(aBuilder, aVisibleRegion)) { + return false; + } + + mVisibleRegion.And(*aVisibleRegion, GetPaintRect()); + return true; +} + +nsDisplayWrapList::nsDisplayWrapList(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame, nsDisplayList* aList) + : nsDisplayWrapList(aBuilder, aFrame, aList, + aBuilder->CurrentActiveScrolledRoot(), false) {} + +nsDisplayWrapList::nsDisplayWrapList( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayList* aList, + const ActiveScrolledRoot* aActiveScrolledRoot, bool aClearClipChain) + : nsDisplayHitTestInfoBase(aBuilder, aFrame, aActiveScrolledRoot), + mFrameActiveScrolledRoot(aBuilder->CurrentActiveScrolledRoot()), + mOverrideZIndex(0), + mHasZIndexOverride(false), + mClearingClipChain(aClearClipChain) { + MOZ_COUNT_CTOR(nsDisplayWrapList); + + mBaseBuildingRect = GetBuildingRect(); + + mListPtr = &mList; + mListPtr->AppendToTop(aList); + nsDisplayWrapList::UpdateBounds(aBuilder); + + if (!aFrame || !aFrame->IsTransformed()) { + return; + } + + // If we're a transformed frame, then we need to find out if we're inside + // the nsDisplayTransform or outside of it. Frames inside the transform + // need mReferenceFrame == mFrame, outside needs the next ancestor + // reference frame. + // If we're inside the transform, then the nsDisplayItem constructor + // will have done the right thing. + // If we're outside the transform, then we should have only one child + // (since nsDisplayTransform wraps all actual content), and that child + // will have the correct reference frame set (since nsDisplayTransform + // handles this explictly). + nsDisplayItem* i = mListPtr->GetBottom(); + if (i && + (!i->GetAbove() || i->GetType() == DisplayItemType::TYPE_TRANSFORM) && + i->Frame() == mFrame) { + mReferenceFrame = i->ReferenceFrame(); + mToReferenceFrame = i->ToReferenceFrame(); + } + + nsRect visible = aBuilder->GetVisibleRect() + + aBuilder->GetCurrentFrameOffsetToReferenceFrame(); + + SetBuildingRect(visible); +} + +nsDisplayWrapList::nsDisplayWrapList(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame, nsDisplayItem* aItem) + : nsDisplayHitTestInfoBase(aBuilder, aFrame, + aBuilder->CurrentActiveScrolledRoot()), + mOverrideZIndex(0), + mHasZIndexOverride(false) { + MOZ_COUNT_CTOR(nsDisplayWrapList); + + mBaseBuildingRect = GetBuildingRect(); + + mListPtr = &mList; + mListPtr->AppendToTop(aItem); + nsDisplayWrapList::UpdateBounds(aBuilder); + + if (!aFrame || !aFrame->IsTransformed()) { + return; + } + + // See the previous nsDisplayWrapList constructor + if (aItem->Frame() == aFrame) { + mReferenceFrame = aItem->ReferenceFrame(); + mToReferenceFrame = aItem->ToReferenceFrame(); + } + + nsRect visible = aBuilder->GetVisibleRect() + + aBuilder->GetCurrentFrameOffsetToReferenceFrame(); + + SetBuildingRect(visible); +} + +nsDisplayWrapList::~nsDisplayWrapList() { MOZ_COUNT_DTOR(nsDisplayWrapList); } + +void nsDisplayWrapList::MergeDisplayListFromItem( + nsDisplayListBuilder* aBuilder, const nsDisplayWrapList* aItem) { + const nsDisplayWrapList* wrappedItem = aItem->AsDisplayWrapList(); + MOZ_ASSERT(wrappedItem); + + // Create a new nsDisplayWrapList using a copy-constructor. This is done + // to preserve the information about bounds. + nsDisplayWrapList* wrapper = + MakeClone<nsDisplayWrapList>(aBuilder, wrappedItem); + MOZ_ASSERT(wrapper); + + // Set the display list pointer of the new wrapper item to the display list + // of the wrapped item. + wrapper->mListPtr = wrappedItem->mListPtr; + + mListPtr->AppendToBottom(wrapper); +} + +void nsDisplayWrapList::HitTest(nsDisplayListBuilder* aBuilder, + const nsRect& aRect, HitTestState* aState, + nsTArray<nsIFrame*>* aOutFrames) { + mListPtr->HitTest(aBuilder, aRect, aState, aOutFrames); +} + +nsRect nsDisplayWrapList::GetBounds(nsDisplayListBuilder* aBuilder, + bool* aSnap) const { + *aSnap = false; + return mBounds; +} + +bool nsDisplayWrapList::ComputeVisibility(nsDisplayListBuilder* aBuilder, + nsRegion* aVisibleRegion) { + return ::ComputeClippedVisibilityForSubList(aBuilder, aVisibleRegion, + GetChildren(), GetPaintRect()); +} + +nsRegion nsDisplayWrapList::GetOpaqueRegion(nsDisplayListBuilder* aBuilder, + bool* aSnap) const { + *aSnap = false; + bool snap; + return ::GetOpaqueRegion(aBuilder, GetChildren(), GetBounds(aBuilder, &snap)); +} + +Maybe<nscolor> nsDisplayWrapList::IsUniform( + nsDisplayListBuilder* aBuilder) const { + // We could try to do something but let's conservatively just return Nothing. + return Nothing(); +} + +void nsDisplayWrapList::Paint(nsDisplayListBuilder* aBuilder, + gfxContext* aCtx) { + NS_ERROR("nsDisplayWrapList should have been flattened away for painting"); +} + +/** + * Returns true if all descendant display items can be placed in the same + * PaintedLayer --- GetLayerState returns LayerState::LAYER_INACTIVE or + * LayerState::LAYER_NONE, and they all have the expected animated geometry + * root. + */ +static LayerState RequiredLayerStateForChildren( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aParameters, const nsDisplayList& aList, + const AnimatedGeometryRoot* aExpectedAGRForChildren, + const ActiveScrolledRoot* aExpectedASRForChildren) { + LayerState result = LayerState::LAYER_INACTIVE; + for (nsDisplayItem* i : aList) { + if (result == LayerState::LAYER_INACTIVE && + (i->GetAnimatedGeometryRoot() != aExpectedAGRForChildren || + i->GetActiveScrolledRoot() != aExpectedASRForChildren)) { + result = LayerState::LAYER_ACTIVE; + } + + LayerState state = i->GetLayerState(aBuilder, aManager, aParameters); + if (state == LayerState::LAYER_ACTIVE && + (i->GetType() == DisplayItemType::TYPE_BLEND_MODE || + i->GetType() == DisplayItemType::TYPE_TABLE_BLEND_MODE)) { + // nsDisplayBlendMode always returns LayerState::LAYER_ACTIVE to ensure + // that the blending operation happens in the intermediate surface of its + // parent display item (usually an nsDisplayBlendContainer). But this does + // not mean that it needs all its ancestor display items to become active. + // So we ignore its layer state and look at its children instead. + state = RequiredLayerStateForChildren( + aBuilder, aManager, aParameters, + *i->GetSameCoordinateSystemChildren(), i->GetAnimatedGeometryRoot(), + i->GetActiveScrolledRoot()); + } + if ((state == LayerState::LAYER_ACTIVE || + state == LayerState::LAYER_ACTIVE_FORCE) && + state > result) { + result = state; + } + if (state == LayerState::LAYER_ACTIVE_EMPTY && state > result) { + result = LayerState::LAYER_ACTIVE_FORCE; + } + if (state == LayerState::LAYER_NONE) { + nsDisplayList* list = i->GetSameCoordinateSystemChildren(); + if (list) { + LayerState childState = RequiredLayerStateForChildren( + aBuilder, aManager, aParameters, *list, aExpectedAGRForChildren, + aExpectedASRForChildren); + if (childState > result) { + result = childState; + } + } + } + } + return result; +} + +nsRect nsDisplayWrapList::GetComponentAlphaBounds( + nsDisplayListBuilder* aBuilder) const { + return mListPtr->GetComponentAlphaBounds(aBuilder); +} + +bool nsDisplayWrapList::CreateWebRenderCommands( + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + aManager->CommandBuilder().CreateWebRenderCommandsFromDisplayList( + GetChildren(), this, aDisplayListBuilder, aSc, aBuilder, aResources); + return true; +} + +static nsresult WrapDisplayList(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame, nsDisplayList* aList, + nsDisplayWrapper* aWrapper) { + if (!aList->GetTop()) { + return NS_OK; + } + nsDisplayItem* item = aWrapper->WrapList(aBuilder, aFrame, aList); + if (!item) { + return NS_ERROR_OUT_OF_MEMORY; + } + // aList was emptied + aList->AppendToTop(item); + return NS_OK; +} + +static nsresult WrapEachDisplayItem(nsDisplayListBuilder* aBuilder, + nsDisplayList* aList, + nsDisplayWrapper* aWrapper) { + nsDisplayList newList; + nsDisplayItem* item; + while ((item = aList->RemoveBottom())) { + item = aWrapper->WrapItem(aBuilder, item); + if (!item) { + return NS_ERROR_OUT_OF_MEMORY; + } + newList.AppendToTop(item); + } + // aList was emptied + aList->AppendToTop(&newList); + return NS_OK; +} + +nsresult nsDisplayWrapper::WrapLists(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame, + const nsDisplayListSet& aIn, + const nsDisplayListSet& aOut) { + nsresult rv = WrapListsInPlace(aBuilder, aFrame, aIn); + NS_ENSURE_SUCCESS(rv, rv); + + if (&aOut == &aIn) { + return NS_OK; + } + aOut.BorderBackground()->AppendToTop(aIn.BorderBackground()); + aOut.BlockBorderBackgrounds()->AppendToTop(aIn.BlockBorderBackgrounds()); + aOut.Floats()->AppendToTop(aIn.Floats()); + aOut.Content()->AppendToTop(aIn.Content()); + aOut.PositionedDescendants()->AppendToTop(aIn.PositionedDescendants()); + aOut.Outlines()->AppendToTop(aIn.Outlines()); + return NS_OK; +} + +nsresult nsDisplayWrapper::WrapListsInPlace(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame, + const nsDisplayListSet& aLists) { + nsresult rv; + if (WrapBorderBackground()) { + // Our border-backgrounds are in-flow + rv = WrapDisplayList(aBuilder, aFrame, aLists.BorderBackground(), this); + NS_ENSURE_SUCCESS(rv, rv); + } + // Our block border-backgrounds are in-flow + rv = WrapDisplayList(aBuilder, aFrame, aLists.BlockBorderBackgrounds(), this); + NS_ENSURE_SUCCESS(rv, rv); + // The floats are not in flow + rv = WrapEachDisplayItem(aBuilder, aLists.Floats(), this); + NS_ENSURE_SUCCESS(rv, rv); + // Our child content is in flow + rv = WrapDisplayList(aBuilder, aFrame, aLists.Content(), this); + NS_ENSURE_SUCCESS(rv, rv); + // The positioned descendants may not be in-flow + rv = WrapEachDisplayItem(aBuilder, aLists.PositionedDescendants(), this); + NS_ENSURE_SUCCESS(rv, rv); + // The outlines may not be in-flow + return WrapEachDisplayItem(aBuilder, aLists.Outlines(), this); +} + +nsDisplayOpacity::nsDisplayOpacity( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayList* aList, + const ActiveScrolledRoot* aActiveScrolledRoot, + bool aForEventsAndPluginsOnly, bool aNeedsActiveLayer) + : nsDisplayWrapList(aBuilder, aFrame, aList, aActiveScrolledRoot, true), + mOpacity(aFrame->StyleEffects()->mOpacity), + mForEventsAndPluginsOnly(aForEventsAndPluginsOnly), + mNeedsActiveLayer(aNeedsActiveLayer), + mChildOpacityState(ChildOpacityState::Unknown) { + MOZ_COUNT_CTOR(nsDisplayOpacity); + mState.mOpacity = mOpacity; +} + +void nsDisplayOpacity::HitTest(nsDisplayListBuilder* aBuilder, + const nsRect& aRect, + nsDisplayItem::HitTestState* aState, + nsTArray<nsIFrame*>* aOutFrames) { + AutoRestore<float> opacity(aState->mCurrentOpacity); + aState->mCurrentOpacity *= mOpacity; + + // TODO(emilio): special-casing zero is a bit arbitrary... Maybe we should + // only consider fully opaque items? Or make this configurable somehow? + if (aBuilder->HitTestIsForVisibility() && mOpacity == 0.0f) { + return; + } + nsDisplayWrapList::HitTest(aBuilder, aRect, aState, aOutFrames); +} + +nsRegion nsDisplayOpacity::GetOpaqueRegion(nsDisplayListBuilder* aBuilder, + bool* aSnap) const { + *aSnap = false; + // The only time where mOpacity == 1.0 should be when we have will-change. + // We could report this as opaque then but when the will-change value starts + // animating the element would become non opaque and could cause repaints. + return nsRegion(); +} + +// nsDisplayOpacity uses layers for rendering +already_AddRefed<Layer> nsDisplayOpacity::BuildLayer( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aContainerParameters) { + MOZ_ASSERT(mChildOpacityState != ChildOpacityState::Applied); + + ContainerLayerParameters params = aContainerParameters; + RefPtr<Layer> container = aManager->GetLayerBuilder()->BuildContainerLayerFor( + aBuilder, aManager, mFrame, this, &mList, params, nullptr, + FrameLayerBuilder::CONTAINER_ALLOW_PULL_BACKGROUND_COLOR); + if (!container) { + return nullptr; + } + + container->SetOpacity(mOpacity); + nsDisplayListBuilder::AddAnimationsAndTransitionsToLayer( + container, aBuilder, this, mFrame, GetType()); + return container.forget(); +} + +/** + * This doesn't take into account layer scaling --- the layer may be + * rendered at a higher (or lower) resolution, affecting the retained layer + * size --- but this should be good enough. + */ +static bool IsItemTooSmallForActiveLayer(nsIFrame* aFrame) { + nsIntRect visibleDevPixels = + aFrame->InkOverflowRectRelativeToSelf().ToOutsidePixels( + aFrame->PresContext()->AppUnitsPerDevPixel()); + return visibleDevPixels.Size() < + nsIntSize(StaticPrefs::layout_min_active_layer_size(), + StaticPrefs::layout_min_active_layer_size()); +} + +/* static */ +bool nsDisplayOpacity::NeedsActiveLayer(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame, + bool aEnforceMinimumSize) { + if (EffectCompositor::HasAnimationsForCompositor( + aFrame, DisplayItemType::TYPE_OPACITY) || + (ActiveLayerTracker::IsStyleAnimated( + aBuilder, aFrame, nsCSSPropertyIDSet::OpacityProperties()) && + !(aEnforceMinimumSize && IsItemTooSmallForActiveLayer(aFrame)))) { + return true; + } + return false; +} + +void nsDisplayOpacity::ApplyOpacity(nsDisplayListBuilder* aBuilder, + float aOpacity, + const DisplayItemClipChain* aClip) { + NS_ASSERTION(CanApplyOpacity(), "ApplyOpacity should be allowed"); + mOpacity = mOpacity * aOpacity; + IntersectClip(aBuilder, aClip, false); +} + +bool nsDisplayOpacity::CanApplyOpacity() const { + return !EffectCompositor::HasAnimationsForCompositor( + mFrame, DisplayItemType::TYPE_OPACITY); +} + +// Only try folding our opacity down if we have at most |kOpacityMaxChildCount| +// children that don't overlap and can all apply the opacity to themselves. +static const size_t kOpacityMaxChildCount = 3; + +// |kOpacityMaxListSize| defines an early exit condition for opacity items that +// are likely have more child items than |kOpacityMaxChildCount|. +static const size_t kOpacityMaxListSize = kOpacityMaxChildCount * 2; + +/** + * Recursively iterates through |aList| and collects at most + * |kOpacityMaxChildCount| display item pointers to items that return true for + * CanApplyOpacity(). The item pointers are added to |aArray|. + * + * LayerEventRegions and WrapList items are ignored. + * + * We need to do this recursively, because the child display items might contain + * nested nsDisplayWrapLists. + * + * Returns false if there are more than |kOpacityMaxChildCount| items, or if an + * item that returns false for CanApplyOpacity() is encountered. + * Otherwise returns true. + */ +static bool CollectItemsWithOpacity(nsDisplayList* aList, + nsTArray<nsPaintedDisplayItem*>& aArray) { + if (aList->Count() > kOpacityMaxListSize) { + // Exit early, since |aList| will likely contain more than + // |kOpacityMaxChildCount| items. + return false; + } + + for (nsDisplayItem* i : *aList) { + const DisplayItemType type = i->GetType(); + + if (type == DisplayItemType::TYPE_COMPOSITOR_HITTEST_INFO) { + continue; + } + + // Descend only into wraplists. + if (type == DisplayItemType::TYPE_WRAP_LIST || + type == DisplayItemType::TYPE_CONTAINER) { + // The current display item has children, process them first. + if (!CollectItemsWithOpacity(i->GetChildren(), aArray)) { + return false; + } + + continue; + } + + if (aArray.Length() == kOpacityMaxChildCount) { + return false; + } + + auto* item = i->AsPaintedDisplayItem(); + if (!item || !item->CanApplyOpacity()) { + return false; + } + + aArray.AppendElement(item); + } + + return true; +} + +bool nsDisplayOpacity::ApplyToChildren(nsDisplayListBuilder* aBuilder) { + if (mChildOpacityState == ChildOpacityState::Deferred) { + return false; + } + + // Iterate through the child display list and copy at most + // |kOpacityMaxChildCount| child display item pointers to a temporary list. + AutoTArray<nsPaintedDisplayItem*, kOpacityMaxChildCount> items; + if (!CollectItemsWithOpacity(&mList, items)) { + mChildOpacityState = ChildOpacityState::Deferred; + return false; + } + + struct { + nsPaintedDisplayItem* item; + nsRect bounds; + } children[kOpacityMaxChildCount]; + + bool snap; + size_t childCount = 0; + for (nsPaintedDisplayItem* item : items) { + children[childCount].item = item; + children[childCount].bounds = item->GetBounds(aBuilder, &snap); + childCount++; + } + + for (size_t i = 0; i < childCount; i++) { + for (size_t j = i + 1; j < childCount; j++) { + if (children[i].bounds.Intersects(children[j].bounds)) { + mChildOpacityState = ChildOpacityState::Deferred; + return false; + } + } + } + + for (uint32_t i = 0; i < childCount; i++) { + children[i].item->ApplyOpacity(aBuilder, mOpacity, mClipChain); + } + + mChildOpacityState = ChildOpacityState::Applied; + return true; +} + +/** + * Returns true if this nsDisplayOpacity contains only a filter or a mask item + * that has the same frame as the opacity item, and that supports painting with + * opacity. In this case the opacity item can be optimized away. + */ +bool nsDisplayOpacity::ApplyToFilterOrMask(const bool aUsingLayers) { + if (mList.Count() != 1) { + return false; + } + + nsDisplayItem* item = mList.GetBottom(); + if (item->Frame() != mFrame) { + // The effect item needs to have the same frame as the opacity item. + return false; + } + + const DisplayItemType type = item->GetType(); + if (type == DisplayItemType::TYPE_MASK || + type == DisplayItemType::TYPE_FILTER) { + auto* filterOrMaskItem = static_cast<nsDisplayEffectsBase*>(item); + filterOrMaskItem->SelectOpacityOptimization(aUsingLayers); + return true; + } + + return false; +} + +bool nsDisplayOpacity::ShouldFlattenAway(nsDisplayListBuilder* aBuilder) { + if (mFrame->GetPrevContinuation() || mFrame->GetNextContinuation() || + mFrame->HasAnyStateBits(NS_FRAME_PART_OF_IBSPLIT)) { + // If we've been split, then we might need to merge, so + // don't flatten us away. + return false; + } + + if (mNeedsActiveLayer || mOpacity == 0.0) { + // If our opacity is zero then we'll discard all descendant display items + // except for layer event regions, so there's no point in doing this + // optimization (and if we do do it, then invalidations of those descendants + // might trigger repainting). + return false; + } + + if (mList.IsEmpty()) { + return false; + } + + const bool usingLayers = !aBuilder->IsPaintingForWebRender(); + + if (ApplyToFilterOrMask(usingLayers)) { + MOZ_ASSERT(SVGIntegrationUtils::UsingEffectsForFrame(mFrame)); + mChildOpacityState = ChildOpacityState::Applied; + return true; + } + + // Return true if we successfully applied opacity to child items, or if + // WebRender is not in use. In the latter case, the opacity gets flattened and + // applied during layer building. + return ApplyToChildren(aBuilder) || usingLayers; +} + +nsDisplayItem::LayerState nsDisplayOpacity::GetLayerState( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aParameters) { + // If we only created this item so that we'd get correct nsDisplayEventRegions + // for child frames, then force us to inactive to avoid unnecessary + // layerization changes for content that won't ever be painted. + if (mForEventsAndPluginsOnly) { + MOZ_ASSERT(mOpacity == 0); + return LayerState::LAYER_INACTIVE; + } + + if (mNeedsActiveLayer) { + // Returns LayerState::LAYER_ACTIVE_FORCE to avoid flatterning the layer for + // async animations. + return LayerState::LAYER_ACTIVE_FORCE; + } + + return RequiredLayerStateForChildren(aBuilder, aManager, aParameters, mList, + GetAnimatedGeometryRoot(), + GetActiveScrolledRoot()); +} + +bool nsDisplayOpacity::ComputeVisibility(nsDisplayListBuilder* aBuilder, + nsRegion* aVisibleRegion) { + // Our children are translucent so we should not allow them to subtract + // area from aVisibleRegion. We do need to find out what is visible under + // our children in the temporary compositing buffer, because if our children + // paint our entire bounds opaquely then we don't need an alpha channel in + // the temporary compositing buffer. + nsRect bounds = GetClippedBounds(aBuilder); + nsRegion visibleUnderChildren; + visibleUnderChildren.And(*aVisibleRegion, bounds); + return nsDisplayWrapList::ComputeVisibility(aBuilder, &visibleUnderChildren); +} + +void nsDisplayOpacity::ComputeInvalidationRegion( + nsDisplayListBuilder* aBuilder, const nsDisplayItemGeometry* aGeometry, + nsRegion* aInvalidRegion) const { + auto* geometry = static_cast<const nsDisplayOpacityGeometry*>(aGeometry); + + bool snap; + if (mOpacity != geometry->mOpacity) { + aInvalidRegion->Or(GetBounds(aBuilder, &snap), geometry->mBounds); + } +} + +void nsDisplayOpacity::WriteDebugInfo(std::stringstream& aStream) { + aStream << " (opacity " << mOpacity << ", mChildOpacityState: "; + switch (mChildOpacityState) { + case ChildOpacityState::Unknown: + aStream << "Unknown"; + break; + case ChildOpacityState::Applied: + aStream << "Applied"; + break; + case ChildOpacityState::Deferred: + aStream << "Deferred"; + break; + default: + break; + } + + aStream << ")"; +} + +bool nsDisplayOpacity::CreateWebRenderCommands( + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + MOZ_ASSERT(mChildOpacityState != ChildOpacityState::Applied); + float* opacityForSC = &mOpacity; + + uint64_t animationsId = + AddAnimationsForWebRender(this, aManager, aDisplayListBuilder); + wr::WrAnimationProperty prop{ + wr::WrAnimationType::Opacity, + animationsId, + }; + + wr::StackingContextParams params; + params.animation = animationsId ? &prop : nullptr; + params.opacity = opacityForSC; + params.clip = + wr::WrStackingContextClip::ClipChain(aBuilder.CurrentClipChainId()); + StackingContextHelper sc(aSc, GetActiveScrolledRoot(), mFrame, this, aBuilder, + params); + + aManager->CommandBuilder().CreateWebRenderCommandsFromDisplayList( + &mList, this, aDisplayListBuilder, sc, aBuilder, aResources); + return true; +} + +nsDisplayBlendMode::nsDisplayBlendMode( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayList* aList, + mozilla::StyleBlend aBlendMode, + const ActiveScrolledRoot* aActiveScrolledRoot, const bool aIsForBackground) + : nsDisplayWrapList(aBuilder, aFrame, aList, aActiveScrolledRoot, true), + mBlendMode(aBlendMode), + mIsForBackground(aIsForBackground) { + MOZ_COUNT_CTOR(nsDisplayBlendMode); +} + +nsRegion nsDisplayBlendMode::GetOpaqueRegion(nsDisplayListBuilder* aBuilder, + bool* aSnap) const { + *aSnap = false; + // We are never considered opaque + return nsRegion(); +} + +LayerState nsDisplayBlendMode::GetLayerState( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aParameters) { + return LayerState::LAYER_ACTIVE; +} + +bool nsDisplayBlendMode::CreateWebRenderCommands( + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + wr::StackingContextParams params; + params.mix_blend_mode = + wr::ToMixBlendMode(nsCSSRendering::GetGFXBlendMode(mBlendMode)); + params.clip = + wr::WrStackingContextClip::ClipChain(aBuilder.CurrentClipChainId()); + StackingContextHelper sc(aSc, GetActiveScrolledRoot(), mFrame, this, aBuilder, + params); + + return nsDisplayWrapList::CreateWebRenderCommands( + aBuilder, aResources, sc, aManager, aDisplayListBuilder); +} + +// nsDisplayBlendMode uses layers for rendering +already_AddRefed<Layer> nsDisplayBlendMode::BuildLayer( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aContainerParameters) { + ContainerLayerParameters newContainerParameters = aContainerParameters; + newContainerParameters.mDisableSubpixelAntialiasingInDescendants = true; + + RefPtr<Layer> container = aManager->GetLayerBuilder()->BuildContainerLayerFor( + aBuilder, aManager, mFrame, this, &mList, newContainerParameters, + nullptr); + if (!container) { + return nullptr; + } + + container->SetMixBlendMode(nsCSSRendering::GetGFXBlendMode(mBlendMode)); + + return container.forget(); +} + +mozilla::gfx::CompositionOp nsDisplayBlendMode::BlendMode() { + return nsCSSRendering::GetGFXBlendMode(mBlendMode); +} + +bool nsDisplayBlendMode::ComputeVisibility(nsDisplayListBuilder* aBuilder, + nsRegion* aVisibleRegion) { + // Our children are need their backdrop so we should not allow them to + // subtract area from aVisibleRegion. We do need to find out what is visible + // under our children in the temporary compositing buffer, because if our + // children paint our entire bounds opaquely then we don't need an alpha + // channel in the temporary compositing buffer. + nsRect bounds = GetClippedBounds(aBuilder); + nsRegion visibleUnderChildren; + visibleUnderChildren.And(*aVisibleRegion, bounds); + return nsDisplayWrapList::ComputeVisibility(aBuilder, &visibleUnderChildren); +} + +bool nsDisplayBlendMode::CanMerge(const nsDisplayItem* aItem) const { + // Items for the same content element should be merged into a single + // compositing group. + if (!HasDifferentFrame(aItem) || !HasSameTypeAndClip(aItem) || + !HasSameContent(aItem)) { + return false; + } + + const auto* item = static_cast<const nsDisplayBlendMode*>(aItem); + if (mIsForBackground || item->mIsForBackground) { + // Don't merge background-blend-mode items + return false; + } + + return true; +} + +/* static */ +nsDisplayBlendContainer* nsDisplayBlendContainer::CreateForMixBlendMode( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayList* aList, + const ActiveScrolledRoot* aActiveScrolledRoot) { + return MakeDisplayItem<nsDisplayBlendContainer>(aBuilder, aFrame, aList, + aActiveScrolledRoot, false); +} + +/* static */ +nsDisplayBlendContainer* nsDisplayBlendContainer::CreateForBackgroundBlendMode( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsIFrame* aSecondaryFrame, + nsDisplayList* aList, const ActiveScrolledRoot* aActiveScrolledRoot) { + if (aSecondaryFrame) { + auto type = GetTableTypeFromFrame(aFrame); + auto index = static_cast<uint16_t>(type); + + return MakeDisplayItemWithIndex<nsDisplayTableBlendContainer>( + aBuilder, aSecondaryFrame, index, aList, aActiveScrolledRoot, true, + aFrame); + } + + return MakeDisplayItemWithIndex<nsDisplayBlendContainer>( + aBuilder, aFrame, 1, aList, aActiveScrolledRoot, true); +} + +nsDisplayBlendContainer::nsDisplayBlendContainer( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayList* aList, + const ActiveScrolledRoot* aActiveScrolledRoot, bool aIsForBackground) + : nsDisplayWrapList(aBuilder, aFrame, aList, aActiveScrolledRoot, true), + mIsForBackground(aIsForBackground) { + MOZ_COUNT_CTOR(nsDisplayBlendContainer); +} + +// nsDisplayBlendContainer uses layers for rendering +already_AddRefed<Layer> nsDisplayBlendContainer::BuildLayer( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aContainerParameters) { + // turn off anti-aliasing in the parent stacking context because it changes + // how the group is initialized. + ContainerLayerParameters newContainerParameters = aContainerParameters; + newContainerParameters.mDisableSubpixelAntialiasingInDescendants = true; + + RefPtr<Layer> container = aManager->GetLayerBuilder()->BuildContainerLayerFor( + aBuilder, aManager, mFrame, this, &mList, newContainerParameters, + nullptr); + if (!container) { + return nullptr; + } + + container->SetForceIsolatedGroup(true); + return container.forget(); +} + +LayerState nsDisplayBlendContainer::GetLayerState( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aParameters) { + return RequiredLayerStateForChildren(aBuilder, aManager, aParameters, mList, + GetAnimatedGeometryRoot(), + GetActiveScrolledRoot()); +} + +bool nsDisplayBlendContainer::CreateWebRenderCommands( + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + wr::StackingContextParams params; + params.flags |= wr::StackingContextFlags::IS_BLEND_CONTAINER; + params.clip = + wr::WrStackingContextClip::ClipChain(aBuilder.CurrentClipChainId()); + StackingContextHelper sc(aSc, GetActiveScrolledRoot(), mFrame, this, aBuilder, + params); + + return nsDisplayWrapList::CreateWebRenderCommands( + aBuilder, aResources, sc, aManager, aDisplayListBuilder); +} + +nsDisplayOwnLayer::nsDisplayOwnLayer( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayList* aList, + const ActiveScrolledRoot* aActiveScrolledRoot, + nsDisplayOwnLayerFlags aFlags, const ScrollbarData& aScrollbarData, + bool aForceActive, bool aClearClipChain) + : nsDisplayWrapList(aBuilder, aFrame, aList, aActiveScrolledRoot, + aClearClipChain), + mFlags(aFlags), + mScrollbarData(aScrollbarData), + mForceActive(aForceActive), + mWrAnimationId(0) { + MOZ_COUNT_CTOR(nsDisplayOwnLayer); + + // For scroll thumb layers, override the AGR to be the thumb's AGR rather + // than the AGR for mFrame (which is the slider frame). + if (IsScrollThumbLayer()) { + if (nsIFrame* thumbFrame = nsIFrame::GetChildXULBox(mFrame)) { + mAnimatedGeometryRoot = aBuilder->FindAnimatedGeometryRootFor(thumbFrame); + } + } +} + +LayerState nsDisplayOwnLayer::GetLayerState( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aParameters) { + if (mForceActive) { + return mozilla::LayerState::LAYER_ACTIVE_FORCE; + } + + return RequiredLayerStateForChildren(aBuilder, aManager, aParameters, mList, + GetAnimatedGeometryRoot(), + GetActiveScrolledRoot()); +} + +bool nsDisplayOwnLayer::IsScrollThumbLayer() const { + return mScrollbarData.mScrollbarLayerType == + layers::ScrollbarLayerType::Thumb; +} + +bool nsDisplayOwnLayer::IsScrollbarContainer() const { + return mScrollbarData.mScrollbarLayerType == + layers::ScrollbarLayerType::Container; +} + +bool nsDisplayOwnLayer::IsRootScrollbarContainer() const { + if (!IsScrollbarContainer()) { + return false; + } + + return mFrame->PresContext()->IsRootContentDocumentCrossProcess() && + mScrollbarData.mTargetViewId == + nsLayoutUtils::ScrollIdForRootScrollFrame(mFrame->PresContext()); +} + +bool nsDisplayOwnLayer::IsZoomingLayer() const { + return GetType() == DisplayItemType::TYPE_ASYNC_ZOOM; +} + +bool nsDisplayOwnLayer::IsFixedPositionLayer() const { + return GetType() == DisplayItemType::TYPE_FIXED_POSITION; +} + +bool nsDisplayOwnLayer::IsStickyPositionLayer() const { + return GetType() == DisplayItemType::TYPE_STICKY_POSITION; +} + +bool nsDisplayOwnLayer::HasDynamicToolbar() const { + if (!mFrame->PresContext()->IsRootContentDocumentCrossProcess()) { + return false; + } + return mFrame->PresContext()->HasDynamicToolbar() || + // For tests on Android, this pref is set to simulate the dynamic + // toolbar + StaticPrefs::apz_fixed_margin_override_enabled(); +} + +// nsDisplayOpacity uses layers for rendering +already_AddRefed<Layer> nsDisplayOwnLayer::BuildLayer( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aContainerParameters) { + RefPtr<ContainerLayer> layer = + aManager->GetLayerBuilder()->BuildContainerLayerFor( + aBuilder, aManager, mFrame, this, &mList, aContainerParameters, + nullptr, FrameLayerBuilder::CONTAINER_ALLOW_PULL_BACKGROUND_COLOR); + + if (IsScrollThumbLayer() || IsScrollbarContainer()) { + layer->SetScrollbarData(mScrollbarData); + } + + if (mFlags & nsDisplayOwnLayerFlags::GenerateSubdocInvalidations) { + mFrame->PresContext()->SetNotifySubDocInvalidationData(layer); + } + return layer.forget(); +} + +bool nsDisplayOwnLayer::CreateWebRenderCommands( + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + Maybe<wr::WrAnimationProperty> prop; + bool needsProp = aManager->LayerManager()->AsyncPanZoomEnabled() && + (IsScrollThumbLayer() || IsZoomingLayer() || + (IsFixedPositionLayer() && HasDynamicToolbar()) || + (IsStickyPositionLayer() && HasDynamicToolbar()) || + (IsRootScrollbarContainer() && HasDynamicToolbar())); + + if (needsProp) { + // APZ is enabled and this is a scroll thumb or zooming layer, so we need + // to create and set an animation id. That way APZ can adjust the position/ + // zoom of this content asynchronously as needed. + RefPtr<WebRenderAPZAnimationData> animationData = + aManager->CommandBuilder() + .CreateOrRecycleWebRenderUserData<WebRenderAPZAnimationData>(this); + mWrAnimationId = animationData->GetAnimationId(); + + prop.emplace(); + prop->id = mWrAnimationId; + prop->effect_type = wr::WrAnimationType::Transform; + } + + wr::StackingContextParams params; + params.animation = prop.ptrOr(nullptr); + params.clip = + wr::WrStackingContextClip::ClipChain(aBuilder.CurrentClipChainId()); + if (IsScrollbarContainer()) { + params.prim_flags |= wr::PrimitiveFlags::IS_SCROLLBAR_CONTAINER; + } + if (IsScrollThumbLayer()) { + params.prim_flags |= wr::PrimitiveFlags::IS_SCROLLBAR_THUMB; + } + if (IsZoomingLayer()) { + params.reference_frame_kind = wr::WrReferenceFrameKind::Zoom; + } + StackingContextHelper sc(aSc, GetActiveScrolledRoot(), mFrame, this, aBuilder, + params); + + nsDisplayWrapList::CreateWebRenderCommands(aBuilder, aResources, sc, aManager, + aDisplayListBuilder); + return true; +} + +bool nsDisplayOwnLayer::UpdateScrollData( + mozilla::layers::WebRenderScrollData* aData, + mozilla::layers::WebRenderLayerScrollData* aLayerData) { + bool isRelevantToApz = + (IsScrollThumbLayer() || IsScrollbarContainer() || IsZoomingLayer() || + (IsFixedPositionLayer() && HasDynamicToolbar()) || + (IsStickyPositionLayer() && HasDynamicToolbar())); + + if (!isRelevantToApz) { + return false; + } + + if (!aLayerData) { + return true; + } + + if (IsZoomingLayer()) { + aLayerData->SetZoomAnimationId(mWrAnimationId); + return true; + } + + if (IsFixedPositionLayer() && HasDynamicToolbar()) { + aLayerData->SetFixedPositionAnimationId(mWrAnimationId); + return true; + } + + if (IsStickyPositionLayer() && HasDynamicToolbar()) { + aLayerData->SetStickyPositionAnimationId(mWrAnimationId); + return true; + } + + MOZ_ASSERT(IsScrollbarContainer() || IsScrollThumbLayer()); + + aLayerData->SetScrollbarData(mScrollbarData); + + if (IsRootScrollbarContainer() && HasDynamicToolbar()) { + aLayerData->SetScrollbarAnimationId(mWrAnimationId); + return true; + } + + if (IsScrollThumbLayer()) { + aLayerData->SetScrollbarAnimationId(mWrAnimationId); + LayoutDeviceRect bounds = LayoutDeviceIntRect::FromAppUnits( + mBounds, mFrame->PresContext()->AppUnitsPerDevPixel()); + // We use a resolution of 1.0 because this is a WebRender codepath which + // always uses containerless scrolling, and so resolution doesn't apply to + // scrollbars. + LayerIntRect layerBounds = + RoundedOut(bounds * LayoutDeviceToLayerScale(1.0f)); + aLayerData->SetVisibleRegion(LayerIntRegion(layerBounds)); + } + return true; +} + +void nsDisplayOwnLayer::WriteDebugInfo(std::stringstream& aStream) { + aStream << nsPrintfCString(" (flags 0x%x) (scrolltarget %" PRIu64 ")", + (int)mFlags, mScrollbarData.mTargetViewId) + .get(); +} + +nsDisplaySubDocument::nsDisplaySubDocument(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame, + nsSubDocumentFrame* aSubDocFrame, + nsDisplayList* aList, + nsDisplayOwnLayerFlags aFlags) + : nsDisplayOwnLayer(aBuilder, aFrame, aList, + aBuilder->CurrentActiveScrolledRoot(), aFlags), + mScrollParentId(aBuilder->GetCurrentScrollParentId()), + mShouldFlatten(false), + mSubDocFrame(aSubDocFrame) { + MOZ_COUNT_CTOR(nsDisplaySubDocument); + + // The SubDocument display item is conceptually outside the viewport frame, + // so in cases where the viewport frame is an AGR, the SubDocument's AGR + // should be not the viewport frame itself, but its parent AGR. + if (*mAnimatedGeometryRoot == mFrame && mAnimatedGeometryRoot->mParentAGR) { + mAnimatedGeometryRoot = mAnimatedGeometryRoot->mParentAGR; + } + + if (mSubDocFrame && mSubDocFrame != mFrame) { + mSubDocFrame->AddDisplayItem(this); + } +} + +nsDisplaySubDocument::~nsDisplaySubDocument() { + MOZ_COUNT_DTOR(nsDisplaySubDocument); + if (mSubDocFrame) { + mSubDocFrame->RemoveDisplayItem(this); + } +} + +nsIFrame* nsDisplaySubDocument::FrameForInvalidation() const { + return mSubDocFrame ? mSubDocFrame : mFrame; +} + +void nsDisplaySubDocument::RemoveFrame(nsIFrame* aFrame) { + if (aFrame == mSubDocFrame) { + mSubDocFrame = nullptr; + SetDeletedFrame(); + } + nsDisplayOwnLayer::RemoveFrame(aFrame); +} + +void nsDisplaySubDocument::Disown() { + if (mFrame) { + mFrame->RemoveDisplayItem(this); + RemoveFrame(mFrame); + } + if (mSubDocFrame) { + mSubDocFrame->RemoveDisplayItem(this); + RemoveFrame(mSubDocFrame); + } +} + +static bool UseDisplayPortForViewport(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame) { + return aBuilder->IsPaintingToWindow() && + DisplayPortUtils::ViewportHasDisplayPort(aFrame->PresContext()); +} + +nsRect nsDisplaySubDocument::GetBounds(nsDisplayListBuilder* aBuilder, + bool* aSnap) const { + bool usingDisplayPort = UseDisplayPortForViewport(aBuilder, mFrame); + + if ((mFlags & nsDisplayOwnLayerFlags::GenerateScrollableLayer) && + usingDisplayPort) { + *aSnap = false; + return mFrame->GetRect() + aBuilder->ToReferenceFrame(mFrame); + } + + return nsDisplayOwnLayer::GetBounds(aBuilder, aSnap); +} + +bool nsDisplaySubDocument::ComputeVisibility(nsDisplayListBuilder* aBuilder, + nsRegion* aVisibleRegion) { + bool usingDisplayPort = UseDisplayPortForViewport(aBuilder, mFrame); + + if (!(mFlags & nsDisplayOwnLayerFlags::GenerateScrollableLayer) || + !usingDisplayPort) { + return nsDisplayWrapList::ComputeVisibility(aBuilder, aVisibleRegion); + } + + nsRect displayport; + nsIFrame* rootScrollFrame = mFrame->PresShell()->GetRootScrollFrame(); + MOZ_ASSERT(rootScrollFrame); + Unused << DisplayPortUtils::GetDisplayPort( + rootScrollFrame->GetContent(), &displayport, + DisplayPortOptions().With(DisplayportRelativeTo::ScrollFrame)); + + nsRegion childVisibleRegion; + // The visible region for the children may be much bigger than the hole we + // are viewing the children from, so that the compositor process has enough + // content to asynchronously pan while content is being refreshed. + childVisibleRegion = + displayport + mFrame->GetOffsetToCrossDoc(ReferenceFrame()); + + nsRect boundedRect = childVisibleRegion.GetBounds().Intersect( + mList.GetClippedBoundsWithRespectToASR(aBuilder, mActiveScrolledRoot)); + bool visible = mList.ComputeVisibilityForSublist( + aBuilder, &childVisibleRegion, boundedRect); + + // If APZ is enabled then don't allow this computation to influence + // aVisibleRegion, on the assumption that the layer can be asynchronously + // scrolled so we'll definitely need all the content under it. + if (!nsLayoutUtils::UsesAsyncScrolling(mFrame)) { + bool snap; + nsRect bounds = GetBounds(aBuilder, &snap); + nsRegion removed; + removed.Sub(bounds, childVisibleRegion); + + aBuilder->SubtractFromVisibleRegion(aVisibleRegion, removed); + } + + return visible; +} + +nsRegion nsDisplaySubDocument::GetOpaqueRegion(nsDisplayListBuilder* aBuilder, + bool* aSnap) const { + bool usingDisplayPort = UseDisplayPortForViewport(aBuilder, mFrame); + + if ((mFlags & nsDisplayOwnLayerFlags::GenerateScrollableLayer) && + usingDisplayPort) { + *aSnap = false; + return nsRegion(); + } + + return nsDisplayOwnLayer::GetOpaqueRegion(aBuilder, aSnap); +} + +/* static */ +nsDisplayFixedPosition* nsDisplayFixedPosition::CreateForFixedBackground( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsIFrame* aSecondaryFrame, + nsDisplayBackgroundImage* aImage, const uint16_t aIndex) { + nsDisplayList temp; + temp.AppendToTop(aImage); + + if (aSecondaryFrame) { + auto tableType = GetTableTypeFromFrame(aFrame); + const uint16_t index = CalculateTablePerFrameKey(aIndex + 1, tableType); + return MakeDisplayItemWithIndex<nsDisplayTableFixedPosition>( + aBuilder, aSecondaryFrame, index, &temp, aFrame); + } + + return MakeDisplayItemWithIndex<nsDisplayFixedPosition>(aBuilder, aFrame, + aIndex + 1, &temp); +} + +nsDisplayFixedPosition::nsDisplayFixedPosition( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayList* aList, + const ActiveScrolledRoot* aActiveScrolledRoot, + const ActiveScrolledRoot* aContainerASR) + : nsDisplayOwnLayer(aBuilder, aFrame, aList, aActiveScrolledRoot), + mContainerASR(aContainerASR), + mIsFixedBackground(false) { + MOZ_COUNT_CTOR(nsDisplayFixedPosition); + Init(aBuilder); +} + +nsDisplayFixedPosition::nsDisplayFixedPosition(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame, + nsDisplayList* aList) + : nsDisplayOwnLayer(aBuilder, aFrame, aList, + aBuilder->CurrentActiveScrolledRoot()), + mContainerASR(nullptr), // XXX maybe this should be something? + mIsFixedBackground(true) { + MOZ_COUNT_CTOR(nsDisplayFixedPosition); + Init(aBuilder); +} + +void nsDisplayFixedPosition::Init(nsDisplayListBuilder* aBuilder) { + mAnimatedGeometryRootForScrollMetadata = mAnimatedGeometryRoot; + if (ShouldFixToViewport(aBuilder)) { + mAnimatedGeometryRoot = aBuilder->FindAnimatedGeometryRootFor(this); + } +} + +already_AddRefed<Layer> nsDisplayFixedPosition::BuildLayer( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aContainerParameters) { + RefPtr<Layer> layer = + nsDisplayOwnLayer::BuildLayer(aBuilder, aManager, aContainerParameters); + + layer->SetIsFixedPosition(true); + + nsPresContext* presContext = mFrame->PresContext(); + nsIFrame* fixedFrame = + mIsFixedBackground ? presContext->PresShell()->GetRootFrame() : mFrame; + + const nsIFrame* viewportFrame = fixedFrame->GetParent(); + // anchorRect will be in the container's coordinate system (aLayer's parent + // layer). This is the same as the display items' reference frame. + nsRect anchorRect; + if (viewportFrame) { + anchorRect.SizeTo(viewportFrame->GetSize()); + // Fixed position frames are reflowed into the scroll-port size if one has + // been set. + if (const ViewportFrame* viewport = do_QueryFrame(viewportFrame)) { + anchorRect.SizeTo( + viewport->AdjustViewportSizeForFixedPosition(anchorRect)); + } + } else { + // A display item directly attached to the viewport. + // For background-attachment:fixed items, the anchor point is always the + // top-left of the viewport currently. + viewportFrame = fixedFrame; + } + // The anchorRect top-left is always the viewport top-left. + anchorRect.MoveTo(viewportFrame->GetOffsetToCrossDoc(ReferenceFrame())); + + nsLayoutUtils::SetFixedPositionLayerData(layer, viewportFrame, anchorRect, + fixedFrame, presContext, + aContainerParameters); + + return layer.forget(); +} + +ViewID nsDisplayFixedPosition::GetScrollTargetId() { + if (mContainerASR && !nsLayoutUtils::IsReallyFixedPos(mFrame)) { + return mContainerASR->GetViewId(); + } + return nsLayoutUtils::ScrollIdForRootScrollFrame(mFrame->PresContext()); +} + +bool nsDisplayFixedPosition::CreateWebRenderCommands( + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + SideBits sides = SideBits::eNone; + if (!mIsFixedBackground) { + sides = nsLayoutUtils::GetSideBitsForFixedPositionContent(mFrame); + } + + // We install this RAII scrolltarget tracker so that any + // nsDisplayCompositorHitTestInfo items inside this fixed-pos item (and that + // share the same ASR as this item) use the correct scroll target. That way + // attempts to scroll on those items will scroll the root scroll frame. + mozilla::wr::DisplayListBuilder::FixedPosScrollTargetTracker tracker( + aBuilder, GetActiveScrolledRoot(), GetScrollTargetId(), sides); + return nsDisplayOwnLayer::CreateWebRenderCommands( + aBuilder, aResources, aSc, aManager, aDisplayListBuilder); +} + +bool nsDisplayFixedPosition::UpdateScrollData( + mozilla::layers::WebRenderScrollData* aData, + mozilla::layers::WebRenderLayerScrollData* aLayerData) { + if (aLayerData) { + if (!mIsFixedBackground) { + aLayerData->SetFixedPositionSides( + nsLayoutUtils::GetSideBitsForFixedPositionContent(mFrame)); + } + aLayerData->SetFixedPositionScrollContainerId(GetScrollTargetId()); + } + nsDisplayOwnLayer::UpdateScrollData(aData, aLayerData); + return true; +} + +void nsDisplayFixedPosition::WriteDebugInfo(std::stringstream& aStream) { + aStream << nsPrintfCString(" (containerASR %s) (scrolltarget %" PRIu64 ")", + ActiveScrolledRoot::ToString(mContainerASR).get(), + GetScrollTargetId()) + .get(); +} + +TableType GetTableTypeFromFrame(nsIFrame* aFrame) { + if (aFrame->IsTableFrame()) { + return TableType::Table; + } + + if (aFrame->IsTableColFrame()) { + return TableType::TableCol; + } + + if (aFrame->IsTableColGroupFrame()) { + return TableType::TableColGroup; + } + + if (aFrame->IsTableRowFrame()) { + return TableType::TableRow; + } + + if (aFrame->IsTableRowGroupFrame()) { + return TableType::TableRowGroup; + } + + if (aFrame->IsTableCellFrame()) { + return TableType::TableCell; + } + + MOZ_ASSERT_UNREACHABLE("Invalid frame."); + return TableType::Table; +} + +nsDisplayTableFixedPosition::nsDisplayTableFixedPosition( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayList* aList, + nsIFrame* aAncestorFrame) + : nsDisplayFixedPosition(aBuilder, aFrame, aList), + mAncestorFrame(aAncestorFrame) { + if (aBuilder->IsRetainingDisplayList()) { + mAncestorFrame->AddDisplayItem(this); + } +} + +nsDisplayStickyPosition::nsDisplayStickyPosition( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayList* aList, + const ActiveScrolledRoot* aActiveScrolledRoot, + const ActiveScrolledRoot* aContainerASR, bool aClippedToDisplayPort) + : nsDisplayOwnLayer(aBuilder, aFrame, aList, aActiveScrolledRoot), + mContainerASR(aContainerASR), + mClippedToDisplayPort(aClippedToDisplayPort) { + MOZ_COUNT_CTOR(nsDisplayStickyPosition); +} + +void nsDisplayStickyPosition::SetClipChain( + const DisplayItemClipChain* aClipChain, bool aStore) { + mClipChain = aClipChain; + mClip = nullptr; + + MOZ_ASSERT(!mClip, + "There should never be a clip on this item because no clip moves " + "with it."); + + if (aStore) { + mState.mClipChain = aClipChain; + mState.mClip = mClip; + } +} + +already_AddRefed<Layer> nsDisplayStickyPosition::BuildLayer( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aContainerParameters) { + RefPtr<Layer> layer = + nsDisplayOwnLayer::BuildLayer(aBuilder, aManager, aContainerParameters); + + StickyScrollContainer* stickyScrollContainer = + StickyScrollContainer::GetStickyScrollContainerForFrame(mFrame); + if (!stickyScrollContainer) { + return layer.forget(); + } + + nsIFrame* scrollFrame = do_QueryFrame(stickyScrollContainer->ScrollFrame()); + nsPresContext* presContext = scrollFrame->PresContext(); + + // Sticky position frames whose scroll frame is the root scroll frame are + // reflowed into the scroll-port size if one has been set. + nsSize scrollFrameSize = scrollFrame->GetSize(); + if (scrollFrame == presContext->PresShell()->GetRootScrollFrame() && + presContext->PresShell()->IsVisualViewportSizeSet()) { + scrollFrameSize = presContext->PresShell()->GetVisualViewportSize(); + } + + nsLayoutUtils::SetFixedPositionLayerData( + layer, scrollFrame, + nsRect(scrollFrame->GetOffsetToCrossDoc(ReferenceFrame()), + scrollFrameSize), + mFrame, presContext, aContainerParameters); + + ViewID scrollId = nsLayoutUtils::FindOrCreateIDFor( + stickyScrollContainer->ScrollFrame()->GetScrolledFrame()->GetContent()); + + float factor = presContext->AppUnitsPerDevPixel(); + LayerRectAbsolute stickyOuter; + LayerRectAbsolute stickyInner; + CalculateLayerScrollRanges( + stickyScrollContainer, factor, aContainerParameters.mXScale, + aContainerParameters.mYScale, stickyOuter, stickyInner); + layer->SetStickyPositionData(scrollId, stickyOuter, stickyInner); + + return layer.forget(); +} + +// Returns the smallest distance from "0" to the range [min, max] where +// min <= max. Despite the name, the return value is actually a 1-D vector, +// and so may be negative if max < 0. +static nscoord DistanceToRange(nscoord min, nscoord max) { + MOZ_ASSERT(min <= max); + if (max < 0) { + return max; + } + if (min > 0) { + return min; + } + MOZ_ASSERT(min <= 0 && max >= 0); + return 0; +} + +// Returns the magnitude of the part of the range [min, max] that is greater +// than zero. The return value is always non-negative. +static nscoord PositivePart(nscoord min, nscoord max) { + MOZ_ASSERT(min <= max); + if (min >= 0) { + return max - min; + } + if (max > 0) { + return max; + } + return 0; +} + +// Returns the magnitude of the part of the range [min, max] that is less +// than zero. The return value is always non-negative. +static nscoord NegativePart(nscoord min, nscoord max) { + MOZ_ASSERT(min <= max); + if (max <= 0) { + return max - min; + } + if (min < 0) { + return 0 - min; + } + return 0; +} + +StickyScrollContainer* nsDisplayStickyPosition::GetStickyScrollContainer() { + StickyScrollContainer* stickyScrollContainer = + StickyScrollContainer::GetStickyScrollContainerForFrame(mFrame); + if (stickyScrollContainer) { + // If there's no ASR for the scrollframe that this sticky item is attached + // to, then don't create a WR sticky item for it either. Trying to do so + // will end in sadness because WR will interpret some coordinates as + // relative to the nearest enclosing scrollframe, which will correspond + // to the nearest ancestor ASR on the gecko side. That ASR will not be the + // same as the scrollframe this sticky item is actually supposed to be + // attached to, thus the sadness. + // Not sending WR the sticky item is ok, because the enclosing scrollframe + // will never be asynchronously scrolled. Instead we will always position + // the sticky items correctly on the gecko side and WR will never need to + // adjust their position itself. + if (!stickyScrollContainer->ScrollFrame() + ->IsMaybeAsynchronouslyScrolled()) { + stickyScrollContainer = nullptr; + } + } + return stickyScrollContainer; +} + +bool nsDisplayStickyPosition::CreateWebRenderCommands( + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + StickyScrollContainer* stickyScrollContainer = GetStickyScrollContainer(); + + Maybe<wr::SpaceAndClipChainHelper> saccHelper; + + if (stickyScrollContainer) { + float auPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel(); + + bool snap; + nsRect itemBounds = GetBounds(aDisplayListBuilder, &snap); + + Maybe<float> topMargin; + Maybe<float> rightMargin; + Maybe<float> bottomMargin; + Maybe<float> leftMargin; + wr::StickyOffsetBounds vBounds = {0.0, 0.0}; + wr::StickyOffsetBounds hBounds = {0.0, 0.0}; + nsPoint appliedOffset; + + nsRectAbsolute outer; + nsRectAbsolute inner; + stickyScrollContainer->GetScrollRanges(mFrame, &outer, &inner); + + nsIFrame* scrollFrame = do_QueryFrame(stickyScrollContainer->ScrollFrame()); + nsPoint offset = scrollFrame->GetOffsetToCrossDoc(ReferenceFrame()); + + // Adjust the scrollPort coordinates to be relative to the reference frame, + // so that it is in the same space as everything else. + nsRect scrollPort = + stickyScrollContainer->ScrollFrame()->GetScrollPortRect(); + scrollPort += offset; + + // The following computations make more sense upon understanding the + // semantics of "inner" and "outer", which is explained in the comment on + // SetStickyPositionData in Layers.h. + + if (outer.YMost() != inner.YMost()) { + // Question: How far will itemBounds.y be from the top of the scrollport + // when we have scrolled from the current scroll position of "0" to + // reach the range [inner.YMost(), outer.YMost()] where the item gets + // stuck? + // Answer: the current distance is "itemBounds.y - scrollPort.y". That + // needs to be adjusted by the distance to the range, less any other + // sticky ranges that fall between 0 and the range. If the distance is + // negative (i.e. inner.YMost() <= outer.YMost() < 0) then we would be + // scrolling upwards (decreasing scroll offset) to reach that range, + // which would increase itemBounds.y and make it farther away from the + // top of the scrollport. So in that case the adjustment is -distance. + // If the distance is positive (0 < inner.YMost() <= outer.YMost()) then + // we would be scrolling downwards, itemBounds.y would decrease, and we + // again need to adjust by -distance. If we are already in the range + // then no adjustment is needed and distance is 0 so again using + // -distance works. If the distance is positive, and the item has both + // top and bottom sticky ranges, then the bottom sticky range may fall + // (entirely[1] or partly[2]) between the current scroll position. + // [1]: 0 <= outer.Y() <= inner.Y() < inner.YMost() <= outer.YMost() + // [2]: outer.Y() < 0 <= inner.Y() < inner.YMost() <= outer.YMost() + // In these cases, the item doesn't actually move for that part of the + // distance, so we need to subtract out that bit, which can be computed + // as the positive portion of the range [outer.Y(), inner.Y()]. + nscoord distance = DistanceToRange(inner.YMost(), outer.YMost()); + if (distance > 0) { + distance -= PositivePart(outer.Y(), inner.Y()); + } + topMargin = Some(NSAppUnitsToFloatPixels( + itemBounds.y - scrollPort.y - distance, auPerDevPixel)); + // Question: What is the maximum positive ("downward") offset that WR + // will have to apply to this item in order to prevent the item from + // visually moving? + // Answer: Since the item is "sticky" in the range [inner.YMost(), + // outer.YMost()], the maximum offset will be the size of the range, which + // is outer.YMost() - inner.YMost(). + vBounds.max = + NSAppUnitsToFloatPixels(outer.YMost() - inner.YMost(), auPerDevPixel); + // Question: how much of an offset has layout already applied to the item? + // Answer: if we are + // (a) inside the sticky range (inner.YMost() < 0 <= outer.YMost()), or + // (b) past the sticky range (inner.YMost() < outer.YMost() < 0) + // then layout has already applied some offset to the position of the + // item. The amount of the adjustment is |0 - inner.YMost()| in case (a) + // and |outer.YMost() - inner.YMost()| in case (b). + if (inner.YMost() < 0) { + appliedOffset.y = std::min(0, outer.YMost()) - inner.YMost(); + MOZ_ASSERT(appliedOffset.y > 0); + } + } + if (outer.Y() != inner.Y()) { + // Similar logic as in the previous section, but this time we care about + // the distance from itemBounds.YMost() to scrollPort.YMost(). + nscoord distance = DistanceToRange(outer.Y(), inner.Y()); + if (distance < 0) { + distance += NegativePart(inner.YMost(), outer.YMost()); + } + bottomMargin = Some(NSAppUnitsToFloatPixels( + scrollPort.YMost() - itemBounds.YMost() + distance, auPerDevPixel)); + // And here WR will be moving the item upwards rather than downwards so + // again things are inverted from the previous block. + vBounds.min = + NSAppUnitsToFloatPixels(outer.Y() - inner.Y(), auPerDevPixel); + // We can't have appliedOffset be both positive and negative, and the top + // adjustment takes priority. So here we only update appliedOffset.y if + // it wasn't set by the top-sticky case above. + if (appliedOffset.y == 0 && inner.Y() > 0) { + appliedOffset.y = std::max(0, outer.Y()) - inner.Y(); + MOZ_ASSERT(appliedOffset.y < 0); + } + } + // Same as above, but for the x-axis + if (outer.XMost() != inner.XMost()) { + nscoord distance = DistanceToRange(inner.XMost(), outer.XMost()); + if (distance > 0) { + distance -= PositivePart(outer.X(), inner.X()); + } + leftMargin = Some(NSAppUnitsToFloatPixels( + itemBounds.x - scrollPort.x - distance, auPerDevPixel)); + hBounds.max = + NSAppUnitsToFloatPixels(outer.XMost() - inner.XMost(), auPerDevPixel); + if (inner.XMost() < 0) { + appliedOffset.x = std::min(0, outer.XMost()) - inner.XMost(); + MOZ_ASSERT(appliedOffset.x > 0); + } + } + if (outer.X() != inner.X()) { + nscoord distance = DistanceToRange(outer.X(), inner.X()); + if (distance < 0) { + distance += NegativePart(inner.XMost(), outer.XMost()); + } + rightMargin = Some(NSAppUnitsToFloatPixels( + scrollPort.XMost() - itemBounds.XMost() + distance, auPerDevPixel)); + hBounds.min = + NSAppUnitsToFloatPixels(outer.X() - inner.X(), auPerDevPixel); + if (appliedOffset.x == 0 && inner.X() > 0) { + appliedOffset.x = std::max(0, outer.X()) - inner.X(); + MOZ_ASSERT(appliedOffset.x < 0); + } + } + + LayoutDeviceRect bounds = + LayoutDeviceRect::FromAppUnits(itemBounds, auPerDevPixel); + wr::LayoutVector2D applied = { + NSAppUnitsToFloatPixels(appliedOffset.x, auPerDevPixel), + NSAppUnitsToFloatPixels(appliedOffset.y, auPerDevPixel)}; + wr::WrSpatialId spatialId = aBuilder.DefineStickyFrame( + wr::ToLayoutRect(bounds), topMargin.ptrOr(nullptr), + rightMargin.ptrOr(nullptr), bottomMargin.ptrOr(nullptr), + leftMargin.ptrOr(nullptr), vBounds, hBounds, applied); + + saccHelper.emplace(aBuilder, spatialId); + aManager->CommandBuilder().PushOverrideForASR(mContainerASR, spatialId); + } + + { + wr::StackingContextParams params; + params.clip = + wr::WrStackingContextClip::ClipChain(aBuilder.CurrentClipChainId()); + StackingContextHelper sc(aSc, GetActiveScrolledRoot(), mFrame, this, + aBuilder, params); + nsDisplayOwnLayer::CreateWebRenderCommands(aBuilder, aResources, sc, + aManager, aDisplayListBuilder); + } + + if (stickyScrollContainer) { + aManager->CommandBuilder().PopOverrideForASR(mContainerASR); + } + + return true; +} + +void nsDisplayStickyPosition::CalculateLayerScrollRanges( + StickyScrollContainer* aStickyScrollContainer, float aAppUnitsPerDevPixel, + float aScaleX, float aScaleY, LayerRectAbsolute& aStickyOuter, + LayerRectAbsolute& aStickyInner) { + nsRectAbsolute outer; + nsRectAbsolute inner; + aStickyScrollContainer->GetScrollRanges(mFrame, &outer, &inner); + aStickyOuter.SetBox( + NSAppUnitsToFloatPixels(outer.X(), aAppUnitsPerDevPixel) * aScaleX, + NSAppUnitsToFloatPixels(outer.Y(), aAppUnitsPerDevPixel) * aScaleY, + NSAppUnitsToFloatPixels(outer.XMost(), aAppUnitsPerDevPixel) * aScaleX, + NSAppUnitsToFloatPixels(outer.YMost(), aAppUnitsPerDevPixel) * aScaleY); + aStickyInner.SetBox( + NSAppUnitsToFloatPixels(inner.X(), aAppUnitsPerDevPixel) * aScaleX, + NSAppUnitsToFloatPixels(inner.Y(), aAppUnitsPerDevPixel) * aScaleY, + NSAppUnitsToFloatPixels(inner.XMost(), aAppUnitsPerDevPixel) * aScaleX, + NSAppUnitsToFloatPixels(inner.YMost(), aAppUnitsPerDevPixel) * aScaleY); +} + +bool nsDisplayStickyPosition::UpdateScrollData( + mozilla::layers::WebRenderScrollData* aData, + mozilla::layers::WebRenderLayerScrollData* aLayerData) { + bool hasDynamicToolbar = HasDynamicToolbar(); + if (aLayerData && hasDynamicToolbar) { + StickyScrollContainer* stickyScrollContainer = GetStickyScrollContainer(); + if (stickyScrollContainer) { + float auPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel(); + float cumulativeResolution = + mFrame->PresShell()->GetCumulativeResolution(); + LayerRectAbsolute stickyOuter; + LayerRectAbsolute stickyInner; + CalculateLayerScrollRanges(stickyScrollContainer, auPerDevPixel, + cumulativeResolution, cumulativeResolution, + stickyOuter, stickyInner); + aLayerData->SetStickyScrollRangeOuter(stickyOuter); + aLayerData->SetStickyScrollRangeInner(stickyInner); + + SideBits sides = + nsLayoutUtils::GetSideBitsForFixedPositionContent(mFrame); + aLayerData->SetFixedPositionSides(sides); + + ViewID scrollId = + nsLayoutUtils::FindOrCreateIDFor(stickyScrollContainer->ScrollFrame() + ->GetScrolledFrame() + ->GetContent()); + aLayerData->SetStickyPositionScrollContainerId(scrollId); + } + } + // Return true if either there is a dynamic toolbar affecting this sticky + // item or the OwnLayer base implementation returns true for some other + // reason. + bool ret = hasDynamicToolbar; + ret |= nsDisplayOwnLayer::UpdateScrollData(aData, aLayerData); + return ret; +} + +nsDisplayScrollInfoLayer::nsDisplayScrollInfoLayer( + nsDisplayListBuilder* aBuilder, nsIFrame* aScrolledFrame, + nsIFrame* aScrollFrame, const CompositorHitTestInfo& aHitInfo, + const nsRect& aHitArea) + : nsDisplayWrapList(aBuilder, aScrollFrame), + mScrollFrame(aScrollFrame), + mScrolledFrame(aScrolledFrame), + mScrollParentId(aBuilder->GetCurrentScrollParentId()), + mHitInfo(aHitInfo), + mHitArea(aHitArea) { +#ifdef NS_BUILD_REFCNT_LOGGING + MOZ_COUNT_CTOR(nsDisplayScrollInfoLayer); +#endif +} + +already_AddRefed<Layer> nsDisplayScrollInfoLayer::BuildLayer( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aContainerParameters) { + // In general for APZ with event-regions we no longer have a need for + // scrollinfo layers. However, in some cases, there might be content that + // cannot be layerized, and so needs to scroll synchronously. To handle those + // cases, we still want to generate scrollinfo layers. + + return aManager->GetLayerBuilder()->BuildContainerLayerFor( + aBuilder, aManager, mFrame, this, &mList, aContainerParameters, nullptr, + FrameLayerBuilder::CONTAINER_ALLOW_PULL_BACKGROUND_COLOR); +} + +LayerState nsDisplayScrollInfoLayer::GetLayerState( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aParameters) { + return LayerState::LAYER_ACTIVE_EMPTY; +} + +UniquePtr<ScrollMetadata> nsDisplayScrollInfoLayer::ComputeScrollMetadata( + nsDisplayListBuilder* aBuilder, LayerManager* aLayerManager, + const ContainerLayerParameters& aContainerParameters) { + ScrollMetadata metadata = nsLayoutUtils::ComputeScrollMetadata( + mScrolledFrame, mScrollFrame, mScrollFrame->GetContent(), + ReferenceFrame(), aLayerManager, mScrollParentId, mScrollFrame->GetSize(), + Nothing(), false, Some(aContainerParameters)); + metadata.GetMetrics().SetIsScrollInfoLayer(true); + nsIScrollableFrame* scrollableFrame = mScrollFrame->GetScrollTargetFrame(); + if (scrollableFrame) { + aBuilder->AddScrollFrameToNotify(scrollableFrame); + } + + return UniquePtr<ScrollMetadata>(new ScrollMetadata(metadata)); +} + +bool nsDisplayScrollInfoLayer::UpdateScrollData( + mozilla::layers::WebRenderScrollData* aData, + mozilla::layers::WebRenderLayerScrollData* aLayerData) { + if (aLayerData) { + UniquePtr<ScrollMetadata> metadata = ComputeScrollMetadata( + aData->GetBuilder(), aData->GetManager(), ContainerLayerParameters()); + MOZ_ASSERT(aData); + MOZ_ASSERT(metadata); + aLayerData->AppendScrollMetadata(*aData, *metadata); + } + return true; +} + +bool nsDisplayScrollInfoLayer::CreateWebRenderCommands( + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + ScrollableLayerGuid::ViewID scrollId = + nsLayoutUtils::FindOrCreateIDFor(mScrollFrame->GetContent()); + + const LayoutDeviceRect devRect = LayoutDeviceRect::FromAppUnits( + mHitArea, mScrollFrame->PresContext()->AppUnitsPerDevPixel()); + + const wr::LayoutRect rect = wr::ToLayoutRect(devRect); + + aBuilder.PushHitTest(rect, rect, !BackfaceIsHidden(), scrollId, mHitInfo, + SideBits::eNone); + + return true; +} + +void nsDisplayScrollInfoLayer::WriteDebugInfo(std::stringstream& aStream) { + aStream << " (scrollframe " << mScrollFrame << " scrolledFrame " + << mScrolledFrame << ")"; +} + +nsDisplayZoom::nsDisplayZoom(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + nsSubDocumentFrame* aSubDocFrame, + nsDisplayList* aList, int32_t aAPD, + int32_t aParentAPD, nsDisplayOwnLayerFlags aFlags) + : nsDisplaySubDocument(aBuilder, aFrame, aSubDocFrame, aList, aFlags), + mAPD(aAPD), + mParentAPD(aParentAPD) { + MOZ_COUNT_CTOR(nsDisplayZoom); +} + +nsRect nsDisplayZoom::GetBounds(nsDisplayListBuilder* aBuilder, + bool* aSnap) const { + nsRect bounds = nsDisplaySubDocument::GetBounds(aBuilder, aSnap); + *aSnap = false; + return bounds.ScaleToOtherAppUnitsRoundOut(mAPD, mParentAPD); +} + +void nsDisplayZoom::HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect, + HitTestState* aState, + nsTArray<nsIFrame*>* aOutFrames) { + nsRect rect; + // A 1x1 rect indicates we are just hit testing a point, so pass down a 1x1 + // rect as well instead of possibly rounding the width or height to zero. + if (aRect.width == 1 && aRect.height == 1) { + rect.MoveTo(aRect.TopLeft().ScaleToOtherAppUnits(mParentAPD, mAPD)); + rect.width = rect.height = 1; + } else { + rect = aRect.ScaleToOtherAppUnitsRoundOut(mParentAPD, mAPD); + } + mList.HitTest(aBuilder, rect, aState, aOutFrames); +} + +bool nsDisplayZoom::ComputeVisibility(nsDisplayListBuilder* aBuilder, + nsRegion* aVisibleRegion) { + // Convert the passed in visible region to our appunits. + nsRegion visibleRegion; + // mVisibleRect has been clipped to GetClippedBounds + visibleRegion.And(*aVisibleRegion, GetPaintRect()); + visibleRegion = visibleRegion.ScaleToOtherAppUnitsRoundOut(mParentAPD, mAPD); + nsRegion originalVisibleRegion = visibleRegion; + + nsRect transformedVisibleRect = + GetPaintRect().ScaleToOtherAppUnitsRoundOut(mParentAPD, mAPD); + bool retval; + // If we are to generate a scrollable layer we call + // nsDisplaySubDocument::ComputeVisibility to make the necessary adjustments + // for ComputeVisibility, it does all it's calculations in the child APD. + bool usingDisplayPort = UseDisplayPortForViewport(aBuilder, mFrame); + if (!(mFlags & nsDisplayOwnLayerFlags::GenerateScrollableLayer) || + !usingDisplayPort) { + retval = mList.ComputeVisibilityForSublist(aBuilder, &visibleRegion, + transformedVisibleRect); + } else { + retval = nsDisplaySubDocument::ComputeVisibility(aBuilder, &visibleRegion); + } + + nsRegion removed; + // removed = originalVisibleRegion - visibleRegion + removed.Sub(originalVisibleRegion, visibleRegion); + // Convert removed region to parent appunits. + removed = removed.ScaleToOtherAppUnitsRoundIn(mAPD, mParentAPD); + // aVisibleRegion = aVisibleRegion - removed (modulo any simplifications + // SubtractFromVisibleRegion does) + aBuilder->SubtractFromVisibleRegion(aVisibleRegion, removed); + + return retval; +} + +nsDisplayAsyncZoom::nsDisplayAsyncZoom( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayList* aList, + const ActiveScrolledRoot* aActiveScrolledRoot, + mozilla::layers::FrameMetrics::ViewID aViewID) + : nsDisplayOwnLayer(aBuilder, aFrame, aList, aActiveScrolledRoot), + mViewID(aViewID) { + MOZ_COUNT_CTOR(nsDisplayAsyncZoom); +} + +#ifdef NS_BUILD_REFCNT_LOGGING +nsDisplayAsyncZoom::~nsDisplayAsyncZoom() { + MOZ_COUNT_DTOR(nsDisplayAsyncZoom); +} +#endif + +void nsDisplayAsyncZoom::HitTest(nsDisplayListBuilder* aBuilder, + const nsRect& aRect, HitTestState* aState, + nsTArray<nsIFrame*>* aOutFrames) { +#ifdef DEBUG + nsIScrollableFrame* scrollFrame = do_QueryFrame(mFrame); + MOZ_ASSERT(scrollFrame && ViewportUtils::IsZoomedContentRoot( + scrollFrame->GetScrolledFrame())); +#endif + nsRect rect = ViewportUtils::VisualToLayout(aRect, mFrame->PresShell()); + mList.HitTest(aBuilder, rect, aState, aOutFrames); +} + +already_AddRefed<Layer> nsDisplayAsyncZoom::BuildLayer( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aContainerParameters) { + PresShell* presShell = mFrame->PresShell(); + ContainerLayerParameters containerParameters( + presShell->GetResolution(), presShell->GetResolution(), nsIntPoint(), + aContainerParameters); + + RefPtr<Layer> layer = + nsDisplayOwnLayer::BuildLayer(aBuilder, aManager, containerParameters); + + layer->SetIsAsyncZoomContainer(Some(mViewID)); + + layer->SetPostScale(1.0f / presShell->GetResolution(), + 1.0f / presShell->GetResolution()); + layer->AsContainerLayer()->SetScaleToResolution(presShell->GetResolution()); + + return layer.forget(); +} + +bool nsDisplayAsyncZoom::UpdateScrollData( + mozilla::layers::WebRenderScrollData* aData, + mozilla::layers::WebRenderLayerScrollData* aLayerData) { + bool ret = nsDisplayOwnLayer::UpdateScrollData(aData, aLayerData); + MOZ_ASSERT(ret); + if (aLayerData) { + aLayerData->SetAsyncZoomContainerId(mViewID); + } + return ret; +} + +/////////////////////////////////////////////////// +// nsDisplayTransform Implementation +// + +#ifndef DEBUG +static_assert(sizeof(nsDisplayTransform) < 512, "nsDisplayTransform has grown"); +#endif + +nsDisplayTransform::nsDisplayTransform(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame, nsDisplayList* aList, + const nsRect& aChildrenBuildingRect) + : nsDisplayHitTestInfoBase(aBuilder, aFrame), + mTransform(Some(Matrix4x4())), + mTransformGetter(nullptr), + mAnimatedGeometryRootForChildren(mAnimatedGeometryRoot), + mAnimatedGeometryRootForScrollMetadata(mAnimatedGeometryRoot), + mChildrenBuildingRect(aChildrenBuildingRect), + mPrerenderDecision(PrerenderDecision::No), + mIsTransformSeparator(true) { + MOZ_COUNT_CTOR(nsDisplayTransform); + MOZ_ASSERT(aFrame, "Must have a frame!"); + Init(aBuilder, aList); +} + +nsDisplayTransform::nsDisplayTransform(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame, nsDisplayList* aList, + const nsRect& aChildrenBuildingRect, + PrerenderDecision aPrerenderDecision) + : nsDisplayHitTestInfoBase(aBuilder, aFrame), + mTransformGetter(nullptr), + mAnimatedGeometryRootForChildren(mAnimatedGeometryRoot), + mAnimatedGeometryRootForScrollMetadata(mAnimatedGeometryRoot), + mChildrenBuildingRect(aChildrenBuildingRect), + mPrerenderDecision(aPrerenderDecision), + mIsTransformSeparator(false) { + MOZ_COUNT_CTOR(nsDisplayTransform); + MOZ_ASSERT(aFrame, "Must have a frame!"); + SetReferenceFrameToAncestor(aBuilder); + Init(aBuilder, aList); +} + +nsDisplayTransform::nsDisplayTransform( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayList* aList, + const nsRect& aChildrenBuildingRect, + ComputeTransformFunction aTransformGetter) + : nsDisplayHitTestInfoBase(aBuilder, aFrame), + mTransformGetter(aTransformGetter), + mAnimatedGeometryRootForChildren(mAnimatedGeometryRoot), + mAnimatedGeometryRootForScrollMetadata(mAnimatedGeometryRoot), + mChildrenBuildingRect(aChildrenBuildingRect), + mPrerenderDecision(PrerenderDecision::No), + mIsTransformSeparator(false) { + MOZ_COUNT_CTOR(nsDisplayTransform); + MOZ_ASSERT(aFrame, "Must have a frame!"); + Init(aBuilder, aList); +} + +void nsDisplayTransform::SetReferenceFrameToAncestor( + nsDisplayListBuilder* aBuilder) { + if (mFrame == aBuilder->RootReferenceFrame()) { + return; + } + nsIFrame* outerFrame = nsLayoutUtils::GetCrossDocParentFrame(mFrame); + mReferenceFrame = aBuilder->FindReferenceFrameFor(outerFrame); + mToReferenceFrame = mFrame->GetOffsetToCrossDoc(mReferenceFrame); + if (DisplayPortUtils::IsFixedPosFrameInDisplayPort(mFrame)) { + // This is an odd special case. If we are both IsFixedPosFrameInDisplayPort + // and transformed that we are our own AGR parent. + // We want our frame to be our AGR because FrameLayerBuilder uses our AGR to + // determine if we are inside a fixed pos subtree. If we use the outer AGR + // from outside the fixed pos subtree FLB can't tell that we are fixed pos. + mAnimatedGeometryRoot = mAnimatedGeometryRootForChildren; + } else if (mFrame->StyleDisplay()->mPosition == + StylePositionProperty::Sticky && + IsStickyFrameActive(aBuilder, mFrame, nullptr)) { + // Similar to the IsFixedPosFrameInDisplayPort case we are our own AGR. + // We are inside the sticky position, so our AGR is the sticky positioned + // frame, which is our AGR, not the parent AGR. + mAnimatedGeometryRoot = mAnimatedGeometryRootForChildren; + } else if (mAnimatedGeometryRoot->mParentAGR) { + mAnimatedGeometryRootForScrollMetadata = mAnimatedGeometryRoot->mParentAGR; + if (!MayBeAnimated(aBuilder)) { + // If we're an animated transform then we want the same AGR as our + // children so that FrameLayerBuilder knows that this layer moves with the + // transform and won't compute occlusions. If we're not animated then use + // our parent AGR so that inactive transform layers can go in the same + // PaintedLayer as surrounding content. + mAnimatedGeometryRoot = mAnimatedGeometryRoot->mParentAGR; + } + } + + SetBuildingRect(aBuilder->GetVisibleRect() + mToReferenceFrame); +} + +void nsDisplayTransform::Init(nsDisplayListBuilder* aBuilder, + nsDisplayList* aChildren) { + mShouldFlatten = false; + mChildren.AppendToTop(aChildren); + UpdateBounds(aBuilder); +} + +bool nsDisplayTransform::ShouldFlattenAway(nsDisplayListBuilder* aBuilder) { + if (gfxVars::UseWebRender() || + !StaticPrefs::layout_display_list_flatten_transform()) { + return false; + } + + MOZ_ASSERT(!mShouldFlatten); + mShouldFlatten = GetTransform().Is2D(); + return mShouldFlatten; +} + +/* Returns the delta specified by the transform-origin property. + * This is a positive delta, meaning that it indicates the direction to move + * to get from (0, 0) of the frame to the transform origin. This function is + * called off the main thread. + */ +/* static */ +Point3D nsDisplayTransform::GetDeltaToTransformOrigin( + const nsIFrame* aFrame, TransformReferenceBox& aRefBox, + float aAppUnitsPerPixel) { + MOZ_ASSERT(aFrame, "Can't get delta for a null frame!"); + MOZ_ASSERT(aFrame->IsTransformed() || aFrame->BackfaceIsHidden() || + aFrame->Combines3DTransformWithAncestors(), + "Shouldn't get a delta for an untransformed frame!"); + + if (!aFrame->IsTransformed()) { + return Point3D(); + } + + /* For both of the coordinates, if the value of transform is a + * percentage, it's relative to the size of the frame. Otherwise, if it's + * a distance, it's already computed for us! + */ + const nsStyleDisplay* display = aFrame->StyleDisplay(); + + const StyleTransformOrigin& transformOrigin = display->mTransformOrigin; + CSSPoint origin = nsStyleTransformMatrix::Convert2DPosition( + transformOrigin.horizontal, transformOrigin.vertical, aRefBox); + + if (aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) { + // SVG frames (unlike other frames) have a reference box that can be (and + // typically is) offset from the TopLeft() of the frame. We need to account + // for that here. + origin.x += CSSPixel::FromAppUnits(aRefBox.X()); + origin.y += CSSPixel::FromAppUnits(aRefBox.Y()); + } + + float scale = mozilla::AppUnitsPerCSSPixel() / float(aAppUnitsPerPixel); + float z = transformOrigin.depth._0; + return Point3D(origin.x * scale, origin.y * scale, z * scale); +} + +/* static */ +bool nsDisplayTransform::ComputePerspectiveMatrix(const nsIFrame* aFrame, + float aAppUnitsPerPixel, + Matrix4x4& aOutMatrix) { + MOZ_ASSERT(aFrame, "Can't get delta for a null frame!"); + MOZ_ASSERT(aFrame->IsTransformed() || aFrame->BackfaceIsHidden() || + aFrame->Combines3DTransformWithAncestors(), + "Shouldn't get a delta for an untransformed frame!"); + MOZ_ASSERT(aOutMatrix.IsIdentity(), "Must have a blank output matrix"); + + if (!aFrame->IsTransformed()) { + return false; + } + + /* Find our containing block, which is the element that provides the + * value for perspective we need to use + */ + + // TODO: Is it possible that the cbFrame's bounds haven't been set correctly + // yet + // (similar to the aBoundsOverride case for GetResultingTransformMatrix)? + nsIFrame* cbFrame = aFrame->GetContainingBlock(nsIFrame::SKIP_SCROLLED_FRAME); + if (!cbFrame) { + return false; + } + + /* Grab the values for perspective and perspective-origin (if present) */ + const nsStyleDisplay* cbDisplay = cbFrame->StyleDisplay(); + if (cbDisplay->mChildPerspective.IsNone()) { + return false; + } + + MOZ_ASSERT(cbDisplay->mChildPerspective.IsLength()); + // TODO(emilio): Seems quite silly to go through app units just to convert to + // float pixels below. + nscoord perspective = cbDisplay->mChildPerspective.length._0.ToAppUnits(); + if (perspective < std::numeric_limits<Float>::epsilon()) { + return true; + } + + TransformReferenceBox refBox(cbFrame); + + Point perspectiveOrigin = nsStyleTransformMatrix::Convert2DPosition( + cbDisplay->mPerspectiveOrigin.horizontal, + cbDisplay->mPerspectiveOrigin.vertical, refBox, aAppUnitsPerPixel); + + /* GetOffsetTo computes the offset required to move from 0,0 in cbFrame to 0,0 + * in aFrame. Although we actually want the inverse of this, it's faster to + * compute this way. + */ + nsPoint frameToCbOffset = -aFrame->GetOffsetTo(cbFrame); + Point frameToCbGfxOffset( + NSAppUnitsToFloatPixels(frameToCbOffset.x, aAppUnitsPerPixel), + NSAppUnitsToFloatPixels(frameToCbOffset.y, aAppUnitsPerPixel)); + + /* Move the perspective origin to be relative to aFrame, instead of relative + * to the containing block which is how it was specified in the style system. + */ + perspectiveOrigin += frameToCbGfxOffset; + + aOutMatrix._34 = + -1.0 / NSAppUnitsToFloatPixels(perspective, aAppUnitsPerPixel); + + aOutMatrix.ChangeBasis(Point3D(perspectiveOrigin.x, perspectiveOrigin.y, 0)); + return true; +} + +nsDisplayTransform::FrameTransformProperties::FrameTransformProperties( + const nsIFrame* aFrame, TransformReferenceBox& aRefBox, + float aAppUnitsPerPixel) + : mFrame(aFrame), + mTranslate(aFrame->StyleDisplay()->mTranslate), + mRotate(aFrame->StyleDisplay()->mRotate), + mScale(aFrame->StyleDisplay()->mScale), + mTransform(aFrame->StyleDisplay()->mTransform), + mMotion(MotionPathUtils::ResolveMotionPath(aFrame, aRefBox)), + mToTransformOrigin( + GetDeltaToTransformOrigin(aFrame, aRefBox, aAppUnitsPerPixel)) {} + +/* Wraps up the transform matrix in a change-of-basis matrix pair that + * translates from local coordinate space to transform coordinate space, then + * hands it back. + */ +Matrix4x4 nsDisplayTransform::GetResultingTransformMatrix( + const FrameTransformProperties& aProperties, TransformReferenceBox& aRefBox, + float aAppUnitsPerPixel) { + return GetResultingTransformMatrixInternal(aProperties, aRefBox, nsPoint(), + aAppUnitsPerPixel, 0); +} + +Matrix4x4 nsDisplayTransform::GetResultingTransformMatrix( + const nsIFrame* aFrame, const nsPoint& aOrigin, float aAppUnitsPerPixel, + uint32_t aFlags) { + TransformReferenceBox refBox(aFrame); + FrameTransformProperties props(aFrame, refBox, aAppUnitsPerPixel); + return GetResultingTransformMatrixInternal(props, refBox, aOrigin, + aAppUnitsPerPixel, aFlags); +} + +Matrix4x4 nsDisplayTransform::GetResultingTransformMatrixInternal( + const FrameTransformProperties& aProperties, TransformReferenceBox& aRefBox, + const nsPoint& aOrigin, float aAppUnitsPerPixel, uint32_t aFlags) { + const nsIFrame* frame = aProperties.mFrame; + NS_ASSERTION(frame || !(aFlags & INCLUDE_PERSPECTIVE), + "Must have a frame to compute perspective!"); + + // Get the underlying transform matrix: + + /* Get the matrix, then change its basis to factor in the origin. */ + Matrix4x4 result; + // Call IsSVGTransformed() regardless of the value of + // disp->mSpecifiedTransform, since we still need any + // parentsChildrenOnlyTransform. + Matrix svgTransform, parentsChildrenOnlyTransform; + bool hasSVGTransforms = + frame && + frame->IsSVGTransformed(&svgTransform, &parentsChildrenOnlyTransform); + + bool shouldRound = nsLayoutUtils::ShouldSnapToGrid(frame); + + /* Transformed frames always have a transform, or are preserving 3d (and might + * still have perspective!) */ + if (aProperties.HasTransform()) { + result = nsStyleTransformMatrix::ReadTransforms( + aProperties.mTranslate, aProperties.mRotate, aProperties.mScale, + aProperties.mMotion, aProperties.mTransform, aRefBox, + aAppUnitsPerPixel); + } else if (hasSVGTransforms) { + // Correct the translation components for zoom: + float pixelsPerCSSPx = AppUnitsPerCSSPixel() / aAppUnitsPerPixel; + svgTransform._31 *= pixelsPerCSSPx; + svgTransform._32 *= pixelsPerCSSPx; + result = Matrix4x4::From2D(svgTransform); + } + + // Apply any translation due to 'transform-origin' and/or 'transform-box': + result.ChangeBasis(aProperties.mToTransformOrigin); + + // See the comment for SVGContainerFrame::HasChildrenOnlyTransform for + // an explanation of what children-only transforms are. + bool parentHasChildrenOnlyTransform = + hasSVGTransforms && !parentsChildrenOnlyTransform.IsIdentity(); + + if (parentHasChildrenOnlyTransform) { + float pixelsPerCSSPx = AppUnitsPerCSSPixel() / aAppUnitsPerPixel; + parentsChildrenOnlyTransform._31 *= pixelsPerCSSPx; + parentsChildrenOnlyTransform._32 *= pixelsPerCSSPx; + + Point3D frameOffset( + NSAppUnitsToFloatPixels(-frame->GetPosition().x, aAppUnitsPerPixel), + NSAppUnitsToFloatPixels(-frame->GetPosition().y, aAppUnitsPerPixel), 0); + Matrix4x4 parentsChildrenOnlyTransform3D = + Matrix4x4::From2D(parentsChildrenOnlyTransform) + .ChangeBasis(frameOffset); + + result *= parentsChildrenOnlyTransform3D; + } + + Matrix4x4 perspectiveMatrix; + bool hasPerspective = aFlags & INCLUDE_PERSPECTIVE; + if (hasPerspective) { + if (ComputePerspectiveMatrix(frame, aAppUnitsPerPixel, perspectiveMatrix)) { + result *= perspectiveMatrix; + } + } + + if ((aFlags & INCLUDE_PRESERVE3D_ANCESTORS) && frame && + frame->Combines3DTransformWithAncestors()) { + // Include the transform set on our parent + nsIFrame* parentFrame = + frame->GetClosestFlattenedTreeAncestorPrimaryFrame(); + NS_ASSERTION(parentFrame && parentFrame->IsTransformed() && + parentFrame->Extend3DContext(), + "Preserve3D mismatch!"); + TransformReferenceBox refBox(parentFrame); + FrameTransformProperties props(parentFrame, refBox, aAppUnitsPerPixel); + + uint32_t flags = + aFlags & (INCLUDE_PRESERVE3D_ANCESTORS | INCLUDE_PERSPECTIVE); + + // If this frame isn't transformed (but we exist for backface-visibility), + // then we're not a reference frame so no offset to origin will be added. + // Otherwise we need to manually translate into our parent's coordinate + // space. + if (frame->IsTransformed()) { + nsLayoutUtils::PostTranslate(result, frame->GetPosition(), + aAppUnitsPerPixel, shouldRound); + } + Matrix4x4 parent = GetResultingTransformMatrixInternal( + props, refBox, nsPoint(0, 0), aAppUnitsPerPixel, flags); + result = result * parent; + } + + if (aFlags & OFFSET_BY_ORIGIN) { + nsLayoutUtils::PostTranslate(result, aOrigin, aAppUnitsPerPixel, + shouldRound); + } + + return result; +} + +bool nsDisplayOpacity::CanUseAsyncAnimations(nsDisplayListBuilder* aBuilder) { + static constexpr nsCSSPropertyIDSet opacitySet = + nsCSSPropertyIDSet::OpacityProperties(); + if (ActiveLayerTracker::IsStyleAnimated(aBuilder, mFrame, opacitySet)) { + return true; + } + + EffectCompositor::SetPerformanceWarning( + mFrame, opacitySet, + AnimationPerformanceWarning( + AnimationPerformanceWarning::Type::OpacityFrameInactive)); + + return false; +} + +bool nsDisplayTransform::CanUseAsyncAnimations(nsDisplayListBuilder* aBuilder) { + return mPrerenderDecision != PrerenderDecision::No; +} + +bool nsDisplayBackgroundColor::CanUseAsyncAnimations( + nsDisplayListBuilder* aBuilder) { + return StaticPrefs::gfx_omta_background_color(); +} + +static bool IsInStickyPositionedSubtree(const nsIFrame* aFrame) { + for (const nsIFrame* frame = aFrame; frame; + frame = nsLayoutUtils::GetCrossDocParentFrame(frame)) { + if (frame->IsStickyPositioned()) { + return true; + } + } + return false; +} + +static bool ShouldUsePartialPrerender(const nsIFrame* aFrame) { + return StaticPrefs::layout_animation_prerender_partial() && + // Bug 1642547: Support partial prerender for position:sticky elements. + !IsInStickyPositionedSubtree(aFrame); +} + +/* static */ +auto nsDisplayTransform::ShouldPrerenderTransformedContent( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsRect* aDirtyRect) + -> PrerenderInfo { + PrerenderInfo result; + // If we are in a preserve-3d tree, and we've disallowed async animations, we + // return No prerender decision directly. + if ((aFrame->Extend3DContext() || + aFrame->Combines3DTransformWithAncestors()) && + !aBuilder->GetPreserves3DAllowAsyncAnimation()) { + return result; + } + + // Elements whose transform has been modified recently, or which + // have a compositor-animated transform, can be prerendered. An element + // might have only just had its transform animated in which case + // the ActiveLayerManager may not have been notified yet. + static constexpr nsCSSPropertyIDSet transformSet = + nsCSSPropertyIDSet::TransformLikeProperties(); + if (!ActiveLayerTracker::IsTransformMaybeAnimated(aFrame) && + !EffectCompositor::HasAnimationsForCompositor( + aFrame, DisplayItemType::TYPE_TRANSFORM)) { + EffectCompositor::SetPerformanceWarning( + aFrame, transformSet, + AnimationPerformanceWarning( + AnimationPerformanceWarning::Type::TransformFrameInactive)); + + // This case happens when we're sure that the frame is not animated and its + // preserve-3d ancestors are not, either. So we don't need to pre-render. + // However, this decision shouldn't affect the decisions for other frames in + // the preserve-3d context. We need this flag to determine whether we should + // block async animations on other frames in the current preserve-3d tree. + result.mHasAnimations = false; + return result; + } + + // We should not allow prerender if any ancestor container element has + // mask/clip-path effects. + // + // With prerender and async transform animation, we do not need to restyle an + // animated element to respect position changes, since that transform is done + // by layer animation. As a result, the container element is not aware of + // position change of that containing element and loses the chance to update + // the content of mask/clip-path. + // + // Why do we need to update a mask? This is relative to how we generate a + // mask layer in ContainerState::SetupMaskLayerForCSSMask. While creating a + // mask layer, to reduce memory usage, we did not choose the size of the + // masked element as mask size. Instead, we read the union of bounds of all + // children display items by nsDisplayWrapList::GetBounds, which is smaller + // than or equal to the masked element's boundary, and use it as the position + // size of the mask layer. That union bounds is actually affected by the + // geometry of the animated element. To keep the content of mask up to date, + // forbidding of prerender is required. + for (nsIFrame* container = nsLayoutUtils::GetCrossDocParentFrame(aFrame); + container; + container = nsLayoutUtils::GetCrossDocParentFrame(container)) { + const nsStyleSVGReset* svgReset = container->StyleSVGReset(); + if (svgReset->HasMask() || svgReset->HasClipPath()) { + return result; + } + } + + // If the incoming dirty rect already contains the entire overflow area, + // we are already rendering the entire content. + nsRect overflow = aFrame->InkOverflowRectRelativeToSelf(); + // UntransformRect will not touch the output rect (`&untranformedDirtyRect`) + // in cases of non-invertible transforms, so we set `untransformedRect` to + // `aDirtyRect` as an initial value for such cases. + nsRect untransformedDirtyRect = *aDirtyRect; + UntransformRect(*aDirtyRect, overflow, aFrame, &untransformedDirtyRect); + if (untransformedDirtyRect.Contains(overflow)) { + *aDirtyRect = untransformedDirtyRect; + result.mDecision = PrerenderDecision::Full; + return result; + } + + float viewportRatio = + StaticPrefs::layout_animation_prerender_viewport_ratio_limit(); + uint32_t absoluteLimitX = + StaticPrefs::layout_animation_prerender_absolute_limit_x(); + uint32_t absoluteLimitY = + StaticPrefs::layout_animation_prerender_absolute_limit_y(); + nsSize refSize = aBuilder->RootReferenceFrame()->GetSize(); + + float resolution = aFrame->PresShell()->GetCumulativeResolution(); + if (resolution < 1.0f) { + refSize.SizeTo( + NSCoordSaturatingNonnegativeMultiply(refSize.width, 1.0f / resolution), + NSCoordSaturatingNonnegativeMultiply(refSize.height, + 1.0f / resolution)); + } + + // Only prerender if the transformed frame's size is <= a multiple of the + // reference frame size (~viewport), and less than an absolute limit. + // Both the ratio and the absolute limit are configurable. + nscoord maxLength = std::max(nscoord(refSize.width * viewportRatio), + nscoord(refSize.height * viewportRatio)); + nsSize relativeLimit(maxLength, maxLength); + nsSize absoluteLimit( + aFrame->PresContext()->DevPixelsToAppUnits(absoluteLimitX), + aFrame->PresContext()->DevPixelsToAppUnits(absoluteLimitY)); + nsSize maxSize = Min(relativeLimit, absoluteLimit); + + const auto transform = nsLayoutUtils::GetTransformToAncestor( + RelativeTo{aFrame}, + RelativeTo{nsLayoutUtils::GetDisplayRootFrame(aFrame)}); + const gfxRect transformedBounds = transform.TransformAndClipBounds( + gfxRect(overflow.x, overflow.y, overflow.width, overflow.height), + gfxRect::MaxIntRect()); + const nsSize frameSize = + nsSize(transformedBounds.width, transformedBounds.height); + + uint64_t maxLimitArea = uint64_t(maxSize.width) * maxSize.height; + uint64_t frameArea = uint64_t(frameSize.width) * frameSize.height; + if (frameArea <= maxLimitArea && frameSize <= absoluteLimit) { + *aDirtyRect = overflow; + result.mDecision = PrerenderDecision::Full; + return result; + } + + if (ShouldUsePartialPrerender(aFrame)) { + *aDirtyRect = nsLayoutUtils::ComputePartialPrerenderArea( + aFrame, untransformedDirtyRect, overflow, maxSize); + result.mDecision = PrerenderDecision::Partial; + return result; + } + + if (frameArea > maxLimitArea) { + uint64_t appUnitsPerPixel = AppUnitsPerCSSPixel(); + EffectCompositor::SetPerformanceWarning( + aFrame, transformSet, + AnimationPerformanceWarning( + AnimationPerformanceWarning::Type::ContentTooLargeArea, + { + int(frameArea / (appUnitsPerPixel * appUnitsPerPixel)), + int(maxLimitArea / (appUnitsPerPixel * appUnitsPerPixel)), + })); + } else { + EffectCompositor::SetPerformanceWarning( + aFrame, transformSet, + AnimationPerformanceWarning( + AnimationPerformanceWarning::Type::ContentTooLarge, + { + nsPresContext::AppUnitsToIntCSSPixels(frameSize.width), + nsPresContext::AppUnitsToIntCSSPixels(frameSize.height), + nsPresContext::AppUnitsToIntCSSPixels(relativeLimit.width), + nsPresContext::AppUnitsToIntCSSPixels(relativeLimit.height), + nsPresContext::AppUnitsToIntCSSPixels(absoluteLimit.width), + nsPresContext::AppUnitsToIntCSSPixels(absoluteLimit.height), + })); + } + + return result; +} + +/* If the matrix is singular, or a hidden backface is shown, the frame won't be + * visible or hit. */ +static bool IsFrameVisible(nsIFrame* aFrame, const Matrix4x4& aMatrix) { + if (aMatrix.IsSingular()) { + return false; + } + if (aFrame->BackfaceIsHidden() && aMatrix.IsBackfaceVisible()) { + return false; + } + return true; +} + +const Matrix4x4Flagged& nsDisplayTransform::GetTransform() const { + if (mTransform) { + return *mTransform; + } + + float scale = mFrame->PresContext()->AppUnitsPerDevPixel(); + + if (mTransformGetter) { + mTransform.emplace(mTransformGetter(mFrame, scale)); + Point3D newOrigin = + Point3D(NSAppUnitsToFloatPixels(mToReferenceFrame.x, scale), + NSAppUnitsToFloatPixels(mToReferenceFrame.y, scale), 0.0f); + mTransform->ChangeBasis(newOrigin.x, newOrigin.y, newOrigin.z); + } else if (!mIsTransformSeparator) { + DebugOnly<bool> isReference = mFrame->IsTransformed() || + mFrame->Combines3DTransformWithAncestors() || + mFrame->Extend3DContext(); + MOZ_ASSERT(isReference); + mTransform.emplace( + GetResultingTransformMatrix(mFrame, ToReferenceFrame(), scale, + INCLUDE_PERSPECTIVE | OFFSET_BY_ORIGIN)); + } else { + // Use identity matrix + mTransform.emplace(); + } + + return *mTransform; +} + +const Matrix4x4Flagged& nsDisplayTransform::GetInverseTransform() const { + if (mInverseTransform) { + return *mInverseTransform; + } + + MOZ_ASSERT(!GetTransform().IsSingular()); + + mInverseTransform.emplace(GetTransform().Inverse()); + + return *mInverseTransform; +} + +Matrix4x4 nsDisplayTransform::GetTransformForRendering( + LayoutDevicePoint* aOutOrigin) const { + if (!mFrame->HasPerspective() || mTransformGetter || mIsTransformSeparator) { + if (!mTransformGetter && !mIsTransformSeparator && aOutOrigin) { + // If aOutOrigin is provided, put the offset to origin into it, because + // we need to keep it separate for webrender. The combination of + // *aOutOrigin and the returned matrix here should always be equivalent + // to what GetTransform() would have returned. + float scale = mFrame->PresContext()->AppUnitsPerDevPixel(); + *aOutOrigin = LayoutDevicePoint::FromAppUnits(ToReferenceFrame(), scale); + + // The rounding behavior should also be the same as GetTransform(). + if (nsLayoutUtils::ShouldSnapToGrid(mFrame)) { + aOutOrigin->Round(); + } + return GetResultingTransformMatrix(mFrame, nsPoint(0, 0), scale, + INCLUDE_PERSPECTIVE); + } + return GetTransform().GetMatrix(); + } + MOZ_ASSERT(!mTransformGetter); + + float scale = mFrame->PresContext()->AppUnitsPerDevPixel(); + // Don't include perspective transform, or the offset to origin, since + // nsDisplayPerspective will handle both of those. + return GetResultingTransformMatrix(mFrame, ToReferenceFrame(), scale, 0); +} + +const Matrix4x4& nsDisplayTransform::GetAccumulatedPreserved3DTransform( + nsDisplayListBuilder* aBuilder) { + MOZ_ASSERT(!mFrame->Extend3DContext() || IsLeafOf3DContext()); + + if (!IsLeafOf3DContext()) { + return GetTransform().GetMatrix(); + } + + // XXX: should go back to fix mTransformGetter. + if (!mTransformPreserves3D) { + const nsIFrame* establisher; // Establisher of the 3D rendering context. + for (establisher = mFrame; + establisher && establisher->Combines3DTransformWithAncestors(); + establisher = + establisher->GetClosestFlattenedTreeAncestorPrimaryFrame()) { + } + const nsIFrame* establisherReference = aBuilder->FindReferenceFrameFor( + nsLayoutUtils::GetCrossDocParentFrame(establisher)); + + nsPoint offset = establisher->GetOffsetToCrossDoc(establisherReference); + float scale = mFrame->PresContext()->AppUnitsPerDevPixel(); + uint32_t flags = + INCLUDE_PRESERVE3D_ANCESTORS | INCLUDE_PERSPECTIVE | OFFSET_BY_ORIGIN; + mTransformPreserves3D = MakeUnique<Matrix4x4>( + GetResultingTransformMatrix(mFrame, offset, scale, flags)); + } + + return *mTransformPreserves3D; +} + +bool nsDisplayTransform::CreateWebRenderCommands( + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + // We want to make sure we don't pollute the transform property in the WR + // stacking context by including the position of this frame (relative to the + // parent reference frame). We need to keep those separate; the position of + // this frame goes into the stacking context bounds while the transform goes + // into the transform. + LayoutDevicePoint position; + Matrix4x4 newTransformMatrix = GetTransformForRendering(&position); + + gfx::Matrix4x4* transformForSC = &newTransformMatrix; + if (newTransformMatrix.IsIdentity()) { + // If the transform is an identity transform, strip it out so that WR + // doesn't turn this stacking context into a reference frame, as it + // affects positioning. Bug 1345577 tracks a better fix. + transformForSC = nullptr; + + // In ChooseScaleAndSetTransform, we round the offset from the reference + // frame used to adjust the transform, if there is no transform, or it + // is just a translation. We need to do the same here. + if (nsLayoutUtils::ShouldSnapToGrid(mFrame)) { + position.Round(); + } + } + + // We don't send animations for transform separator display items. + uint64_t animationsId = + mIsTransformSeparator + ? 0 + : AddAnimationsForWebRender( + this, aManager, aDisplayListBuilder, + IsPartialPrerender() ? Some(position) : Nothing()); + wr::WrAnimationProperty prop{ + wr::WrAnimationType::Transform, + animationsId, + }; + + Maybe<nsDisplayTransform*> deferredTransformItem; + if (!mFrame->ChildrenHavePerspective()) { + // If it has perspective, we create a new scroll data via the + // UpdateScrollData call because that scenario is more complex. Otherwise + // we can just stash the transform on the StackingContextHelper and + // apply it to any scroll data that are created inside this + // nsDisplayTransform. + deferredTransformItem = Some(this); + } + + // Determine if we're possibly animated (= would need an active layer in FLB). + bool animated = !mIsTransformSeparator && + ActiveLayerTracker::IsTransformMaybeAnimated(Frame()); + + wr::StackingContextParams params; + params.mBoundTransform = &newTransformMatrix; + params.animation = animationsId ? &prop : nullptr; + params.mTransformPtr = transformForSC; + params.prim_flags = !BackfaceIsHidden() + ? wr::PrimitiveFlags::IS_BACKFACE_VISIBLE + : wr::PrimitiveFlags{0}; + params.mDeferredTransformItem = deferredTransformItem; + params.mAnimated = animated; + // Determine if we would have to rasterize any items in local raster space + // (i.e. disable subpixel AA). We don't always need to rasterize locally even + // if the stacking context is possibly animated (at the cost of potentially + // some false negatives with respect to will-change handling), so we pass in + // this determination separately to accurately match with when FLB would + // normally disable subpixel AA. + params.mRasterizeLocally = animated && Frame()->HasAnimationOfTransform(); + params.SetPreserve3D(mFrame->Extend3DContext() && !mIsTransformSeparator); + params.clip = + wr::WrStackingContextClip::ClipChain(aBuilder.CurrentClipChainId()); + + LayoutDeviceSize boundsSize = LayoutDeviceSize::FromAppUnits( + mChildBounds.Size(), mFrame->PresContext()->AppUnitsPerDevPixel()); + + StackingContextHelper sc(aSc, GetActiveScrolledRoot(), mFrame, this, aBuilder, + params, LayoutDeviceRect(position, boundsSize)); + + aManager->CommandBuilder().CreateWebRenderCommandsFromDisplayList( + GetChildren(), this, aDisplayListBuilder, sc, aBuilder, aResources); + return true; +} + +bool nsDisplayTransform::UpdateScrollData( + mozilla::layers::WebRenderScrollData* aData, + mozilla::layers::WebRenderLayerScrollData* aLayerData) { + if (!mFrame->ChildrenHavePerspective()) { + // This case is handled in CreateWebRenderCommands by stashing the transform + // on the stacking context. + return false; + } + if (aLayerData) { + aLayerData->SetTransform(GetTransform().GetMatrix()); + aLayerData->SetTransformIsPerspective(true); + } + return true; +} + +bool nsDisplayTransform::ShouldSkipTransform( + nsDisplayListBuilder* aBuilder) const { + return (aBuilder->RootReferenceFrame() == mFrame) && + aBuilder->IsForGenerateGlyphMask(); +} + +already_AddRefed<Layer> nsDisplayTransform::BuildLayer( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aContainerParameters) { + // While generating a glyph mask, the transform vector of the root frame had + // been applied into the target context, so stop applying it again here. + const bool shouldSkipTransform = ShouldSkipTransform(aBuilder); + + /* For frames without transform, it would not be removed for + * backface hidden here. But, it would be removed by the init + * function of nsDisplayTransform. + */ + const Matrix4x4 newTransformMatrix = + shouldSkipTransform ? Matrix4x4() : GetTransformForRendering(); + + uint32_t flags = FrameLayerBuilder::CONTAINER_ALLOW_PULL_BACKGROUND_COLOR; + RefPtr<ContainerLayer> container = + aManager->GetLayerBuilder()->BuildContainerLayerFor( + aBuilder, aManager, mFrame, this, GetChildren(), aContainerParameters, + &newTransformMatrix, flags); + + if (!container) { + return nullptr; + } + + // Add the preserve-3d flag for this layer, BuildContainerLayerFor clears all + // flags, so we never need to explicitly unset this flag. + if (mFrame->Extend3DContext() && !mIsTransformSeparator) { + container->SetContentFlags(container->GetContentFlags() | + Layer::CONTENT_EXTEND_3D_CONTEXT); + } else { + container->SetContentFlags(container->GetContentFlags() & + ~Layer::CONTENT_EXTEND_3D_CONTEXT); + } + + if (CanUseAsyncAnimations(aBuilder)) { + mFrame->SetProperty(nsIFrame::RefusedAsyncAnimationProperty(), false); + } + + // We don't send animations for transform separator display items. + if (!mIsTransformSeparator) { + nsDisplayListBuilder::AddAnimationsAndTransitionsToLayer( + container, aBuilder, this, mFrame, GetType()); + } + + if (CanUseAsyncAnimations(aBuilder) && MayBeAnimated(aBuilder)) { + // Only allow async updates to the transform if we're an animated layer, + // since that's what triggers us to set the correct AGR in the constructor + // and makes sure FrameLayerBuilder won't compute occlusions for this layer. + container->SetUserData(nsIFrame::LayerIsPrerenderedDataKey(), + /*the value is irrelevant*/ nullptr); + container->SetContentFlags(container->GetContentFlags() | + Layer::CONTENT_MAY_CHANGE_TRANSFORM); + } else { + container->RemoveUserData(nsIFrame::LayerIsPrerenderedDataKey()); + container->SetContentFlags(container->GetContentFlags() & + ~Layer::CONTENT_MAY_CHANGE_TRANSFORM); + } + return container.forget(); +} + +bool nsDisplayTransform::MayBeAnimated(nsDisplayListBuilder* aBuilder, + bool aEnforceMinimumSize) const { + // If EffectCompositor::HasAnimationsForCompositor() is true then we can + // completely bypass the main thread for this animation, so it is always + // worthwhile. + // For ActiveLayerTracker::IsTransformAnimated() cases the main thread is + // already involved so there is less to be gained. + // Therefore we check that the *post-transform* bounds of this item are + // big enough to justify an active layer. + if (EffectCompositor::HasAnimationsForCompositor( + mFrame, DisplayItemType::TYPE_TRANSFORM) || + (ActiveLayerTracker::IsTransformAnimated(aBuilder, mFrame) && + !(aEnforceMinimumSize && IsItemTooSmallForActiveLayer(mFrame)))) { + return true; + } + return false; +} + +nsDisplayItem::LayerState nsDisplayTransform::GetLayerState( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aParameters) { + // If the transform is 3d, the layer takes part in preserve-3d + // sorting, or the layer is a separator then we *always* want this + // to be an active layer. + // Checking HasPerspective() is needed to handle perspective value 0 when + // the transform is 2D. + if (!GetTransform().Is2D() || Combines3DTransformWithAncestors() || + mIsTransformSeparator || mFrame->HasPerspective()) { + return LayerState::LAYER_ACTIVE_FORCE; + } + + if (MayBeAnimated(aBuilder)) { + // Returns LayerState::LAYER_ACTIVE_FORCE to avoid flatterning the layer for + // async animations. + return LayerState::LAYER_ACTIVE_FORCE; + } + + // Expect the child display items to have this frame as their animated + // geometry root (since it will be their reference frame). If they have a + // different animated geometry root, we'll make this an active layer so the + // animation can be accelerated. + return RequiredLayerStateForChildren( + aBuilder, aManager, aParameters, *GetChildren(), + mAnimatedGeometryRootForChildren, GetActiveScrolledRoot()); +} + +bool nsDisplayTransform::ComputeVisibility(nsDisplayListBuilder* aBuilder, + nsRegion* aVisibleRegion) { + // nsDisplayTransform::GetBounds() returns an empty rect in nested 3d context. + // Calling mStoredList.RecomputeVisibility below for such transform causes the + // child display items to end up with empty visible rect. + // We avoid this by bailing out always if we are dealing with a 3d context. + if (mFrame->Extend3DContext() || Combines3DTransformWithAncestors()) { + return true; + } + + /* As we do this, we need to be sure to + * untransform the visible rect, since we want everything that's painting to + * think that it's painting in its original rectangular coordinate space. + * If we can't untransform, take the entire overflow rect */ + nsRect untransformedVisibleRect; + if (!UntransformPaintRect(aBuilder, &untransformedVisibleRect)) { + untransformedVisibleRect = mFrame->InkOverflowRectRelativeToSelf(); + } + + bool snap; + const nsRect bounds = GetUntransformedBounds(aBuilder, &snap); + nsRegion visibleRegion; + visibleRegion.And(bounds, untransformedVisibleRect); + GetChildren()->ComputeVisibilityForSublist(aBuilder, &visibleRegion, + visibleRegion.GetBounds()); + + return true; +} + +nsRect nsDisplayTransform::TransformUntransformedBounds( + nsDisplayListBuilder* aBuilder, const Matrix4x4Flagged& aMatrix) const { + bool snap; + const nsRect untransformedBounds = GetUntransformedBounds(aBuilder, &snap); + // GetTransform always operates in dev pixels. + const float factor = mFrame->PresContext()->AppUnitsPerDevPixel(); + return nsLayoutUtils::MatrixTransformRect(untransformedBounds, aMatrix, + factor); +} + +/** + * Returns the bounds for this transform. The bounds are calculated during + * display list building and merging, see |nsDisplayTransform::UpdateBounds()|. + */ +nsRect nsDisplayTransform::GetBounds(nsDisplayListBuilder* aBuilder, + bool* aSnap) const { + *aSnap = false; + return mBounds; +} + +void nsDisplayTransform::ComputeBounds(nsDisplayListBuilder* aBuilder) { + MOZ_ASSERT(mFrame->Extend3DContext() || IsLeafOf3DContext()); + + /* Some transforms can get empty bounds in 2D, but might get transformed again + * and get non-empty bounds. A simple example of this would be a 180 degree + * rotation getting applied twice. + * We should not depend on transforming bounds level by level. + * + * This function collects the bounds of this transform and stores it in + * nsDisplayListBuilder. If this is not a leaf of a 3D context, we recurse + * down and include the bounds of the child transforms. + * The bounds are transformed with the accumulated transformation matrix up to + * the 3D context root coordinate space. + */ + nsDisplayListBuilder::AutoAccumulateTransform accTransform(aBuilder); + accTransform.Accumulate(GetTransform().GetMatrix()); + + // Do not dive into another 3D context. + if (!IsLeafOf3DContext()) { + for (nsDisplayItem* i : *GetChildren()) { + i->DoUpdateBoundsPreserves3D(aBuilder); + } + } + + /* The child transforms that extend 3D context further will have empty bounds, + * so the untransformed bounds here is the bounds of all the non-preserve-3d + * content under this transform. + */ + const nsRect rect = TransformUntransformedBounds( + aBuilder, accTransform.GetCurrentTransform()); + aBuilder->AccumulateRect(rect); +} + +void nsDisplayTransform::DoUpdateBoundsPreserves3D( + nsDisplayListBuilder* aBuilder) { + MOZ_ASSERT(mFrame->Combines3DTransformWithAncestors() || + IsTransformSeparator()); + // Updating is not going through to child 3D context. + ComputeBounds(aBuilder); +} + +void nsDisplayTransform::UpdateBounds(nsDisplayListBuilder* aBuilder) { + UpdateUntransformedBounds(aBuilder); + + if (IsTransformSeparator()) { + MOZ_ASSERT(GetTransform().IsIdentity()); + mBounds = mChildBounds; + return; + } + + if (mFrame->Extend3DContext()) { + if (!Combines3DTransformWithAncestors()) { + // The transform establishes a 3D context. |UpdateBoundsFor3D()| will + // collect the bounds from the child transforms. + UpdateBoundsFor3D(aBuilder); + } else { + // With nested 3D transforms, the 2D bounds might not be useful. + mBounds = nsRect(); + } + + return; + } + + MOZ_ASSERT(!mFrame->Extend3DContext()); + + // We would like to avoid calculating 2D bounds here for nested 3D transforms, + // but mix-blend-mode relies on having bounds set. See bug 1556956. + + // A stand-alone transform. + mBounds = TransformUntransformedBounds(aBuilder, GetTransform()); +} + +void nsDisplayTransform::UpdateBoundsFor3D(nsDisplayListBuilder* aBuilder) { + MOZ_ASSERT(mFrame->Extend3DContext() && + !mFrame->Combines3DTransformWithAncestors() && + !IsTransformSeparator()); + + // Always start updating from an establisher of a 3D rendering context. + nsDisplayListBuilder::AutoAccumulateRect accRect(aBuilder); + nsDisplayListBuilder::AutoAccumulateTransform accTransform(aBuilder); + accTransform.StartRoot(); + ComputeBounds(aBuilder); + mBounds = aBuilder->GetAccumulatedRect(); +} + +void nsDisplayTransform::UpdateUntransformedBounds( + nsDisplayListBuilder* aBuilder) { + mChildBounds = GetChildren()->GetClippedBoundsWithRespectToASR( + aBuilder, mActiveScrolledRoot); +} + +#ifdef DEBUG_HIT +# include <time.h> +#endif + +/* HitTest does some fun stuff with matrix transforms to obtain the answer. */ +void nsDisplayTransform::HitTest(nsDisplayListBuilder* aBuilder, + const nsRect& aRect, HitTestState* aState, + nsTArray<nsIFrame*>* aOutFrames) { + if (aState->mInPreserves3D) { + GetChildren()->HitTest(aBuilder, aRect, aState, aOutFrames); + return; + } + + /* Here's how this works: + * 1. Get the matrix. If it's singular, abort (clearly we didn't hit + * anything). + * 2. Invert the matrix. + * 3. Use it to transform the rect into the correct space. + * 4. Pass that rect down through to the list's version of HitTest. + */ + // GetTransform always operates in dev pixels. + float factor = mFrame->PresContext()->AppUnitsPerDevPixel(); + Matrix4x4 matrix = GetAccumulatedPreserved3DTransform(aBuilder); + + if (!IsFrameVisible(mFrame, matrix)) { + return; + } + + /* We want to go from transformed-space to regular space. + * Thus we have to invert the matrix, which normally does + * the reverse operation (e.g. regular->transformed) + */ + + /* Now, apply the transform and pass it down the channel. */ + matrix.Invert(); + nsRect resultingRect; + if (aRect.width == 1 && aRect.height == 1) { + // Magic width/height indicating we're hit testing a point, not a rect + Point4D point = + matrix.ProjectPoint(Point(NSAppUnitsToFloatPixels(aRect.x, factor), + NSAppUnitsToFloatPixels(aRect.y, factor))); + if (!point.HasPositiveWCoord()) { + return; + } + + Point point2d = point.As2DPoint(); + + resultingRect = + nsRect(NSFloatPixelsToAppUnits(float(point2d.x), factor), + NSFloatPixelsToAppUnits(float(point2d.y), factor), 1, 1); + + } else { + Rect originalRect(NSAppUnitsToFloatPixels(aRect.x, factor), + NSAppUnitsToFloatPixels(aRect.y, factor), + NSAppUnitsToFloatPixels(aRect.width, factor), + NSAppUnitsToFloatPixels(aRect.height, factor)); + + bool snap; + nsRect childBounds = GetUntransformedBounds(aBuilder, &snap); + Rect childGfxBounds(NSAppUnitsToFloatPixels(childBounds.x, factor), + NSAppUnitsToFloatPixels(childBounds.y, factor), + NSAppUnitsToFloatPixels(childBounds.width, factor), + NSAppUnitsToFloatPixels(childBounds.height, factor)); + + Rect rect = matrix.ProjectRectBounds(originalRect, childGfxBounds); + + resultingRect = + nsRect(NSFloatPixelsToAppUnits(float(rect.X()), factor), + NSFloatPixelsToAppUnits(float(rect.Y()), factor), + NSFloatPixelsToAppUnits(float(rect.Width()), factor), + NSFloatPixelsToAppUnits(float(rect.Height()), factor)); + } + + if (resultingRect.IsEmpty()) { + return; + } + +#ifdef DEBUG_HIT + printf("Frame: %p\n", dynamic_cast<void*>(mFrame)); + printf(" Untransformed point: (%f, %f)\n", resultingRect.X(), + resultingRect.Y()); + uint32_t originalFrameCount = aOutFrames.Length(); +#endif + + GetChildren()->HitTest(aBuilder, resultingRect, aState, aOutFrames); + +#ifdef DEBUG_HIT + if (originalFrameCount != aOutFrames.Length()) + printf(" Hit! Time: %f, first frame: %p\n", static_cast<double>(clock()), + dynamic_cast<void*>(aOutFrames.ElementAt(0))); + printf("=== end of hit test ===\n"); +#endif +} + +float nsDisplayTransform::GetHitDepthAtPoint(nsDisplayListBuilder* aBuilder, + const nsPoint& aPoint) { + // GetTransform always operates in dev pixels. + float factor = mFrame->PresContext()->AppUnitsPerDevPixel(); + Matrix4x4 matrix = GetAccumulatedPreserved3DTransform(aBuilder); + + NS_ASSERTION(IsFrameVisible(mFrame, matrix), + "We can't have hit a frame that isn't visible!"); + + Matrix4x4 inverse = matrix; + inverse.Invert(); + Point4D point = + inverse.ProjectPoint(Point(NSAppUnitsToFloatPixels(aPoint.x, factor), + NSAppUnitsToFloatPixels(aPoint.y, factor))); + + Point point2d = point.As2DPoint(); + + Point3D transformed = matrix.TransformPoint(Point3D(point2d.x, point2d.y, 0)); + return transformed.z; +} + +/* The transform is opaque iff the transform consists solely of scales and + * translations and if the underlying content is opaque. Thus if the transform + * is of the form + * + * |a c e| + * |b d f| + * |0 0 1| + * + * We need b and c to be zero. + * + * We also need to check whether the underlying opaque content completely fills + * our visible rect. We use UntransformRect which expands to the axis-aligned + * bounding rect, but that's OK since if + * mStoredList.GetVisibleRect().Contains(untransformedVisible), then it + * certainly contains the actual (non-axis-aligned) untransformed rect. + */ +nsRegion nsDisplayTransform::GetOpaqueRegion(nsDisplayListBuilder* aBuilder, + bool* aSnap) const { + *aSnap = false; + + nsRect untransformedVisible; + if (!UntransformBuildingRect(aBuilder, &untransformedVisible)) { + return nsRegion(); + } + + const Matrix4x4Flagged& matrix = GetTransform(); + Matrix matrix2d; + if (!matrix.Is2D(&matrix2d) || !matrix2d.PreservesAxisAlignedRectangles()) { + return nsRegion(); + } + + nsRegion result; + + bool tmpSnap; + const nsRect bounds = GetUntransformedBounds(aBuilder, &tmpSnap); + const nsRegion opaque = ::GetOpaqueRegion(aBuilder, GetChildren(), bounds); + + if (opaque.Contains(untransformedVisible)) { + result = GetBuildingRect().Intersect(GetBounds(aBuilder, &tmpSnap)); + } + return result; +} + +nsRect nsDisplayTransform::GetComponentAlphaBounds( + nsDisplayListBuilder* aBuilder) const { + if (GetChildren()->GetComponentAlphaBounds(aBuilder).IsEmpty()) { + return nsRect(); + } + + bool snap; + return GetBounds(aBuilder, &snap); +} + +/* TransformRect takes in as parameters a rectangle (in app space) and returns + * the smallest rectangle (in app space) containing the transformed image of + * that rectangle. That is, it takes the four corners of the rectangle, + * transforms them according to the matrix associated with the specified frame, + * then returns the smallest rectangle containing the four transformed points. + * + * @param aUntransformedBounds The rectangle (in app units) to transform. + * @param aFrame The frame whose transformation should be applied. + * @param aOrigin The delta from the frame origin to the coordinate space origin + * @return The smallest rectangle containing the image of the transformed + * rectangle. + */ +nsRect nsDisplayTransform::TransformRect(const nsRect& aUntransformedBounds, + const nsIFrame* aFrame, + TransformReferenceBox& aRefBox) { + MOZ_ASSERT(aFrame, "Can't take the transform based on a null frame!"); + + float factor = aFrame->PresContext()->AppUnitsPerDevPixel(); + + uint32_t flags = + INCLUDE_PERSPECTIVE | OFFSET_BY_ORIGIN | INCLUDE_PRESERVE3D_ANCESTORS; + FrameTransformProperties props(aFrame, aRefBox, factor); + return nsLayoutUtils::MatrixTransformRect( + aUntransformedBounds, + GetResultingTransformMatrixInternal(props, aRefBox, nsPoint(0, 0), factor, + flags), + factor); +} + +bool nsDisplayTransform::UntransformRect(const nsRect& aTransformedBounds, + const nsRect& aChildBounds, + const nsIFrame* aFrame, + nsRect* aOutRect) { + MOZ_ASSERT(aFrame, "Can't take the transform based on a null frame!"); + + float factor = aFrame->PresContext()->AppUnitsPerDevPixel(); + + uint32_t flags = + INCLUDE_PERSPECTIVE | OFFSET_BY_ORIGIN | INCLUDE_PRESERVE3D_ANCESTORS; + + Matrix4x4 transform = + GetResultingTransformMatrix(aFrame, nsPoint(0, 0), factor, flags); + if (transform.IsSingular()) { + return false; + } + + RectDouble result(NSAppUnitsToFloatPixels(aTransformedBounds.x, factor), + NSAppUnitsToFloatPixels(aTransformedBounds.y, factor), + NSAppUnitsToFloatPixels(aTransformedBounds.width, factor), + NSAppUnitsToFloatPixels(aTransformedBounds.height, factor)); + + RectDouble childGfxBounds( + NSAppUnitsToFloatPixels(aChildBounds.x, factor), + NSAppUnitsToFloatPixels(aChildBounds.y, factor), + NSAppUnitsToFloatPixels(aChildBounds.width, factor), + NSAppUnitsToFloatPixels(aChildBounds.height, factor)); + + result = transform.Inverse().ProjectRectBounds(result, childGfxBounds); + *aOutRect = nsLayoutUtils::RoundGfxRectToAppRect(ThebesRect(result), factor); + return true; +} + +bool nsDisplayTransform::UntransformRect(nsDisplayListBuilder* aBuilder, + const nsRect& aRect, + nsRect* aOutRect) const { + if (GetTransform().IsSingular()) { + return false; + } + + // GetTransform always operates in dev pixels. + float factor = mFrame->PresContext()->AppUnitsPerDevPixel(); + RectDouble result(NSAppUnitsToFloatPixels(aRect.x, factor), + NSAppUnitsToFloatPixels(aRect.y, factor), + NSAppUnitsToFloatPixels(aRect.width, factor), + NSAppUnitsToFloatPixels(aRect.height, factor)); + + bool snap; + nsRect childBounds = GetUntransformedBounds(aBuilder, &snap); + RectDouble childGfxBounds( + NSAppUnitsToFloatPixels(childBounds.x, factor), + NSAppUnitsToFloatPixels(childBounds.y, factor), + NSAppUnitsToFloatPixels(childBounds.width, factor), + NSAppUnitsToFloatPixels(childBounds.height, factor)); + + /* We want to untransform the matrix, so invert the transformation first! */ + result = GetInverseTransform().ProjectRectBounds(result, childGfxBounds); + + *aOutRect = nsLayoutUtils::RoundGfxRectToAppRect(ThebesRect(result), factor); + + return true; +} + +void nsDisplayTransform::WriteDebugInfo(std::stringstream& aStream) { + aStream << GetTransform().GetMatrix(); + if (IsTransformSeparator()) { + aStream << " transform-separator"; + } + if (IsLeafOf3DContext()) { + aStream << " 3d-context-leaf"; + } + if (mFrame->Extend3DContext()) { + aStream << " extends-3d-context"; + } + if (mFrame->Combines3DTransformWithAncestors()) { + aStream << " combines-3d-with-ancestors"; + } + + aStream << " prerender("; + switch (mPrerenderDecision) { + case PrerenderDecision::No: + aStream << "no"; + break; + case PrerenderDecision::Partial: + aStream << "partial"; + break; + case PrerenderDecision::Full: + aStream << "full"; + break; + } + aStream << ")"; + aStream << " childrenBuildingRect" << mChildrenBuildingRect; +} + +nsDisplayPerspective::nsDisplayPerspective(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame, + nsDisplayList* aList) + : nsDisplayHitTestInfoBase(aBuilder, aFrame) { + mList.AppendToTop(aList); + MOZ_ASSERT(mList.Count() == 1); + MOZ_ASSERT(mList.GetTop()->GetType() == DisplayItemType::TYPE_TRANSFORM); + mAnimatedGeometryRoot = aBuilder->FindAnimatedGeometryRootFor( + mFrame->GetContainingBlock(nsIFrame::SKIP_SCROLLED_FRAME)); +} + +already_AddRefed<Layer> nsDisplayPerspective::BuildLayer( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aContainerParameters) { + float appUnitsPerPixel = mFrame->PresContext()->AppUnitsPerDevPixel(); + + Matrix4x4 perspectiveMatrix; + DebugOnly<bool> hasPerspective = nsDisplayTransform::ComputePerspectiveMatrix( + mFrame, appUnitsPerPixel, perspectiveMatrix); + MOZ_ASSERT(hasPerspective, "Why did we create nsDisplayPerspective?"); + + /* + * ClipListToRange can remove our child after we were created. + */ + if (!GetChildren()->GetTop()) { + return nullptr; + } + + /* + * The resulting matrix is still in the coordinate space of the transformed + * frame. Append a translation to the reference frame coordinates. + */ + nsDisplayTransform* transform = + static_cast<nsDisplayTransform*>(GetChildren()->GetTop()); + + Point3D newOrigin = + Point3D(NSAppUnitsToFloatPixels(transform->ToReferenceFrame().x, + appUnitsPerPixel), + NSAppUnitsToFloatPixels(transform->ToReferenceFrame().y, + appUnitsPerPixel), + 0.0f); + Point3D roundedOrigin(NS_round(newOrigin.x), NS_round(newOrigin.y), 0); + + perspectiveMatrix.PostTranslate(roundedOrigin); + + RefPtr<ContainerLayer> container = + aManager->GetLayerBuilder()->BuildContainerLayerFor( + aBuilder, aManager, mFrame, this, GetChildren(), aContainerParameters, + &perspectiveMatrix, 0); + + if (!container) { + return nullptr; + } + + // Sort of a lie, but we want to pretend that the perspective layer extends a + // 3d context so that it gets its transform combined with children. Might need + // a better name that reflects this use case and isn't specific to + // preserve-3d. + container->SetContentFlags(container->GetContentFlags() | + Layer::CONTENT_EXTEND_3D_CONTEXT); + container->SetTransformIsPerspective(true); + + return container.forget(); +} + +LayerState nsDisplayPerspective::GetLayerState( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aParameters) { + return LayerState::LAYER_ACTIVE_FORCE; +} + +nsRegion nsDisplayPerspective::GetOpaqueRegion(nsDisplayListBuilder* aBuilder, + bool* aSnap) const { + if (!GetChildren()->GetTop()) { + *aSnap = false; + return nsRegion(); + } + + return GetChildren()->GetTop()->GetOpaqueRegion(aBuilder, aSnap); +} + +bool nsDisplayPerspective::CreateWebRenderCommands( + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + float appUnitsPerPixel = mFrame->PresContext()->AppUnitsPerDevPixel(); + Matrix4x4 perspectiveMatrix; + DebugOnly<bool> hasPerspective = nsDisplayTransform::ComputePerspectiveMatrix( + mFrame, appUnitsPerPixel, perspectiveMatrix); + MOZ_ASSERT(hasPerspective, "Why did we create nsDisplayPerspective?"); + + /* + * ClipListToRange can remove our child after we were created. + */ + if (!GetChildren()->GetTop()) { + return false; + } + + /* + * The resulting matrix is still in the coordinate space of the transformed + * frame. Append a translation to the reference frame coordinates. + */ + nsDisplayTransform* transform = + static_cast<nsDisplayTransform*>(GetChildren()->GetTop()); + + Point3D newOrigin = + Point3D(NSAppUnitsToFloatPixels(transform->ToReferenceFrame().x, + appUnitsPerPixel), + NSAppUnitsToFloatPixels(transform->ToReferenceFrame().y, + appUnitsPerPixel), + 0.0f); + Point3D roundedOrigin(NS_round(newOrigin.x), NS_round(newOrigin.y), 0); + + perspectiveMatrix.PostTranslate(roundedOrigin); + + nsIFrame* perspectiveFrame = + mFrame->GetContainingBlock(nsIFrame::SKIP_SCROLLED_FRAME); + + // Passing true here is always correct, since perspective always combines + // transforms with the descendants. However that'd make WR do a lot of work + // that it doesn't really need to do if there aren't other transforms forming + // part of the 3D context. + // + // WR knows how to treat perspective in that case, so the only thing we need + // to do is to ensure we pass true when we're involved in a 3d context in any + // other way via the transform-style property on either the transformed frame + // or the perspective frame in order to not confuse WR's preserve-3d code in + // very awful ways. + bool preserve3D = + mFrame->Extend3DContext() || perspectiveFrame->Extend3DContext(); + + wr::StackingContextParams params; + params.mTransformPtr = &perspectiveMatrix; + params.reference_frame_kind = wr::WrReferenceFrameKind::Perspective; + params.prim_flags = !BackfaceIsHidden() + ? wr::PrimitiveFlags::IS_BACKFACE_VISIBLE + : wr::PrimitiveFlags{0}; + params.SetPreserve3D(preserve3D); + params.clip = + wr::WrStackingContextClip::ClipChain(aBuilder.CurrentClipChainId()); + + Maybe<uint64_t> scrollingRelativeTo; + for (auto* asr = GetActiveScrolledRoot(); asr; asr = asr->mParent) { + if (nsLayoutUtils::IsAncestorFrameCrossDoc( + asr->mScrollableFrame->GetScrolledFrame(), perspectiveFrame)) { + scrollingRelativeTo.emplace(asr->GetViewId()); + break; + } + } + + // We put the perspective reference frame wrapping the transformed frame, + // even though there may be arbitrarily nested scroll frames in between. + // + // We need to know how many ancestor scroll-frames are we nested in, in order + // for the async scrolling code in WebRender to calculate the right + // transformation for the perspective contents. + params.scrolling_relative_to = scrollingRelativeTo.ptrOr(nullptr); + + StackingContextHelper sc(aSc, GetActiveScrolledRoot(), mFrame, this, aBuilder, + params); + + aManager->CommandBuilder().CreateWebRenderCommandsFromDisplayList( + GetChildren(), this, aDisplayListBuilder, sc, aBuilder, aResources); + + return true; +} + +bool nsDisplayPerspective::ComputeVisibility(nsDisplayListBuilder* aBuilder, + nsRegion* aVisibleRegion) { + return mList.ComputeVisibilityForSublist(aBuilder, aVisibleRegion, + GetPaintRect()); +} + +nsDisplayText::nsDisplayText(nsDisplayListBuilder* aBuilder, + nsTextFrame* aFrame, + const Maybe<bool>& aIsSelected) + : nsPaintedDisplayItem(aBuilder, aFrame), + mOpacity(1.0f), + mVisIStartEdge(0), + mVisIEndEdge(0) { + MOZ_COUNT_CTOR(nsDisplayText); + mIsFrameSelected = aIsSelected; + mBounds = mFrame->InkOverflowRectRelativeToSelf() + ToReferenceFrame(); + // Bug 748228 + mBounds.Inflate(mFrame->PresContext()->AppUnitsPerDevPixel()); +} + +bool nsDisplayText::CanApplyOpacity() const { + if (IsSelected()) { + return false; + } + + nsTextFrame* f = static_cast<nsTextFrame*>(mFrame); + const nsStyleText* textStyle = f->StyleText(); + if (textStyle->HasTextShadow()) { + return false; + } + + nsTextFrame::TextDecorations decorations; + f->GetTextDecorations(f->PresContext(), nsTextFrame::eResolvedColors, + decorations); + if (decorations.HasDecorationLines()) { + return false; + } + + return true; +} + +void nsDisplayText::Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) { + AUTO_PROFILER_LABEL("nsDisplayText::Paint", GRAPHICS); + + DrawTargetAutoDisableSubpixelAntialiasing disable(aCtx->GetDrawTarget(), + IsSubpixelAADisabled()); + RenderToContext(aCtx, aBuilder); +} + +bool nsDisplayText::CreateWebRenderCommands( + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + auto* f = static_cast<nsTextFrame*>(mFrame); + auto appUnitsPerDevPixel = f->PresContext()->AppUnitsPerDevPixel(); + + nsRect bounds = f->WebRenderBounds() + ToReferenceFrame(); + // Bug 748228 + bounds.Inflate(appUnitsPerDevPixel); + + if (bounds.IsEmpty()) { + return true; + } + + // For large font sizes, punt to a blob image, to avoid the blurry rendering + // that results from WR clamping the glyph size used for rasterization. + // + // (See FONT_SIZE_LIMIT in webrender/src/glyph_rasterizer/mod.rs.) + // + // This is not strictly accurate, as final used font sizes might not be the + // same as claimed by the fontGroup's style.size (eg: due to font-size-adjust + // altering the used size of the font actually used). + // It also fails to consider how transforms might affect the device-font-size + // that webrender uses (and clamps). + // But it should be near enough for practical purposes; the limitations just + // mean we might sometimes end up with webrender still applying some bitmap + // scaling, or bail out when we didn't really need to. + constexpr float kWebRenderFontSizeLimit = 320.0; + f->EnsureTextRun(nsTextFrame::eInflated); + gfxTextRun* textRun = f->GetTextRun(nsTextFrame::eInflated); + if (textRun && + textRun->GetFontGroup()->GetStyle()->size > kWebRenderFontSizeLimit) { + return false; + } + + gfx::Point deviceOffset = + LayoutDevicePoint::FromAppUnits(bounds.TopLeft(), appUnitsPerDevPixel) + .ToUnknownPoint(); + + // Clipping the bounds to the PaintRect (factoring in what's covered by parent + // frames) let's us early reject a bunch of things, but it can produce + // incorrect results for shadows, because they can translate things back into + // view. Also if we're selected we might have some shadows from the + // ::selected and ::inctive-selected pseudo-selectors. So don't do this + // optimization if we have shadows or a selection. + if (!(IsSelected() || f->StyleText()->HasTextShadow())) { + nsRect visible = GetPaintRect(); + visible.Inflate(3 * appUnitsPerDevPixel); + bounds = bounds.Intersect(visible); + } + + RefPtr<gfxContext> textDrawer = aBuilder.GetTextContext( + aResources, aSc, aManager, this, bounds, deviceOffset); + + aBuilder.StartGroup(this); + + RenderToContext(textDrawer, aDisplayListBuilder, true); + const bool result = textDrawer->GetTextDrawer()->Finish(); + + if (result) { + aBuilder.FinishGroup(); + } else { + aBuilder.CancelGroup(true); + } + + return result; +} + +void nsDisplayText::RenderToContext(gfxContext* aCtx, + nsDisplayListBuilder* aBuilder, + bool aIsRecording) { + nsTextFrame* f = static_cast<nsTextFrame*>(mFrame); + + // Add 1 pixel of dirty area around mVisibleRect to allow us to paint + // antialiased pixels beyond the measured text extents. + // This is temporary until we do this in the actual calculation of text + // extents. + auto A2D = mFrame->PresContext()->AppUnitsPerDevPixel(); + LayoutDeviceRect extraVisible = + LayoutDeviceRect::FromAppUnits(GetPaintRect(), A2D); + extraVisible.Inflate(1); + + gfxRect pixelVisible(extraVisible.x, extraVisible.y, extraVisible.width, + extraVisible.height); + pixelVisible.Inflate(2); + pixelVisible.RoundOut(); + + bool willClip = !aBuilder->IsForGenerateGlyphMask() && !aIsRecording; + if (willClip) { + aCtx->NewPath(); + aCtx->Rectangle(pixelVisible); + aCtx->Clip(); + } + + NS_ASSERTION(mVisIStartEdge >= 0, "illegal start edge"); + NS_ASSERTION(mVisIEndEdge >= 0, "illegal end edge"); + + gfxContextMatrixAutoSaveRestore matrixSR; + + nsPoint framePt = ToReferenceFrame(); + if (f->Style()->IsTextCombined()) { + float scaleFactor = nsTextFrame::GetTextCombineScaleFactor(f); + if (scaleFactor != 1.0f) { + if (auto* textDrawer = aCtx->GetTextDrawer()) { + // WebRender doesn't support scaling text like this yet + textDrawer->FoundUnsupportedFeature(); + return; + } + matrixSR.SetContext(aCtx); + // Setup matrix to compress text for text-combine-upright if + // necessary. This is done here because we want selection be + // compressed at the same time as text. + gfxPoint pt = nsLayoutUtils::PointToGfxPoint(framePt, A2D); + gfxMatrix mat = aCtx->CurrentMatrixDouble() + .PreTranslate(pt) + .PreScale(scaleFactor, 1.0) + .PreTranslate(-pt); + aCtx->SetMatrixDouble(mat); + } + } + nsTextFrame::PaintTextParams params(aCtx); + params.framePt = gfx::Point(framePt.x, framePt.y); + params.dirtyRect = extraVisible; + + if (aBuilder->IsForGenerateGlyphMask()) { + params.state = nsTextFrame::PaintTextParams::GenerateTextMask; + } else { + params.state = nsTextFrame::PaintTextParams::PaintText; + } + + f->PaintText(params, mVisIStartEdge, mVisIEndEdge, ToReferenceFrame(), + IsSelected(), mOpacity); + + if (willClip) { + aCtx->PopClip(); + } +} + +bool nsDisplayText::IsSelected() const { + if (mIsFrameSelected.isNothing()) { + MOZ_ASSERT((nsTextFrame*)do_QueryFrame(mFrame)); + auto* f = static_cast<nsTextFrame*>(mFrame); + mIsFrameSelected.emplace(f->IsSelected()); + } + + return mIsFrameSelected.value(); +} + +class nsDisplayTextGeometry : public nsDisplayItemGenericGeometry { + public: + nsDisplayTextGeometry(nsDisplayText* aItem, nsDisplayListBuilder* aBuilder) + : nsDisplayItemGenericGeometry(aItem, aBuilder), + mOpacity(aItem->Opacity()), + mVisIStartEdge(aItem->VisIStartEdge()), + mVisIEndEdge(aItem->VisIEndEdge()) { + nsTextFrame* f = static_cast<nsTextFrame*>(aItem->Frame()); + f->GetTextDecorations(f->PresContext(), nsTextFrame::eResolvedColors, + mDecorations); + } + + /** + * We store the computed text decorations here since they are + * computed using style data from parent frames. Any changes to these + * styles will only invalidate the parent frame and not this frame. + */ + nsTextFrame::TextDecorations mDecorations; + float mOpacity; + nscoord mVisIStartEdge; + nscoord mVisIEndEdge; +}; + +nsDisplayItemGeometry* nsDisplayText::AllocateGeometry( + nsDisplayListBuilder* aBuilder) { + return new nsDisplayTextGeometry(this, aBuilder); +} + +void nsDisplayText::ComputeInvalidationRegion( + nsDisplayListBuilder* aBuilder, const nsDisplayItemGeometry* aGeometry, + nsRegion* aInvalidRegion) const { + const nsDisplayTextGeometry* geometry = + static_cast<const nsDisplayTextGeometry*>(aGeometry); + nsTextFrame* f = static_cast<nsTextFrame*>(mFrame); + + nsTextFrame::TextDecorations decorations; + f->GetTextDecorations(f->PresContext(), nsTextFrame::eResolvedColors, + decorations); + + bool snap; + const nsRect& newRect = geometry->mBounds; + nsRect oldRect = GetBounds(aBuilder, &snap); + if (decorations != geometry->mDecorations || + mVisIStartEdge != geometry->mVisIStartEdge || + mVisIEndEdge != geometry->mVisIEndEdge || + !oldRect.IsEqualInterior(newRect) || + !geometry->mBorderRect.IsEqualInterior(GetBorderRect()) || + mOpacity != geometry->mOpacity) { + aInvalidRegion->Or(oldRect, newRect); + } +} + +void nsDisplayText::WriteDebugInfo(std::stringstream& aStream) { +#ifdef DEBUG + aStream << " (\""; + + nsTextFrame* f = static_cast<nsTextFrame*>(mFrame); + nsCString buf; + int32_t totalContentLength; + f->ToCString(buf, &totalContentLength); + + aStream << buf.get() << "\")"; +#endif +} + +nsDisplayEffectsBase::nsDisplayEffectsBase( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayList* aList, + const ActiveScrolledRoot* aActiveScrolledRoot, bool aClearClipChain) + : nsDisplayWrapList(aBuilder, aFrame, aList, aActiveScrolledRoot, + aClearClipChain), + mHandleOpacity(false) { + MOZ_COUNT_CTOR(nsDisplayEffectsBase); +} + +nsDisplayEffectsBase::nsDisplayEffectsBase(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame, + nsDisplayList* aList) + : nsDisplayWrapList(aBuilder, aFrame, aList), mHandleOpacity(false) { + MOZ_COUNT_CTOR(nsDisplayEffectsBase); +} + +nsRegion nsDisplayEffectsBase::GetOpaqueRegion(nsDisplayListBuilder* aBuilder, + bool* aSnap) const { + *aSnap = false; + return nsRegion(); +} + +void nsDisplayEffectsBase::HitTest(nsDisplayListBuilder* aBuilder, + const nsRect& aRect, HitTestState* aState, + nsTArray<nsIFrame*>* aOutFrames) { + nsPoint rectCenter(aRect.x + aRect.width / 2, aRect.y + aRect.height / 2); + if (SVGIntegrationUtils::HitTestFrameForEffects( + mFrame, rectCenter - ToReferenceFrame())) { + mList.HitTest(aBuilder, aRect, aState, aOutFrames); + } +} + +gfxRect nsDisplayEffectsBase::BBoxInUserSpace() const { + return SVGUtils::GetBBox(mFrame); +} + +gfxPoint nsDisplayEffectsBase::UserSpaceOffset() const { + return SVGUtils::FrameSpaceInCSSPxToUserSpaceOffset(mFrame); +} + +void nsDisplayEffectsBase::ComputeInvalidationRegion( + nsDisplayListBuilder* aBuilder, const nsDisplayItemGeometry* aGeometry, + nsRegion* aInvalidRegion) const { + auto* geometry = static_cast<const nsDisplaySVGEffectGeometry*>(aGeometry); + bool snap; + nsRect bounds = GetBounds(aBuilder, &snap); + if (geometry->mFrameOffsetToReferenceFrame != ToReferenceFrame() || + geometry->mUserSpaceOffset != UserSpaceOffset() || + !geometry->mBBox.IsEqualInterior(BBoxInUserSpace()) || + geometry->mOpacity != mFrame->StyleEffects()->mOpacity || + geometry->mHandleOpacity != ShouldHandleOpacity()) { + // Filter and mask output can depend on the location of the frame's user + // space and on the frame's BBox. We need to invalidate if either of these + // change relative to the reference frame. + // Invalidations from our inactive layer manager are not enough to catch + // some of these cases because filters can produce output even if there's + // nothing in the filter input. + aInvalidRegion->Or(bounds, geometry->mBounds); + } +} + +bool nsDisplayEffectsBase::ValidateSVGFrame() { + const nsIContent* content = mFrame->GetContent(); + bool hasSVGLayout = mFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT); + if (hasSVGLayout) { + ISVGDisplayableFrame* svgFrame = do_QueryFrame(mFrame); + if (!svgFrame || !mFrame->GetContent()->IsSVGElement()) { + NS_ASSERTION(false, "why?"); + return false; + } + if (!static_cast<const SVGElement*>(content)->HasValidDimensions()) { + return false; // The SVG spec says not to draw filters for this + } + } + + return true; +} + +typedef SVGIntegrationUtils::PaintFramesParams PaintFramesParams; + +static void ComputeMaskGeometry(PaintFramesParams& aParams) { + // Properties are added lazily and may have been removed by a restyle, so + // make sure all applicable ones are set again. + nsIFrame* firstFrame = + nsLayoutUtils::FirstContinuationOrIBSplitSibling(aParams.frame); + + const nsStyleSVGReset* svgReset = firstFrame->StyleSVGReset(); + + nsTArray<SVGMaskFrame*> maskFrames; + // XXX check return value? + SVGObserverUtils::GetAndObserveMasks(firstFrame, &maskFrames); + + if (maskFrames.Length() == 0) { + return; + } + + gfxContext& ctx = aParams.ctx; + nsIFrame* frame = aParams.frame; + + nsPoint offsetToUserSpace = + nsLayoutUtils::ComputeOffsetToUserSpace(aParams.builder, aParams.frame); + + gfxPoint devPixelOffsetToUserSpace = nsLayoutUtils::PointToGfxPoint( + offsetToUserSpace, frame->PresContext()->AppUnitsPerDevPixel()); + + gfxContextMatrixAutoSaveRestore matSR(&ctx); + ctx.SetMatrixDouble( + ctx.CurrentMatrixDouble().PreTranslate(devPixelOffsetToUserSpace)); + + // Convert boaderArea and dirtyRect to user space. + int32_t appUnitsPerDevPixel = frame->PresContext()->AppUnitsPerDevPixel(); + nsRect userSpaceBorderArea = aParams.borderArea - offsetToUserSpace; + nsRect userSpaceDirtyRect = aParams.dirtyRect - offsetToUserSpace; + + // Union all mask layer rectangles in user space. + gfxRect maskInUserSpace; + for (size_t i = 0; i < maskFrames.Length(); i++) { + SVGMaskFrame* maskFrame = maskFrames[i]; + gfxRect currentMaskSurfaceRect; + + if (maskFrame) { + currentMaskSurfaceRect = maskFrame->GetMaskArea(aParams.frame); + } else { + nsCSSRendering::ImageLayerClipState clipState; + nsCSSRendering::GetImageLayerClip( + svgReset->mMask.mLayers[i], frame, *frame->StyleBorder(), + userSpaceBorderArea, userSpaceDirtyRect, false, /* aWillPaintBorder */ + appUnitsPerDevPixel, &clipState); + currentMaskSurfaceRect = clipState.mDirtyRectInDevPx; + } + + maskInUserSpace = maskInUserSpace.Union(currentMaskSurfaceRect); + } + + if (!maskInUserSpace.IsEmpty()) { + aParams.maskRect = Some(ToRect(maskInUserSpace)); + } else { + aParams.maskRect = Nothing(); + } +} + +nsDisplayMasksAndClipPaths::nsDisplayMasksAndClipPaths( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayList* aList, + const ActiveScrolledRoot* aActiveScrolledRoot) + : nsDisplayEffectsBase(aBuilder, aFrame, aList, aActiveScrolledRoot, true), + mApplyOpacityWithSimpleClipPath(false) { + MOZ_COUNT_CTOR(nsDisplayMasksAndClipPaths); + + nsPresContext* presContext = mFrame->PresContext(); + uint32_t flags = + aBuilder->GetBackgroundPaintFlags() | nsCSSRendering::PAINTBG_MASK_IMAGE; + const nsStyleSVGReset* svgReset = aFrame->StyleSVGReset(); + NS_FOR_VISIBLE_IMAGE_LAYERS_BACK_TO_FRONT(i, svgReset->mMask) { + if (!svgReset->mMask.mLayers[i].mImage.IsResolved()) { + continue; + } + bool isTransformedFixed; + nsBackgroundLayerState state = nsCSSRendering::PrepareImageLayer( + presContext, aFrame, flags, mFrame->GetRectRelativeToSelf(), + mFrame->GetRectRelativeToSelf(), svgReset->mMask.mLayers[i], + &isTransformedFixed); + mDestRects.AppendElement(state.mDestArea); + } +} + +static bool CanMergeDisplayMaskFrame(nsIFrame* aFrame) { + // Do not merge items for box-decoration-break:clone elements, + // since each box should have its own mask in that case. + if (aFrame->StyleBorder()->mBoxDecorationBreak == + mozilla::StyleBoxDecorationBreak::Clone) { + return false; + } + + // Do not merge if either frame has a mask. Continuation frames should apply + // the mask independently (just like nsDisplayBackgroundImage). + if (aFrame->StyleSVGReset()->HasMask()) { + return false; + } + + return true; +} + +bool nsDisplayMasksAndClipPaths::CanMerge(const nsDisplayItem* aItem) const { + // Items for the same content element should be merged into a single + // compositing group. + if (!HasDifferentFrame(aItem) || !HasSameTypeAndClip(aItem) || + !HasSameContent(aItem)) { + return false; + } + + return CanMergeDisplayMaskFrame(mFrame) && + CanMergeDisplayMaskFrame(aItem->Frame()); +} + +bool nsDisplayMasksAndClipPaths::IsValidMask() { + if (!ValidateSVGFrame()) { + return false; + } + + if (mFrame->StyleEffects()->mOpacity == 0.0f && mHandleOpacity) { + return false; + } + + nsIFrame* firstFrame = + nsLayoutUtils::FirstContinuationOrIBSplitSibling(mFrame); + + if (SVGObserverUtils::GetAndObserveClipPath(firstFrame, nullptr) == + SVGObserverUtils::eHasRefsSomeInvalid || + SVGObserverUtils::GetAndObserveMasks(firstFrame, nullptr) == + SVGObserverUtils::eHasRefsSomeInvalid) { + return false; + } + + return true; +} + +already_AddRefed<Layer> nsDisplayMasksAndClipPaths::BuildLayer( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aContainerParameters) { + if (!IsValidMask()) { + return nullptr; + } + + RefPtr<ContainerLayer> container = + aManager->GetLayerBuilder()->BuildContainerLayerFor( + aBuilder, aManager, mFrame, this, &mList, aContainerParameters, + nullptr); + + return container.forget(); +} + +bool nsDisplayMasksAndClipPaths::PaintMask(nsDisplayListBuilder* aBuilder, + gfxContext* aMaskContext, + bool* aMaskPainted) { + MOZ_ASSERT(aMaskContext->GetDrawTarget()->GetFormat() == SurfaceFormat::A8); + + imgDrawingParams imgParams(aBuilder->GetImageDecodeFlags()); + nsRect borderArea = nsRect(ToReferenceFrame(), mFrame->GetSize()); + SVGIntegrationUtils::PaintFramesParams params(*aMaskContext, mFrame, mBounds, + borderArea, aBuilder, nullptr, + mHandleOpacity, imgParams); + ComputeMaskGeometry(params); + bool maskIsComplete = false; + bool painted = SVGIntegrationUtils::PaintMask(params, maskIsComplete); + if (aMaskPainted) { + *aMaskPainted = painted; + } + + nsDisplayMasksAndClipPathsGeometry::UpdateDrawResult(this, imgParams.result); + + return maskIsComplete && + (imgParams.result == ImgDrawResult::SUCCESS || + imgParams.result == ImgDrawResult::SUCCESS_NOT_COMPLETE || + imgParams.result == ImgDrawResult::WRONG_SIZE); +} + +LayerState nsDisplayMasksAndClipPaths::GetLayerState( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aParameters) { + if (CanPaintOnMaskLayer(aManager)) { + LayerState result = RequiredLayerStateForChildren( + aBuilder, aManager, aParameters, mList, GetAnimatedGeometryRoot(), + GetActiveScrolledRoot()); + // When we're not active, FrameLayerBuilder will call PaintAsLayer() + // on us during painting. In that case we don't want a mask layer to + // be created, because PaintAsLayer() takes care of applying the mask. + // So we return LayerState::LAYER_SVG_EFFECTS instead of + // LayerState::LAYER_INACTIVE so that FrameLayerBuilder doesn't set a mask + // layer on our layer. + return result == LayerState::LAYER_INACTIVE ? LayerState::LAYER_SVG_EFFECTS + : result; + } + + return LayerState::LAYER_SVG_EFFECTS; +} + +bool nsDisplayMasksAndClipPaths::CanPaintOnMaskLayer(LayerManager* aManager) { + if (!aManager->IsWidgetLayerManager()) { + return false; + } + + if (!SVGIntegrationUtils::IsMaskResourceReady(mFrame)) { + return false; + } + + if (StaticPrefs::layers_draw_mask_debug()) { + return false; + } + + // We don't currently support this item creating a mask + // for both the clip-path, and rounded rect clipping. + if (GetClip().GetRoundedRectCount() != 0) { + return false; + } + + return true; +} + +bool nsDisplayMasksAndClipPaths::ComputeVisibility( + nsDisplayListBuilder* aBuilder, nsRegion* aVisibleRegion) { + // Our children may be made translucent or arbitrarily deformed so we should + // not allow them to subtract area from aVisibleRegion. + nsRegion childrenVisible(GetPaintRect()); + nsRect r = GetPaintRect().Intersect(mList.GetClippedBounds(aBuilder)); + mList.ComputeVisibilityForSublist(aBuilder, &childrenVisible, r); + return true; +} + +void nsDisplayMasksAndClipPaths::ComputeInvalidationRegion( + nsDisplayListBuilder* aBuilder, const nsDisplayItemGeometry* aGeometry, + nsRegion* aInvalidRegion) const { + nsDisplayEffectsBase::ComputeInvalidationRegion(aBuilder, aGeometry, + aInvalidRegion); + + auto* geometry = + static_cast<const nsDisplayMasksAndClipPathsGeometry*>(aGeometry); + bool snap; + nsRect bounds = GetBounds(aBuilder, &snap); + + if (mDestRects.Length() != geometry->mDestRects.Length()) { + aInvalidRegion->Or(bounds, geometry->mBounds); + } else { + for (size_t i = 0; i < mDestRects.Length(); i++) { + if (!mDestRects[i].IsEqualInterior(geometry->mDestRects[i])) { + aInvalidRegion->Or(bounds, geometry->mBounds); + break; + } + } + } + + if (aBuilder->ShouldSyncDecodeImages() && + geometry->ShouldInvalidateToSyncDecodeImages()) { + const nsStyleSVGReset* svgReset = mFrame->StyleSVGReset(); + NS_FOR_VISIBLE_IMAGE_LAYERS_BACK_TO_FRONT(i, svgReset->mMask) { + const auto& image = svgReset->mMask.mLayers[i].mImage; + if (image.IsImageRequestType()) { + aInvalidRegion->Or(*aInvalidRegion, bounds); + break; + } + } + } +} + +void nsDisplayMasksAndClipPaths::PaintAsLayer(nsDisplayListBuilder* aBuilder, + gfxContext* aCtx, + LayerManager* aManager) { + // Clip the drawing target by mVisibleRect, which contains the visible + // region of the target frame and its out-of-flow and inflow descendants. + gfxContext* context = aCtx; + + Rect bounds = NSRectToRect(GetPaintRect(), + mFrame->PresContext()->AppUnitsPerDevPixel()); + bounds.RoundOut(); + context->Clip(bounds); + + imgDrawingParams imgParams(aBuilder->GetImageDecodeFlags()); + nsRect borderArea = nsRect(ToReferenceFrame(), mFrame->GetSize()); + SVGIntegrationUtils::PaintFramesParams params(*aCtx, mFrame, GetPaintRect(), + borderArea, aBuilder, aManager, + mHandleOpacity, imgParams); + + ComputeMaskGeometry(params); + + SVGIntegrationUtils::PaintMaskAndClipPath(params); + + context->PopClip(); + + nsDisplayMasksAndClipPathsGeometry::UpdateDrawResult(this, imgParams.result); +} + +void nsDisplayMasksAndClipPaths::PaintWithContentsPaintCallback( + nsDisplayListBuilder* aBuilder, gfxContext* aCtx, + const std::function<void()>& aPaintChildren) { + // Clip the drawing target by mVisibleRect, which contains the visible + // region of the target frame and its out-of-flow and inflow descendants. + gfxContext* context = aCtx; + + Rect bounds = NSRectToRect(GetPaintRect(), + mFrame->PresContext()->AppUnitsPerDevPixel()); + bounds.RoundOut(); + context->Clip(bounds); + + imgDrawingParams imgParams(aBuilder->GetImageDecodeFlags()); + nsRect borderArea = nsRect(ToReferenceFrame(), mFrame->GetSize()); + SVGIntegrationUtils::PaintFramesParams params(*aCtx, mFrame, GetPaintRect(), + borderArea, aBuilder, nullptr, + mHandleOpacity, imgParams); + + ComputeMaskGeometry(params); + + SVGIntegrationUtils::PaintMaskAndClipPath(params, aPaintChildren); + + context->PopClip(); + + nsDisplayMasksAndClipPathsGeometry::UpdateDrawResult(this, imgParams.result); +} + +static Maybe<wr::WrClipId> CreateSimpleClipRegion( + const nsDisplayMasksAndClipPaths& aDisplayItem, + wr::DisplayListBuilder& aBuilder) { + nsIFrame* frame = aDisplayItem.Frame(); + auto* style = frame->StyleSVGReset(); + MOZ_ASSERT(style->HasClipPath() || style->HasMask()); + if (!SVGIntegrationUtils::UsingSimpleClipPathForFrame(frame)) { + return Nothing(); + } + + const auto& clipPath = style->mClipPath; + const auto& shape = *clipPath.AsShape()._0; + + auto appUnitsPerDevPixel = frame->PresContext()->AppUnitsPerDevPixel(); + const nsRect refBox = + nsLayoutUtils::ComputeGeometryBox(frame, clipPath.AsShape()._1); + + AutoTArray<wr::ComplexClipRegion, 1> clipRegions; + + wr::LayoutRect rect; + switch (shape.tag) { + case StyleBasicShape::Tag::Inset: { + const nsRect insetRect = ShapeUtils::ComputeInsetRect(shape, refBox) + + aDisplayItem.ToReferenceFrame(); + + nscoord radii[8] = {0}; + + if (ShapeUtils::ComputeInsetRadii(shape, insetRect, refBox, radii)) { + clipRegions.AppendElement( + wr::ToComplexClipRegion(insetRect, radii, appUnitsPerDevPixel)); + } + + rect = wr::ToLayoutRect( + LayoutDeviceRect::FromAppUnits(insetRect, appUnitsPerDevPixel)); + break; + } + case StyleBasicShape::Tag::Ellipse: + case StyleBasicShape::Tag::Circle: { + nsPoint center = ShapeUtils::ComputeCircleOrEllipseCenter(shape, refBox); + + nsSize radii; + if (shape.IsEllipse()) { + radii = ShapeUtils::ComputeEllipseRadii(shape, center, refBox); + } else { + nscoord radius = ShapeUtils::ComputeCircleRadius(shape, center, refBox); + radii = {radius, radius}; + } + + nsRect ellipseRect(aDisplayItem.ToReferenceFrame() + center - + nsPoint(radii.width, radii.height), + radii * 2); + + nscoord ellipseRadii[8]; + for (const auto corner : mozilla::AllPhysicalHalfCorners()) { + ellipseRadii[corner] = + HalfCornerIsX(corner) ? radii.width : radii.height; + } + + clipRegions.AppendElement(wr::ToComplexClipRegion( + ellipseRect, ellipseRadii, appUnitsPerDevPixel)); + + rect = wr::ToLayoutRect( + LayoutDeviceRect::FromAppUnits(ellipseRect, appUnitsPerDevPixel)); + break; + } + default: + // Please don't add more exceptions, try to find a way to define the clip + // without using a mask image. + // + // And if you _really really_ need to add an exception, add it to + // SVGIntegrationUtils::UsingSimpleClipPathForFrame + MOZ_ASSERT_UNREACHABLE("Unhandled shape id?"); + return Nothing(); + } + wr::WrClipId clipId = aBuilder.DefineClip(Nothing(), rect, &clipRegions); + return Some(clipId); +} + +static Maybe<wr::WrClipId> CreateWRClipPathAndMasks( + nsDisplayMasksAndClipPaths* aDisplayItem, const LayoutDeviceRect& aBounds, + wr::IpcResourceUpdateQueue& aResources, wr::DisplayListBuilder& aBuilder, + const StackingContextHelper& aSc, layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + if (auto clip = CreateSimpleClipRegion(*aDisplayItem, aBuilder)) { + return clip; + } + + Maybe<wr::ImageMask> mask = aManager->CommandBuilder().BuildWrMaskImage( + aDisplayItem, aBuilder, aResources, aSc, aDisplayListBuilder, aBounds); + if (!mask) { + return Nothing(); + } + + wr::WrClipId clipId = aBuilder.DefineImageMaskClip(mask.ref()); + + return Some(clipId); +} + +bool nsDisplayMasksAndClipPaths::CreateWebRenderCommands( + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + bool snap; + auto appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel(); + nsRect displayBounds = GetBounds(aDisplayListBuilder, &snap); + LayoutDeviceRect bounds = + LayoutDeviceRect::FromAppUnits(displayBounds, appUnitsPerDevPixel); + + Maybe<wr::WrClipId> clip = CreateWRClipPathAndMasks( + this, bounds, aResources, aBuilder, aSc, aManager, aDisplayListBuilder); + + Maybe<StackingContextHelper> layer; + const StackingContextHelper* sc = &aSc; + if (clip) { + // Create a new stacking context to attach the mask to, ensuring the mask is + // applied to the aggregate, and not the individual elements. + + // The stacking context shouldn't have any offset. + bounds.MoveTo(0, 0); + + Maybe<float> opacity = mApplyOpacityWithSimpleClipPath + ? Some(mFrame->StyleEffects()->mOpacity) + : Nothing(); + + wr::StackingContextParams params; + params.clip = wr::WrStackingContextClip::ClipId(*clip); + params.opacity = opacity.ptrOr(nullptr); + layer.emplace(aSc, GetActiveScrolledRoot(), mFrame, this, aBuilder, params, + bounds); + sc = layer.ptr(); + } + + nsDisplayEffectsBase::CreateWebRenderCommands(aBuilder, aResources, *sc, + aManager, aDisplayListBuilder); + + return true; +} + +void nsDisplayMasksAndClipPaths::SelectOpacityOptimization( + const bool aUsingLayers) { + if (aUsingLayers || + !SVGIntegrationUtils::UsingSimpleClipPathForFrame(mFrame)) { + // Handle opacity in mask and clip-path drawing code. + SetHandleOpacity(); + MOZ_ASSERT(!mApplyOpacityWithSimpleClipPath); + } else { + // Allow WebRender simple clip paths to also handle opacity. + mApplyOpacityWithSimpleClipPath = true; + } +} + +Maybe<nsRect> nsDisplayMasksAndClipPaths::GetClipWithRespectToASR( + nsDisplayListBuilder* aBuilder, const ActiveScrolledRoot* aASR) const { + if (const DisplayItemClip* clip = + DisplayItemClipChain::ClipForASR(GetClipChain(), aASR)) { + return Some(clip->GetClipRect()); + } + // This item does not have a clip with respect to |aASR|. However, we + // might still have finite bounds with respect to |aASR|. Check our + // children. + nsDisplayList* childList = GetSameCoordinateSystemChildren(); + if (childList) { + return Some(childList->GetClippedBoundsWithRespectToASR(aBuilder, aASR)); + } +#ifdef DEBUG + MOZ_ASSERT(false, "item should have finite clip with respect to aASR"); +#endif + return Nothing(); +} + +#ifdef MOZ_DUMP_PAINTING +void nsDisplayMasksAndClipPaths::PrintEffects(nsACString& aTo) { + nsIFrame* firstFrame = + nsLayoutUtils::FirstContinuationOrIBSplitSibling(mFrame); + bool first = true; + aTo += " effects=("; + if (mHandleOpacity) { + first = false; + aTo += nsPrintfCString("opacity(%f)", mFrame->StyleEffects()->mOpacity); + } + SVGClipPathFrame* clipPathFrame; + // XXX Check return value? + SVGObserverUtils::GetAndObserveClipPath(firstFrame, &clipPathFrame); + if (clipPathFrame) { + if (!first) { + aTo += ", "; + } + aTo += nsPrintfCString( + "clip(%s)", clipPathFrame->IsTrivial() ? "trivial" : "non-trivial"); + first = false; + } else if (mFrame->StyleSVGReset()->HasClipPath()) { + if (!first) { + aTo += ", "; + } + aTo += "clip(basic-shape)"; + first = false; + } + + nsTArray<SVGMaskFrame*> masks; + // XXX check return value? + SVGObserverUtils::GetAndObserveMasks(firstFrame, &masks); + if (!masks.IsEmpty() && masks[0]) { + if (!first) { + aTo += ", "; + } + aTo += "mask"; + } + aTo += ")"; +} +#endif + +already_AddRefed<Layer> nsDisplayBackdropRootContainer::BuildLayer( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aContainerParameters) { + RefPtr<Layer> container = aManager->GetLayerBuilder()->BuildContainerLayerFor( + aBuilder, aManager, mFrame, this, &mList, aContainerParameters, nullptr); + if (!container) { + return nullptr; + } + + return container.forget(); +} + +LayerState nsDisplayBackdropRootContainer::GetLayerState( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aParameters) { + return RequiredLayerStateForChildren(aBuilder, aManager, aParameters, mList, + GetAnimatedGeometryRoot(), + GetActiveScrolledRoot()); +} + +bool nsDisplayBackdropRootContainer::CreateWebRenderCommands( + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + wr::StackingContextParams params; + params.flags |= wr::StackingContextFlags::IS_BACKDROP_ROOT; + params.clip = + wr::WrStackingContextClip::ClipChain(aBuilder.CurrentClipChainId()); + StackingContextHelper sc(aSc, GetActiveScrolledRoot(), mFrame, this, aBuilder, + params); + + nsDisplayWrapList::CreateWebRenderCommands(aBuilder, aResources, sc, aManager, + aDisplayListBuilder); + return true; +} + +/* static */ +bool nsDisplayBackdropFilters::CanCreateWebRenderCommands( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame) { + return SVGIntegrationUtils::CanCreateWebRenderFiltersForFrame(aFrame); +} + +bool nsDisplayBackdropFilters::CreateWebRenderCommands( + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + WrFiltersHolder wrFilters; + Maybe<nsRect> filterClip; + auto filterChain = mFrame->StyleEffects()->mBackdropFilters.AsSpan(); + if (!SVGIntegrationUtils::CreateWebRenderCSSFilters(filterChain, mFrame, + wrFilters) && + !SVGIntegrationUtils::BuildWebRenderFilters(mFrame, filterChain, + wrFilters, filterClip)) { + return false; + } + + nsCSSRendering::ImageLayerClipState clip; + nsCSSRendering::GetImageLayerClip( + mFrame->StyleBackground()->BottomLayer(), mFrame, *mFrame->StyleBorder(), + mBackdropRect, mBackdropRect, false, + mFrame->PresContext()->AppUnitsPerDevPixel(), &clip); + + LayoutDeviceRect bounds = LayoutDeviceRect::FromAppUnits( + mBackdropRect, mFrame->PresContext()->AppUnitsPerDevPixel()); + + wr::ComplexClipRegion region = + wr::ToComplexClipRegion(clip.mBGClipArea, clip.mRadii, + mFrame->PresContext()->AppUnitsPerDevPixel()); + + aBuilder.PushBackdropFilter(wr::ToLayoutRect(bounds), region, + wrFilters.filters, wrFilters.filter_datas, + !BackfaceIsHidden()); + + wr::StackingContextParams params; + params.clip = + wr::WrStackingContextClip::ClipChain(aBuilder.CurrentClipChainId()); + StackingContextHelper sc(aSc, GetActiveScrolledRoot(), mFrame, this, aBuilder, + params); + + nsDisplayWrapList::CreateWebRenderCommands(aBuilder, aResources, sc, aManager, + aDisplayListBuilder); + return true; +} + +/* static */ +nsDisplayFilters::nsDisplayFilters(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame, nsDisplayList* aList) + : nsDisplayEffectsBase(aBuilder, aFrame, aList), + mEffectsBounds(aFrame->InkOverflowRectRelativeToSelf()) { + MOZ_COUNT_CTOR(nsDisplayFilters); +} + +already_AddRefed<Layer> nsDisplayFilters::BuildLayer( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aContainerParameters) { + if (!ValidateSVGFrame()) { + return nullptr; + } + + if (mFrame->StyleEffects()->mOpacity == 0.0f && mHandleOpacity) { + return nullptr; + } + + nsIFrame* firstFrame = + nsLayoutUtils::FirstContinuationOrIBSplitSibling(mFrame); + + // We may exist for a mix of CSS filter functions and/or references to SVG + // filters. If we have invalid references to SVG filters then we paint + // nothing, so no need for a layer. + if (SVGObserverUtils::GetAndObserveFilters(firstFrame, nullptr) == + SVGObserverUtils::eHasRefsSomeInvalid) { + return nullptr; + } + + ContainerLayerParameters newContainerParameters = aContainerParameters; + newContainerParameters.mDisableSubpixelAntialiasingInDescendants = true; + + RefPtr<ContainerLayer> container = + aManager->GetLayerBuilder()->BuildContainerLayerFor( + aBuilder, aManager, mFrame, this, &mList, newContainerParameters, + nullptr); + return container.forget(); +} + +LayerState nsDisplayFilters::GetLayerState( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aParameters) { + return LayerState::LAYER_SVG_EFFECTS; +} + +bool nsDisplayFilters::ComputeVisibility(nsDisplayListBuilder* aBuilder, + nsRegion* aVisibleRegion) { + nsPoint offset = ToReferenceFrame(); + nsRect dirtyRect = SVGIntegrationUtils::GetRequiredSourceForInvalidArea( + mFrame, GetPaintRect() - offset) + + offset; + + // Our children may be made translucent or arbitrarily deformed so we should + // not allow them to subtract area from aVisibleRegion. + nsRegion childrenVisible(dirtyRect); + nsRect r = dirtyRect.Intersect( + mList.GetClippedBoundsWithRespectToASR(aBuilder, mActiveScrolledRoot)); + mList.ComputeVisibilityForSublist(aBuilder, &childrenVisible, r); + return true; +} + +void nsDisplayFilters::ComputeInvalidationRegion( + nsDisplayListBuilder* aBuilder, const nsDisplayItemGeometry* aGeometry, + nsRegion* aInvalidRegion) const { + nsDisplayEffectsBase::ComputeInvalidationRegion(aBuilder, aGeometry, + aInvalidRegion); + + auto* geometry = static_cast<const nsDisplayFiltersGeometry*>(aGeometry); + + if (aBuilder->ShouldSyncDecodeImages() && + geometry->ShouldInvalidateToSyncDecodeImages()) { + bool snap; + nsRect bounds = GetBounds(aBuilder, &snap); + aInvalidRegion->Or(*aInvalidRegion, bounds); + } +} + +void nsDisplayFilters::PaintAsLayer(nsDisplayListBuilder* aBuilder, + gfxContext* aCtx, LayerManager* aManager) { + imgDrawingParams imgParams(aBuilder->GetImageDecodeFlags()); + nsRect borderArea = nsRect(ToReferenceFrame(), mFrame->GetSize()); + SVGIntegrationUtils::PaintFramesParams params(*aCtx, mFrame, GetPaintRect(), + borderArea, aBuilder, aManager, + mHandleOpacity, imgParams); + SVGIntegrationUtils::PaintFilter(params); + nsDisplayFiltersGeometry::UpdateDrawResult(this, imgParams.result); +} + +bool nsDisplayFilters::CanCreateWebRenderCommands() { + return SVGIntegrationUtils::CanCreateWebRenderFiltersForFrame(mFrame); +} + +bool nsDisplayFilters::CreateWebRenderCommands( + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + float auPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel(); + + WrFiltersHolder wrFilters; + Maybe<nsRect> filterClip; + auto filterChain = mFrame->StyleEffects()->mFilters.AsSpan(); + if (!SVGIntegrationUtils::CreateWebRenderCSSFilters(filterChain, mFrame, + wrFilters) && + !SVGIntegrationUtils::BuildWebRenderFilters(mFrame, filterChain, + wrFilters, filterClip)) { + return false; + } + + wr::WrStackingContextClip clip; + if (filterClip) { + auto devPxRect = LayoutDeviceRect::FromAppUnits( + filterClip.value() + ToReferenceFrame(), auPerDevPixel); + wr::WrClipId clipId = aBuilder.DefineRectClip(wr::ToLayoutRect(devPxRect)); + clip = wr::WrStackingContextClip::ClipId(clipId); + } else { + clip = wr::WrStackingContextClip::ClipChain(aBuilder.CurrentClipChainId()); + } + + float opacity = mFrame->StyleEffects()->mOpacity; + wr::StackingContextParams params; + params.mFilters = std::move(wrFilters.filters); + params.mFilterDatas = std::move(wrFilters.filter_datas); + params.opacity = opacity != 1.0f && mHandleOpacity ? &opacity : nullptr; + params.clip = clip; + StackingContextHelper sc(aSc, GetActiveScrolledRoot(), mFrame, this, aBuilder, + params); + + nsDisplayEffectsBase::CreateWebRenderCommands(aBuilder, aResources, sc, + aManager, aDisplayListBuilder); + + return true; +} + +#ifdef MOZ_DUMP_PAINTING +void nsDisplayFilters::PrintEffects(nsACString& aTo) { + nsIFrame* firstFrame = + nsLayoutUtils::FirstContinuationOrIBSplitSibling(mFrame); + bool first = true; + aTo += " effects=("; + if (mHandleOpacity) { + first = false; + aTo += nsPrintfCString("opacity(%f)", mFrame->StyleEffects()->mOpacity); + } + // We may exist for a mix of CSS filter functions and/or references to SVG + // filters. If we have invalid references to SVG filters then we paint + // nothing, but otherwise we will apply one or more filters. + if (SVGObserverUtils::GetAndObserveFilters(firstFrame, nullptr) != + SVGObserverUtils::eHasRefsSomeInvalid) { + if (!first) { + aTo += ", "; + } + aTo += "filter"; + } + aTo += ")"; +} +#endif + +nsDisplaySVGWrapper::nsDisplaySVGWrapper(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame, nsDisplayList* aList) + : nsDisplayWrapList(aBuilder, aFrame, aList) { + MOZ_COUNT_CTOR(nsDisplaySVGWrapper); +} + +LayerState nsDisplaySVGWrapper::GetLayerState( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aParameters) { + RefPtr<LayerManager> layerManager = aBuilder->GetWidgetLayerManager(); + if (layerManager && + layerManager->GetBackendType() == layers::LayersBackend::LAYERS_WR) { + return LayerState::LAYER_ACTIVE_FORCE; + } + return LayerState::LAYER_NONE; +} + +bool nsDisplaySVGWrapper::ShouldFlattenAway(nsDisplayListBuilder* aBuilder) { + RefPtr<LayerManager> layerManager = aBuilder->GetWidgetLayerManager(); + if (layerManager && + layerManager->GetBackendType() == layers::LayersBackend::LAYERS_WR) { + return false; + } + return true; +} + +already_AddRefed<Layer> nsDisplaySVGWrapper::BuildLayer( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aContainerParameters) { + ContainerLayerParameters newContainerParameters = aContainerParameters; + newContainerParameters.mDisableSubpixelAntialiasingInDescendants = true; + + RefPtr<ContainerLayer> container = + aManager->GetLayerBuilder()->BuildContainerLayerFor( + aBuilder, aManager, mFrame, this, &mList, newContainerParameters, + nullptr); + + return container.forget(); +} + +bool nsDisplaySVGWrapper::CreateWebRenderCommands( + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + return nsDisplayWrapList::CreateWebRenderCommands( + aBuilder, aResources, aSc, aManager, aDisplayListBuilder); +} + +nsDisplayForeignObject::nsDisplayForeignObject(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame, + nsDisplayList* aList) + : nsDisplayWrapList(aBuilder, aFrame, aList) { + MOZ_COUNT_CTOR(nsDisplayForeignObject); +} + +#ifdef NS_BUILD_REFCNT_LOGGING +nsDisplayForeignObject::~nsDisplayForeignObject() { + MOZ_COUNT_DTOR(nsDisplayForeignObject); +} +#endif + +LayerState nsDisplayForeignObject::GetLayerState( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aParameters) { + RefPtr<LayerManager> layerManager = aBuilder->GetWidgetLayerManager(); + if (layerManager && + layerManager->GetBackendType() == layers::LayersBackend::LAYERS_WR) { + return LayerState::LAYER_ACTIVE_FORCE; + } + return LayerState::LAYER_NONE; +} + +bool nsDisplayForeignObject::ShouldFlattenAway(nsDisplayListBuilder* aBuilder) { + RefPtr<LayerManager> layerManager = aBuilder->GetWidgetLayerManager(); + if (layerManager && + layerManager->GetBackendType() == layers::LayersBackend::LAYERS_WR) { + return false; + } + return true; +} + +already_AddRefed<Layer> nsDisplayForeignObject::BuildLayer( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aContainerParameters) { + ContainerLayerParameters newContainerParameters = aContainerParameters; + newContainerParameters.mDisableSubpixelAntialiasingInDescendants = true; + + RefPtr<ContainerLayer> container = + aManager->GetLayerBuilder()->BuildContainerLayerFor( + aBuilder, aManager, mFrame, this, &mList, newContainerParameters, + nullptr); + + return container.forget(); +} + +bool nsDisplayForeignObject::CreateWebRenderCommands( + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + AutoRestore<bool> restoreDoGrouping(aManager->CommandBuilder().mDoGrouping); + aManager->CommandBuilder().mDoGrouping = false; + return nsDisplayWrapList::CreateWebRenderCommands( + aBuilder, aResources, aSc, aManager, aDisplayListBuilder); +} + +void nsDisplayListCollection::SerializeWithCorrectZOrder( + nsDisplayList* aOutResultList, nsIContent* aContent) { + // Sort PositionedDescendants() in CSS 'z-order' order. The list is already + // in content document order and SortByZOrder is a stable sort which + // guarantees that boxes produced by the same element are placed together + // in the sort. Consider a position:relative inline element that breaks + // across lines and has absolutely positioned children; all the abs-pos + // children should be z-ordered after all the boxes for the position:relative + // element itself. + PositionedDescendants()->SortByZOrder(); + + // Now follow the rules of http://www.w3.org/TR/CSS21/zindex.html + // 1,2: backgrounds and borders + aOutResultList->AppendToTop(BorderBackground()); + // 3: negative z-index children. + for (;;) { + nsDisplayItem* item = PositionedDescendants()->GetBottom(); + if (item && item->ZIndex() < 0) { + PositionedDescendants()->RemoveBottom(); + aOutResultList->AppendToTop(item); + continue; + } + break; + } + // 4: block backgrounds + aOutResultList->AppendToTop(BlockBorderBackgrounds()); + // 5: floats + aOutResultList->AppendToTop(Floats()); + // 7: general content + aOutResultList->AppendToTop(Content()); + // 7.5: outlines, in content tree order. We need to sort by content order + // because an element with outline that breaks and has children with outline + // might have placed child outline items between its own outline items. + // The element's outline items need to all come before any child outline + // items. + if (aContent) { + Outlines()->SortByContentOrder(aContent); + } + aOutResultList->AppendToTop(Outlines()); + // 8, 9: non-negative z-index children + aOutResultList->AppendToTop(PositionedDescendants()); +} + +namespace mozilla { + +uint32_t PaintTelemetry::sPaintLevel = 0; +uint32_t PaintTelemetry::sMetricLevel = 0; +EnumeratedArray<PaintTelemetry::Metric, PaintTelemetry::Metric::COUNT, double> + PaintTelemetry::sMetrics; + +PaintTelemetry::AutoRecordPaint::AutoRecordPaint() { + // Don't record nested paints. + if (sPaintLevel++ > 0) { + return; + } + + // Reset metrics for a new paint. + for (auto& metric : sMetrics) { + metric = 0.0; + } + mStart = TimeStamp::Now(); +} + +PaintTelemetry::AutoRecordPaint::~AutoRecordPaint() { + MOZ_ASSERT(sPaintLevel != 0); + if (--sPaintLevel > 0) { + return; + } + + // If we're in multi-process mode, don't include paint times for the parent + // process. + if (gfxVars::BrowserTabsRemoteAutostart() && XRE_IsParentProcess()) { + return; + } + + double totalMs = (TimeStamp::Now() - mStart).ToMilliseconds(); + + // Record the total time. + Telemetry::Accumulate(Telemetry::CONTENT_PAINT_TIME, + static_cast<uint32_t>(totalMs)); + + // Helpers for recording large/small paints. + auto recordLarge = [=](const nsCString& aKey, double aDurationMs) -> void { + MOZ_ASSERT(aDurationMs <= totalMs); + uint32_t amount = static_cast<int32_t>((aDurationMs / totalMs) * 100.0); + Telemetry::Accumulate(Telemetry::CONTENT_LARGE_PAINT_PHASE_WEIGHT, aKey, + amount); + }; + auto recordSmall = [=](const nsCString& aKey, double aDurationMs) -> void { + MOZ_ASSERT(aDurationMs <= totalMs); + uint32_t amount = static_cast<int32_t>((aDurationMs / totalMs) * 100.0); + Telemetry::Accumulate(Telemetry::CONTENT_SMALL_PAINT_PHASE_WEIGHT, aKey, + amount); + }; + + double dlMs = sMetrics[Metric::DisplayList]; + double flbMs = sMetrics[Metric::Layerization]; + double frMs = sMetrics[Metric::FlushRasterization]; + double rMs = sMetrics[Metric::Rasterization]; + + // If the total time was >= 16ms, then it's likely we missed a frame due to + // painting. We bucket these metrics separately. + if (totalMs >= 16.0) { + recordLarge("dl"_ns, dlMs); + recordLarge("flb"_ns, flbMs); + recordLarge("fr"_ns, frMs); + recordLarge("r"_ns, rMs); + } else { + recordSmall("dl"_ns, dlMs); + recordSmall("flb"_ns, flbMs); + recordSmall("fr"_ns, frMs); + recordSmall("r"_ns, rMs); + } + + Telemetry::Accumulate(Telemetry::PAINT_BUILD_LAYERS_TIME, flbMs); +} + +PaintTelemetry::AutoRecord::AutoRecord(Metric aMetric) : mMetric(aMetric) { + // Don't double-record anything nested. + if (sMetricLevel++ > 0) { + return; + } + + // Don't record inside nested paints, or outside of paints. + if (sPaintLevel != 1) { + return; + } + + mStart = TimeStamp::Now(); +} + +PaintTelemetry::AutoRecord::~AutoRecord() { + MOZ_ASSERT(sMetricLevel != 0); + + sMetricLevel--; + if (mStart.IsNull()) { + return; + } + + sMetrics[mMetric] += (TimeStamp::Now() - mStart).ToMilliseconds(); +} + +} // namespace mozilla + +static nsIFrame* GetSelfOrPlaceholderFor(nsIFrame* aFrame) { + if (aFrame->HasAnyStateBits(NS_FRAME_IS_PUSHED_FLOAT)) { + return aFrame; + } + + if (aFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW) && + !aFrame->GetPrevInFlow()) { + return aFrame->GetPlaceholderFrame(); + } + + return aFrame; +} + +static nsIFrame* GetAncestorFor(nsIFrame* aFrame) { + nsIFrame* f = GetSelfOrPlaceholderFor(aFrame); + MOZ_ASSERT(f); + return nsLayoutUtils::GetCrossDocParentFrame(f); +} + +nsDisplayListBuilder::AutoBuildingDisplayList::AutoBuildingDisplayList( + nsDisplayListBuilder* aBuilder, nsIFrame* aForChild, + const nsRect& aVisibleRect, const nsRect& aDirtyRect, + const bool aIsTransformed) + : mBuilder(aBuilder), + mPrevFrame(aBuilder->mCurrentFrame), + mPrevReferenceFrame(aBuilder->mCurrentReferenceFrame), + mPrevHitTestArea(aBuilder->mHitTestArea), + mPrevHitTestInfo(aBuilder->mHitTestInfo), + mPrevOffset(aBuilder->mCurrentOffsetToReferenceFrame), + mPrevAdditionalOffset(aBuilder->mAdditionalOffset), + mPrevVisibleRect(aBuilder->mVisibleRect), + mPrevDirtyRect(aBuilder->mDirtyRect), + mPrevAGR(aBuilder->mCurrentAGR), + mPrevAncestorHasApzAwareEventHandler( + aBuilder->mAncestorHasApzAwareEventHandler), + mPrevBuildingInvisibleItems(aBuilder->mBuildingInvisibleItems), + mPrevInInvalidSubtree(aBuilder->mInInvalidSubtree) { + if (aIsTransformed) { + aBuilder->mCurrentOffsetToReferenceFrame = + aBuilder->AdditionalOffset().refOr(nsPoint()); + aBuilder->mCurrentReferenceFrame = aForChild; + } else if (aBuilder->mCurrentFrame == aForChild->GetParent()) { + aBuilder->mCurrentOffsetToReferenceFrame += aForChild->GetPosition(); + } else { + aBuilder->mCurrentReferenceFrame = aBuilder->FindReferenceFrameFor( + aForChild, &aBuilder->mCurrentOffsetToReferenceFrame); + } + + bool isAsync; + mCurrentAGRState = aBuilder->IsAnimatedGeometryRoot(aForChild, isAsync); + + if (aBuilder->mCurrentFrame == aForChild->GetParent()) { + if (mCurrentAGRState == AGR_YES) { + aBuilder->mCurrentAGR = + aBuilder->WrapAGRForFrame(aForChild, isAsync, aBuilder->mCurrentAGR); + } + } else if (aBuilder->mCurrentFrame != aForChild) { + aBuilder->mCurrentAGR = aBuilder->FindAnimatedGeometryRootFor(aForChild); + } + + MOZ_ASSERT(nsLayoutUtils::IsAncestorFrameCrossDoc( + aBuilder->RootReferenceFrame(), *aBuilder->mCurrentAGR)); + + // If aForChild is being visited from a frame other than it's ancestor frame, + // mInInvalidSubtree will need to be recalculated the slow way. + if (aForChild == mPrevFrame || GetAncestorFor(aForChild) == mPrevFrame) { + aBuilder->mInInvalidSubtree = + aBuilder->mInInvalidSubtree || aForChild->IsFrameModified(); + } else { + aBuilder->mInInvalidSubtree = AnyContentAncestorModified(aForChild); + } + + aBuilder->mCurrentFrame = aForChild; + aBuilder->mVisibleRect = aVisibleRect; + aBuilder->mDirtyRect = + aBuilder->mInInvalidSubtree ? aVisibleRect : aDirtyRect; +} diff --git a/layout/painting/nsDisplayList.h b/layout/painting/nsDisplayList.h new file mode 100644 index 0000000000..37161e62ce --- /dev/null +++ b/layout/painting/nsDisplayList.h @@ -0,0 +1,7616 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +/* + * structures that represent things to be painted (ordered in z-order), + * used during painting and hit testing + */ + +#ifndef NSDISPLAYLIST_H_ +#define NSDISPLAYLIST_H_ + +#include "mozilla/Attributes.h" +#include "gfxContext.h" +#include "mozilla/ArenaAllocator.h" +#include "mozilla/Array.h" +#include "mozilla/Assertions.h" +#include "mozilla/Attributes.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/EnumSet.h" +#include "mozilla/Maybe.h" +#include "mozilla/RefPtr.h" +#include "mozilla/TemplateLib.h" // mozilla::tl::Max +#include "nsCOMPtr.h" +#include "nsContainerFrame.h" +#include "nsPoint.h" +#include "nsRect.h" +#include "nsRegion.h" +#include "nsDisplayListInvalidation.h" +#include "DisplayItemClipChain.h" +#include "DisplayListClipState.h" +#include "LayerState.h" +#include "FrameMetrics.h" +#include "ImgDrawResult.h" +#include "mozilla/dom/EffectsInfo.h" +#include "mozilla/dom/RemoteBrowser.h" +#include "mozilla/EffectCompositor.h" +#include "mozilla/EnumeratedArray.h" +#include "mozilla/MotionPathUtils.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/gfx/UserData.h" +#include "mozilla/layers/LayerAttributes.h" +#include "mozilla/layers/ScrollableLayerGuid.h" +#include "nsCSSRenderingBorders.h" +#include "nsPresArena.h" +#include "nsAutoLayoutPhase.h" +#include "nsDisplayItemTypes.h" +#include "RetainedDisplayListHelpers.h" +#include "Units.h" + +#include <stdint.h> +#include "nsClassHashtable.h" +#include "nsTHashtable.h" + +#include <stdlib.h> +#include <algorithm> +#include <unordered_set> + +// XXX Includes that could be avoided by moving function implementations to the +// cpp file. +#include "gfxPlatform.h" + +class gfxContext; +class nsIContent; +class nsDisplayList; +class nsDisplayTableItem; +class nsIScrollableFrame; +class nsSubDocumentFrame; +class nsDisplayCompositorHitTestInfo; +class nsDisplayScrollInfoLayer; +class nsDisplayTableBackgroundSet; +class nsCaret; +enum class nsDisplayOwnLayerFlags; +struct WrFiltersHolder; + +namespace nsStyleTransformMatrix { +class TransformReferenceBox; +} + +namespace mozilla { +class FrameLayerBuilder; +class PresShell; +class StickyScrollContainer; +namespace layers { +struct FrameMetrics; +class RenderRootStateManager; +class Layer; +class ImageLayer; +class ImageContainer; +class StackingContextHelper; +class WebRenderCommand; +class WebRenderScrollData; +class WebRenderLayerScrollData; +} // namespace layers +namespace wr { +class DisplayListBuilder; +} // namespace wr +namespace dom { +class Selection; +} // namespace dom + +enum class DisplayListArenaObjectId { +#define DISPLAY_LIST_ARENA_OBJECT(name_) name_, +#include "nsDisplayListArenaTypes.h" +#undef DISPLAY_LIST_ARENA_OBJECT + COUNT +}; + +} // namespace mozilla + +/* + * An nsIFrame can have many different visual parts. For example an image frame + * can have a background, border, and outline, the image itself, and a + * translucent selection overlay. In general these parts can be drawn at + * discontiguous z-levels; see CSS2.1 appendix E: + * http://www.w3.org/TR/CSS21/zindex.html + * + * We construct a display list for a frame tree that contains one item + * for each visual part. The display list is itself a tree since some items + * are containers for other items; however, its structure does not match + * the structure of its source frame tree. The display list items are sorted + * by z-order. A display list can be used to paint the frames, to determine + * which frame is the target of a mouse event, and to determine what areas + * need to be repainted when scrolling. The display lists built for each task + * may be different for efficiency; in particular some frames need special + * display list items only for event handling, and do not create these items + * when the display list will be used for painting (the common case). For + * example, when painting we avoid creating nsDisplayBackground items for + * frames that don't display a visible background, but for event handling + * we need those backgrounds because they are not transparent to events. + * + * We could avoid constructing an explicit display list by traversing the + * frame tree multiple times in clever ways. However, reifying the display list + * reduces code complexity and reduces the number of times each frame must be + * traversed to one, which seems to be good for performance. It also means + * we can share code for painting, event handling and scroll analysis. + * + * Display lists are short-lived; content and frame trees cannot change + * between a display list being created and destroyed. Display lists should + * not be created during reflow because the frame tree may be in an + * inconsistent state (e.g., a frame's stored overflow-area may not include + * the bounds of all its children). However, it should be fine to create + * a display list while a reflow is pending, before it starts. + * + * A display list covers the "extended" frame tree; the display list for + * a frame tree containing FRAME/IFRAME elements can include frames from + * the subdocuments. + * + * Display item's coordinates are relative to their nearest reference frame + * ancestor. Both the display root and any frame with a transform act as a + * reference frame for their frame subtrees. + */ + +/** + * Represents a frame that is considered to have (or will have) "animated + * geometry" for itself and descendant frames. + * + * For example the scrolled frames of scrollframes which are actively being + * scrolled fall into this category. Frames with certain CSS properties that are + * being animated (e.g. 'left'/'top' etc) are also placed in this category. + * Frames with different active geometry roots are in different PaintedLayers, + * so that we can animate the geometry root by changing its transform (either on + * the main thread or in the compositor). + * + * nsDisplayListBuilder constructs a tree of these (for fast traversals) and + * assigns one for each display item. + * + * The animated geometry root for a display item is required to be a descendant + * (or equal to) the item's ReferenceFrame(), which means that we will fall back + * to returning aItem->ReferenceFrame() when we can't find another animated + * geometry root. + * + * The animated geometry root isn't strongly defined for a frame as transforms + * and background-attachment:fixed can cause it to vary between display items + * for a given frame. + */ +struct AnimatedGeometryRoot { + static already_AddRefed<AnimatedGeometryRoot> CreateAGRForFrame( + nsIFrame* aFrame, AnimatedGeometryRoot* aParent, bool aIsAsync, + bool aIsRetained) { + RefPtr<AnimatedGeometryRoot> result; + if (aIsRetained) { + result = aFrame->GetProperty(AnimatedGeometryRootCache()); + } + + if (result) { + result->mParentAGR = aParent; + result->mIsAsync = aIsAsync; + } else { + result = new AnimatedGeometryRoot(aFrame, aParent, aIsAsync, aIsRetained); + } + return result.forget(); + } + + operator nsIFrame*() { return mFrame; } + + nsIFrame* operator->() const { return mFrame; } + + AnimatedGeometryRoot* GetAsyncAGR() { + AnimatedGeometryRoot* agr = this; + while (!agr->mIsAsync && agr->mParentAGR) { + agr = agr->mParentAGR; + } + return agr; + } + + NS_INLINE_DECL_REFCOUNTING(AnimatedGeometryRoot) + + nsIFrame* mFrame; + RefPtr<AnimatedGeometryRoot> mParentAGR; + bool mIsAsync; + bool mIsRetained; + + protected: + static void DetachAGR(AnimatedGeometryRoot* aAGR) { + aAGR->mFrame = nullptr; + aAGR->mParentAGR = nullptr; + NS_RELEASE(aAGR); + } + + NS_DECLARE_FRAME_PROPERTY_WITH_DTOR(AnimatedGeometryRootCache, + AnimatedGeometryRoot, DetachAGR) + + AnimatedGeometryRoot(nsIFrame* aFrame, AnimatedGeometryRoot* aParent, + bool aIsAsync, bool aIsRetained) + : mFrame(aFrame), + mParentAGR(aParent), + mIsAsync(aIsAsync), + mIsRetained(aIsRetained) { + MOZ_ASSERT(mParentAGR || mIsAsync, + "The root AGR should always be treated as an async AGR."); + if (mIsRetained) { + NS_ADDREF(this); + aFrame->SetProperty(AnimatedGeometryRootCache(), this); + } + } + + ~AnimatedGeometryRoot() { + if (mFrame && mIsRetained) { + mFrame->RemoveProperty(AnimatedGeometryRootCache()); + } + } +}; + +namespace mozilla { + +/** + * An active scrolled root (ASR) is similar to an animated geometry root (AGR). + * The differences are: + * - ASRs are only created for async-scrollable scroll frames. This is a + * (hopefully) temporary restriction. In the future we will want to create + * ASRs for all the things that are currently creating AGRs, and then + * replace AGRs with ASRs and rename them from "active scrolled root" to + * "animated geometry root". + * - ASR objects are created during display list construction by the nsIFrames + * that induce ASRs. This is done using AutoCurrentActiveScrolledRootSetter. + * The current ASR is returned by + * nsDisplayListBuilder::CurrentActiveScrolledRoot(). + * - There is no way to go from an nsIFrame pointer to the ASR of that frame. + * If you need to look up an ASR after display list construction, you need + * to store it while the AutoCurrentActiveScrolledRootSetter that creates it + * is on the stack. + */ +struct ActiveScrolledRoot { + static already_AddRefed<ActiveScrolledRoot> CreateASRForFrame( + const ActiveScrolledRoot* aParent, nsIScrollableFrame* aScrollableFrame, + bool aIsRetained); + + static const ActiveScrolledRoot* PickAncestor( + const ActiveScrolledRoot* aOne, const ActiveScrolledRoot* aTwo) { + MOZ_ASSERT(IsAncestor(aOne, aTwo) || IsAncestor(aTwo, aOne)); + return Depth(aOne) <= Depth(aTwo) ? aOne : aTwo; + } + + static const ActiveScrolledRoot* PickDescendant( + const ActiveScrolledRoot* aOne, const ActiveScrolledRoot* aTwo) { + MOZ_ASSERT(IsAncestor(aOne, aTwo) || IsAncestor(aTwo, aOne)); + return Depth(aOne) >= Depth(aTwo) ? aOne : aTwo; + } + + static bool IsAncestor(const ActiveScrolledRoot* aAncestor, + const ActiveScrolledRoot* aDescendant); + + static nsCString ToString( + const mozilla::ActiveScrolledRoot* aActiveScrolledRoot); + + // Call this when inserting an ancestor. + void IncrementDepth() { mDepth++; } + + /** + * Find the view ID (or generate a new one) for the content element + * corresponding to the ASR. + */ + mozilla::layers::ScrollableLayerGuid::ViewID GetViewId() const { + if (!mViewId.isSome()) { + mViewId = Some(ComputeViewId()); + } + return *mViewId; + } + + RefPtr<const ActiveScrolledRoot> mParent; + nsIScrollableFrame* mScrollableFrame; + + NS_INLINE_DECL_REFCOUNTING(ActiveScrolledRoot) + + private: + ActiveScrolledRoot() + : mScrollableFrame(nullptr), mDepth(0), mRetained(false) {} + + ~ActiveScrolledRoot(); + + static void DetachASR(ActiveScrolledRoot* aASR) { + aASR->mParent = nullptr; + aASR->mScrollableFrame = nullptr; + NS_RELEASE(aASR); + } + NS_DECLARE_FRAME_PROPERTY_WITH_DTOR(ActiveScrolledRootCache, + ActiveScrolledRoot, DetachASR) + + static uint32_t Depth(const ActiveScrolledRoot* aActiveScrolledRoot) { + return aActiveScrolledRoot ? aActiveScrolledRoot->mDepth : 0; + } + + mozilla::layers::ScrollableLayerGuid::ViewID ComputeViewId() const; + + // This field is lazily populated in GetViewId(). We don't want to do the + // work of populating if webrender is disabled, because it is often not + // needed. + mutable Maybe<mozilla::layers::ScrollableLayerGuid::ViewID> mViewId; + + uint32_t mDepth; + bool mRetained; +}; +} // namespace mozilla + +enum class nsDisplayListBuilderMode : uint8_t { + Painting, + EventDelivery, + PluginGeometry, + FrameVisibility, + TransformComputation, + GenerateGlyph, +}; + +class nsDisplayWrapList; + +/** + * This manages a display list and is passed as a parameter to + * nsIFrame::BuildDisplayList. + * It contains the parameters that don't change from frame to frame and manages + * the display list memory using an arena. It also establishes the reference + * coordinate system for all display list items. Some of the parameters are + * available from the prescontext/presshell, but we copy them into the builder + * for faster/more convenient access. + */ +class nsDisplayListBuilder { + typedef mozilla::LayoutDeviceIntRect LayoutDeviceIntRect; + typedef mozilla::LayoutDeviceIntSize LayoutDeviceIntSize; + typedef mozilla::LayoutDeviceIntRegion LayoutDeviceIntRegion; + typedef mozilla::LayoutDeviceRect LayoutDeviceRect; + + /** + * This manages status of a 3d context to collect visible rects of + * descendants and passing a dirty rect. + * + * Since some transforms maybe singular, passing visible rects or + * the dirty rect level by level from parent to children may get a + * wrong result, being different from the result of appling with + * effective transform directly. + * + * nsIFrame::BuildDisplayListForStackingContext() uses + * AutoPreserves3DContext to install an instance on the builder. + * + * \see AutoAccumulateTransform, AutoAccumulateRect, + * AutoPreserves3DContext, Accumulate, GetCurrentTransform, + * StartRoot. + */ + class Preserves3DContext { + public: + typedef mozilla::gfx::Matrix4x4 Matrix4x4; + + Preserves3DContext() + : mAccumulatedRectLevels(0), mAllowAsyncAnimation(true) {} + + Preserves3DContext(const Preserves3DContext& aOther) + : mAccumulatedTransform(), + mAccumulatedRect(), + mAccumulatedRectLevels(0), + mVisibleRect(aOther.mVisibleRect), + mAllowAsyncAnimation(aOther.mAllowAsyncAnimation) {} + + // Accmulate transforms of ancestors on the preserves-3d chain. + Matrix4x4 mAccumulatedTransform; + // Accmulate visible rect of descendants in the preserves-3d context. + nsRect mAccumulatedRect; + // How far this frame is from the root of the current 3d context. + int mAccumulatedRectLevels; + nsRect mVisibleRect; + // Allow async animation for this 3D context. + bool mAllowAsyncAnimation; + }; + + /** + * A frame can be in one of three states of AGR. + * AGR_NO means the frame is not an AGR for now. + * AGR_YES means the frame is an AGR for now. + */ + enum AGRState { AGR_NO, AGR_YES }; + + public: + typedef mozilla::FrameLayerBuilder FrameLayerBuilder; + typedef mozilla::DisplayItemClip DisplayItemClip; + typedef mozilla::DisplayItemClipChain DisplayItemClipChain; + typedef mozilla::DisplayItemClipChainHasher DisplayItemClipChainHasher; + typedef mozilla::DisplayItemClipChainEqualer DisplayItemClipChainEqualer; + typedef mozilla::DisplayListClipState DisplayListClipState; + typedef mozilla::ActiveScrolledRoot ActiveScrolledRoot; + typedef nsIWidget::ThemeGeometry ThemeGeometry; + typedef mozilla::layers::Layer Layer; + typedef mozilla::layers::FrameMetrics FrameMetrics; + typedef mozilla::layers::ScrollableLayerGuid ScrollableLayerGuid; + typedef mozilla::layers::ScrollableLayerGuid::ViewID ViewID; + typedef mozilla::gfx::CompositorHitTestInfo CompositorHitTestInfo; + typedef mozilla::gfx::Matrix4x4 Matrix4x4; + typedef mozilla::Maybe<mozilla::layers::ScrollDirection> MaybeScrollDirection; + typedef mozilla::dom::EffectsInfo EffectsInfo; + typedef mozilla::layers::LayersId LayersId; + typedef mozilla::dom::RemoteBrowser RemoteBrowser; + + /** + * @param aReferenceFrame the frame at the root of the subtree; its origin + * is the origin of the reference coordinate system for this display list + * @param aMode encodes what the builder is being used for. + * @param aBuildCaret whether or not we should include the caret in any + * display lists that we make. + */ + nsDisplayListBuilder(nsIFrame* aReferenceFrame, + nsDisplayListBuilderMode aMode, bool aBuildCaret, + bool aRetainingDisplayList = false); + ~nsDisplayListBuilder(); + + void BeginFrame(); + void EndFrame(); + + void AddTemporaryItem(nsDisplayItem* aItem) { + mTemporaryItems.AppendElement(aItem); + } + + void SetWillComputePluginGeometry(bool aWillComputePluginGeometry) { + mWillComputePluginGeometry = aWillComputePluginGeometry; + } + + void SetForPluginGeometry(bool aForPlugin) { + if (aForPlugin) { + NS_ASSERTION(mMode == nsDisplayListBuilderMode::Painting, + "Can only switch from Painting to PluginGeometry"); + NS_ASSERTION(mWillComputePluginGeometry, + "Should have signalled this in advance"); + mMode = nsDisplayListBuilderMode::PluginGeometry; + } else { + NS_ASSERTION(mMode == nsDisplayListBuilderMode::PluginGeometry, + "Can only switch from Painting to PluginGeometry"); + mMode = nsDisplayListBuilderMode::Painting; + } + } + + mozilla::layers::LayerManager* GetWidgetLayerManager( + nsView** aView = nullptr); + + /** + * @return true if the display is being built in order to determine which + * frame is under the mouse position. + */ + bool IsForEventDelivery() const { + return mMode == nsDisplayListBuilderMode::EventDelivery; + } + + /** + * Be careful with this. The display list will be built in Painting mode + * first and then switched to PluginGeometry before a second call to + * ComputeVisibility. + * @return true if the display list is being built to compute geometry + * for plugins. + */ + bool IsForPluginGeometry() const { + return mMode == nsDisplayListBuilderMode::PluginGeometry; + } + + /** + * @return true if the display list is being built for painting. + */ + bool IsForPainting() const { + return mMode == nsDisplayListBuilderMode::Painting; + } + + /** + * @return true if the display list is being built for determining frame + * visibility. + */ + bool IsForFrameVisibility() const { + return mMode == nsDisplayListBuilderMode::FrameVisibility; + } + + /** + * @return true if the display list is being built for creating the glyph + * mask from text items. + */ + bool IsForGenerateGlyphMask() const { + return mMode == nsDisplayListBuilderMode::GenerateGlyph; + } + + bool BuildCompositorHitTestInfo() const { + return mBuildCompositorHitTestInfo; + } + + bool WillComputePluginGeometry() const { return mWillComputePluginGeometry; } + + /** + * @return true if "painting is suppressed" during page load and we + * should paint only the background of the document. + */ + bool IsBackgroundOnly() { + NS_ASSERTION(mPresShellStates.Length() > 0, + "don't call this if we're not in a presshell"); + return CurrentPresShellState()->mIsBackgroundOnly; + } + + /** + * @return the root of given frame's (sub)tree, whose origin + * establishes the coordinate system for the child display items. + */ + const nsIFrame* FindReferenceFrameFor(const nsIFrame* aFrame, + nsPoint* aOffset = nullptr) const; + + const mozilla::Maybe<nsPoint>& AdditionalOffset() const { + return mAdditionalOffset; + } + + /** + * @return the root of the display list's frame (sub)tree, whose origin + * establishes the coordinate system for the display list + */ + nsIFrame* RootReferenceFrame() const { return mReferenceFrame; } + + /** + * @return a point pt such that adding pt to a coordinate relative to aFrame + * makes it relative to ReferenceFrame(), i.e., returns + * aFrame->GetOffsetToCrossDoc(ReferenceFrame()). The returned point is in + * the appunits of aFrame. + */ + const nsPoint ToReferenceFrame(const nsIFrame* aFrame) const { + nsPoint result; + FindReferenceFrameFor(aFrame, &result); + return result; + } + /** + * When building the display list, the scrollframe aFrame will be "ignored" + * for the purposes of clipping, and its scrollbars will be hidden. We use + * this to allow RenderOffscreen to render a whole document without beign + * clipped by the viewport or drawing the viewport scrollbars. + */ + void SetIgnoreScrollFrame(nsIFrame* aFrame) { mIgnoreScrollFrame = aFrame; } + /** + * Get the scrollframe to ignore, if any. + */ + nsIFrame* GetIgnoreScrollFrame() { return mIgnoreScrollFrame; } + void SetIsRelativeToLayoutViewport(); + bool IsRelativeToLayoutViewport() const { + return mIsRelativeToLayoutViewport; + } + /** + * Get the ViewID of the nearest scrolling ancestor frame. + */ + ViewID GetCurrentScrollParentId() const { return mCurrentScrollParentId; } + /** + * Get and set the flag that indicates if scroll parents should have layers + * forcibly created. This flag is set when a deeply nested scrollframe has + * a displayport, and for scroll handoff to work properly the ancestor + * scrollframes should also get their own scrollable layers. + */ + void ForceLayerForScrollParent() { mForceLayerForScrollParent = true; } + /** + * Get the ViewID and the scrollbar flags corresponding to the scrollbar for + * which we are building display items at the moment. + */ + ViewID GetCurrentScrollbarTarget() const { return mCurrentScrollbarTarget; } + MaybeScrollDirection GetCurrentScrollbarDirection() const { + return mCurrentScrollbarDirection; + } + /** + * Returns true if building a scrollbar, and the scrollbar will not be + * layerized. + */ + bool IsBuildingNonLayerizedScrollbar() const { + return mIsBuildingScrollbar && !mCurrentScrollbarWillHaveLayer; + } + /** + * Calling this setter makes us include all out-of-flow descendant + * frames in the display list, wherever they may be positioned (even + * outside the dirty rects). + */ + void SetIncludeAllOutOfFlows() { mIncludeAllOutOfFlows = true; } + bool GetIncludeAllOutOfFlows() const { return mIncludeAllOutOfFlows; } + /** + * Calling this setter makes us exclude all leaf frames that aren't + * selected. + */ + void SetSelectedFramesOnly() { mSelectedFramesOnly = true; } + bool GetSelectedFramesOnly() { return mSelectedFramesOnly; } + /** + * Calling this setter makes us compute accurate visible regions at the cost + * of performance if regions get very complex. + */ + bool GetAccurateVisibleRegions() { + return mMode == nsDisplayListBuilderMode::PluginGeometry; + } + /** + * @return Returns true if we should include the caret in any display lists + * that we make. + */ + bool IsBuildingCaret() const { return mBuildCaret; } + + bool IsRetainingDisplayList() const { return mRetainingDisplayList; } + + bool IsPartialUpdate() const { return mPartialUpdate; } + void SetPartialUpdate(bool aPartial) { mPartialUpdate = aPartial; } + + bool IsBuilding() const { return mIsBuilding; } + void SetIsBuilding(bool aIsBuilding) { mIsBuilding = aIsBuilding; } + + bool InInvalidSubtree() const { return mInInvalidSubtree; } + + /** + * Allows callers to selectively override the regular paint suppression + * checks, so that methods like GetFrameForPoint work when painting is + * suppressed. + */ + void IgnorePaintSuppression() { mIgnoreSuppression = true; } + /** + * @return Returns if this builder will ignore paint suppression. + */ + bool IsIgnoringPaintSuppression() { return mIgnoreSuppression; } + /** + * Call this if we're doing normal painting to the window. + */ + void SetPaintingToWindow(bool aToWindow) { mIsPaintingToWindow = aToWindow; } + bool IsPaintingToWindow() const { return mIsPaintingToWindow; } + /** + * Call this if we're using high quality scaling for image decoding. + * It is also implied by IsPaintingToWindow. + */ + void SetUseHighQualityScaling(bool aUseHighQualityScaling) { + mUseHighQualityScaling = aUseHighQualityScaling; + } + bool UseHighQualityScaling() const { + return mIsPaintingToWindow || mUseHighQualityScaling; + } + /** + * Call this if we're doing painting for WebRender + */ + void SetPaintingForWebRender(bool aForWebRender) { + mIsPaintingForWebRender = true; + } + bool IsPaintingForWebRender() const { return mIsPaintingForWebRender; } + /** + * Call this to prevent descending into subdocuments. + */ + void SetDescendIntoSubdocuments(bool aDescend) { + mDescendIntoSubdocuments = aDescend; + } + + bool GetDescendIntoSubdocuments() { return mDescendIntoSubdocuments; } + + /** + * Get dirty rect relative to current frame (the frame that we're calling + * BuildDisplayList on right now). + */ + const nsRect& GetVisibleRect() { return mVisibleRect; } + const nsRect& GetDirtyRect() { return mDirtyRect; } + + void SetVisibleRect(const nsRect& aVisibleRect) { + mVisibleRect = aVisibleRect; + } + + void IntersectVisibleRect(const nsRect& aVisibleRect) { + mVisibleRect.IntersectRect(mVisibleRect, aVisibleRect); + } + + void SetDirtyRect(const nsRect& aDirtyRect) { mDirtyRect = aDirtyRect; } + + void IntersectDirtyRect(const nsRect& aDirtyRect) { + mDirtyRect.IntersectRect(mDirtyRect, aDirtyRect); + } + + const nsIFrame* GetCurrentFrame() { return mCurrentFrame; } + const nsIFrame* GetCurrentReferenceFrame() { return mCurrentReferenceFrame; } + + const nsPoint& GetCurrentFrameOffsetToReferenceFrame() const { + return mCurrentOffsetToReferenceFrame; + } + + AnimatedGeometryRoot* GetCurrentAnimatedGeometryRoot() { return mCurrentAGR; } + AnimatedGeometryRoot* GetRootAnimatedGeometryRoot() { return mRootAGR; } + + void RecomputeCurrentAnimatedGeometryRoot(); + + void Check() { mPool.Check(); } + + /** + * Returns true if merging and flattening of display lists should be + * performed while computing visibility. + */ + bool AllowMergingAndFlattening() { return mAllowMergingAndFlattening; } + void SetAllowMergingAndFlattening(bool aAllow) { + mAllowMergingAndFlattening = aAllow; + } + + /** + * Sets the current compositor hit test area and info to |aHitTestArea| and + * |aHitTestInfo|. + * This is used during display list building to determine if the parent frame + * hit test info contains the same information that child frame needs. + */ + void SetCompositorHitTestInfo(const nsRect& aHitTestArea, + const CompositorHitTestInfo& aHitTestInfo) { + mHitTestArea = aHitTestArea; + mHitTestInfo = aHitTestInfo; + } + + const nsRect& GetHitTestArea() const { return mHitTestArea; } + const CompositorHitTestInfo& GetHitTestInfo() const { return mHitTestInfo; } + + /** + * Builds a new nsDisplayCompositorHitTestInfo for the frame |aFrame| if + * needed, and adds it to the top of |aList|. If |aBuildNew| is true, the + * previous hit test info will not be reused. + */ + void BuildCompositorHitTestInfoIfNeeded(nsIFrame* aFrame, + nsDisplayList* aList, + const bool aBuildNew); + + bool IsInsidePointerEventsNoneDoc() { + return CurrentPresShellState()->mInsidePointerEventsNoneDoc; + } + + bool IsTouchEventPrefEnabledDoc() { + return CurrentPresShellState()->mTouchEventPrefEnabledDoc; + } + + bool GetAncestorHasApzAwareEventHandler() const { + return mAncestorHasApzAwareEventHandler; + } + + void SetAncestorHasApzAwareEventHandler(bool aValue) { + mAncestorHasApzAwareEventHandler = aValue; + } + + bool HaveScrollableDisplayPort() const { return mHaveScrollableDisplayPort; } + void SetHaveScrollableDisplayPort() { mHaveScrollableDisplayPort = true; } + void ClearHaveScrollableDisplayPort() { mHaveScrollableDisplayPort = false; } + + bool SetIsCompositingCheap(bool aCompositingCheap) { + bool temp = mIsCompositingCheap; + mIsCompositingCheap = aCompositingCheap; + return temp; + } + + bool IsCompositingCheap() const { return mIsCompositingCheap; } + /** + * Display the caret if needed. + */ + bool DisplayCaret(nsIFrame* aFrame, nsDisplayList* aList) { + nsIFrame* frame = GetCaretFrame(); + if (aFrame == frame && !IsBackgroundOnly()) { + frame->DisplayCaret(this, aList); + return true; + } + return false; + } + /** + * Get the frame that the caret is supposed to draw in. + * If the caret is currently invisible, this will be null. + */ + nsIFrame* GetCaretFrame() { return mCaretFrame; } + /** + * Get the rectangle we're supposed to draw the caret into. + */ + const nsRect& GetCaretRect() { return mCaretRect; } + /** + * Get the caret associated with the current presshell. + */ + nsCaret* GetCaret(); + + /** + * Returns the root scroll frame for the current PresShell, if the PresShell + * is ignoring viewport scrolling. + */ + nsIFrame* GetPresShellIgnoreScrollFrame() { + return CurrentPresShellState()->mPresShellIgnoreScrollFrame; + } + + /** + * Notify the display list builder that we're entering a presshell. + * aReferenceFrame should be a frame in the new presshell. + * aPointerEventsNoneDoc should be set to true if the frame generating this + * document is pointer-events:none. + */ + void EnterPresShell(const nsIFrame* aReferenceFrame, + bool aPointerEventsNoneDoc = false); + /** + * For print-preview documents, we sometimes need to build display items for + * the same frames multiple times in the same presentation, with different + * clipping. Between each such batch of items, call + * ResetMarkedFramesForDisplayList to make sure that the results of + * MarkFramesForDisplayList do not carry over between batches. + */ + void ResetMarkedFramesForDisplayList(const nsIFrame* aReferenceFrame); + /** + * Notify the display list builder that we're leaving a presshell. + */ + void LeavePresShell(const nsIFrame* aReferenceFrame, + nsDisplayList* aPaintedContents); + + void IncrementPresShellPaintCount(mozilla::PresShell* aPresShell); + + /** + * Returns true if we're currently building a display list that's + * directly or indirectly under an nsDisplayTransform. + */ + bool IsInTransform() const { return mInTransform; } + + bool InEventsAndPluginsOnly() const { return mInEventsAndPluginsOnly; } + /** + * Indicate whether or not we're directly or indirectly under and + * nsDisplayTransform or SVG foreignObject. + */ + void SetInTransform(bool aInTransform) { mInTransform = aInTransform; } + + /** + * Returns true if we're currently building a display list that's + * under an nsDisplayFilters. + */ + bool IsInFilter() const { return mInFilter; } + + bool IsInPageSequence() const { return mInPageSequence; } + void SetInPageSequence(bool aInPage) { mInPageSequence = aInPage; } + + /** + * Return true if we're currently building a display list for a + * nested presshell. + */ + bool IsInSubdocument() const { return mPresShellStates.Length() > 1; } + + void SetDisablePartialUpdates(bool aDisable) { + mDisablePartialUpdates = aDisable; + } + bool DisablePartialUpdates() const { return mDisablePartialUpdates; } + + void SetPartialBuildFailed(bool aFailed) { mPartialBuildFailed = aFailed; } + bool PartialBuildFailed() const { return mPartialBuildFailed; } + + bool IsInActiveDocShell() const { return mIsInActiveDocShell; } + void SetInActiveDocShell(bool aActive) { mIsInActiveDocShell = aActive; } + + /** + * Return true if we're currently building a display list for the presshell + * of a chrome document, or if we're building the display list for a popup. + */ + bool IsInChromeDocumentOrPopup() const { + return mIsInChromePresContext || mIsBuildingForPopup; + } + + /** + * @return true if images have been set to decode synchronously. + */ + bool ShouldSyncDecodeImages() const { return mSyncDecodeImages; } + + /** + * Indicates whether we should synchronously decode images. If true, we decode + * and draw whatever image data has been loaded. If false, we just draw + * whatever has already been decoded. + */ + void SetSyncDecodeImages(bool aSyncDecodeImages) { + mSyncDecodeImages = aSyncDecodeImages; + } + + nsDisplayTableBackgroundSet* SetTableBackgroundSet( + nsDisplayTableBackgroundSet* aTableSet) { + nsDisplayTableBackgroundSet* old = mTableBackgroundSet; + mTableBackgroundSet = aTableSet; + return old; + } + nsDisplayTableBackgroundSet* GetTableBackgroundSet() const { + return mTableBackgroundSet; + } + + void FreeClipChains(); + + /* + * Frees the temporary display items created during merging. + */ + void FreeTemporaryItems(); + + /** + * Helper method to generate background painting flags based on the + * information available in the display list builder. Currently only + * accounts for mSyncDecodeImages. + */ + uint32_t GetBackgroundPaintFlags(); + + /** + * Helper method to generate image decoding flags based on the + * information available in the display list builder. + */ + uint32_t GetImageDecodeFlags() const; + + /** + * Subtracts aRegion from *aVisibleRegion. We avoid letting + * aVisibleRegion become overcomplex by simplifying it if necessary. + */ + void SubtractFromVisibleRegion(nsRegion* aVisibleRegion, + const nsRegion& aRegion); + + /** + * Mark the frames in aFrames to be displayed if they intersect aDirtyRect + * (which is relative to aDirtyFrame). If the frames have placeholders + * that might not be displayed, we mark the placeholders and their ancestors + * to ensure that display list construction descends into them + * anyway. nsDisplayListBuilder will take care of unmarking them when it is + * destroyed. + */ + void MarkFramesForDisplayList(nsIFrame* aDirtyFrame, + const nsFrameList& aFrames); + void MarkFrameForDisplay(nsIFrame* aFrame, const nsIFrame* aStopAtFrame); + void MarkFrameForDisplayIfVisible(nsIFrame* aFrame, + const nsIFrame* aStopAtFrame); + void AddFrameMarkedForDisplayIfVisible(nsIFrame* aFrame); + + void ClearFixedBackgroundDisplayData(); + /** + * Mark all child frames that Preserve3D() as needing display. + * Because these frames include transforms set on their parent, dirty rects + * for intermediate frames may be empty, yet child frames could still be + * visible. + */ + void MarkPreserve3DFramesForDisplayList(nsIFrame* aDirtyFrame); + + /** + * Returns true if we need to descend into this frame when building + * the display list, even though it doesn't intersect the dirty + * rect, because it may have out-of-flows that do so. + */ + bool ShouldDescendIntoFrame(nsIFrame* aFrame, bool aVisible) const { + return aFrame->HasAnyStateBits(NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO) || + (aVisible && aFrame->ForceDescendIntoIfVisible()) || + GetIncludeAllOutOfFlows(); + } + + /** + * Returns the list of registered theme geometries. + */ + nsTArray<ThemeGeometry> GetThemeGeometries() const { + nsTArray<ThemeGeometry> geometries; + + for (auto iter = mThemeGeometries.ConstIter(); !iter.Done(); iter.Next()) { + geometries.AppendElements(*iter.Data()); + } + + return geometries; + } + + /** + * Notifies the builder that a particular themed widget exists + * at the given rectangle within the currently built display list. + * For certain appearance values (currently only StyleAppearance::Toolbar and + * StyleAppearance::WindowTitlebar) this gets called during every display list + * construction, for every themed widget of the right type within the + * display list, except for themed widgets which are transformed or have + * effects applied to them (e.g. CSS opacity or filters). + * + * @param aWidgetType the -moz-appearance value for the themed widget + * @param aItem the item associated with the theme geometry + * @param aRect the device-pixel rect relative to the widget's displayRoot + * for the themed widget + */ + void RegisterThemeGeometry(uint8_t aWidgetType, nsDisplayItem* aItem, + const mozilla::LayoutDeviceIntRect& aRect) { + if (!mIsPaintingToWindow) { + return; + } + + nsTArray<ThemeGeometry>* geometries = mThemeGeometries.LookupOrAdd(aItem); + geometries->AppendElement(ThemeGeometry(aWidgetType, aRect)); + } + + /** + * Removes theme geometries associated with the given display item |aItem|. + */ + void UnregisterThemeGeometry(nsDisplayItem* aItem) { + mThemeGeometries.Remove(aItem); + } + + /** + * Adjusts mWindowDraggingRegion to take into account aFrame. If aFrame's + * -moz-window-dragging value is |drag|, its border box is added to the + * collected dragging region; if the value is |no-drag|, the border box is + * subtracted from the region; if the value is |default|, that frame does + * not influence the window dragging region. + */ + void AdjustWindowDraggingRegion(nsIFrame* aFrame); + + LayoutDeviceIntRegion GetWindowDraggingRegion() const; + + void RemoveModifiedWindowRegions(); + void ClearRetainedWindowRegions(); + + const nsDataHashtable<nsPtrHashKey<RemoteBrowser>, EffectsInfo>& + GetEffectUpdates() const { + return mEffectsUpdates; + } + + void AddEffectUpdate(RemoteBrowser* aBrowser, EffectsInfo aUpdate) { + mEffectsUpdates.Put(aBrowser, aUpdate); + } + + /** + * Allocate memory in our arena. It will only be freed when this display list + * builder is destroyed. This memory holds nsDisplayItems and + * DisplayItemClipChain objects. + * + * Destructors are called as soon as the item is no longer used. + */ + void* Allocate(size_t aSize, mozilla::DisplayListArenaObjectId aId) { + return mPool.Allocate(aId, aSize); + } + void* Allocate(size_t aSize, DisplayItemType aType) { + static_assert(size_t(DisplayItemType::TYPE_ZERO) == + size_t(mozilla::DisplayListArenaObjectId::CLIPCHAIN), + ""); +#define DECLARE_DISPLAY_ITEM_TYPE(name_, ...) \ + static_assert(size_t(DisplayItemType::TYPE_##name_) == \ + size_t(mozilla::DisplayListArenaObjectId::name_), \ + ""); +#include "nsDisplayItemTypesList.h" +#undef DECLARE_DISPLAY_ITEM_TYPE + return Allocate(aSize, mozilla::DisplayListArenaObjectId(size_t(aType))); + } + + void Destroy(mozilla::DisplayListArenaObjectId aId, void* aPtr) { + return mPool.Free(aId, aPtr); + } + void Destroy(DisplayItemType aType, void* aPtr) { + return Destroy(mozilla::DisplayListArenaObjectId(size_t(aType)), aPtr); + } + + /** + * Allocate a new ActiveScrolledRoot in the arena. Will be cleaned up + * automatically when the arena goes away. + */ + ActiveScrolledRoot* AllocateActiveScrolledRoot( + const ActiveScrolledRoot* aParent, nsIScrollableFrame* aScrollableFrame); + + /** + * Allocate a new DisplayItemClipChain object in the arena. Will be cleaned + * up automatically when the arena goes away. + */ + const DisplayItemClipChain* AllocateDisplayItemClipChain( + const DisplayItemClip& aClip, const ActiveScrolledRoot* aASR, + const DisplayItemClipChain* aParent); + + /** + * Intersect two clip chains, allocating the new clip chain items in this + * builder's arena. The result is parented to aAncestor, and no intersections + * happen past aAncestor's ASR. + * That means aAncestor has to be living in this builder's arena already. + * aLeafClip1 and aLeafClip2 only need to outlive the call to this function, + * their values are copied into the newly-allocated intersected clip chain + * and this function does not hold on to any pointers to them. + */ + const DisplayItemClipChain* CreateClipChainIntersection( + const DisplayItemClipChain* aAncestor, + const DisplayItemClipChain* aLeafClip1, + const DisplayItemClipChain* aLeafClip2); + + /** + * Clone the supplied clip chain's chain items into this builder's arena. + */ + const DisplayItemClipChain* CopyWholeChain( + const DisplayItemClipChain* aClipChain); + + /** + * Returns a new clip chain containing an intersection of all clips of + * |aClipChain| up to and including |aASR|. + * If there is no clip, returns nullptr. + */ + const DisplayItemClipChain* FuseClipChainUpTo( + const DisplayItemClipChain* aClipChain, const ActiveScrolledRoot* aASR); + + const ActiveScrolledRoot* GetFilterASR() const { return mFilterASR; } + + /** + * Transfer off main thread animations to the layer. May be called + * with aBuilder and aItem both null, but only if the caller has + * already checked that off main thread animations should be sent to + * the layer. When they are both null, the animations are added to + * the layer as pending animations. + */ + static void AddAnimationsAndTransitionsToLayer(Layer* aLayer, + nsDisplayListBuilder* aBuilder, + nsDisplayItem* aItem, + nsIFrame* aFrame, + DisplayItemType aType); + + /** + * Merges the display items in |aMergedItems| and returns a new temporary + * display item. + * The display items in |aMergedItems| have to be mergeable with each other. + */ + nsDisplayWrapList* MergeItems(nsTArray<nsDisplayWrapList*>& aItems); + + /** + * A helper class used to temporarily set nsDisplayListBuilder properties for + * building display items. + * aVisibleRect and aDirtyRect are relative to aForChild. + */ + class AutoBuildingDisplayList { + public: + AutoBuildingDisplayList(nsDisplayListBuilder* aBuilder, nsIFrame* aForChild) + : AutoBuildingDisplayList( + aBuilder, aForChild, aBuilder->GetVisibleRect(), + aBuilder->GetDirtyRect(), aForChild->IsTransformed()) {} + + AutoBuildingDisplayList(nsDisplayListBuilder* aBuilder, nsIFrame* aForChild, + const nsRect& aVisibleRect, + const nsRect& aDirtyRect) + : AutoBuildingDisplayList(aBuilder, aForChild, aVisibleRect, aDirtyRect, + aForChild->IsTransformed()) {} + + AutoBuildingDisplayList(nsDisplayListBuilder* aBuilder, nsIFrame* aForChild, + const nsRect& aVisibleRect, + const nsRect& aDirtyRect, + const bool aIsTransformed); + + void SetReferenceFrameAndCurrentOffset(const nsIFrame* aFrame, + const nsPoint& aOffset) { + mBuilder->mCurrentReferenceFrame = aFrame; + mBuilder->mCurrentOffsetToReferenceFrame = aOffset; + } + + void SetAdditionalOffset(const nsPoint& aOffset) { + MOZ_ASSERT(!mBuilder->mAdditionalOffset); + mBuilder->mAdditionalOffset = mozilla::Some(aOffset); + + mBuilder->mCurrentOffsetToReferenceFrame += aOffset; + mBuilder->mAdditionalOffsetFrame = mBuilder->mCurrentReferenceFrame; + } + + bool IsAnimatedGeometryRoot() const { return mCurrentAGRState == AGR_YES; } + + void RestoreBuildingInvisibleItemsValue() { + mBuilder->mBuildingInvisibleItems = mPrevBuildingInvisibleItems; + } + + ~AutoBuildingDisplayList() { + mBuilder->mCurrentFrame = mPrevFrame; + mBuilder->mCurrentReferenceFrame = mPrevReferenceFrame; + mBuilder->mHitTestArea = mPrevHitTestArea; + mBuilder->mHitTestInfo = mPrevHitTestInfo; + mBuilder->mCurrentOffsetToReferenceFrame = mPrevOffset; + mBuilder->mVisibleRect = mPrevVisibleRect; + mBuilder->mDirtyRect = mPrevDirtyRect; + mBuilder->mCurrentAGR = mPrevAGR; + mBuilder->mAncestorHasApzAwareEventHandler = + mPrevAncestorHasApzAwareEventHandler; + mBuilder->mBuildingInvisibleItems = mPrevBuildingInvisibleItems; + mBuilder->mInInvalidSubtree = mPrevInInvalidSubtree; + mBuilder->mAdditionalOffset = mPrevAdditionalOffset; + } + + private: + nsDisplayListBuilder* mBuilder; + AGRState mCurrentAGRState; + const nsIFrame* mPrevFrame; + const nsIFrame* mPrevReferenceFrame; + nsRect mPrevHitTestArea; + CompositorHitTestInfo mPrevHitTestInfo; + nsPoint mPrevOffset; + mozilla::Maybe<nsPoint> mPrevAdditionalOffset; + nsRect mPrevVisibleRect; + nsRect mPrevDirtyRect; + RefPtr<AnimatedGeometryRoot> mPrevAGR; + bool mPrevAncestorHasApzAwareEventHandler; + bool mPrevBuildingInvisibleItems; + bool mPrevInInvalidSubtree; + }; + + /** + * A helper class to temporarily set the value of mInTransform. + */ + class AutoInTransformSetter { + public: + AutoInTransformSetter(nsDisplayListBuilder* aBuilder, bool aInTransform) + : mBuilder(aBuilder), mOldValue(aBuilder->mInTransform) { + aBuilder->mInTransform = aInTransform; + } + + ~AutoInTransformSetter() { mBuilder->mInTransform = mOldValue; } + + private: + nsDisplayListBuilder* mBuilder; + bool mOldValue; + }; + + class AutoInEventsAndPluginsOnly { + public: + AutoInEventsAndPluginsOnly(nsDisplayListBuilder* aBuilder, + bool aInEventsAndPluginsOnly) + : mBuilder(aBuilder), mOldValue(aBuilder->mInEventsAndPluginsOnly) { + aBuilder->mInEventsAndPluginsOnly |= aInEventsAndPluginsOnly; + } + + ~AutoInEventsAndPluginsOnly() { + mBuilder->mInEventsAndPluginsOnly = mOldValue; + } + + private: + nsDisplayListBuilder* mBuilder; + bool mOldValue; + }; + + /** + * A helper class to temporarily set the value of mFilterASR and + * mInFilter. + */ + class AutoEnterFilter { + public: + AutoEnterFilter(nsDisplayListBuilder* aBuilder, bool aUsingFilter) + : mBuilder(aBuilder), + mOldValue(aBuilder->mFilterASR), + mOldInFilter(aBuilder->mInFilter) { + if (!aBuilder->mFilterASR && aUsingFilter) { + aBuilder->mFilterASR = aBuilder->CurrentActiveScrolledRoot(); + aBuilder->mInFilter = true; + } + } + + ~AutoEnterFilter() { + mBuilder->mFilterASR = mOldValue; + mBuilder->mInFilter = mOldInFilter; + } + + private: + nsDisplayListBuilder* mBuilder; + const ActiveScrolledRoot* mOldValue; + bool mOldInFilter; + }; + + /** + * A helper class to temporarily set the value of mCurrentScrollParentId. + */ + class AutoCurrentScrollParentIdSetter { + public: + AutoCurrentScrollParentIdSetter(nsDisplayListBuilder* aBuilder, + ViewID aScrollId) + : mBuilder(aBuilder), + mOldValue(aBuilder->mCurrentScrollParentId), + mOldForceLayer(aBuilder->mForceLayerForScrollParent) { + // If this AutoCurrentScrollParentIdSetter has the same scrollId as the + // previous one on the stack, then that means the scrollframe that + // created this isn't actually scrollable and cannot participate in + // scroll handoff. We set mCanBeScrollParent to false to indicate this. + mCanBeScrollParent = (mOldValue != aScrollId); + aBuilder->mCurrentScrollParentId = aScrollId; + aBuilder->mForceLayerForScrollParent = false; + } + + bool ShouldForceLayerForScrollParent() const { + // Only scrollframes participating in scroll handoff can be forced to + // layerize + return mCanBeScrollParent && mBuilder->mForceLayerForScrollParent; + } + + ~AutoCurrentScrollParentIdSetter() { + mBuilder->mCurrentScrollParentId = mOldValue; + if (mCanBeScrollParent) { + // If this flag is set, caller code is responsible for having dealt + // with the current value of mBuilder->mForceLayerForScrollParent, so + // we can just restore the old value. + mBuilder->mForceLayerForScrollParent = mOldForceLayer; + } else { + // Otherwise we need to keep propagating the force-layerization flag + // upwards to the next ancestor scrollframe that does participate in + // scroll handoff. + mBuilder->mForceLayerForScrollParent |= mOldForceLayer; + } + } + + private: + nsDisplayListBuilder* mBuilder; + ViewID mOldValue; + bool mOldForceLayer; + bool mCanBeScrollParent; + }; + + /** + * Used to update the current active scrolled root on the display list + * builder, and to create new active scrolled roots. + */ + class AutoCurrentActiveScrolledRootSetter { + public: + explicit AutoCurrentActiveScrolledRootSetter(nsDisplayListBuilder* aBuilder) + : mBuilder(aBuilder), + mSavedActiveScrolledRoot(aBuilder->mCurrentActiveScrolledRoot), + mContentClipASR(aBuilder->ClipState().GetContentClipASR()), + mDescendantsStartIndex(aBuilder->mActiveScrolledRoots.Length()), + mUsed(false) {} + + ~AutoCurrentActiveScrolledRootSetter() { + mBuilder->mCurrentActiveScrolledRoot = mSavedActiveScrolledRoot; + } + + void SetCurrentActiveScrolledRoot( + const ActiveScrolledRoot* aActiveScrolledRoot); + + void EnterScrollFrame(nsIScrollableFrame* aScrollableFrame) { + MOZ_ASSERT(!mUsed); + ActiveScrolledRoot* asr = mBuilder->AllocateActiveScrolledRoot( + mBuilder->mCurrentActiveScrolledRoot, aScrollableFrame); + mBuilder->mCurrentActiveScrolledRoot = asr; + mUsed = true; + } + + void InsertScrollFrame(nsIScrollableFrame* aScrollableFrame); + + private: + nsDisplayListBuilder* mBuilder; + /** + * The builder's mCurrentActiveScrolledRoot at construction time which + * needs to be restored at destruction time. + */ + const ActiveScrolledRoot* mSavedActiveScrolledRoot; + /** + * If there's a content clip on the builder at construction time, then + * mContentClipASR is that content clip's ASR, otherwise null. The + * assumption is that the content clip doesn't get relaxed while this + * object is on the stack. + */ + const ActiveScrolledRoot* mContentClipASR; + /** + * InsertScrollFrame needs to mutate existing ASRs (those that were + * created while this object was on the stack), and mDescendantsStartIndex + * makes it easier to skip ASRs that were created in the past. + */ + size_t mDescendantsStartIndex; + /** + * Flag to make sure that only one of SetCurrentActiveScrolledRoot / + * EnterScrollFrame / InsertScrollFrame is called per instance of this + * class. + */ + bool mUsed; + }; + + /** + * Keeps track of the innermost ASR that can be used as the ASR for a + * container item that wraps all items that were created while this + * object was on the stack. + * The rule is: all child items of the container item need to have + * clipped bounds with respect to the container ASR. + */ + class AutoContainerASRTracker { + public: + explicit AutoContainerASRTracker(nsDisplayListBuilder* aBuilder) + : mBuilder(aBuilder), + mSavedContainerASR(aBuilder->mCurrentContainerASR) { + mBuilder->mCurrentContainerASR = ActiveScrolledRoot::PickDescendant( + mBuilder->ClipState().GetContentClipASR(), + mBuilder->mCurrentActiveScrolledRoot); + } + + const ActiveScrolledRoot* GetContainerASR() { + return mBuilder->mCurrentContainerASR; + } + + ~AutoContainerASRTracker() { + mBuilder->mCurrentContainerASR = ActiveScrolledRoot::PickAncestor( + mBuilder->mCurrentContainerASR, mSavedContainerASR); + } + + private: + nsDisplayListBuilder* mBuilder; + const ActiveScrolledRoot* mSavedContainerASR; + }; + + /** + * A helper class to temporarily set the value of mCurrentScrollbarTarget + * and mCurrentScrollbarFlags. + */ + class AutoCurrentScrollbarInfoSetter { + public: + AutoCurrentScrollbarInfoSetter( + nsDisplayListBuilder* aBuilder, ViewID aScrollTargetID, + const MaybeScrollDirection& aScrollbarDirection, bool aWillHaveLayer) + : mBuilder(aBuilder) { + aBuilder->mIsBuildingScrollbar = true; + aBuilder->mCurrentScrollbarTarget = aScrollTargetID; + aBuilder->mCurrentScrollbarDirection = aScrollbarDirection; + aBuilder->mCurrentScrollbarWillHaveLayer = aWillHaveLayer; + } + + ~AutoCurrentScrollbarInfoSetter() { + // No need to restore old values because scrollbars cannot be nested. + mBuilder->mIsBuildingScrollbar = false; + mBuilder->mCurrentScrollbarTarget = ScrollableLayerGuid::NULL_SCROLL_ID; + mBuilder->mCurrentScrollbarDirection.reset(); + mBuilder->mCurrentScrollbarWillHaveLayer = false; + } + + private: + nsDisplayListBuilder* mBuilder; + }; + + /** + * A helper class to temporarily set mBuildingExtraPagesForPageNum. + */ + class MOZ_RAII AutoPageNumberSetter { + public: + AutoPageNumberSetter(nsDisplayListBuilder* aBuilder, const uint8_t aPageNum) + : mBuilder(aBuilder), + mOldPageNum(aBuilder->GetBuildingExtraPagesForPageNum()) { + mBuilder->SetBuildingExtraPagesForPageNum(aPageNum); + } + ~AutoPageNumberSetter() { + mBuilder->SetBuildingExtraPagesForPageNum(mOldPageNum); + } + + private: + nsDisplayListBuilder* mBuilder; + uint8_t mOldPageNum; + }; + + /** + * A helper class to track current effective transform for items. + * + * For frames that is Combines3DTransformWithAncestors(), we need to + * apply all transforms of ancestors on the same preserves3D chain + * on the bounds of current frame to the coordination of the 3D + * context root. The 3D context root computes it's bounds from + * these transformed bounds. + */ + class AutoAccumulateTransform { + public: + typedef mozilla::gfx::Matrix4x4 Matrix4x4; + + explicit AutoAccumulateTransform(nsDisplayListBuilder* aBuilder) + : mBuilder(aBuilder), + mSavedTransform(aBuilder->mPreserves3DCtx.mAccumulatedTransform) {} + + ~AutoAccumulateTransform() { + mBuilder->mPreserves3DCtx.mAccumulatedTransform = mSavedTransform; + } + + void Accumulate(const Matrix4x4& aTransform) { + mBuilder->mPreserves3DCtx.mAccumulatedTransform = + aTransform * mBuilder->mPreserves3DCtx.mAccumulatedTransform; + } + + const Matrix4x4& GetCurrentTransform() { + return mBuilder->mPreserves3DCtx.mAccumulatedTransform; + } + + void StartRoot() { + mBuilder->mPreserves3DCtx.mAccumulatedTransform = Matrix4x4(); + } + + private: + nsDisplayListBuilder* mBuilder; + Matrix4x4 mSavedTransform; + }; + + /** + * A helper class to collect bounds rects of descendants. + * + * For a 3D context root, it's bounds is computed from the bounds of + * descendants. If we transform bounds frame by frame applying + * transforms, the bounds may turn to empty for any singular + * transform on the path, but it is not empty for the accumulated + * transform. + */ + class AutoAccumulateRect { + public: + explicit AutoAccumulateRect(nsDisplayListBuilder* aBuilder) + : mBuilder(aBuilder), + mSavedRect(aBuilder->mPreserves3DCtx.mAccumulatedRect) { + aBuilder->mPreserves3DCtx.mAccumulatedRect = nsRect(); + aBuilder->mPreserves3DCtx.mAccumulatedRectLevels++; + } + + ~AutoAccumulateRect() { + mBuilder->mPreserves3DCtx.mAccumulatedRect = mSavedRect; + mBuilder->mPreserves3DCtx.mAccumulatedRectLevels--; + } + + private: + nsDisplayListBuilder* mBuilder; + nsRect mSavedRect; + }; + + void AccumulateRect(const nsRect& aRect) { + mPreserves3DCtx.mAccumulatedRect.UnionRect(mPreserves3DCtx.mAccumulatedRect, + aRect); + } + + const nsRect& GetAccumulatedRect() { + return mPreserves3DCtx.mAccumulatedRect; + } + + /** + * The level is increased by one for items establishing 3D rendering + * context and starting a new accumulation. + */ + int GetAccumulatedRectLevels() { + return mPreserves3DCtx.mAccumulatedRectLevels; + } + + struct OutOfFlowDisplayData { + OutOfFlowDisplayData( + const DisplayItemClipChain* aContainingBlockClipChain, + const DisplayItemClipChain* aCombinedClipChain, + const ActiveScrolledRoot* aContainingBlockActiveScrolledRoot, + const nsRect& aVisibleRect, const nsRect& aDirtyRect) + : mContainingBlockClipChain(aContainingBlockClipChain), + mCombinedClipChain(aCombinedClipChain), + mContainingBlockActiveScrolledRoot( + aContainingBlockActiveScrolledRoot), + mVisibleRect(aVisibleRect), + mDirtyRect(aDirtyRect) {} + const DisplayItemClipChain* mContainingBlockClipChain; + const DisplayItemClipChain* + mCombinedClipChain; // only necessary for the special case of top layer + const ActiveScrolledRoot* mContainingBlockActiveScrolledRoot; + + // If this OutOfFlowDisplayData is associated with the ViewportFrame + // of a document that has a resolution (creating separate visual and + // layout viewports with their own coordinate spaces), these rects + // are in layout coordinates. Similarly, GetVisibleRectForFrame() in + // such a case returns a quantity in layout coordinates. + nsRect mVisibleRect; + nsRect mDirtyRect; + + static nsRect ComputeVisibleRectForFrame(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame, + const nsRect& aVisibleRect, + const nsRect& aDirtyRect, + nsRect* aOutDirtyRect); + + nsRect GetVisibleRectForFrame(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame, nsRect* aDirtyRect) { + return ComputeVisibleRectForFrame(aBuilder, aFrame, mVisibleRect, + mDirtyRect, aDirtyRect); + } + }; + + NS_DECLARE_FRAME_PROPERTY_DELETABLE(OutOfFlowDisplayDataProperty, + OutOfFlowDisplayData) + + struct DisplayListBuildingData { + RefPtr<AnimatedGeometryRoot> mModifiedAGR = nullptr; + nsRect mDirtyRect; + }; + NS_DECLARE_FRAME_PROPERTY_DELETABLE(DisplayListBuildingRect, + DisplayListBuildingData) + + NS_DECLARE_FRAME_PROPERTY_DELETABLE(DisplayListBuildingDisplayPortRect, + nsRect) + + static OutOfFlowDisplayData* GetOutOfFlowData(nsIFrame* aFrame) { + if (!aFrame->GetParent()) { + return nullptr; + } + return aFrame->GetParent()->GetProperty(OutOfFlowDisplayDataProperty()); + } + + nsPresContext* CurrentPresContext(); + + OutOfFlowDisplayData* GetCurrentFixedBackgroundDisplayData() { + auto& displayData = CurrentPresShellState()->mFixedBackgroundDisplayData; + return displayData ? displayData.ptr() : nullptr; + } + + /** + * Accumulates the bounds of box frames that have moz-appearance + * -moz-win-exclude-glass style. Used in setting glass margins on + * Windows. + * + * We set the window opaque region (from which glass margins are computed) + * to the intersection of the glass region specified here and the opaque + * region computed during painting. So the excluded glass region actually + * *limits* the extent of the opaque area reported to Windows. We limit it + * so that changes to the computed opaque region (which can vary based on + * region optimizations and the placement of UI elements) outside the + * -moz-win-exclude-glass area don't affect the glass margins reported to + * Windows; changing those margins willy-nilly can cause the Windows 7 glass + * haze effect to jump around disconcertingly. + */ + void AddWindowExcludeGlassRegion(nsIFrame* aFrame, const nsRect& aBounds) { + mWindowExcludeGlassRegion.Add(aFrame, aBounds); + } + + /** + * Returns the window exclude glass region. + */ + nsRegion GetWindowExcludeGlassRegion() const { + return mWindowExcludeGlassRegion.ToRegion(); + } + + /** + * Accumulates opaque stuff into the window opaque region. + */ + void AddWindowOpaqueRegion(nsIFrame* aFrame, const nsRect& aBounds) { + if (IsRetainingDisplayList()) { + mRetainedWindowOpaqueRegion.Add(aFrame, aBounds); + return; + } + mWindowOpaqueRegion.Or(mWindowOpaqueRegion, aBounds); + } + /** + * Returns the window opaque region built so far. This may be incomplete + * since the opaque region is built during layer construction. + */ + const nsRegion GetWindowOpaqueRegion() { + return IsRetainingDisplayList() ? mRetainedWindowOpaqueRegion.ToRegion() + : mWindowOpaqueRegion; + } + + void SetGlassDisplayItem(nsDisplayItem* aItem); + void ClearGlassDisplayItem() { mGlassDisplayItem = nullptr; } + nsDisplayItem* GetGlassDisplayItem() { return mGlassDisplayItem; } + + bool NeedToForceTransparentSurfaceForItem(nsDisplayItem* aItem); + + void SetContainsPluginItem() { mContainsPluginItem = true; } + bool ContainsPluginItem() { return mContainsPluginItem; } + + /** + * mContainsBlendMode is true if we processed a display item that + * has a blend mode attached. We do this so we can insert a + * nsDisplayBlendContainer in the parent stacking context. + */ + void SetContainsBlendMode(bool aContainsBlendMode) { + mContainsBlendMode = aContainsBlendMode; + } + bool ContainsBlendMode() const { return mContainsBlendMode; } + + /** + * mContainsBackdropFilter is true if we proccessed a display item that + * has a backdrop filter set. We track this so we can insert a + * nsDisplayBackdropRootContainer in the stacking context of the nearest + * ancestor that forms a backdrop root. + */ + void SetContainsBackdropFilter(bool aContainsBackdropFilter) { + mContainsBackdropFilter = aContainsBackdropFilter; + } + bool ContainsBackdropFilter() const { return mContainsBackdropFilter; } + + DisplayListClipState& ClipState() { return mClipState; } + const ActiveScrolledRoot* CurrentActiveScrolledRoot() { + return mCurrentActiveScrolledRoot; + } + const ActiveScrolledRoot* CurrentAncestorASRStackingContextContents() { + return mCurrentContainerASR; + } + + /** + * Add the current frame to the will-change budget if possible and + * remeber the outcome. Subsequent calls to IsInWillChangeBudget + * will return the same value as return here. + */ + bool AddToWillChangeBudget(nsIFrame* aFrame, const nsSize& aSize); + + /** + * This will add the current frame to the will-change budget the first + * time it is seen. On subsequent calls this will return the same + * answer. This effectively implements a first-come, first-served + * allocation of the will-change budget. + */ + bool IsInWillChangeBudget(nsIFrame* aFrame, const nsSize& aSize); + + /** + * Clears the will-change budget status for the given |aFrame|. + * This will also remove the frame from will-change budgets. + */ + void ClearWillChangeBudgetStatus(nsIFrame* aFrame); + + /** + * Removes the given |aFrame| from will-change budgets. + */ + void RemoveFromWillChangeBudgets(const nsIFrame* aFrame); + + /** + * Clears the will-change budgets. + */ + void ClearWillChangeBudgets(); + + void EnterSVGEffectsContents(nsIFrame* aEffectsFrame, + nsDisplayList* aHoistedItemsStorage); + void ExitSVGEffectsContents(); + + bool ShouldBuildScrollInfoItemsForHoisting() const; + + void AppendNewScrollInfoItemForHoisting( + nsDisplayScrollInfoLayer* aScrollInfoItem); + + /** + * A helper class to install/restore nsDisplayListBuilder::mPreserves3DCtx. + * + * mPreserves3DCtx is used by class AutoAccumulateTransform & + * AutoAccumulateRect to passing data between frames in the 3D + * context. If a frame create a new 3D context, it should restore + * the value of mPreserves3DCtx before returning back to the parent. + * This class do it for the users. + */ + class AutoPreserves3DContext { + public: + explicit AutoPreserves3DContext(nsDisplayListBuilder* aBuilder) + : mBuilder(aBuilder), mSavedCtx(aBuilder->mPreserves3DCtx) {} + + ~AutoPreserves3DContext() { mBuilder->mPreserves3DCtx = mSavedCtx; } + + private: + nsDisplayListBuilder* mBuilder; + Preserves3DContext mSavedCtx; + }; + + const nsRect GetPreserves3DRect() const { + return mPreserves3DCtx.mVisibleRect; + } + + void SavePreserves3DRect() { mPreserves3DCtx.mVisibleRect = mVisibleRect; } + + void SavePreserves3DAllowAsyncAnimation(bool aValue) { + mPreserves3DCtx.mAllowAsyncAnimation = aValue; + } + + bool GetPreserves3DAllowAsyncAnimation() const { + return mPreserves3DCtx.mAllowAsyncAnimation; + } + + bool IsBuildingInvisibleItems() const { return mBuildingInvisibleItems; } + + void SetBuildingInvisibleItems(bool aBuildingInvisibleItems) { + mBuildingInvisibleItems = aBuildingInvisibleItems; + } + + void SetBuildingExtraPagesForPageNum(uint8_t aPageNum) { + mBuildingExtraPagesForPageNum = aPageNum; + } + uint8_t GetBuildingExtraPagesForPageNum() const { + return mBuildingExtraPagesForPageNum; + } + + /** + * This is a convenience function to ease the transition until AGRs and ASRs + * are unified. + */ + AnimatedGeometryRoot* AnimatedGeometryRootForASR( + const ActiveScrolledRoot* aASR); + + bool HitTestIsForVisibility() const { return mVisibleThreshold.isSome(); } + + float VisibilityThreshold() const { + MOZ_DIAGNOSTIC_ASSERT(HitTestIsForVisibility()); + return mVisibleThreshold.valueOr(1.0f); + } + + void SetHitTestIsForVisibility(float aVisibleThreshold) { + mVisibleThreshold = mozilla::Some(aVisibleThreshold); + } + + bool ShouldBuildAsyncZoomContainer() const { + return mBuildAsyncZoomContainer; + } + void UpdateShouldBuildAsyncZoomContainer(); + + void UpdateShouldBuildBackdropRootContainer(); + + bool ShouldRebuildDisplayListDueToPrefChange(); + + /** + * Represents a region composed of frame/rect pairs. + * WeakFrames are used to track whether a rect still belongs to the region. + * Modified frames and rects are removed and re-added to the region if needed. + */ + struct WeakFrameRegion { + /** + * A wrapper to store WeakFrame and the pointer to the underlying frame. + * This is needed because WeakFrame does not store the frame pointer after + * the frame has been deleted. + */ + struct WeakFrameWrapper { + explicit WeakFrameWrapper(nsIFrame* aFrame) + : mWeakFrame(new WeakFrame(aFrame)), mFrame(aFrame) {} + + mozilla::UniquePtr<WeakFrame> mWeakFrame; + void* mFrame; + }; + + nsTHashtable<nsPtrHashKey<void>> mFrameSet; + nsTArray<WeakFrameWrapper> mFrames; + nsTArray<pixman_box32_t> mRects; + + template <typename RectType> + void Add(nsIFrame* aFrame, const RectType& aRect) { + if (mFrameSet.Contains(aFrame)) { + return; + } + + mFrameSet.PutEntry(aFrame); + mFrames.AppendElement(WeakFrameWrapper(aFrame)); + mRects.AppendElement(nsRegion::RectToBox(aRect)); + } + + void Clear() { + mFrameSet.Clear(); + mFrames.Clear(); + mRects.Clear(); + } + + void RemoveModifiedFramesAndRects(); + + size_t SizeOfExcludingThis(mozilla::MallocSizeOf) const; + + typedef mozilla::gfx::ArrayView<pixman_box32_t> BoxArrayView; + + nsRegion ToRegion() const { return nsRegion(BoxArrayView(mRects)); } + + LayoutDeviceIntRegion ToLayoutDeviceIntRegion() const { + return LayoutDeviceIntRegion(BoxArrayView(mRects)); + } + }; + + void AddScrollFrameToNotify(nsIScrollableFrame* aScrollFrame); + void NotifyAndClearScrollFrames(); + + private: + bool MarkOutOfFlowFrameForDisplay(nsIFrame* aDirtyFrame, nsIFrame* aFrame, + const nsRect& aVisibleRect, + const nsRect& aDirtyRect); + + /** + * Returns whether a frame acts as an animated geometry root, optionally + * returning the next ancestor to check. + */ + AGRState IsAnimatedGeometryRoot(nsIFrame* aFrame, bool& aIsAsync, + nsIFrame** aParent = nullptr); + + /** + * Returns the nearest ancestor frame to aFrame that is considered to have + * (or will have) animated geometry. This can return aFrame. + */ + nsIFrame* FindAnimatedGeometryRootFrameFor(nsIFrame* aFrame, bool& aIsAsync); + + friend class nsDisplayCanvasBackgroundImage; + friend class nsDisplayBackgroundImage; + friend class nsDisplayFixedPosition; + friend class nsDisplayPerspective; + AnimatedGeometryRoot* FindAnimatedGeometryRootFor(nsDisplayItem* aItem); + + friend class nsDisplayItem; + friend class nsDisplayOwnLayer; + friend struct RetainedDisplayListBuilder; + friend struct HitTestInfo; + AnimatedGeometryRoot* FindAnimatedGeometryRootFor(nsIFrame* aFrame); + + AnimatedGeometryRoot* WrapAGRForFrame( + nsIFrame* aAnimatedGeometryRoot, bool aIsAsync, + AnimatedGeometryRoot* aParent = nullptr); + + nsDataHashtable<nsPtrHashKey<nsIFrame>, RefPtr<AnimatedGeometryRoot>> + mFrameToAnimatedGeometryRootMap; + + /** + * Add the current frame to the AGR budget if possible and remember + * the outcome. Subsequent calls will return the same value as + * returned here. + */ + bool AddToAGRBudget(nsIFrame* aFrame); + + struct PresShellState { + mozilla::PresShell* mPresShell; +#ifdef DEBUG + mozilla::Maybe<nsAutoLayoutPhase> mAutoLayoutPhase; +#endif + mozilla::Maybe<OutOfFlowDisplayData> mFixedBackgroundDisplayData; + uint32_t mFirstFrameMarkedForDisplay; + uint32_t mFirstFrameWithOOFData; + bool mIsBackgroundOnly; + // This is a per-document flag turning off event handling for all content + // in the document, and is set when we enter a subdocument for a pointer- + // events:none frame. + bool mInsidePointerEventsNoneDoc; + bool mTouchEventPrefEnabledDoc; + nsIFrame* mPresShellIgnoreScrollFrame; + }; + + PresShellState* CurrentPresShellState() { + NS_ASSERTION(mPresShellStates.Length() > 0, + "Someone forgot to enter a presshell"); + return &mPresShellStates[mPresShellStates.Length() - 1]; + } + + void AddSizeOfExcludingThis(nsWindowSizes&) const; + + struct FrameWillChangeBudget { + FrameWillChangeBudget() : mPresContext(nullptr), mUsage(0) {} + + FrameWillChangeBudget(const nsPresContext* aPresContext, uint32_t aUsage) + : mPresContext(aPresContext), mUsage(aUsage) {} + + const nsPresContext* mPresContext; + uint32_t mUsage; + }; + + nsIFrame* const mReferenceFrame; + nsIFrame* mIgnoreScrollFrame; + + using Arena = nsPresArena<32768, mozilla::DisplayListArenaObjectId, + size_t(mozilla::DisplayListArenaObjectId::COUNT)>; + Arena mPool; + + AutoTArray<PresShellState, 8> mPresShellStates; + AutoTArray<nsIFrame*, 400> mFramesMarkedForDisplay; + AutoTArray<nsIFrame*, 40> mFramesMarkedForDisplayIfVisible; + AutoTArray<nsIFrame*, 20> mFramesWithOOFData; + nsClassHashtable<nsPtrHashKey<nsDisplayItem>, nsTArray<ThemeGeometry>> + mThemeGeometries; + DisplayListClipState mClipState; + const ActiveScrolledRoot* mCurrentActiveScrolledRoot; + const ActiveScrolledRoot* mCurrentContainerASR; + // mCurrentFrame is the frame that we're currently calling (or about to call) + // BuildDisplayList on. + const nsIFrame* mCurrentFrame; + // The reference frame for mCurrentFrame. + const nsIFrame* mCurrentReferenceFrame; + // The offset from mCurrentFrame to mCurrentReferenceFrame. + nsPoint mCurrentOffsetToReferenceFrame; + + const nsIFrame* mAdditionalOffsetFrame; + mozilla::Maybe<nsPoint> mAdditionalOffset; + + RefPtr<AnimatedGeometryRoot> mRootAGR; + RefPtr<AnimatedGeometryRoot> mCurrentAGR; + + // will-change budget tracker + typedef uint32_t DocumentWillChangeBudget; + nsDataHashtable<nsPtrHashKey<const nsPresContext>, DocumentWillChangeBudget> + mDocumentWillChangeBudgets; + + // Any frame listed in this set is already counted in the budget + // and thus is in-budget. + nsDataHashtable<nsPtrHashKey<const nsIFrame>, FrameWillChangeBudget> + mFrameWillChangeBudgets; + + uint8_t mBuildingExtraPagesForPageNum; + + // Area of animated geometry root budget already allocated + uint32_t mUsedAGRBudget; + // Set of frames already counted in budget + nsTHashtable<nsPtrHashKey<nsIFrame>> mAGRBudgetSet; + + nsDataHashtable<nsPtrHashKey<RemoteBrowser>, EffectsInfo> mEffectsUpdates; + + // Relative to mCurrentFrame. + nsRect mVisibleRect; + nsRect mDirtyRect; + + // Tracked regions used for retained display list. + WeakFrameRegion mWindowExcludeGlassRegion; + WeakFrameRegion mRetainedWindowDraggingRegion; + WeakFrameRegion mRetainedWindowNoDraggingRegion; + + // Window opaque region is calculated during layer building. + WeakFrameRegion mRetainedWindowOpaqueRegion; + + // Optimized versions for non-retained display list. + LayoutDeviceIntRegion mWindowDraggingRegion; + LayoutDeviceIntRegion mWindowNoDraggingRegion; + nsRegion mWindowOpaqueRegion; + + // The display item for the Windows window glass background, if any + // Set during full display list builds or during display list merging only, + // partial display list builds don't touch this. + nsDisplayItem* mGlassDisplayItem; + // If we've encountered a glass item yet, only used during partial display + // list builds. + bool mHasGlassItemDuringPartial; + + nsIFrame* mCaretFrame; + nsRect mCaretRect; + + // A temporary list that we append scroll info items to while building + // display items for the contents of frames with SVG effects. + // Only non-null when ShouldBuildScrollInfoItemsForHoisting() is true. + // This is a pointer and not a real nsDisplayList value because the + // nsDisplayList class is defined below this class, so we can't use it here. + nsDisplayList* mScrollInfoItemsForHoisting; + nsTArray<RefPtr<ActiveScrolledRoot>> mActiveScrolledRoots; + std::unordered_set<const DisplayItemClipChain*, DisplayItemClipChainHasher, + DisplayItemClipChainEqualer> + mClipDeduplicator; + DisplayItemClipChain* mFirstClipChainToDestroy; + nsTArray<nsDisplayItem*> mTemporaryItems; + nsDisplayListBuilderMode mMode; + nsDisplayTableBackgroundSet* mTableBackgroundSet; + ViewID mCurrentScrollParentId; + ViewID mCurrentScrollbarTarget; + MaybeScrollDirection mCurrentScrollbarDirection; + Preserves3DContext mPreserves3DCtx; + nsTArray<nsIFrame*> mSVGEffectsFrames; + // When we are inside a filter, the current ASR at the time we entered the + // filter. Otherwise nullptr. + const ActiveScrolledRoot* mFilterASR; + std::unordered_set<nsIScrollableFrame*> mScrollFramesToNotify; + bool mContainsBlendMode; + bool mIsBuildingScrollbar; + bool mCurrentScrollbarWillHaveLayer; + bool mBuildCaret; + bool mRetainingDisplayList; + bool mPartialUpdate; + bool mIgnoreSuppression; + bool mIncludeAllOutOfFlows; + bool mDescendIntoSubdocuments; + bool mSelectedFramesOnly; + bool mAllowMergingAndFlattening; + bool mWillComputePluginGeometry; + // True when we're building a display list that's directly or indirectly + // under an nsDisplayTransform + bool mInTransform; + bool mInEventsAndPluginsOnly; + bool mInFilter; + bool mInPageSequence; + bool mIsInChromePresContext; + bool mSyncDecodeImages; + bool mIsPaintingToWindow; + bool mUseHighQualityScaling; + bool mIsPaintingForWebRender; + bool mIsCompositingCheap; + bool mContainsPluginItem; + bool mAncestorHasApzAwareEventHandler; + // True when the first async-scrollable scroll frame for which we build a + // display list has a display port. An async-scrollable scroll frame is one + // which WantsAsyncScroll(). + bool mHaveScrollableDisplayPort; + bool mWindowDraggingAllowed; + bool mIsBuildingForPopup; + bool mForceLayerForScrollParent; + bool mAsyncPanZoomEnabled; + bool mBuildingInvisibleItems; + bool mIsBuilding; + bool mInInvalidSubtree; + bool mBuildCompositorHitTestInfo; + bool mDisablePartialUpdates; + bool mPartialBuildFailed; + bool mIsInActiveDocShell; + bool mBuildAsyncZoomContainer; + bool mContainsBackdropFilter; + bool mIsRelativeToLayoutViewport; + bool mUseOverlayScrollbars; + + mozilla::Maybe<float> mVisibleThreshold; + nsRect mHitTestArea; + CompositorHitTestInfo mHitTestInfo; +}; + +class nsDisplayItem; +class nsDisplayItemBase; +class nsPaintedDisplayItem; +class nsDisplayList; +class RetainedDisplayList; + +// All types are defined in nsDisplayItemTypes.h +#define NS_DISPLAY_DECL_NAME(n, e) \ + const char* Name() const override { return n; } \ + constexpr static DisplayItemType ItemType() { return DisplayItemType::e; } \ + \ + private: \ + void* operator new(size_t aSize, nsDisplayListBuilder* aBuilder) { \ + return aBuilder->Allocate(aSize, DisplayItemType::e); \ + } \ + \ + template <typename T, typename F, typename... Args> \ + friend T* ::MakeDisplayItemWithIndex(nsDisplayListBuilder* aBuilder, \ + F* aFrame, const uint16_t aIndex, \ + Args&&... aArgs); \ + \ + public: + +#define NS_DISPLAY_ALLOW_CLONING() \ + template <typename T> \ + friend T* MakeClone(nsDisplayListBuilder* aBuilder, const T* aItem); \ + \ + nsDisplayWrapList* Clone(nsDisplayListBuilder* aBuilder) const override { \ + return MakeClone(aBuilder, this); \ + } + +template <typename T> +MOZ_ALWAYS_INLINE T* MakeClone(nsDisplayListBuilder* aBuilder, const T* aItem) { + static_assert(std::is_base_of<nsDisplayWrapList, T>::value, + "Display item type should be derived from nsDisplayWrapList"); + T* item = new (aBuilder) T(aBuilder, *aItem); + item->SetType(T::ItemType()); + return item; +} + +#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED +void AssertUniqueItem(nsDisplayItem* aItem); +#endif + +/** + * Returns true, if a display item of given |aType| needs to be built within + * opacity:0 container. + */ +bool ShouldBuildItemForEventsOrPlugins(const DisplayItemType aType); + +void UpdateDisplayItemData(nsPaintedDisplayItem* aItem); + +template <typename T, typename F, typename... Args> +MOZ_ALWAYS_INLINE T* MakeDisplayItemWithIndex(nsDisplayListBuilder* aBuilder, + F* aFrame, const uint16_t aIndex, + Args&&... aArgs) { + static_assert(std::is_base_of<nsDisplayItem, T>::value, + "Display item type should be derived from nsDisplayItem"); + static_assert(std::is_base_of<nsIFrame, F>::value, + "Frame type should be derived from nsIFrame"); + + const DisplayItemType type = T::ItemType(); + if (aBuilder->InEventsAndPluginsOnly() && + !ShouldBuildItemForEventsOrPlugins(type)) { + // This item is not needed for events or plugins. + return nullptr; + } + + T* item = new (aBuilder) T(aBuilder, aFrame, std::forward<Args>(aArgs)...); + + if (type != DisplayItemType::TYPE_GENERIC) { + item->SetType(type); + } + + item->SetPerFrameIndex(aIndex); + item->SetExtraPageForPageNum(aBuilder->GetBuildingExtraPagesForPageNum()); + + nsPaintedDisplayItem* paintedItem = item->AsPaintedDisplayItem(); + if (paintedItem) { + UpdateDisplayItemData(paintedItem); + } + + if (aBuilder->InInvalidSubtree() || + item->FrameForInvalidation()->IsFrameModified()) { + item->SetModifiedFrame(true); + } + +#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + if (aBuilder->IsRetainingDisplayList() && aBuilder->IsBuilding()) { + AssertUniqueItem(item); + } + + // Verify that InInvalidSubtree matches invalidation frame's modified state. + if (aBuilder->InInvalidSubtree()) { + MOZ_DIAGNOSTIC_ASSERT( + AnyContentAncestorModified(item->FrameForInvalidation())); + } + + mozilla::DebugOnly<bool> isContainerType = + (GetDisplayItemFlagsForType(type) & TYPE_IS_CONTAINER); + + MOZ_ASSERT(item->HasChildren() == isContainerType, + "Container items must have container display item flag set."); +#endif + + return item; +} + +template <typename T, typename F, typename... Args> +MOZ_ALWAYS_INLINE T* MakeDisplayItem(nsDisplayListBuilder* aBuilder, F* aFrame, + Args&&... aArgs) { + return MakeDisplayItemWithIndex<T>(aBuilder, aFrame, 0, + std::forward<Args>(aArgs)...); +} + +/** + * nsDisplayItems are put in singly-linked lists rooted in an nsDisplayList. + * nsDisplayItemLink holds the link. The lists are linked from lowest to + * highest in z-order. + */ +class nsDisplayItemLink { + // This is never instantiated directly, so no need to count constructors and + // destructors. + protected: + nsDisplayItemLink() : mAbove(nullptr) {} + nsDisplayItemLink(const nsDisplayItemLink&) : mAbove(nullptr) {} + ~nsDisplayItemLink() { MOZ_RELEASE_ASSERT(!mAbove); } + nsDisplayItem* mAbove; + + friend class nsDisplayList; +}; + +class nsPaintedDisplayItem; + +/* + * nsDisplayItemBase is a base-class for all display items. It is mainly + * responsible for handling the frame-display item 1:n relationship, as well as + * storing the state needed for display list merging. + * + * Display items are arena-allocated during display list construction. + * + * Display items can be containers --- i.e., they can perform hit testing + * and painting by recursively traversing a list of child items. + * + * Display items belong to a list at all times (except temporarily as they + * move from one list to another). + */ +class nsDisplayItemBase : public nsDisplayItemLink { + public: + nsDisplayItemBase() = delete; + + /** + * Downcasts this item to nsPaintedDisplayItem, if possible. + */ + virtual nsPaintedDisplayItem* AsPaintedDisplayItem() { return nullptr; } + virtual const nsPaintedDisplayItem* AsPaintedDisplayItem() const { + return nullptr; + } + + /** + * Downcasts this item to nsDisplayWrapList, if possible. + */ + virtual nsDisplayWrapList* AsDisplayWrapList() { return nullptr; } + virtual const nsDisplayWrapList* AsDisplayWrapList() const { return nullptr; } + + /** + * Create a clone of this item. + */ + virtual nsDisplayItem* Clone(nsDisplayListBuilder* aBuilder) const { + return nullptr; + } + + /** + * Frees the memory allocated for this display item. + * The given display list builder must have allocated this display item. + */ + virtual void Destroy(nsDisplayListBuilder* aBuilder) { + const DisplayItemType type = GetType(); + this->~nsDisplayItemBase(); + aBuilder->Destroy(type, this); + } + + /** + * Returns the frame that this display item was created for. + * Never returns null. + */ + inline nsIFrame* Frame() const { + MOZ_ASSERT(mFrame, "Trying to use display item after deletion!"); + return mFrame; + } + + /** + * Called when the display item is prepared for deletion. The display item + * should not be used after calling this function. + */ + virtual void RemoveFrame(nsIFrame* aFrame) { + MOZ_ASSERT(aFrame); + + if (mFrame && aFrame == mFrame) { + MOZ_ASSERT(!mFrame->HasDisplayItem(this)); + mFrame = nullptr; + SetDeletedFrame(); + } + } + + /** + * A display item can depend on multiple different frames for invalidation. + */ + virtual nsIFrame* GetDependentFrame() { return nullptr; } + + /** + * Returns the frame that provides the style data, and should + * be checked when deciding if this display item can be reused. + */ + virtual nsIFrame* FrameForInvalidation() const { return Frame(); } + + /** + * Returns the printable name of this display item. + */ + virtual const char* Name() const = 0; + + /** + * Some consecutive items should be rendered together as a unit, e.g., + * outlines for the same element. For this, we need a way for items to + * identify their type. We use the type for other purposes too. + */ + DisplayItemType GetType() const { + MOZ_ASSERT(mType != DisplayItemType::TYPE_ZERO, + "Display item should have a valid type!"); + return mType; + } + + /** + * Pairing this with the Frame() pointer gives a key that + * uniquely identifies this display item in the display item tree. + */ + uint32_t GetPerFrameKey() const { + // The top 8 bits are the page index + // The middle 16 bits of the per frame key uniquely identify the display + // item when there are more than one item of the same type for a frame. + // The low 8 bits are the display item type. + return (static_cast<uint32_t>(mExtraPageForPageNum) + << (TYPE_BITS + (sizeof(mPerFrameIndex) * 8))) | + (static_cast<uint32_t>(mPerFrameIndex) << TYPE_BITS) | + static_cast<uint32_t>(mType); + } + + /** + * Returns true if this item was reused during display list merging. + */ + bool IsReused() const { + return mItemFlags.contains(ItemBaseFlag::ReusedItem); + } + + void SetReused(bool aReused) { + if (aReused) { + mItemFlags += ItemBaseFlag::ReusedItem; + } else { + mItemFlags -= ItemBaseFlag::ReusedItem; + } + } + + /** + * Returns true if this item can be reused during display list merging. + */ + bool CanBeReused() const { + return !mItemFlags.contains(ItemBaseFlag::CantBeReused); + } + + void SetCantBeReused() { mItemFlags += ItemBaseFlag::CantBeReused; } + + bool CanBeCached() const { + return !mItemFlags.contains(ItemBaseFlag::CantBeCached); + } + + void SetCantBeCached() { mItemFlags += ItemBaseFlag::CantBeCached; } + + bool IsOldItem() const { return !!mOldList; } + + /** + * Returns true if the frame of this display item is in a modified subtree. + */ + bool HasModifiedFrame() const; + void SetModifiedFrame(bool aModified); + bool HasDeletedFrame() const; + + /** + * Set the nsDisplayList that this item belongs to, and what index it is + * within that list. + * Temporary state for merging used by RetainedDisplayListBuilder. + */ + void SetOldListIndex(nsDisplayList* aList, OldListIndex aIndex, + uint32_t aListKey, uint32_t aNestingDepth) { +#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + mOldListKey = aListKey; + mOldNestingDepth = aNestingDepth; +#endif + mOldList = reinterpret_cast<uintptr_t>(aList); + mOldListIndex = aIndex; + } + + bool GetOldListIndex(nsDisplayList* aList, uint32_t aListKey, + OldListIndex* aOutIndex) { + if (mOldList != reinterpret_cast<uintptr_t>(aList)) { +#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + MOZ_CRASH_UNSAFE_PRINTF( + "Item found was in the wrong list! type %d " + "(outer type was %d at depth %d, now is %d)", + GetPerFrameKey(), mOldListKey, mOldNestingDepth, aListKey); +#endif + return false; + } + *aOutIndex = mOldListIndex; + return true; + } + + /** + * Returns the display list containing the children of this display item. + * The children may be in a different coordinate system than this item. + */ + virtual RetainedDisplayList* GetChildren() const { return nullptr; } + bool HasChildren() const { return GetChildren(); } + + /** + * Display items with children may return true here. This causes the + * display list iterator to descend into the child display list. + */ + virtual bool ShouldFlattenAway(nsDisplayListBuilder* aBuilder) { + return false; + } + + protected: + nsDisplayItemBase(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame) + : mFrame(aFrame), mType(DisplayItemType::TYPE_ZERO) { + MOZ_COUNT_CTOR(nsDisplayItemBase); + MOZ_ASSERT(mFrame); + + if (aBuilder->IsRetainingDisplayList()) { + mFrame->AddDisplayItem(this); + } + } + + nsDisplayItemBase(nsDisplayListBuilder* aBuilder, + const nsDisplayItemBase& aOther) + : mFrame(aOther.mFrame), + mItemFlags(aOther.mItemFlags), + mType(aOther.mType), + mExtraPageForPageNum(aOther.mExtraPageForPageNum), + mPerFrameIndex(aOther.mPerFrameIndex) { + MOZ_COUNT_CTOR(nsDisplayItemBase); + } + + virtual ~nsDisplayItemBase() { + MOZ_COUNT_DTOR(nsDisplayItemBase); + if (mFrame) { + mFrame->RemoveDisplayItem(this); + } + } + + void SetType(const DisplayItemType aType) { mType = aType; } + + void SetPerFrameIndex(const uint16_t aIndex) { mPerFrameIndex = aIndex; } + + // Display list building for printing can build duplicate + // container display items when they contain a mixture of + // OOF and normal content that is spread across multiple + // pages. We include the page number for the duplicates + // to make our GetPerFrameKey unique. + void SetExtraPageForPageNum(const uint8_t aPageNum) { + mExtraPageForPageNum = aPageNum; + } + + void SetDeletedFrame(); + + nsIFrame* mFrame; // 8 + + private: + enum class ItemBaseFlag : uint8_t { + CantBeReused, + CantBeCached, + DeletedFrame, + ModifiedFrame, + ReusedItem, +#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + MergedItem, + PreProcessedItem, +#endif + }; + + mozilla::EnumSet<ItemBaseFlag, uint8_t> mItemFlags; // 1 + DisplayItemType mType; // 1 + uint8_t mExtraPageForPageNum = 0; // 1 + uint16_t mPerFrameIndex; // 2 + OldListIndex mOldListIndex; // 4 + uintptr_t mOldList = 0; // 8 + +#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + public: + bool IsMergedItem() const { + return mItemFlags.contains(ItemBaseFlag::MergedItem); + } + + bool IsPreProcessedItem() const { + return mItemFlags.contains(ItemBaseFlag::PreProcessedItem); + } + + void SetMergedPreProcessed(bool aMerged, bool aPreProcessed) { + if (aMerged) { + mItemFlags += ItemBaseFlag::MergedItem; + } else { + mItemFlags -= ItemBaseFlag::MergedItem; + } + + if (aPreProcessed) { + mItemFlags += ItemBaseFlag::PreProcessedItem; + } else { + mItemFlags -= ItemBaseFlag::PreProcessedItem; + } + } + + uint32_t mOldListKey = 0; + uint32_t mOldNestingDepth = 0; +#endif +}; + +/** + * This is the unit of rendering and event testing. Each instance of this + * class represents an entity that can be drawn on the screen, e.g., a + * frame's CSS background, or a frame's text string. + */ +class nsDisplayItem : public nsDisplayItemBase { + public: + typedef mozilla::ContainerLayerParameters ContainerLayerParameters; + typedef mozilla::DisplayItemClip DisplayItemClip; + typedef mozilla::DisplayItemClipChain DisplayItemClipChain; + typedef mozilla::ActiveScrolledRoot ActiveScrolledRoot; + typedef mozilla::layers::FrameMetrics FrameMetrics; + typedef mozilla::layers::ScrollMetadata ScrollMetadata; + typedef mozilla::layers::ScrollableLayerGuid::ViewID ViewID; + typedef mozilla::layers::Layer Layer; + typedef mozilla::layers::LayerManager LayerManager; + typedef mozilla::layers::StackingContextHelper StackingContextHelper; + typedef mozilla::layers::WebRenderCommand WebRenderCommand; + typedef mozilla::layers::WebRenderParentCommand WebRenderParentCommand; + typedef mozilla::LayerState LayerState; + typedef mozilla::image::imgDrawingParams imgDrawingParams; + typedef mozilla::image::ImgDrawResult ImgDrawResult; + typedef class mozilla::gfx::DrawTarget DrawTarget; + typedef mozilla::gfx::CompositorHitTestInfo CompositorHitTestInfo; + + protected: + // This is never instantiated directly (it has pure virtual methods), so no + // need to count constructors and destructors. + nsDisplayItem(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame); + nsDisplayItem(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + const ActiveScrolledRoot* aActiveScrolledRoot); + + MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayItem) + + /** + * The custom copy-constructor is implemented to prevent copying the saved + * state of the item. + * This is currently only used when creating temporary items for merging. + */ + nsDisplayItem(nsDisplayListBuilder* aBuilder, const nsDisplayItem& aOther) + : nsDisplayItemBase(aBuilder, aOther), + mClipChain(aOther.mClipChain), + mClip(aOther.mClip), + mActiveScrolledRoot(aOther.mActiveScrolledRoot), + mReferenceFrame(aOther.mReferenceFrame), + mAnimatedGeometryRoot(aOther.mAnimatedGeometryRoot), + mToReferenceFrame(aOther.mToReferenceFrame), + mBuildingRect(aOther.mBuildingRect), + mPaintRect(aOther.mPaintRect) { + MOZ_COUNT_CTOR(nsDisplayItem); + // TODO: It might be better to remove the flags that aren't copied. + if (aOther.ForceNotVisible()) { + mItemFlags += ItemFlag::ForceNotVisible; + } + if (aOther.IsSubpixelAADisabled()) { + mItemFlags += ItemFlag::DisableSubpixelAA; + } + if (mFrame->In3DContextAndBackfaceIsHidden()) { + mItemFlags += ItemFlag::BackfaceHidden; + } + if (aOther.Combines3DTransformWithAncestors()) { + mItemFlags += ItemFlag::Combines3DTransformWithAncestors; + } + } + + public: + nsDisplayItem() = delete; + nsDisplayItem(const nsDisplayItem&) = delete; + + /** + * Roll back side effects carried out by processing the display list. + * + * @return true if the rollback actually modified anything, to help the caller + * decide whether to invalidate cached information about this node. + */ + virtual bool RestoreState() { + if (mClipChain == mState.mClipChain && mClip == mState.mClip && + !mItemFlags.contains(ItemFlag::DisableSubpixelAA)) { + return false; + } + + mClipChain = mState.mClipChain; + mClip = mState.mClip; + mItemFlags -= ItemFlag::DisableSubpixelAA; + return true; + } + + /** + * Invalidate cached information that depends on this node's contents, after + * a mutation of those contents. + * + * Specifically, if you mutate an |nsDisplayItem| in a way that would change + * the WebRender display list items generated for it, you should call this + * method. + * + * If a |RestoreState| method exists to restore some piece of state, that's a + * good indication that modifications to said state should be accompanied by a + * call to this method. Opacity flattening's effects on + * |nsDisplayBackgroundColor| items are one example. + */ + virtual void InvalidateItemCacheEntry() {} + + struct HitTestState { + explicit HitTestState() = default; + + ~HitTestState() { + NS_ASSERTION(mItemBuffer.Length() == 0, + "mItemBuffer should have been cleared"); + } + + // Handling transform items for preserve 3D frames. + bool mInPreserves3D = false; + // When hit-testing for visibility, we may hit an fully opaque item in a + // nested display list. We want to stop at that point, without looking + // further on other items. + bool mHitOccludingItem = false; + + float mCurrentOpacity = 1.0f; + + AutoTArray<nsDisplayItem*, 100> mItemBuffer; + }; + + uint8_t GetFlags() const { return GetDisplayItemFlagsForType(GetType()); } + + virtual bool IsContentful() const { return GetFlags() & TYPE_IS_CONTENTFUL; } + + /** + * This is called after we've constructed a display list for event handling. + * When this is called, we've already ensured that aRect intersects the + * item's bounds and that clipping has been taking into account. + * + * @param aRect the point or rect being tested, relative to the reference + * frame. If the width and height are both 1 app unit, it indicates we're + * hit testing a point, not a rect. + * @param aState must point to a HitTestState. If you don't have one, + * just create one with the default constructor and pass it in. + * @param aOutFrames each item appends the frame(s) in this display item that + * the rect is considered over (if any) to aOutFrames. + */ + virtual void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect, + HitTestState* aState, nsTArray<nsIFrame*>* aOutFrames) {} + + virtual nsIFrame* StyleFrame() const { return mFrame; } + + /** + * Compute the used z-index of our frame; returns zero for elements to which + * z-index does not apply, and for z-index:auto. + * @note This can be overridden, @see nsDisplayWrapList::SetOverrideZIndex. + */ + virtual int32_t ZIndex() const; + /** + * The default bounds is the frame border rect. + * @param aSnap *aSnap is set to true if the returned rect will be + * snapped to nearest device pixel edges during actual drawing. + * It might be set to false and snap anyway, so code computing the set of + * pixels affected by this display item needs to round outwards to pixel + * boundaries when *aSnap is set to false. + * This does not take the item's clipping into account. + * @return a rectangle relative to aBuilder->ReferenceFrame() that + * contains the area drawn by this display item + */ + virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) const { + *aSnap = false; + return nsRect(ToReferenceFrame(), Frame()->GetSize()); + } + + /** + * Returns the untransformed bounds of this display item. + */ + virtual nsRect GetUntransformedBounds(nsDisplayListBuilder* aBuilder, + bool* aSnap) const { + return GetBounds(aBuilder, aSnap); + } + + virtual nsRegion GetTightBounds(nsDisplayListBuilder* aBuilder, + bool* aSnap) const { + *aSnap = false; + return nsRegion(); + } + + /** + * Returns true if nothing will be rendered inside aRect, false if uncertain. + * aRect is assumed to be contained in this item's bounds. + */ + virtual bool IsInvisibleInRect(const nsRect& aRect) const { return false; } + + /** + * Returns the result of GetBounds intersected with the item's clip. + * The intersection is approximate since rounded corners are not taking into + * account. + */ + nsRect GetClippedBounds(nsDisplayListBuilder* aBuilder) const; + + nsRect GetBorderRect() const { + return nsRect(ToReferenceFrame(), Frame()->GetSize()); + } + + nsRect GetPaddingRect() const { + return Frame()->GetPaddingRectRelativeToSelf() + ToReferenceFrame(); + } + + nsRect GetContentRect() const { + return Frame()->GetContentRectRelativeToSelf() + ToReferenceFrame(); + } + + /** + * Checks if the frame(s) owning this display item have been marked as + * invalid, and needing repainting. + */ + virtual bool IsInvalid(nsRect& aRect) const { + bool result = mFrame ? mFrame->IsInvalid(aRect) : false; + aRect += ToReferenceFrame(); + return result; + } + + /** + * Creates and initializes an nsDisplayItemGeometry object that retains the + * current areas covered by this display item. These need to retain enough + * information such that they can be compared against a future nsDisplayItem + * of the same type, and determine if repainting needs to happen. + * + * Subclasses wishing to store more information need to override both this + * and ComputeInvalidationRegion, as well as implementing an + * nsDisplayItemGeometry subclass. + * + * The default implementation tracks both the display item bounds, and the + * frame's border rect. + */ + virtual nsDisplayItemGeometry* AllocateGeometry( + nsDisplayListBuilder* aBuilder) { + return new nsDisplayItemGenericGeometry(this, aBuilder); + } + + /** + * Compares an nsDisplayItemGeometry object from a previous paint against the + * current item. Computes if the geometry of the item has changed, and the + * invalidation area required for correct repainting. + * + * The existing geometry will have been created from a display item with a + * matching GetPerFrameKey()/mFrame pair to the current item. + * + * The default implementation compares the display item bounds, and the + * frame's border rect, and invalidates the entire bounds if either rect + * changes. + * + * @param aGeometry The geometry of the matching display item from the + * previous paint. + * @param aInvalidRegion Output param, the region to invalidate, or + * unchanged if none. + */ + virtual void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder, + const nsDisplayItemGeometry* aGeometry, + nsRegion* aInvalidRegion) const { + const nsDisplayItemGenericGeometry* geometry = + static_cast<const nsDisplayItemGenericGeometry*>(aGeometry); + bool snap; + if (!geometry->mBounds.IsEqualInterior(GetBounds(aBuilder, &snap)) || + !geometry->mBorderRect.IsEqualInterior(GetBorderRect())) { + aInvalidRegion->Or(GetBounds(aBuilder, &snap), geometry->mBounds); + } + } + + /** + * An alternative default implementation of ComputeInvalidationRegion, + * that instead invalidates only the changed area between the two items. + */ + void ComputeInvalidationRegionDifference( + nsDisplayListBuilder* aBuilder, + const nsDisplayItemBoundsGeometry* aGeometry, + nsRegion* aInvalidRegion) const { + bool snap; + nsRect bounds = GetBounds(aBuilder, &snap); + + if (!aGeometry->mBounds.IsEqualInterior(bounds)) { + nscoord radii[8]; + if (aGeometry->mHasRoundedCorners || Frame()->GetBorderRadii(radii)) { + aInvalidRegion->Or(aGeometry->mBounds, bounds); + } else { + aInvalidRegion->Xor(aGeometry->mBounds, bounds); + } + } + } + + /** + * This function is called when an item's list of children has been modified + * by RetainedDisplayListBuilder. + */ + virtual void InvalidateCachedChildInfo(nsDisplayListBuilder* aBuilder) {} + + virtual void AddSizeOfExcludingThis(nsWindowSizes&) const {} + + /** + * @param aSnap set to true if the edges of the rectangles of the opaque + * region would be snapped to device pixels when drawing + * @return a region of the item that is opaque --- that is, every pixel + * that is visible is painted with an opaque + * color. This is useful for determining when one piece + * of content completely obscures another so that we can do occlusion + * culling. + * This does not take clipping into account. + * This must return a simple region (1 rect) for painting display lists. + * It is only allowed to be a complex region for hit testing. + */ + virtual nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder, + bool* aSnap) const { + *aSnap = false; + return nsRegion(); + } + /** + * @return Some(nscolor) if the item is guaranteed to paint every pixel in its + * bounds with the same (possibly translucent) color + */ + virtual mozilla::Maybe<nscolor> IsUniform( + nsDisplayListBuilder* aBuilder) const { + return mozilla::Nothing(); + } + + /** + * @return true if the contents of this item are rendered fixed relative + * to the nearest viewport. + */ + virtual bool ShouldFixToViewport(nsDisplayListBuilder* aBuilder) const { + return false; + } + + /** + * Returns true if all layers that can be active should be forced to be + * active. Requires setting the pref layers.force-active=true. + */ + static bool ForceActiveLayers(); + + /** + * @return LAYER_NONE if BuildLayer will return null. In this case + * there is no layer for the item, and Paint should be called instead + * to paint the content using Thebes. + * Return LAYER_INACTIVE if there is a layer --- BuildLayer will + * not return null (unless there's an error) --- but the layer contents + * are not changing frequently. In this case it makes sense to composite + * the layer into a PaintedLayer with other content, so we don't have to + * recomposite it every time we paint. + * Note: GetLayerState is only allowed to return LAYER_INACTIVE if all + * descendant display items returned LAYER_INACTIVE or LAYER_NONE. Also, + * all descendant display item frames must have an active scrolled root + * that's either the same as this item's frame's active scrolled root, or + * a descendant of this item's frame. This ensures that the entire + * set of display items can be collapsed onto a single PaintedLayer. + * Return LAYER_ACTIVE if the layer is active, that is, its contents are + * changing frequently. In this case it makes sense to keep the layer + * as a separate buffer in VRAM and composite it into the destination + * every time we paint. + * + * Users of GetLayerState should check ForceActiveLayers() and if it returns + * true, change a returned value of LAYER_INACTIVE to LAYER_ACTIVE. + */ + virtual LayerState GetLayerState( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aParameters) { + return mozilla::LayerState::LAYER_NONE; + } + +#ifdef MOZ_DUMP_PAINTING + /** + * Mark this display item as being painted via + * FrameLayerBuilder::DrawPaintedLayer. + */ + bool Painted() const { return mItemFlags.contains(ItemFlag::Painted); } + + /** + * Check if this display item has been painted. + */ + void SetPainted() { mItemFlags += ItemFlag::Painted; } +#endif + + void SetIsGlassItem() { mItemFlags += ItemFlag::IsGlassItem; } + bool IsGlassItem() { return mItemFlags.contains(ItemFlag::IsGlassItem); } + + /** + * Function to create the WebRenderCommands. + * We should check if the layer state is + * active first and have an early return if the layer state is + * not active. + * + * @return true if successfully creating webrender commands. + */ + virtual bool CreateWebRenderCommands( + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + return false; + } + + /** + * Updates the provided aLayerData with any APZ-relevant scroll data + * that is specific to this display item. This is stuff that would normally + * be put on the layer during BuildLayer, but this is only called in + * layers-free webrender mode, where we don't have layers. + * + * This function returns true if and only if it has APZ-relevant scroll data + * to provide. Note that the arguments passed in may be nullptr, in which case + * the function should still return true if and only if it has APZ-relevant + * scroll data, but obviously in this case it can't actually put the + * data onto aLayerData, because there isn't one. + * + * This function assumes that aData and aLayerData will either both be null, + * or will both be non-null. The caller is responsible for enforcing this. + */ + virtual bool UpdateScrollData( + mozilla::layers::WebRenderScrollData* aData, + mozilla::layers::WebRenderLayerScrollData* aLayerData) { + return false; + } + + /** + * On entry, aVisibleRegion contains the region (relative to ReferenceFrame()) + * which may be visible. If the display item opaquely covers an area, it + * can remove that area from aVisibleRegion before returning. + * nsDisplayList::ComputeVisibility automatically subtracts the region + * returned by GetOpaqueRegion, and automatically removes items whose bounds + * do not intersect the visible area, so implementations of + * nsDisplayItem::ComputeVisibility do not need to do these things. + * nsDisplayList::ComputeVisibility will already have set mVisibleRect on + * this item to the intersection of *aVisibleRegion and this item's bounds. + * We rely on that, so this should only be called by + * nsDisplayList::ComputeVisibility or nsDisplayItem::RecomputeVisibility. + * aAllowVisibleRegionExpansion is a rect where we are allowed to + * expand the visible region and is only used for making sure the + * background behind a plugin is visible. + * This method needs to be idempotent. + * + * @return true if the item is visible, false if no part of the item + * is visible. + */ + virtual bool ComputeVisibility(nsDisplayListBuilder* aBuilder, + nsRegion* aVisibleRegion); + + /** + * Returns true if this item needs to have its geometry updated, despite + * returning empty invalidation region. + */ + virtual bool NeedsGeometryUpdates() const { return false; } + + /** + * Some items such as those calling into the native themed widget machinery + * have to be painted on the content process. In this case it is best to avoid + * allocating layers that serializes and forwards the work to the compositor. + */ + virtual bool MustPaintOnContentSide() const { return false; } + + /** + * If this has a child list where the children are in the same coordinate + * system as this item (i.e., they have the same reference frame), + * return the list. + */ + virtual RetainedDisplayList* GetSameCoordinateSystemChildren() const { + return nullptr; + } + + virtual void UpdateBounds(nsDisplayListBuilder* aBuilder) {} + /** + * Do UpdateBounds() for items with frames establishing or extending + * 3D rendering context. + * + * This function is called by UpdateBoundsFor3D() of + * nsDisplayTransform(), and it is called by + * BuildDisplayListForStackingContext() on transform items + * establishing 3D rendering context. + * + * The bounds of a transform item with the frame establishing 3D + * rendering context should be computed by calling + * DoUpdateBoundsPreserves3D() on all descendants that participate + * the same 3d rendering context. + */ + virtual void DoUpdateBoundsPreserves3D(nsDisplayListBuilder* aBuilder) {} + + /** + * Returns the building rectangle used by nsDisplayListBuilder when + * this item was constructed. + */ + const nsRect& GetBuildingRect() const { return mBuildingRect; } + + void SetBuildingRect(const nsRect& aBuildingRect) { + if (aBuildingRect == mBuildingRect) { + // Avoid unnecessary paint rect recompution when the + // building rect is staying the same. + return; + } + mPaintRect = mBuildingRect = aBuildingRect; + mItemFlags -= ItemFlag::PaintRectValid; + } + + void SetPaintRect(const nsRect& aPaintRect) { + mPaintRect = aPaintRect; + mItemFlags += ItemFlag::PaintRectValid; + } + bool HasPaintRect() const { + return mItemFlags.contains(ItemFlag::PaintRectValid); + } + + /** + * Returns the building rect for the children, relative to their + * reference frame. Can be different from mBuildingRect for + * nsDisplayTransform, since the reference frame for the children is different + * from the reference frame for the item itself. + */ + virtual const nsRect& GetBuildingRectForChildren() const { + return mBuildingRect; + } + + virtual void WriteDebugInfo(std::stringstream& aStream) {} + + nsDisplayItem* GetAbove() { return mAbove; } + + /** + * Like ComputeVisibility, but does the work that nsDisplayList + * does per-item: + * -- Intersects GetBounds with aVisibleRegion and puts the result + * in mVisibleRect + * -- Subtracts bounds from aVisibleRegion if the item is opaque + */ + bool RecomputeVisibility(nsDisplayListBuilder* aBuilder, + nsRegion* aVisibleRegion); + + /** + * Returns the result of aBuilder->ToReferenceFrame(GetUnderlyingFrame()) + */ + const nsPoint& ToReferenceFrame() const { + NS_ASSERTION(mFrame, "No frame?"); + return mToReferenceFrame; + } + /** + * @return the root of the display list's frame (sub)tree, whose origin + * establishes the coordinate system for the display list + */ + const nsIFrame* ReferenceFrame() const { return mReferenceFrame; } + + /** + * Returns the reference frame for display item children of this item. + */ + virtual const nsIFrame* ReferenceFrameForChildren() const { + return mReferenceFrame; + } + + AnimatedGeometryRoot* GetAnimatedGeometryRoot() const { + MOZ_ASSERT(mAnimatedGeometryRoot, + "Must have cached AGR before accessing it!"); + return mAnimatedGeometryRoot; + } + + virtual struct AnimatedGeometryRoot* AnimatedGeometryRootForScrollMetadata() + const { + return GetAnimatedGeometryRoot(); + } + + /** + * Checks if this display item (or any children) contains content that might + * be rendered with component alpha (e.g. subpixel antialiasing). Returns the + * bounds of the area that needs component alpha, or an empty rect if nothing + * in the item does. + */ + virtual nsRect GetComponentAlphaBounds(nsDisplayListBuilder* aBuilder) const { + return nsRect(); + } + + /** + * Disable usage of component alpha. Currently only relevant for items that + * have text. + */ + void DisableComponentAlpha() { mItemFlags += ItemFlag::DisableSubpixelAA; } + + bool IsSubpixelAADisabled() const { + return mItemFlags.contains(ItemFlag::DisableSubpixelAA); + } + + /** + * Check if we can add async animations to the layer for this display item. + */ + virtual bool CanUseAsyncAnimations(nsDisplayListBuilder* aBuilder) { + return false; + } + + virtual bool SupportsOptimizingToImage() const { return false; } + + const DisplayItemClip& GetClip() const { + return mClip ? *mClip : DisplayItemClip::NoClip(); + } + void IntersectClip(nsDisplayListBuilder* aBuilder, + const DisplayItemClipChain* aOther, bool aStore); + + virtual void SetActiveScrolledRoot( + const ActiveScrolledRoot* aActiveScrolledRoot) { + mActiveScrolledRoot = aActiveScrolledRoot; + } + const ActiveScrolledRoot* GetActiveScrolledRoot() const { + return mActiveScrolledRoot; + } + + virtual void SetClipChain(const DisplayItemClipChain* aClipChain, + bool aStore); + const DisplayItemClipChain* GetClipChain() const { return mClipChain; } + + /** + * Intersect all clips in our clip chain up to (and including) aASR and set + * set the intersection as this item's clip. + */ + void FuseClipChainUpTo(nsDisplayListBuilder* aBuilder, + const ActiveScrolledRoot* aASR); + + bool BackfaceIsHidden() const { + return mItemFlags.contains(ItemFlag::BackfaceHidden); + } + + bool Combines3DTransformWithAncestors() const { + return mItemFlags.contains(ItemFlag::Combines3DTransformWithAncestors); + } + + bool ForceNotVisible() const { + return mItemFlags.contains(ItemFlag::ForceNotVisible); + } + + bool In3DContextAndBackfaceIsHidden() const { + return mItemFlags.contains(ItemFlag::BackfaceHidden) && + mItemFlags.contains(ItemFlag::Combines3DTransformWithAncestors); + } + + bool HasDifferentFrame(const nsDisplayItem* aOther) const { + return mFrame != aOther->mFrame; + } + + bool HasSameTypeAndClip(const nsDisplayItem* aOther) const { + return GetPerFrameKey() == aOther->GetPerFrameKey() && + GetClipChain() == aOther->GetClipChain(); + } + + bool HasSameContent(const nsDisplayItem* aOther) const { + return mFrame->GetContent() == aOther->Frame()->GetContent(); + } + + virtual void NotifyUsed(nsDisplayListBuilder* aBuilder) {} + + virtual mozilla::Maybe<nsRect> GetClipWithRespectToASR( + nsDisplayListBuilder* aBuilder, const ActiveScrolledRoot* aASR) const; + + const nsRect& GetPaintRect() const { return mPaintRect; } + + virtual const nsRect& GetUntransformedPaintRect() const { + return GetPaintRect(); + } + + virtual bool HasHitTestInfo() const { return false; } + +#ifdef DEBUG + virtual bool IsHitTestItem() const { return false; } +#endif + + protected: + typedef bool (*PrefFunc)(void); + bool ShouldUseAdvancedLayer(LayerManager* aManager, PrefFunc aFunc) const; + bool CanUseAdvancedLayer(LayerManager* aManager) const; + + RefPtr<const DisplayItemClipChain> mClipChain; + const DisplayItemClip* mClip; + RefPtr<const ActiveScrolledRoot> mActiveScrolledRoot; + // Result of FindReferenceFrameFor(mFrame), if mFrame is non-null + const nsIFrame* mReferenceFrame; + RefPtr<struct AnimatedGeometryRoot> mAnimatedGeometryRoot; + + struct { + RefPtr<const DisplayItemClipChain> mClipChain; + const DisplayItemClip* mClip; + } mState; + + // Result of ToReferenceFrame(mFrame), if mFrame is non-null + nsPoint mToReferenceFrame; + + private: + // This is the rectangle that nsDisplayListBuilder was using as the visible + // rect to decide which items to construct. + nsRect mBuildingRect; + + // nsDisplayList::ComputeVisibility sets this to the visible region + // of the item by intersecting the visible region with the bounds + // of the item. Paint implementations can use this to limit their drawing. + // Guaranteed to be contained in GetBounds(). + nsRect mPaintRect; + + enum class ItemFlag : uint8_t { + BackfaceHidden, + Combines3DTransformWithAncestors, + DisableSubpixelAA, + ForceNotVisible, + PaintRectValid, + IsGlassItem, +#ifdef MOZ_DUMP_PAINTING + // True if this frame has been painted. + Painted, +#endif + }; + + mozilla::EnumSet<ItemFlag, uint8_t> mItemFlags; +}; + +class nsPaintedDisplayItem : public nsDisplayItem { + public: + nsPaintedDisplayItem* AsPaintedDisplayItem() final { return this; } + const nsPaintedDisplayItem* AsPaintedDisplayItem() const final { + return this; + } + + ~nsPaintedDisplayItem() override { SetDisplayItemData(nullptr, nullptr); } + + void SetDisplayItemData(mozilla::DisplayItemData* aDID, + mozilla::layers::LayerManager* aLayerManager) { + if (mDisplayItemData) { + MOZ_ASSERT(!mDisplayItemData->GetItem() || + mDisplayItemData->GetItem() == this); + mDisplayItemData->SetItem(nullptr); + } + if (aDID) { + if (aDID->GetItem()) { + aDID->GetItem()->SetDisplayItemData(nullptr, nullptr); + } + aDID->SetItem(this); + } + mDisplayItemData = aDID; + mDisplayItemDataLayerManager = aLayerManager; + } + + mozilla::DisplayItemData* GetDisplayItemData() { return mDisplayItemData; } + mozilla::layers::LayerManager* GetDisplayItemDataLayerManager() { + return mDisplayItemDataLayerManager; + } + + /** + * Stores the given opacity value to be applied when drawing. It is an error + * to call this if CanApplyOpacity returned false. + */ + virtual void ApplyOpacity(nsDisplayListBuilder* aBuilder, float aOpacity, + const DisplayItemClipChain* aClip) { + MOZ_ASSERT(CanApplyOpacity(), "ApplyOpacity is not supported on this type"); + } + + /** + * Get the layer drawn by this display item. Call this only if + * GetLayerState() returns something other than LAYER_NONE. + * If GetLayerState returned LAYER_NONE then Paint will be called + * instead. + * This is called while aManager is in the construction phase. + * + * The caller (nsDisplayList) is responsible for setting the visible + * region of the layer. + * + * @param aContainerParameters should be passed to + * FrameLayerBuilder::BuildContainerLayerFor if a ContainerLayer is + * constructed. + */ + virtual already_AddRefed<Layer> BuildLayer( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aContainerParameters) { + return nullptr; + } + + /** + * Returns true if this display item would return true from ApplyOpacity + * without actually applying the opacity. Otherwise returns false. + */ + virtual bool CanApplyOpacity() const { return false; } + + /** + * Returns true if this item supports PaintWithClip, where the clipping + * is used directly as the primitive geometry instead of needing an explicit + * clip. + */ + virtual bool CanPaintWithClip(const DisplayItemClip& aClip) { return false; } + + /** + * Same as |Paint()|, except provides a clip to use the geometry to draw with. + * Must not be called unless |CanPaintWithClip()| returned true. + */ + virtual void PaintWithClip(nsDisplayListBuilder* aBuilder, gfxContext* aCtx, + const DisplayItemClip& aClip) { + MOZ_ASSERT_UNREACHABLE("PaintWithClip() is not implemented!"); + } + + /** + * Paint this item to some rendering context. + */ + virtual void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) { + // TODO(miko): Make this a pure virtual function to force implementation. + MOZ_ASSERT_UNREACHABLE("Paint() is not implemented!"); + } + + /** + * External storage used by |DisplayItemCache| to avoid hashmap lookups. + * If an item is reused and has the cache index set, it means that + * |DisplayItemCache| has assigned a cache slot for the item. + */ + mozilla::Maybe<uint16_t>& CacheIndex() { return mCacheIndex; } + + void InvalidateItemCacheEntry() override { + // |nsPaintedDisplayItem|s may have |DisplayItemCache| entries + // that no longer match after a mutation. The cache will notice + // on its own that the entry is no longer in use, and free it. + mCacheIndex = mozilla::Nothing(); + } + + protected: + nsPaintedDisplayItem(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame) + : nsDisplayItem(aBuilder, aFrame) {} + + nsPaintedDisplayItem(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + const ActiveScrolledRoot* aActiveScrolledRoot) + : nsDisplayItem(aBuilder, aFrame, aActiveScrolledRoot) {} + + nsPaintedDisplayItem(nsDisplayListBuilder* aBuilder, + const nsPaintedDisplayItem& aOther) + : nsDisplayItem(aBuilder, aOther) {} + + private: + mozilla::DisplayItemData* mDisplayItemData = nullptr; + mozilla::layers::LayerManager* mDisplayItemDataLayerManager = nullptr; + mozilla::Maybe<uint16_t> mCacheIndex; +}; + +/** + * Manages a singly-linked list of display list items. + * + * mSentinel is the sentinel list value, the first value in the null-terminated + * linked list of items. mTop is the last item in the list (whose 'above' + * pointer is null). This class has no virtual methods. So list objects are just + * two pointers. + * + * Stepping upward through this list is very fast. Stepping downward is very + * slow so we don't support it. The methods that need to step downward + * (HitTest(), ComputeVisibility()) internally build a temporary array of all + * the items while they do the downward traversal, so overall they're still + * linear time. We have optimized for efficient AppendToTop() of both + * items and lists, with minimal codesize. AppendToBottom() is efficient too. + */ +class nsDisplayList { + public: + typedef mozilla::ActiveScrolledRoot ActiveScrolledRoot; + typedef mozilla::layers::Layer Layer; + typedef mozilla::layers::LayerManager LayerManager; + typedef mozilla::layers::PaintedLayer PaintedLayer; + + template <typename T> + class Iterator { + public: + Iterator() : mItem(nullptr) {} + ~Iterator() = default; + Iterator(const Iterator& aOther) = default; + Iterator& operator=(const Iterator& aOther) = default; + + explicit Iterator(const nsDisplayList* aList) : mItem(aList->GetBottom()) {} + explicit Iterator(const nsDisplayItem* aItem) : mItem(aItem) {} + + Iterator& operator++() { + mItem = mItem ? mItem->GetAbove() : mItem; + return *this; + } + + bool operator==(const Iterator& aOther) const { + return mItem == aOther.mItem; + } + + bool operator!=(const Iterator& aOther) const { + return !operator==(aOther); + } + + T* operator*() { return mItem; } + + private: + T* mItem; + }; + + using DisplayItemIterator = Iterator<nsDisplayItem>; + + DisplayItemIterator begin() const { return DisplayItemIterator(this); } + DisplayItemIterator end() const { return DisplayItemIterator(); } + + /** + * Create an empty list. + */ + nsDisplayList() + : mLength(0), mIsOpaque(false), mForceTransparentSurface(false) { + mTop = &mSentinel; + mSentinel.mAbove = nullptr; + } + + virtual ~nsDisplayList() { + MOZ_RELEASE_ASSERT(!mSentinel.mAbove, "Nonempty list left over?"); + } + + nsDisplayList(nsDisplayList&& aOther) { + mIsOpaque = aOther.mIsOpaque; + mForceTransparentSurface = aOther.mForceTransparentSurface; + + if (aOther.mSentinel.mAbove) { + AppendToTop(&aOther); + } else { + mTop = &mSentinel; + mLength = 0; + } + } + + nsDisplayList& operator=(nsDisplayList&& aOther) { + if (this != &aOther) { + if (aOther.mSentinel.mAbove) { + nsDisplayList tmp; + tmp.AppendToTop(&aOther); + aOther.AppendToTop(this); + AppendToTop(&tmp); + } else { + mTop = &mSentinel; + mLength = 0; + } + mIsOpaque = aOther.mIsOpaque; + mForceTransparentSurface = aOther.mForceTransparentSurface; + } + return *this; + } + + nsDisplayList(const nsDisplayList&) = delete; + nsDisplayList& operator=(const nsDisplayList& aOther) = delete; + + /** + * Append an item to the top of the list. The item must not currently + * be in a list and cannot be null. + */ + void AppendToTop(nsDisplayItem* aItem) { + if (!aItem) { + return; + } + MOZ_ASSERT(!aItem->mAbove, "Already in a list!"); + mTop->mAbove = aItem; + mTop = aItem; + mLength++; + } + + template <typename T, typename F, typename... Args> + void AppendNewToTop(nsDisplayListBuilder* aBuilder, F* aFrame, + Args&&... aArgs) { + AppendNewToTopWithIndex<T>(aBuilder, aFrame, 0, + std::forward<Args>(aArgs)...); + } + + template <typename T, typename F, typename... Args> + void AppendNewToTopWithIndex(nsDisplayListBuilder* aBuilder, F* aFrame, + const uint16_t aIndex, Args&&... aArgs) { + nsDisplayItem* item = MakeDisplayItemWithIndex<T>( + aBuilder, aFrame, aIndex, std::forward<Args>(aArgs)...); + + if (item) { + AppendToTop(item); + } + } + + /** + * Append a new item to the bottom of the list. The item must be non-null + * and not already in a list. + */ + void AppendToBottom(nsDisplayItem* aItem) { + if (!aItem) { + return; + } + MOZ_ASSERT(!aItem->mAbove, "Already in a list!"); + aItem->mAbove = mSentinel.mAbove; + mSentinel.mAbove = aItem; + if (mTop == &mSentinel) { + mTop = aItem; + } + mLength++; + } + + template <typename T, typename F, typename... Args> + void AppendNewToBottom(nsDisplayListBuilder* aBuilder, F* aFrame, + Args&&... aArgs) { + AppendNewToBottomWithIndex<T>(aBuilder, aFrame, 0, + std::forward<Args>(aArgs)...); + } + + template <typename T, typename F, typename... Args> + void AppendNewToBottomWithIndex(nsDisplayListBuilder* aBuilder, F* aFrame, + const uint16_t aIndex, Args&&... aArgs) { + nsDisplayItem* item = MakeDisplayItemWithIndex<T>( + aBuilder, aFrame, aIndex, std::forward<Args>(aArgs)...); + + if (item) { + AppendToBottom(item); + } + } + + /** + * Removes all items from aList and appends them to the top of this list + */ + void AppendToTop(nsDisplayList* aList) { + if (aList->mSentinel.mAbove) { + mTop->mAbove = aList->mSentinel.mAbove; + mTop = aList->mTop; + aList->mTop = &aList->mSentinel; + aList->mSentinel.mAbove = nullptr; + mLength += aList->mLength; + aList->mLength = 0; + } + } + + /** + * Removes all items from aList and prepends them to the bottom of this list + */ + void AppendToBottom(nsDisplayList* aList) { + if (aList->mSentinel.mAbove) { + aList->mTop->mAbove = mSentinel.mAbove; + mSentinel.mAbove = aList->mSentinel.mAbove; + if (mTop == &mSentinel) { + mTop = aList->mTop; + } + + aList->mTop = &aList->mSentinel; + aList->mSentinel.mAbove = nullptr; + mLength += aList->mLength; + aList->mLength = 0; + } + } + + /** + * Remove an item from the bottom of the list and return it. + */ + nsDisplayItem* RemoveBottom(); + + /** + * Remove all items from the list and call their destructors. + */ + virtual void DeleteAll(nsDisplayListBuilder* aBuilder); + + /** + * @return the item at the top of the list, or null if the list is empty + */ + nsDisplayItem* GetTop() const { + return mTop != &mSentinel ? static_cast<nsDisplayItem*>(mTop) : nullptr; + } + /** + * @return the item at the bottom of the list, or null if the list is empty + */ + nsDisplayItem* GetBottom() const { return mSentinel.mAbove; } + bool IsEmpty() const { return mTop == &mSentinel; } + + /** + * @return the number of items in the list + */ + uint32_t Count() const { return mLength; } + /** + * Stable sort the list by the z-order of GetUnderlyingFrame() on + * each item. 'auto' is counted as zero. + * It is assumed that the list is already in content document order. + */ + void SortByZOrder(); + /** + * Stable sort the list by the tree order of the content of + * GetUnderlyingFrame() on each item. z-index is ignored. + * @param aCommonAncestor a common ancestor of all the content elements + * associated with the display items, for speeding up tree order + * checks, or nullptr if not known; it's only a hint, if it is not an + * ancestor of some elements, then we lose performance but not correctness + */ + void SortByContentOrder(nsIContent* aCommonAncestor); + + /** + * Sort the display list using a stable sort. Take care, because some of the + * items might be nsDisplayLists themselves. + * aComparator(Item item1, Item item2) should return true if item1 should go + * before item2. + * We sort the items into increasing order. + */ + template <typename Item, typename Comparator> + void Sort(const Comparator& aComparator) { + if (Count() < 2) { + // Only sort lists with more than one item. + return; + } + + // Some casual local browsing testing suggests that a local preallocated + // array of 20 items should be able to avoid a lot of dynamic allocations + // here. + AutoTArray<Item, 20> items; + + while (nsDisplayItem* item = RemoveBottom()) { + items.AppendElement(Item(item)); + } + + std::stable_sort(items.begin(), items.end(), aComparator); + + for (Item& item : items) { + AppendToTop(item); + } + } + + /** + * Compute visiblity for the items in the list. + * We put this logic here so it can be shared by top-level + * painting and also display items that maintain child lists. + * This is also a good place to put ComputeVisibility-related logic + * that must be applied to every display item. In particular, this + * sets mVisibleRect on each display item. + * This sets mIsOpaque if the entire visible area of this list has + * been removed from aVisibleRegion when we return. + * This does not remove any items from the list, so we can recompute + * visiblity with different regions later (see + * FrameLayerBuilder::DrawPaintedLayer). + * This method needs to be idempotent. + * + * @param aVisibleRegion the area that is visible, relative to the + * reference frame; on return, this contains the area visible under the list. + * I.e., opaque contents of this list are subtracted from aVisibleRegion. + * @param aListVisibleBounds must be equal to the bounds of the intersection + * of aVisibleRegion and GetBounds() for this list. + * @return true if any item in the list is visible. + */ + bool ComputeVisibilityForSublist(nsDisplayListBuilder* aBuilder, + nsRegion* aVisibleRegion, + const nsRect& aListVisibleBounds); + + /** + * As ComputeVisibilityForSublist, but computes visibility for a root + * list (a list that does not belong to an nsDisplayItem). + * This method needs to be idempotent. + * + * @param aVisibleRegion the area that is visible + */ + bool ComputeVisibilityForRoot(nsDisplayListBuilder* aBuilder, + nsRegion* aVisibleRegion); + + /** + * Returns true if the visible region output from ComputeVisiblity was + * empty, i.e. everything visible in this list is opaque. + */ + bool IsOpaque() const { return mIsOpaque; } + + /** + * Returns true if any display item requires the surface to be transparent. + */ + bool NeedsTransparentSurface() const { return mForceTransparentSurface; } + /** + * Paint the list to the rendering context. We assume that (0,0) in aCtx + * corresponds to the origin of the reference frame. For best results, + * aCtx's current transform should make (0,0) pixel-aligned. The + * rectangle in aDirtyRect is painted, which *must* be contained in the + * dirty rect used to construct the display list. + * + * If aFlags contains PAINT_USE_WIDGET_LAYERS and + * ShouldUseWidgetLayerManager() is set, then we will paint using + * the reference frame's widget's layer manager (and ctx may be null), + * otherwise we will use a temporary BasicLayerManager and ctx must + * not be null. + * + * If PAINT_EXISTING_TRANSACTION is set, the reference frame's widget's + * layer manager has already had BeginTransaction() called on it and + * we should not call it again. + * + * If PAINT_COMPRESSED is set, the FrameLayerBuilder should be set to + * compressed mode to avoid short cut optimizations. + * + * This must only be called on the root display list of the display list + * tree. + * + * We return the layer manager used for painting --- mainly so that + * callers can dump its layer tree if necessary. + */ + enum { + PAINT_DEFAULT = 0, + PAINT_USE_WIDGET_LAYERS = 0x01, + PAINT_EXISTING_TRANSACTION = 0x04, + PAINT_NO_COMPOSITE = 0x08, + PAINT_COMPRESSED = 0x10, + PAINT_IDENTICAL_DISPLAY_LIST = 0x20 + }; + already_AddRefed<LayerManager> PaintRoot(nsDisplayListBuilder* aBuilder, + gfxContext* aCtx, uint32_t aFlags); + + mozilla::FrameLayerBuilder* BuildLayers(nsDisplayListBuilder* aBuilder, + LayerManager* aLayerManager, + uint32_t aFlags, + bool aIsWidgetTransaction); + /** + * Get the bounds. Takes the union of the bounds of all children. + * The result is not cached. + */ + nsRect GetClippedBounds(nsDisplayListBuilder* aBuilder) const; + + /** + * Get this list's bounds, respecting clips relative to aASR. The result is + * the union of each item's clipped bounds with respect to aASR. That means + * that if an item can move asynchronously with an ASR that is a descendant + * of aASR, then the clipped bounds with respect to aASR will be the clip of + * that item for aASR, because the item can move anywhere inside that clip. + * If there is an item in this list which is not bounded with respect to + * aASR (i.e. which does not have "finite bounds" with respect to aASR), + * then this method trigger an assertion failure. + * The optional aBuildingRect out argument can be set to non-null if the + * caller is also interested to know the building rect. This can be used + * to get the visible rect efficiently without traversing the display list + * twice. + */ + nsRect GetClippedBoundsWithRespectToASR( + nsDisplayListBuilder* aBuilder, const ActiveScrolledRoot* aASR, + nsRect* aBuildingRect = nullptr) const; + + /** + * Returns the opaque region of this display list. + */ + nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder) { + nsRegion result; + bool snap; + for (nsDisplayItem* item : *this) { + result.OrWith(item->GetOpaqueRegion(aBuilder, &snap)); + } + return result; + } + + /** + * Returns the bounds of the area that needs component alpha. + */ + nsRect GetComponentAlphaBounds(nsDisplayListBuilder* aBuilder) const { + nsRect bounds; + for (nsDisplayItem* item : *this) { + bounds.UnionRect(bounds, item->GetComponentAlphaBounds(aBuilder)); + } + return bounds; + } + + /** + * Find the topmost display item that returns a non-null frame, and return + * the frame. + */ + void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect, + nsDisplayItem::HitTestState* aState, + nsTArray<nsIFrame*>* aOutFrames) const; + /** + * Compute the union of the visible rects of the items in the list. The + * result is not cached. + */ + nsRect GetBuildingRect() const; + + void SetIsOpaque() { mIsOpaque = true; } + + void SetNeedsTransparentSurface() { mForceTransparentSurface = true; } + + void RestoreState() { + mIsOpaque = false; + mForceTransparentSurface = false; + } + + private: + nsDisplayItemLink mSentinel; + nsDisplayItemLink* mTop; + + uint32_t mLength; + + // This is set to true by FrameLayerBuilder if the final visible region + // is empty (i.e. everything that was visible is covered by some + // opaque content in this list). + bool mIsOpaque; + // This is set to true by FrameLayerBuilder if any display item in this + // list needs to force the surface containing this list to be transparent. + bool mForceTransparentSurface; +}; + +/** + * This is passed as a parameter to nsIFrame::BuildDisplayList. That method + * will put any generated items onto the appropriate list given here. It's + * basically just a collection with one list for each separate stacking layer. + * The lists themselves are external to this object and thus can be shared + * with others. Some of the list pointers may even refer to the same list. + */ +class nsDisplayListSet { + public: + /** + * @return a list where one should place the border and/or background for + * this frame (everything from steps 1 and 2 of CSS 2.1 appendix E) + */ + nsDisplayList* BorderBackground() const { return mBorderBackground; } + /** + * @return a list where one should place the borders and/or backgrounds for + * block-level in-flow descendants (step 4 of CSS 2.1 appendix E) + */ + nsDisplayList* BlockBorderBackgrounds() const { + return mBlockBorderBackgrounds; + } + /** + * @return a list where one should place descendant floats (step 5 of + * CSS 2.1 appendix E) + */ + nsDisplayList* Floats() const { return mFloats; } + /** + * @return a list where one should place the (pseudo) stacking contexts + * for descendants of this frame (everything from steps 3, 7 and 8 + * of CSS 2.1 appendix E) + */ + nsDisplayList* PositionedDescendants() const { return mPositioned; } + /** + * @return a list where one should place the outlines + * for this frame and its descendants (step 9 of CSS 2.1 appendix E) + */ + nsDisplayList* Outlines() const { return mOutlines; } + /** + * @return a list where one should place all other content + */ + nsDisplayList* Content() const { return mContent; } + + void DeleteAll(nsDisplayListBuilder* aBuilder) { + BorderBackground()->DeleteAll(aBuilder); + BlockBorderBackgrounds()->DeleteAll(aBuilder); + Floats()->DeleteAll(aBuilder); + PositionedDescendants()->DeleteAll(aBuilder); + Outlines()->DeleteAll(aBuilder); + Content()->DeleteAll(aBuilder); + } + + nsDisplayListSet(nsDisplayList* aBorderBackground, + nsDisplayList* aBlockBorderBackgrounds, + nsDisplayList* aFloats, nsDisplayList* aContent, + nsDisplayList* aPositionedDescendants, + nsDisplayList* aOutlines) + : mBorderBackground(aBorderBackground), + mBlockBorderBackgrounds(aBlockBorderBackgrounds), + mFloats(aFloats), + mContent(aContent), + mPositioned(aPositionedDescendants), + mOutlines(aOutlines) {} + + /** + * A copy constructor that lets the caller override the BorderBackground + * list. + */ + nsDisplayListSet(const nsDisplayListSet& aLists, + nsDisplayList* aBorderBackground) + : mBorderBackground(aBorderBackground), + mBlockBorderBackgrounds(aLists.BlockBorderBackgrounds()), + mFloats(aLists.Floats()), + mContent(aLists.Content()), + mPositioned(aLists.PositionedDescendants()), + mOutlines(aLists.Outlines()) {} + + /** + * Move all display items in our lists to top of the corresponding lists in + * the destination. + */ + void MoveTo(const nsDisplayListSet& aDestination) const; + + private: + // This class is only used on stack, so we don't have to worry about leaking + // it. Don't let us be heap-allocated! + void* operator new(size_t sz) noexcept(true); + + protected: + nsDisplayList* mBorderBackground; + nsDisplayList* mBlockBorderBackgrounds; + nsDisplayList* mFloats; + nsDisplayList* mContent; + nsDisplayList* mPositioned; + nsDisplayList* mOutlines; +}; + +/** + * A specialization of nsDisplayListSet where the lists are actually internal + * to the object, and all distinct. + */ +struct nsDisplayListCollection : public nsDisplayListSet { + explicit nsDisplayListCollection(nsDisplayListBuilder* aBuilder) + : nsDisplayListSet(&mLists[0], &mLists[1], &mLists[2], &mLists[3], + &mLists[4], &mLists[5]) {} + + explicit nsDisplayListCollection(nsDisplayListBuilder* aBuilder, + nsDisplayList* aBorderBackground) + : nsDisplayListSet(aBorderBackground, &mLists[1], &mLists[2], &mLists[3], + &mLists[4], &mLists[5]) {} + + /** + * Sort all lists by content order. + */ + void SortAllByContentOrder(nsIContent* aCommonAncestor) { + for (auto& mList : mLists) { + mList.SortByContentOrder(aCommonAncestor); + } + } + + /** + * Serialize this display list collection into a display list with the items + * in the correct Z order. + * @param aOutList the result display list + * @param aContent the content element to use for content ordering + */ + void SerializeWithCorrectZOrder(nsDisplayList* aOutResultList, + nsIContent* aContent); + + private: + // This class is only used on stack, so we don't have to worry about leaking + // it. Don't let us be heap-allocated! + void* operator new(size_t sz) noexcept(true); + + nsDisplayList mLists[6]; +}; + +/** + * A display list that also retains the partial build + * information (in the form of a DAG) used to create it. + * + * Display lists built from a partial list aren't necessarily + * in the same order as a full build, and the DAG retains + * the information needing to interpret the current + * order correctly. + */ +class RetainedDisplayList : public nsDisplayList { + public: + RetainedDisplayList() = default; + RetainedDisplayList(RetainedDisplayList&& aOther) { + AppendToTop(&aOther); + mDAG = std::move(aOther.mDAG); + } + + ~RetainedDisplayList() override { + MOZ_ASSERT(mOldItems.IsEmpty(), "Must empty list before destroying"); + } + + RetainedDisplayList& operator=(RetainedDisplayList&& aOther) { + MOZ_ASSERT(!Count(), "Can only move into an empty list!"); + MOZ_ASSERT(mOldItems.IsEmpty(), "Can only move into an empty list!"); + AppendToTop(&aOther); + mDAG = std::move(aOther.mDAG); + mOldItems = std::move(aOther.mOldItems); + return *this; + } + + void DeleteAll(nsDisplayListBuilder* aBuilder) override { + for (OldItemInfo& i : mOldItems) { + if (i.mItem && i.mOwnsItem) { + i.mItem->Destroy(aBuilder); + MOZ_ASSERT(!GetBottom(), + "mOldItems should not be owning items if we also have items " + "in the normal list"); + } + } + mOldItems.Clear(); + mDAG.Clear(); + nsDisplayList::DeleteAll(aBuilder); + } + + void AddSizeOfExcludingThis(nsWindowSizes&) const; + + DirectedAcyclicGraph<MergedListUnits> mDAG; + + // Temporary state initialized during the preprocess pass + // of RetainedDisplayListBuilder and then used during merging. + nsTArray<OldItemInfo> mOldItems; +}; + +struct HitTestInfo { + HitTestInfo(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + const mozilla::gfx::CompositorHitTestInfo& aHitTestFlags) + : mArea(aFrame->GetCompositorHitTestArea(aBuilder)), + mFlags(aHitTestFlags), + mAGR(aBuilder->FindAnimatedGeometryRootFor(aFrame)), + mASR(aBuilder->CurrentActiveScrolledRoot()), + mClipChain(aBuilder->ClipState().GetCurrentCombinedClipChain(aBuilder)), + mClip(mozilla::DisplayItemClipChain::ClipForASR(mClipChain, mASR)) {} + + HitTestInfo(const nsRect& aArea, + const mozilla::gfx::CompositorHitTestInfo& aHitTestFlags) + : mArea(aArea), + mFlags(aHitTestFlags), + mAGR(nullptr), + mASR(nullptr), + mClipChain(nullptr), + mClip(nullptr) {} + + nsRect mArea; + mozilla::gfx::CompositorHitTestInfo mFlags; + + RefPtr<AnimatedGeometryRoot> mAGR; + RefPtr<const mozilla::ActiveScrolledRoot> mASR; + RefPtr<const mozilla::DisplayItemClipChain> mClipChain; + const mozilla::DisplayItemClip* mClip; +}; + +class nsDisplayHitTestInfoBase : public nsPaintedDisplayItem { + public: + nsDisplayHitTestInfoBase(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame) + : nsPaintedDisplayItem(aBuilder, aFrame) {} + + nsDisplayHitTestInfoBase(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + const ActiveScrolledRoot* aActiveScrolledRoot) + : nsPaintedDisplayItem(aBuilder, aFrame, aActiveScrolledRoot) {} + + nsDisplayHitTestInfoBase(nsDisplayListBuilder* aBuilder, + const nsDisplayHitTestInfoBase& aOther) + : nsPaintedDisplayItem(aBuilder, aOther) {} + + const HitTestInfo& GetHitTestInfo() const { + MOZ_ASSERT(HasHitTestInfo()); + return *mHitTestInfo; + } + + void SetActiveScrolledRoot( + const ActiveScrolledRoot* aActiveScrolledRoot) override { + nsPaintedDisplayItem::SetActiveScrolledRoot(aActiveScrolledRoot); + UpdateHitTestInfoActiveScrolledRoot(aActiveScrolledRoot); + } + + /** + * Updates mASR and mClip fields using the given |aActiveScrolledRoot|. + */ + void UpdateHitTestInfoActiveScrolledRoot( + const ActiveScrolledRoot* aActiveScrolledRoot) { + if (HasHitTestInfo()) { + mHitTestInfo->mASR = aActiveScrolledRoot; + mHitTestInfo->mClip = mozilla::DisplayItemClipChain::ClipForASR( + mHitTestInfo->mClipChain, mHitTestInfo->mASR); + } + } + + void SetHitTestInfo(mozilla::UniquePtr<HitTestInfo>&& aHitTestInfo) { + MOZ_ASSERT(aHitTestInfo); + MOZ_ASSERT(aHitTestInfo->mFlags != + mozilla::gfx::CompositorHitTestInvisibleToHit); + + mHitTestInfo = std::move(aHitTestInfo); + } + + void SetHitTestInfo( + const nsRect& aArea, + const mozilla::gfx::CompositorHitTestInfo& aHitTestFlags) { + MOZ_ASSERT(aHitTestFlags != mozilla::gfx::CompositorHitTestInvisibleToHit); + + mHitTestInfo = mozilla::MakeUnique<HitTestInfo>(aArea, aHitTestFlags); + mHitTestInfo->mAGR = mAnimatedGeometryRoot; + mHitTestInfo->mASR = mActiveScrolledRoot; + mHitTestInfo->mClipChain = mClipChain; + mHitTestInfo->mClip = mClip; + } + + const nsRect& HitTestArea() const { return mHitTestInfo->mArea; } + + const mozilla::gfx::CompositorHitTestInfo& HitTestFlags() const { + return mHitTestInfo->mFlags; + } + + bool HasHitTestInfo() const final { return mHitTestInfo.get(); } + + void AddSizeOfExcludingThis(nsWindowSizes&) const override; + +#ifdef DEBUG + bool IsHitTestItem() const final { return true; } +#endif + + protected: + mozilla::UniquePtr<HitTestInfo> mHitTestInfo; +}; + +class nsDisplayContainer final : public nsDisplayItem { + public: + nsDisplayContainer(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + const ActiveScrolledRoot* aActiveScrolledRoot, + nsDisplayList* aList); + + ~nsDisplayContainer() override { MOZ_COUNT_DTOR(nsDisplayContainer); } + + NS_DISPLAY_DECL_NAME("nsDisplayContainer", TYPE_CONTAINER) + + void Destroy(nsDisplayListBuilder* aBuilder) override { + mChildren.DeleteAll(aBuilder); + nsDisplayItem::Destroy(aBuilder); + } + + bool ComputeVisibility(nsDisplayListBuilder* aBuilder, + nsRegion* aVisibleRegion) override; + + bool CreateWebRenderCommands( + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) override; + + nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) const override; + + nsRect GetComponentAlphaBounds(nsDisplayListBuilder* aBuilder) const override; + + nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder, + bool* aSnap) const override; + + mozilla::Maybe<nscolor> IsUniform( + nsDisplayListBuilder* aBuilder) const override { + return mozilla::Nothing(); + } + + RetainedDisplayList* GetChildren() const override { return &mChildren; } + RetainedDisplayList* GetSameCoordinateSystemChildren() const override { + return GetChildren(); + } + + mozilla::Maybe<nsRect> GetClipWithRespectToASR( + nsDisplayListBuilder* aBuilder, + const ActiveScrolledRoot* aASR) const override; + + void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect, + HitTestState* aState, nsTArray<nsIFrame*>* aOutFrames) override; + + bool ShouldFlattenAway(nsDisplayListBuilder* aBuilder) override { + return true; + } + + void SetClipChain(const DisplayItemClipChain* aClipChain, + bool aStore) override { + MOZ_ASSERT_UNREACHABLE("nsDisplayContainer does not support clipping"); + } + + void UpdateBounds(nsDisplayListBuilder* aBuilder) override; + + private: + mutable RetainedDisplayList mChildren; + nsRect mBounds; +}; + +class nsDisplayImageContainer : public nsPaintedDisplayItem { + public: + typedef mozilla::LayerIntPoint LayerIntPoint; + typedef mozilla::LayoutDeviceRect LayoutDeviceRect; + typedef mozilla::layers::ImageContainer ImageContainer; + typedef mozilla::layers::ImageLayer ImageLayer; + + nsDisplayImageContainer(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame) + : nsPaintedDisplayItem(aBuilder, aFrame) {} + + /** + * @return true if this display item can be optimized into an image layer. + * It is an error to call GetContainer() unless you've called + * CanOptimizeToImageLayer() first and it returned true. + */ + virtual bool CanOptimizeToImageLayer(LayerManager* aManager, + nsDisplayListBuilder* aBuilder); + + already_AddRefed<ImageContainer> GetContainer(LayerManager* aManager, + nsDisplayListBuilder* aBuilder); + void ConfigureLayer(ImageLayer* aLayer, + const ContainerLayerParameters& aParameters); + + virtual void UpdateDrawResult(mozilla::image::ImgDrawResult aResult) = 0; + virtual already_AddRefed<imgIContainer> GetImage() = 0; + virtual nsRect GetDestRect() const = 0; + + bool SupportsOptimizingToImage() const override { return true; } +}; + +/** + * Use this class to implement not-very-frequently-used display items + * that are not opaque, do not receive events, and are bounded by a frame's + * border-rect. + * + * This should not be used for display items which are created frequently, + * because each item is one or two pointers bigger than an item from a + * custom display item class could be, and fractionally slower. However it does + * save code size. We use this for infrequently-used item types. + */ +class nsDisplayGeneric : public nsPaintedDisplayItem { + public: + typedef void (*PaintCallback)(nsIFrame* aFrame, DrawTarget* aDrawTarget, + const nsRect& aDirtyRect, nsPoint aFramePt); + + // XXX: should be removed eventually + typedef void (*OldPaintCallback)(nsIFrame* aFrame, gfxContext* aCtx, + const nsRect& aDirtyRect, nsPoint aFramePt); + + nsDisplayGeneric(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + PaintCallback aPaint, const char* aName, + DisplayItemType aType) + : nsPaintedDisplayItem(aBuilder, aFrame), + mPaint(aPaint), + mOldPaint(nullptr), + mName(aName) { + MOZ_COUNT_CTOR(nsDisplayGeneric); + SetType(aType); + } + + // XXX: should be removed eventually + nsDisplayGeneric(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + OldPaintCallback aOldPaint, const char* aName, + DisplayItemType aType) + : nsPaintedDisplayItem(aBuilder, aFrame), + mPaint(nullptr), + mOldPaint(aOldPaint), + mName(aName) { + MOZ_COUNT_CTOR(nsDisplayGeneric); + SetType(aType); + } + + constexpr static DisplayItemType ItemType() { + return DisplayItemType::TYPE_GENERIC; + } + + MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayGeneric) + + void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override { + MOZ_ASSERT(!!mPaint != !!mOldPaint); + if (mPaint) { + mPaint(mFrame, aCtx->GetDrawTarget(), GetPaintRect(), ToReferenceFrame()); + } else { + mOldPaint(mFrame, aCtx, GetPaintRect(), ToReferenceFrame()); + } + } + + const char* Name() const override { return mName; } + + // This override is needed because GetType() for nsDisplayGeneric subclasses + // does not match TYPE_GENERIC that was used to allocate the object. + void Destroy(nsDisplayListBuilder* aBuilder) override { + this->~nsDisplayGeneric(); + aBuilder->Destroy(DisplayItemType::TYPE_GENERIC, this); + } + + protected: + void* operator new(size_t aSize, nsDisplayListBuilder* aBuilder) { + return aBuilder->Allocate(aSize, DisplayItemType::TYPE_GENERIC); + } + + template <typename T, typename F, typename... Args> + friend T* ::MakeDisplayItemWithIndex(nsDisplayListBuilder* aBuilder, + F* aFrame, const uint16_t aIndex, + Args&&... aArgs); + + PaintCallback mPaint; + OldPaintCallback mOldPaint; // XXX: should be removed eventually + const char* mName; +}; + +#if defined(MOZ_REFLOW_PERF_DSP) && defined(MOZ_REFLOW_PERF) +/** + * This class implements painting of reflow counts. Ideally, we would simply + * make all the frame names be those returned by nsIFrame::GetFrameName + * (except that tosses in the content tag name!) and support only one color + * and eliminate this class altogether in favor of nsDisplayGeneric, but for + * the time being we can't pass args to a PaintCallback, so just have a + * separate class to do the right thing. Sadly, this alsmo means we need to + * hack all leaf frame classes to handle this. + * + * XXXbz the color thing is a bit of a mess, but 0 basically means "not set" + * here... I could switch it all to nscolor, but why bother? + */ +class nsDisplayReflowCount : public nsPaintedDisplayItem { + public: + nsDisplayReflowCount(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + const char* aFrameName, uint32_t aColor = 0) + : nsPaintedDisplayItem(aBuilder, aFrame), + mFrameName(aFrameName), + mColor(aColor) { + MOZ_COUNT_CTOR(nsDisplayReflowCount); + } + + MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayReflowCount) + + NS_DISPLAY_DECL_NAME("nsDisplayReflowCount", TYPE_REFLOW_COUNT) + + void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override; + + protected: + const char* mFrameName; + nscolor mColor; +}; + +# define DO_GLOBAL_REFLOW_COUNT_DSP(_name) \ + PR_BEGIN_MACRO \ + if (!aBuilder->IsBackgroundOnly() && !aBuilder->IsForEventDelivery() && \ + PresShell()->IsPaintingFrameCounts()) { \ + aLists.Outlines()->AppendNewToTop<nsDisplayReflowCount>(aBuilder, this, \ + _name); \ + } \ + PR_END_MACRO + +# define DO_GLOBAL_REFLOW_COUNT_DSP_COLOR(_name, _color) \ + PR_BEGIN_MACRO \ + if (!aBuilder->IsBackgroundOnly() && !aBuilder->IsForEventDelivery() && \ + PresShell()->IsPaintingFrameCounts()) { \ + aLists.Outlines()->AppendNewToTop<nsDisplayReflowCount>(aBuilder, this, \ + _name, _color); \ + } \ + PR_END_MACRO + +/* + Macro to be used for classes that don't actually implement BuildDisplayList + */ +# define DECL_DO_GLOBAL_REFLOW_COUNT_DSP(_class, _super) \ + void BuildDisplayList(nsDisplayListBuilder* aBuilder, \ + const nsRect& aDirtyRect, \ + const nsDisplayListSet& aLists) { \ + DO_GLOBAL_REFLOW_COUNT_DSP(#_class); \ + _super::BuildDisplayList(aBuilder, aDirtyRect, aLists); \ + } + +#else // MOZ_REFLOW_PERF_DSP && MOZ_REFLOW_PERF + +# define DO_GLOBAL_REFLOW_COUNT_DSP(_name) +# define DO_GLOBAL_REFLOW_COUNT_DSP_COLOR(_name, _color) +# define DECL_DO_GLOBAL_REFLOW_COUNT_DSP(_class, _super) + +#endif // MOZ_REFLOW_PERF_DSP && MOZ_REFLOW_PERF + +class nsDisplayCaret : public nsPaintedDisplayItem { + public: + nsDisplayCaret(nsDisplayListBuilder* aBuilder, nsIFrame* aCaretFrame); + +#ifdef NS_BUILD_REFCNT_LOGGING + ~nsDisplayCaret() override; +#endif + + NS_DISPLAY_DECL_NAME("Caret", TYPE_CARET) + + nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) const override; + void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override; + bool CreateWebRenderCommands( + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) override; + + protected: + RefPtr<nsCaret> mCaret; + nsRect mBounds; +}; + +/** + * The standard display item to paint the CSS borders of a frame. + */ +class nsDisplayBorder : public nsPaintedDisplayItem { + public: + nsDisplayBorder(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame); + + MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayBorder) + + NS_DISPLAY_DECL_NAME("Border", TYPE_BORDER) + + bool IsInvisibleInRect(const nsRect& aRect) const override; + nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) const override; + LayerState GetLayerState( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aParameters) override; + bool CreateWebRenderCommands( + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) override; + void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override; + nsDisplayItemGeometry* AllocateGeometry( + nsDisplayListBuilder* aBuilder) override; + void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder, + const nsDisplayItemGeometry* aGeometry, + nsRegion* aInvalidRegion) const override; + + nsRegion GetTightBounds(nsDisplayListBuilder* aBuilder, + bool* aSnap) const override { + *aSnap = true; + return CalculateBounds<nsRegion>(*mFrame->StyleBorder()); + } + + protected: + template <typename T> + T CalculateBounds(const nsStyleBorder& aStyleBorder) const { + nsRect borderBounds(ToReferenceFrame(), mFrame->GetSize()); + if (aStyleBorder.IsBorderImageSizeAvailable()) { + borderBounds.Inflate(aStyleBorder.GetImageOutset()); + return borderBounds; + } + + nsMargin border = aStyleBorder.GetComputedBorder(); + T result; + if (border.top > 0) { + result = nsRect(borderBounds.X(), borderBounds.Y(), borderBounds.Width(), + border.top); + } + if (border.right > 0) { + result.OrWith(nsRect(borderBounds.XMost() - border.right, + borderBounds.Y(), border.right, + borderBounds.Height())); + } + if (border.bottom > 0) { + result.OrWith(nsRect(borderBounds.X(), + borderBounds.YMost() - border.bottom, + borderBounds.Width(), border.bottom)); + } + if (border.left > 0) { + result.OrWith(nsRect(borderBounds.X(), borderBounds.Y(), border.left, + borderBounds.Height())); + } + + nscoord radii[8]; + if (mFrame->GetBorderRadii(radii)) { + if (border.left > 0 || border.top > 0) { + nsSize cornerSize(radii[mozilla::eCornerTopLeftX], + radii[mozilla::eCornerTopLeftY]); + result.OrWith(nsRect(borderBounds.TopLeft(), cornerSize)); + } + if (border.top > 0 || border.right > 0) { + nsSize cornerSize(radii[mozilla::eCornerTopRightX], + radii[mozilla::eCornerTopRightY]); + result.OrWith( + nsRect(borderBounds.TopRight() - nsPoint(cornerSize.width, 0), + cornerSize)); + } + if (border.right > 0 || border.bottom > 0) { + nsSize cornerSize(radii[mozilla::eCornerBottomRightX], + radii[mozilla::eCornerBottomRightY]); + result.OrWith(nsRect(borderBounds.BottomRight() - + nsPoint(cornerSize.width, cornerSize.height), + cornerSize)); + } + if (border.bottom > 0 || border.left > 0) { + nsSize cornerSize(radii[mozilla::eCornerBottomLeftX], + radii[mozilla::eCornerBottomLeftY]); + result.OrWith( + nsRect(borderBounds.BottomLeft() - nsPoint(0, cornerSize.height), + cornerSize)); + } + } + return result; + } + + nsRect mBounds; +}; + +/** + * A simple display item that just renders a solid color across the + * specified bounds. For canvas frames (in the CSS sense) we split off the + * drawing of the background color into this class (from nsDisplayBackground + * via nsDisplayCanvasBackground). This is done so that we can always draw a + * background color to avoid ugly flashes of white when we can't draw a full + * frame tree (ie when a page is loading). The bounds can differ from the + * frame's bounds -- this is needed when a frame/iframe is loading and there + * is not yet a frame tree to go in the frame/iframe so we use the subdoc + * frame of the parent document as a standin. + */ +class nsDisplaySolidColorBase : public nsPaintedDisplayItem { + public: + nsDisplaySolidColorBase(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + nscolor aColor) + : nsPaintedDisplayItem(aBuilder, aFrame), mColor(aColor) {} + + nsDisplayItemGeometry* AllocateGeometry( + nsDisplayListBuilder* aBuilder) override { + return new nsDisplaySolidColorGeometry(this, aBuilder, mColor); + } + + void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder, + const nsDisplayItemGeometry* aGeometry, + nsRegion* aInvalidRegion) const override { + const nsDisplaySolidColorGeometry* geometry = + static_cast<const nsDisplaySolidColorGeometry*>(aGeometry); + if (mColor != geometry->mColor) { + bool dummy; + aInvalidRegion->Or(geometry->mBounds, GetBounds(aBuilder, &dummy)); + return; + } + ComputeInvalidationRegionDifference(aBuilder, geometry, aInvalidRegion); + } + + nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder, + bool* aSnap) const override { + *aSnap = false; + nsRegion result; + if (NS_GET_A(mColor) == 255) { + result = GetBounds(aBuilder, aSnap); + } + return result; + } + + mozilla::Maybe<nscolor> IsUniform( + nsDisplayListBuilder* aBuilder) const override { + return mozilla::Some(mColor); + } + + protected: + nscolor mColor; +}; + +class nsDisplaySolidColor : public nsDisplaySolidColorBase { + public: + nsDisplaySolidColor(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + const nsRect& aBounds, nscolor aColor, + bool aCanBeReused = true) + : nsDisplaySolidColorBase(aBuilder, aFrame, aColor), mBounds(aBounds) { + NS_ASSERTION(NS_GET_A(aColor) > 0, + "Don't create invisible nsDisplaySolidColors!"); + MOZ_COUNT_CTOR(nsDisplaySolidColor); + if (!aCanBeReused) { + SetCantBeReused(); + } + } + + MOZ_COUNTED_DTOR_OVERRIDE(nsDisplaySolidColor) + + NS_DISPLAY_DECL_NAME("SolidColor", TYPE_SOLID_COLOR) + + nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) const override; + LayerState GetLayerState( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aParameters) override; + already_AddRefed<Layer> BuildLayer( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aContainerParameters) override; + void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override; + void WriteDebugInfo(std::stringstream& aStream) override; + bool CreateWebRenderCommands( + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) override; + + int32_t ZIndex() const override { + if (mOverrideZIndex) { + return mOverrideZIndex.value(); + } + return nsDisplaySolidColorBase::ZIndex(); + } + + void SetOverrideZIndex(int32_t aZIndex) { + mOverrideZIndex = mozilla::Some(aZIndex); + } + + private: + nsRect mBounds; + mozilla::Maybe<int32_t> mOverrideZIndex; +}; + +/** + * A display item that renders a solid color over a region. This is not + * exposed through CSS, its only purpose is efficient invalidation of + * the find bar highlighter dimmer. + */ +class nsDisplaySolidColorRegion : public nsPaintedDisplayItem { + typedef mozilla::gfx::sRGBColor sRGBColor; + + public: + nsDisplaySolidColorRegion(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + const nsRegion& aRegion, nscolor aColor) + : nsPaintedDisplayItem(aBuilder, aFrame), + mRegion(aRegion), + mColor(sRGBColor::FromABGR(aColor)) { + NS_ASSERTION(NS_GET_A(aColor) > 0, + "Don't create invisible nsDisplaySolidColorRegions!"); + MOZ_COUNT_CTOR(nsDisplaySolidColorRegion); + } + + MOZ_COUNTED_DTOR_OVERRIDE(nsDisplaySolidColorRegion) + + NS_DISPLAY_DECL_NAME("SolidColorRegion", TYPE_SOLID_COLOR_REGION) + + nsDisplayItemGeometry* AllocateGeometry( + nsDisplayListBuilder* aBuilder) override { + return new nsDisplaySolidColorRegionGeometry(this, aBuilder, mRegion, + mColor); + } + + void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder, + const nsDisplayItemGeometry* aGeometry, + nsRegion* aInvalidRegion) const override { + const nsDisplaySolidColorRegionGeometry* geometry = + static_cast<const nsDisplaySolidColorRegionGeometry*>(aGeometry); + if (mColor == geometry->mColor) { + aInvalidRegion->Xor(geometry->mRegion, mRegion); + } else { + aInvalidRegion->Or(geometry->mRegion.GetBounds(), mRegion.GetBounds()); + } + } + + bool CreateWebRenderCommands( + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) override; + + protected: + nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) const override; + void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override; + void WriteDebugInfo(std::stringstream& aStream) override; + + private: + nsRegion mRegion; + sRGBColor mColor; +}; + +/** + * A display item to paint one background-image for a frame. Each background + * image layer gets its own nsDisplayBackgroundImage. + */ +class nsDisplayBackgroundImage : public nsDisplayImageContainer { + public: + typedef mozilla::StyleGeometryBox StyleGeometryBox; + + struct InitData { + nsDisplayListBuilder* builder; + mozilla::ComputedStyle* backgroundStyle; + nsCOMPtr<imgIContainer> image; + nsRect backgroundRect; + nsRect fillArea; + nsRect destArea; + uint32_t layer; + bool isRasterImage; + bool shouldFixToViewport; + }; + + /** + * aLayer signifies which background layer this item represents. + * aIsThemed should be the value of aFrame->IsThemed. + * aBackgroundStyle should be the result of + * nsCSSRendering::FindBackground, or null if FindBackground returned false. + * aBackgroundRect is relative to aFrame. + */ + static InitData GetInitData(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + uint16_t aLayer, const nsRect& aBackgroundRect, + mozilla::ComputedStyle* aBackgroundStyle); + + explicit nsDisplayBackgroundImage(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame, const InitData& aInitData, + nsIFrame* aFrameForBounds = nullptr); + ~nsDisplayBackgroundImage() override; + + NS_DISPLAY_DECL_NAME("Background", TYPE_BACKGROUND) + + // This will create and append new items for all the layers of the + // background. Returns whether we appended a themed background. + // aAllowWillPaintBorderOptimization should usually be left at true, unless + // aFrame has special border drawing that causes opaque borders to not + // actually be opaque. + static bool AppendBackgroundItemsToTop( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + const nsRect& aBackgroundRect, nsDisplayList* aList, + bool aAllowWillPaintBorderOptimization = true, + mozilla::ComputedStyle* aComputedStyle = nullptr, + const nsRect& aBackgroundOriginRect = nsRect(), + nsIFrame* aSecondaryReferenceFrame = nullptr, + mozilla::Maybe<nsDisplayListBuilder::AutoBuildingDisplayList>* + aAutoBuildingDisplayList = nullptr); + + LayerState GetLayerState( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aParameters) override; + already_AddRefed<Layer> BuildLayer( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aContainerParameters) override; + bool CreateWebRenderCommands( + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) override; + void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect, + HitTestState* aState, nsTArray<nsIFrame*>* aOutFrames) override; + bool ComputeVisibility(nsDisplayListBuilder* aBuilder, + nsRegion* aVisibleRegion) override; + nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder, + bool* aSnap) const override; + mozilla::Maybe<nscolor> IsUniform( + nsDisplayListBuilder* aBuilder) const override; + + /** + * GetBounds() returns the background painting area. + */ + nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) const override; + + void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override; + + /** + * Return the background positioning area. + * (GetBounds() returns the background painting area.) + * Can be called only when mBackgroundStyle is non-null. + */ + nsRect GetPositioningArea() const; + + /** + * Returns true if existing rendered pixels of this display item may need + * to be redrawn if the positioning area size changes but its position does + * not. + * If false, only the changed painting area needs to be redrawn when the + * positioning area size changes but its position does not. + */ + bool RenderingMightDependOnPositioningAreaSizeChange() const; + + nsDisplayItemGeometry* AllocateGeometry( + nsDisplayListBuilder* aBuilder) override { + return new nsDisplayBackgroundGeometry(this, aBuilder); + } + + void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder, + const nsDisplayItemGeometry* aGeometry, + nsRegion* aInvalidRegion) const override; + bool CanOptimizeToImageLayer(LayerManager* aManager, + nsDisplayListBuilder* aBuilder) override; + already_AddRefed<imgIContainer> GetImage() override; + nsRect GetDestRect() const override; + + void UpdateDrawResult(mozilla::image::ImgDrawResult aResult) override { + nsDisplayBackgroundGeometry::UpdateDrawResult(this, aResult); + } + + static nsRegion GetInsideClipRegion(const nsDisplayItem* aItem, + StyleGeometryBox aClip, + const nsRect& aRect, + const nsRect& aBackgroundRect); + + bool ShouldFixToViewport(nsDisplayListBuilder* aBuilder) const override { + return mShouldFixToViewport; + } + + nsIFrame* GetDependentFrame() override { return mDependentFrame; } + + void SetDependentFrame(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame) { + if (!aBuilder->IsRetainingDisplayList()) { + return; + } + mDependentFrame = aFrame; + if (aFrame) { + mDependentFrame->AddDisplayItem(this); + } + } + + void RemoveFrame(nsIFrame* aFrame) override { + if (aFrame == mDependentFrame) { + mDependentFrame = nullptr; + } + nsDisplayImageContainer::RemoveFrame(aFrame); + } + + // Match https://w3c.github.io/paint-timing/#contentful-image + bool IsContentful() const override { + const auto& styleImage = + mBackgroundStyle->StyleBackground()->mImage.mLayers[mLayer].mImage; + + return styleImage.IsSizeAvailable() && styleImage.FinalImage().IsUrl(); + } + + protected: + typedef class mozilla::layers::ImageContainer ImageContainer; + typedef class mozilla::layers::ImageLayer ImageLayer; + + bool CanBuildWebRenderDisplayItems(LayerManager* aManager, + nsDisplayListBuilder* aBuilder); + nsRect GetBoundsInternal(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrameForBounds = nullptr); + + void PaintInternal(nsDisplayListBuilder* aBuilder, gfxContext* aCtx, + const nsRect& aBounds, nsRect* aClipRect); + + // Determine whether we want to be separated into our own layer, independent + // of whether this item can actually be layerized. + enum ImageLayerization { + WHENEVER_POSSIBLE, + ONLY_FOR_SCALING, + NO_LAYER_NEEDED + }; + ImageLayerization ShouldCreateOwnLayer(nsDisplayListBuilder* aBuilder, + LayerManager* aManager); + + // Cache the result of nsCSSRendering::FindBackground. Always null if + // mIsThemed is true or if FindBackground returned false. + RefPtr<mozilla::ComputedStyle> mBackgroundStyle; + nsCOMPtr<imgIContainer> mImage; + nsIFrame* mDependentFrame; + nsRect mBackgroundRect; // relative to the reference frame + nsRect mFillRect; + nsRect mDestRect; + /* Bounds of this display item */ + nsRect mBounds; + uint16_t mLayer; + bool mIsRasterImage; + /* Whether the image should be treated as fixed to the viewport. */ + bool mShouldFixToViewport; + uint32_t mImageFlags; +}; + +/** + * A display item to paint background image for table. For table parts, such + * as row, row group, col, col group, when drawing its background, we'll + * create separate background image display item for its containning cell. + * Those background image display items will reference to same DisplayItemData + * if we keep the mFrame point to cell's ancestor frame. We don't want to this + * happened bacause share same DisplatItemData will cause many bugs. So that + * we let mFrame point to cell frame and store the table type of the ancestor + * frame. And use mFrame and table type as key to generate DisplayItemData to + * avoid sharing DisplayItemData. + * + * Also store ancestor frame as mStyleFrame for all rendering informations. + */ +class nsDisplayTableBackgroundImage : public nsDisplayBackgroundImage { + public: + nsDisplayTableBackgroundImage(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame, const InitData& aInitData, + nsIFrame* aCellFrame); + ~nsDisplayTableBackgroundImage() override; + + NS_DISPLAY_DECL_NAME("TableBackgroundImage", TYPE_TABLE_BACKGROUND_IMAGE) + + bool IsInvalid(nsRect& aRect) const override; + + nsIFrame* FrameForInvalidation() const override { return mStyleFrame; } + + void RemoveFrame(nsIFrame* aFrame) override { + if (aFrame == mStyleFrame) { + mStyleFrame = nullptr; + SetDeletedFrame(); + } + nsDisplayBackgroundImage::RemoveFrame(aFrame); + } + + protected: + nsIFrame* StyleFrame() const override { return mStyleFrame; } + nsIFrame* mStyleFrame; +}; + +/** + * A display item to paint the native theme background for a frame. + */ +class nsDisplayThemedBackground : public nsPaintedDisplayItem { + public: + nsDisplayThemedBackground(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + const nsRect& aBackgroundRect); + + MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayThemedBackground) + + NS_DISPLAY_DECL_NAME("ThemedBackground", TYPE_THEMED_BACKGROUND) + + void Init(nsDisplayListBuilder* aBuilder); + + void Destroy(nsDisplayListBuilder* aBuilder) override { + aBuilder->UnregisterThemeGeometry(this); + nsPaintedDisplayItem::Destroy(aBuilder); + } + + void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect, + HitTestState* aState, nsTArray<nsIFrame*>* aOutFrames) override; + nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder, + bool* aSnap) const override; + mozilla::Maybe<nscolor> IsUniform( + nsDisplayListBuilder* aBuilder) const override; + bool CreateWebRenderCommands( + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) override; + + bool MustPaintOnContentSide() const override { return true; } + + /** + * GetBounds() returns the background painting area. + */ + nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) const override; + + void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override; + + /** + * Return the background positioning area. + * (GetBounds() returns the background painting area.) + * Can be called only when mBackgroundStyle is non-null. + */ + nsRect GetPositioningArea() const; + + /** + * Return whether our frame's document does not have the state + * NS_DOCUMENT_STATE_WINDOW_INACTIVE. + */ + bool IsWindowActive() const; + + nsDisplayItemGeometry* AllocateGeometry( + nsDisplayListBuilder* aBuilder) override { + return new nsDisplayThemedBackgroundGeometry(this, aBuilder); + } + + void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder, + const nsDisplayItemGeometry* aGeometry, + nsRegion* aInvalidRegion) const override; + + void WriteDebugInfo(std::stringstream& aStream) override; + + protected: + nsRect GetBoundsInternal(); + + void PaintInternal(nsDisplayListBuilder* aBuilder, gfxContext* aCtx, + const nsRect& aBounds, nsRect* aClipRect); + + nsRect mBackgroundRect; + nsRect mBounds; + nsITheme::Transparency mThemeTransparency; + mozilla::StyleAppearance mAppearance; +}; + +class nsDisplayTableThemedBackground : public nsDisplayThemedBackground { + public: + nsDisplayTableThemedBackground(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame, + const nsRect& aBackgroundRect, + nsIFrame* aAncestorFrame) + : nsDisplayThemedBackground(aBuilder, aFrame, aBackgroundRect), + mAncestorFrame(aAncestorFrame) { + if (aBuilder->IsRetainingDisplayList()) { + mAncestorFrame->AddDisplayItem(this); + } + } + + ~nsDisplayTableThemedBackground() override { + if (mAncestorFrame) { + mAncestorFrame->RemoveDisplayItem(this); + } + } + + NS_DISPLAY_DECL_NAME("TableThemedBackground", + TYPE_TABLE_THEMED_BACKGROUND_IMAGE) + + nsIFrame* FrameForInvalidation() const override { return mAncestorFrame; } + + void RemoveFrame(nsIFrame* aFrame) override { + if (aFrame == mAncestorFrame) { + mAncestorFrame = nullptr; + SetDeletedFrame(); + } + nsDisplayThemedBackground::RemoveFrame(aFrame); + } + + protected: + nsIFrame* StyleFrame() const override { return mAncestorFrame; } + nsIFrame* mAncestorFrame; +}; + +class nsDisplayBackgroundColor : public nsPaintedDisplayItem { + typedef mozilla::gfx::sRGBColor sRGBColor; + + public: + nsDisplayBackgroundColor(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + const nsRect& aBackgroundRect, + const mozilla::ComputedStyle* aBackgroundStyle, + const nscolor& aColor) + : nsPaintedDisplayItem(aBuilder, aFrame), + mBackgroundRect(aBackgroundRect), + mHasStyle(aBackgroundStyle), + mDependentFrame(nullptr), + mColor(sRGBColor::FromABGR(aColor)) { + mState.mColor = mColor; + + if (mHasStyle) { + mBottomLayerClip = + aBackgroundStyle->StyleBackground()->BottomLayer().mClip; + } else { + MOZ_ASSERT(aBuilder->IsForEventDelivery()); + } + } + + ~nsDisplayBackgroundColor() override { + if (mDependentFrame) { + mDependentFrame->RemoveDisplayItem(this); + } + } + + NS_DISPLAY_DECL_NAME("BackgroundColor", TYPE_BACKGROUND_COLOR) + + bool RestoreState() override { + if (!nsPaintedDisplayItem::RestoreState() && mColor == mState.mColor) { + return false; + } + + mColor = mState.mColor; + return true; + } + + bool HasBackgroundClipText() const { + MOZ_ASSERT(mHasStyle); + return mBottomLayerClip == mozilla::StyleGeometryBox::Text; + } + + LayerState GetLayerState( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aParameters) override; + void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override; + void PaintWithClip(nsDisplayListBuilder* aBuilder, gfxContext* aCtx, + const DisplayItemClip& aClip) override; + already_AddRefed<Layer> BuildLayer( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aContainerParameters) override; + bool CreateWebRenderCommands( + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) override; + nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder, + bool* aSnap) const override; + mozilla::Maybe<nscolor> IsUniform( + nsDisplayListBuilder* aBuilder) const override; + void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect, + HitTestState* aState, nsTArray<nsIFrame*>* aOutFrames) override; + void ApplyOpacity(nsDisplayListBuilder* aBuilder, float aOpacity, + const DisplayItemClipChain* aClip) override; + + bool CanApplyOpacity() const override; + + float GetOpacity() const { return mColor.a; } + + nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) const override { + *aSnap = true; + return mBackgroundRect; + } + + bool CanPaintWithClip(const DisplayItemClip& aClip) override { + if (HasBackgroundClipText()) { + return false; + } + + if (aClip.GetRoundedRectCount() > 1) { + return false; + } + + return true; + } + + nsDisplayItemGeometry* AllocateGeometry( + nsDisplayListBuilder* aBuilder) override { + return new nsDisplaySolidColorGeometry(this, aBuilder, mColor.ToABGR()); + } + + void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder, + const nsDisplayItemGeometry* aGeometry, + nsRegion* aInvalidRegion) const override { + const nsDisplaySolidColorGeometry* geometry = + static_cast<const nsDisplaySolidColorGeometry*>(aGeometry); + + if (mColor.ToABGR() != geometry->mColor) { + bool dummy; + aInvalidRegion->Or(geometry->mBounds, GetBounds(aBuilder, &dummy)); + return; + } + ComputeInvalidationRegionDifference(aBuilder, geometry, aInvalidRegion); + } + + nsIFrame* GetDependentFrame() override { return mDependentFrame; } + + void SetDependentFrame(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame) { + if (!aBuilder->IsRetainingDisplayList()) { + return; + } + mDependentFrame = aFrame; + if (aFrame) { + mDependentFrame->AddDisplayItem(this); + } + } + + void RemoveFrame(nsIFrame* aFrame) override { + if (aFrame == mDependentFrame) { + mDependentFrame = nullptr; + } + + nsPaintedDisplayItem::RemoveFrame(aFrame); + } + + void WriteDebugInfo(std::stringstream& aStream) override; + + bool CanUseAsyncAnimations(nsDisplayListBuilder* aBuilder) override; + + protected: + const nsRect mBackgroundRect; + const bool mHasStyle; + mozilla::StyleGeometryBox mBottomLayerClip; + nsIFrame* mDependentFrame; + mozilla::gfx::sRGBColor mColor; + + struct { + mozilla::gfx::sRGBColor mColor; + } mState; +}; + +class nsDisplayTableBackgroundColor : public nsDisplayBackgroundColor { + public: + nsDisplayTableBackgroundColor(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame, const nsRect& aBackgroundRect, + const mozilla::ComputedStyle* aBackgroundStyle, + const nscolor& aColor, nsIFrame* aAncestorFrame) + : nsDisplayBackgroundColor(aBuilder, aFrame, aBackgroundRect, + aBackgroundStyle, aColor), + mAncestorFrame(aAncestorFrame) { + if (aBuilder->IsRetainingDisplayList()) { + mAncestorFrame->AddDisplayItem(this); + } + } + + ~nsDisplayTableBackgroundColor() override { + if (mAncestorFrame) { + mAncestorFrame->RemoveDisplayItem(this); + } + } + + NS_DISPLAY_DECL_NAME("TableBackgroundColor", TYPE_TABLE_BACKGROUND_COLOR) + + nsIFrame* FrameForInvalidation() const override { return mAncestorFrame; } + + void RemoveFrame(nsIFrame* aFrame) override { + if (aFrame == mAncestorFrame) { + mAncestorFrame = nullptr; + SetDeletedFrame(); + } + nsDisplayBackgroundColor::RemoveFrame(aFrame); + } + + bool CanUseAsyncAnimations(nsDisplayListBuilder* aBuilder) override { + return false; + } + + protected: + nsIFrame* mAncestorFrame; +}; + +/** + * The standard display item to paint the outer CSS box-shadows of a frame. + */ +class nsDisplayBoxShadowOuter final : public nsPaintedDisplayItem { + public: + nsDisplayBoxShadowOuter(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame) + : nsPaintedDisplayItem(aBuilder, aFrame), mOpacity(1.0f) { + MOZ_COUNT_CTOR(nsDisplayBoxShadowOuter); + mBounds = GetBoundsInternal(); + } + + MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayBoxShadowOuter) + + NS_DISPLAY_DECL_NAME("BoxShadowOuter", TYPE_BOX_SHADOW_OUTER) + + bool RestoreState() override { + if (!nsPaintedDisplayItem::RestoreState() && mOpacity == 1.0f && + mVisibleRegion.IsEmpty()) { + return false; + } + + mVisibleRegion.SetEmpty(); + mOpacity = 1.0f; + return true; + } + + void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override; + nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) const override; + bool IsInvisibleInRect(const nsRect& aRect) const override; + bool ComputeVisibility(nsDisplayListBuilder* aBuilder, + nsRegion* aVisibleRegion) override; + void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder, + const nsDisplayItemGeometry* aGeometry, + nsRegion* aInvalidRegion) const override; + + void ApplyOpacity(nsDisplayListBuilder* aBuilder, float aOpacity, + const DisplayItemClipChain* aClip) override { + NS_ASSERTION(CanApplyOpacity(), "ApplyOpacity should be allowed"); + mOpacity = aOpacity; + IntersectClip(aBuilder, aClip, false); + } + + bool CanApplyOpacity() const override { return true; } + + nsDisplayItemGeometry* AllocateGeometry( + nsDisplayListBuilder* aBuilder) override { + return new nsDisplayBoxShadowOuterGeometry(this, aBuilder, mOpacity); + } + + bool CanBuildWebRenderDisplayItems(); + bool CreateWebRenderCommands( + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) override; + nsRect GetBoundsInternal(); + + private: + nsRegion mVisibleRegion; + nsRect mBounds; + float mOpacity; +}; + +/** + * The standard display item to paint the inner CSS box-shadows of a frame. + */ +class nsDisplayBoxShadowInner : public nsPaintedDisplayItem { + public: + nsDisplayBoxShadowInner(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame) + : nsPaintedDisplayItem(aBuilder, aFrame) { + MOZ_COUNT_CTOR(nsDisplayBoxShadowInner); + } + + MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayBoxShadowInner) + + NS_DISPLAY_DECL_NAME("BoxShadowInner", TYPE_BOX_SHADOW_INNER) + + bool RestoreState() override { + if (!nsPaintedDisplayItem::RestoreState() && mVisibleRegion.IsEmpty()) { + return false; + } + + mVisibleRegion.SetEmpty(); + return true; + } + + void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override; + bool ComputeVisibility(nsDisplayListBuilder* aBuilder, + nsRegion* aVisibleRegion) override; + + nsDisplayItemGeometry* AllocateGeometry( + nsDisplayListBuilder* aBuilder) override { + return new nsDisplayBoxShadowInnerGeometry(this, aBuilder); + } + + void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder, + const nsDisplayItemGeometry* aGeometry, + nsRegion* aInvalidRegion) const override { + const nsDisplayBoxShadowInnerGeometry* geometry = + static_cast<const nsDisplayBoxShadowInnerGeometry*>(aGeometry); + if (!geometry->mPaddingRect.IsEqualInterior(GetPaddingRect())) { + // nsDisplayBoxShadowInner is based around the padding rect, but it can + // touch pixels outside of this. We should invalidate the entire bounds. + bool snap; + aInvalidRegion->Or(geometry->mBounds, GetBounds(aBuilder, &snap)); + } + } + + static bool CanCreateWebRenderCommands(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame, + const nsPoint& aReferencePoint); + static void CreateInsetBoxShadowWebRenderCommands( + mozilla::wr::DisplayListBuilder& aBuilder, + const StackingContextHelper& aSc, nsRegion& aVisibleRegion, + nsIFrame* aFrame, const nsRect& aBorderRect); + bool CreateWebRenderCommands( + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) override; + + private: + nsRegion mVisibleRegion; +}; + +/** + * The standard display item to paint the CSS outline of a frame. + */ +class nsDisplayOutline final : public nsPaintedDisplayItem { + public: + nsDisplayOutline(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame) + : nsPaintedDisplayItem(aBuilder, aFrame) { + MOZ_COUNT_CTOR(nsDisplayOutline); + } + + MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayOutline) + + NS_DISPLAY_DECL_NAME("Outline", TYPE_OUTLINE) + + bool MustPaintOnContentSide() const override { + MOZ_ASSERT(IsThemedOutline(), + "The only fallback path we have is for themed outlines"); + return true; + } + + bool CreateWebRenderCommands( + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) override; + bool IsInvisibleInRect(const nsRect& aRect) const override; + nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) const override; + void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override; + + private: + bool IsThemedOutline() const; +}; + +/** + * A class that lets you receive events within the frame bounds but never + * paints. + */ +class nsDisplayEventReceiver final : public nsDisplayItem { + public: + nsDisplayEventReceiver(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame) + : nsDisplayItem(aBuilder, aFrame) { + MOZ_COUNT_CTOR(nsDisplayEventReceiver); + } + + MOZ_COUNTED_DTOR_FINAL(nsDisplayEventReceiver) + + NS_DISPLAY_DECL_NAME("EventReceiver", TYPE_EVENT_RECEIVER) + + void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect, + HitTestState* aState, nsTArray<nsIFrame*>* aOutFrames) final; +}; + +/** + * Similar to nsDisplayEventReceiver in that it is used for hit-testing. However + * this gets built when we're doing widget painting and we need to send the + * compositor some hit-test info for a frame. This is effectively a dummy item + * whose sole purpose is to carry the hit-test info to the compositor. + */ +class nsDisplayCompositorHitTestInfo : public nsDisplayHitTestInfoBase { + public: + nsDisplayCompositorHitTestInfo( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + const mozilla::gfx::CompositorHitTestInfo& aHitTestFlags, + const mozilla::Maybe<nsRect>& aArea = mozilla::Nothing()); + + nsDisplayCompositorHitTestInfo( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + mozilla::UniquePtr<HitTestInfo>&& aHitTestInfo); + + MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayCompositorHitTestInfo) + + NS_DISPLAY_DECL_NAME("CompositorHitTestInfo", TYPE_COMPOSITOR_HITTEST_INFO) + + void InitializeScrollTarget(nsDisplayListBuilder* aBuilder); + + bool CreateWebRenderCommands( + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) override; + + int32_t ZIndex() const override; + void SetOverrideZIndex(int32_t aZIndex); + + /** + * ApplyOpacity() is overriden for opacity flattening. + */ + void ApplyOpacity(nsDisplayListBuilder* aBuilder, float aOpacity, + const DisplayItemClipChain* aClip) override {} + + /** + * CanApplyOpacity() is overriden for opacity flattening. + */ + bool CanApplyOpacity() const override { return true; } + + nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) const override { + *aSnap = false; + return nsRect(); + } + + private: + mozilla::Maybe<mozilla::layers::ScrollableLayerGuid::ViewID> mScrollTarget; + mozilla::Maybe<int32_t> mOverrideZIndex; + int32_t mAppUnitsPerDevPixel; +}; + +/** + * A class that lets you wrap a display list as a display item. + * + * GetUnderlyingFrame() is troublesome for wrapped lists because if the wrapped + * list has many items, it's not clear which one has the 'underlying frame'. + * Thus we force the creator to specify what the underlying frame is. The + * underlying frame should be the root of a stacking context, because sorting + * a list containing this item will not get at the children. + * + * In some cases (e.g., clipping) we want to wrap a list but we don't have a + * particular underlying frame that is a stacking context root. In that case + * we allow the frame to be nullptr. Callers to GetUnderlyingFrame must + * detect and handle this case. + */ +class nsDisplayWrapList : public nsDisplayHitTestInfoBase { + public: + /** + * Takes all the items from aList and puts them in our list. + */ + nsDisplayWrapList(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + nsDisplayList* aList); + + nsDisplayWrapList(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + nsDisplayItem* aItem); + + nsDisplayWrapList(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + nsDisplayList* aList, + const ActiveScrolledRoot* aActiveScrolledRoot, + bool aClearClipChain = false); + + nsDisplayWrapList(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame) + : nsDisplayHitTestInfoBase(aBuilder, aFrame), + mFrameActiveScrolledRoot(aBuilder->CurrentActiveScrolledRoot()), + mOverrideZIndex(0), + mHasZIndexOverride(false) { + MOZ_COUNT_CTOR(nsDisplayWrapList); + mBaseBuildingRect = GetBuildingRect(); + mListPtr = &mList; + } + + nsDisplayWrapList() = delete; + + /** + * A custom copy-constructor that does not copy mList, as this would mutate + * the other item. + */ + nsDisplayWrapList(const nsDisplayWrapList& aOther) = delete; + nsDisplayWrapList(nsDisplayListBuilder* aBuilder, + const nsDisplayWrapList& aOther) + : nsDisplayHitTestInfoBase(aBuilder, aOther), + mListPtr(&mList), + mFrameActiveScrolledRoot(aOther.mFrameActiveScrolledRoot), + mMergedFrames(aOther.mMergedFrames.Clone()), + mBounds(aOther.mBounds), + mBaseBuildingRect(aOther.mBaseBuildingRect), + mOverrideZIndex(aOther.mOverrideZIndex), + mHasZIndexOverride(aOther.mHasZIndexOverride), + mClearingClipChain(aOther.mClearingClipChain) { + MOZ_COUNT_CTOR(nsDisplayWrapList); + } + + ~nsDisplayWrapList() override; + + NS_DISPLAY_DECL_NAME("WrapList", TYPE_WRAP_LIST) + + const nsDisplayWrapList* AsDisplayWrapList() const final { return this; } + nsDisplayWrapList* AsDisplayWrapList() final { return this; } + + void Destroy(nsDisplayListBuilder* aBuilder) override { + mList.DeleteAll(aBuilder); + nsDisplayHitTestInfoBase::Destroy(aBuilder); + } + + /** + * Creates a new nsDisplayWrapList that holds a pointer to the display list + * owned by the given nsDisplayItem. The new nsDisplayWrapList will be added + * to the bottom of this item's contents. + */ + void MergeDisplayListFromItem(nsDisplayListBuilder* aBuilder, + const nsDisplayWrapList* aItem); + + /** + * Call this if the wrapped list is changed. + */ + void UpdateBounds(nsDisplayListBuilder* aBuilder) override { + // Clear the clip chain up to the asr, but don't store it, so that we'll + // recover it when we reuse the item. + if (mClearingClipChain) { + const DisplayItemClipChain* clip = mState.mClipChain; + while (clip && ActiveScrolledRoot::IsAncestor(GetActiveScrolledRoot(), + clip->mASR)) { + clip = clip->mParent; + } + SetClipChain(clip, false); + } + + nsRect buildingRect; + mBounds = mListPtr->GetClippedBoundsWithRespectToASR( + aBuilder, mActiveScrolledRoot, &buildingRect); + // The display list may contain content that's visible outside the visible + // rect (i.e. the current dirty rect) passed in when the item was created. + // This happens when the dirty rect has been restricted to the visual + // overflow rect of a frame for some reason (e.g. when setting up dirty + // rects in nsDisplayListBuilder::MarkOutOfFlowFrameForDisplay), but that + // frame contains placeholders for out-of-flows that aren't descendants of + // the frame. + buildingRect.UnionRect(mBaseBuildingRect, buildingRect); + SetBuildingRect(buildingRect); + } + + void SetActiveScrolledRoot( + const ActiveScrolledRoot* aActiveScrolledRoot) override { + // Skip unnecessary call to + // |nsDisplayHitTestInfoBase::UpdateHitTestInfoActiveScrolledRoot()|, since + // callers will manually call that with different ASR. + nsDisplayHitTestInfoBase::SetActiveScrolledRoot(aActiveScrolledRoot); + } + + void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect, + HitTestState* aState, nsTArray<nsIFrame*>* aOutFrames) override; + nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) const override; + nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder, + bool* aSnap) const override; + mozilla::Maybe<nscolor> IsUniform( + nsDisplayListBuilder* aBuilder) const override; + void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override; + bool ComputeVisibility(nsDisplayListBuilder* aBuilder, + nsRegion* aVisibleRegion) override; + + /** + * Checks if the given display item can be merged with this item. + * @return true if the merging is possible, otherwise false. + */ + virtual bool CanMerge(const nsDisplayItem* aItem) const { return false; } + + /** + * Try to merge with the other item (which is below us in the display + * list). This gets used by nsDisplayClip to coalesce clipping operations + * (optimization), by nsDisplayOpacity to merge rendering for the same + * content element into a single opacity group (correctness), and will be + * used by nsDisplayOutline to merge multiple outlines for the same element + * (also for correctness). + */ + virtual void Merge(const nsDisplayItem* aItem) { + MOZ_ASSERT(CanMerge(aItem)); + MOZ_ASSERT(Frame() != aItem->Frame()); + MergeFromTrackingMergedFrames(static_cast<const nsDisplayWrapList*>(aItem)); + } + + /** + * Returns the underlying frames of all display items that have been + * merged into this one (excluding this item's own underlying frame) + * to aFrames. + */ + const nsTArray<nsIFrame*>& GetMergedFrames() const { return mMergedFrames; } + + bool HasMergedFrames() const { return !mMergedFrames.IsEmpty(); } + + bool ShouldFlattenAway(nsDisplayListBuilder* aBuilder) override { + return true; + } + + bool IsInvalid(nsRect& aRect) const override { + if (mFrame->IsInvalid(aRect) && aRect.IsEmpty()) { + return true; + } + nsRect temp; + for (uint32_t i = 0; i < mMergedFrames.Length(); i++) { + if (mMergedFrames[i]->IsInvalid(temp) && temp.IsEmpty()) { + aRect.SetEmpty(); + return true; + } + aRect = aRect.Union(temp); + } + aRect += ToReferenceFrame(); + return !aRect.IsEmpty(); + } + + nsRect GetComponentAlphaBounds(nsDisplayListBuilder* aBuilder) const override; + + RetainedDisplayList* GetSameCoordinateSystemChildren() const override { + NS_ASSERTION( + mListPtr->IsEmpty() || !ReferenceFrame() || + !mListPtr->GetBottom()->ReferenceFrame() || + mListPtr->GetBottom()->ReferenceFrame() == ReferenceFrame(), + "Children must have same reference frame"); + return mListPtr; + } + + RetainedDisplayList* GetChildren() const override { return mListPtr; } + + int32_t ZIndex() const override { + return (mHasZIndexOverride) ? mOverrideZIndex + : nsDisplayHitTestInfoBase::ZIndex(); + } + + void SetOverrideZIndex(int32_t aZIndex) { + mHasZIndexOverride = true; + mOverrideZIndex = aZIndex; + } + + /** + * This creates a copy of this item, but wrapping aItem instead of + * our existing list. Only gets called if this item returned nullptr + * for GetUnderlyingFrame(). aItem is guaranteed to return non-null from + * GetUnderlyingFrame(). + */ + nsDisplayWrapList* WrapWithClone(nsDisplayListBuilder* aBuilder, + nsDisplayItem* aItem) { + MOZ_ASSERT_UNREACHABLE("We never returned nullptr for GetUnderlyingFrame!"); + return nullptr; + } + + bool CreateWebRenderCommands( + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) override; + + const ActiveScrolledRoot* GetFrameActiveScrolledRoot() { + return mFrameActiveScrolledRoot; + } + + protected: + void MergeFromTrackingMergedFrames(const nsDisplayWrapList* aOther) { + mBounds.UnionRect(mBounds, aOther->mBounds); + nsRect buildingRect; + buildingRect.UnionRect(GetBuildingRect(), aOther->GetBuildingRect()); + SetBuildingRect(buildingRect); + mMergedFrames.AppendElement(aOther->mFrame); + mMergedFrames.AppendElements(aOther->mMergedFrames.Clone()); + } + + RetainedDisplayList mList; + RetainedDisplayList* mListPtr; + // The active scrolled root for the frame that created this + // wrap list. + RefPtr<const ActiveScrolledRoot> mFrameActiveScrolledRoot; + // The frames from items that have been merged into this item, excluding + // this item's own frame. + nsTArray<nsIFrame*> mMergedFrames; + nsRect mBounds; + // Displaylist building rect contributed by this display item itself. + // Our mBuildingRect may include the visible areas of children. + nsRect mBaseBuildingRect; + int32_t mOverrideZIndex; + bool mHasZIndexOverride; + bool mClearingClipChain = false; + + private: + NS_DISPLAY_ALLOW_CLONING() + friend class nsDisplayListBuilder; +}; + +/** + * We call WrapDisplayList on the in-flow lists: BorderBackground(), + * BlockBorderBackgrounds() and Content(). + * We call WrapDisplayItem on each item of Outlines(), PositionedDescendants(), + * and Floats(). This is done to support special wrapping processing for frames + * that may not be in-flow descendants of the current frame. + */ +class nsDisplayWrapper { + public: + // This is never instantiated directly (it has pure virtual methods), so no + // need to count constructors and destructors. + + bool WrapBorderBackground() { return true; } + virtual nsDisplayItem* WrapList(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame, nsDisplayList* aList) = 0; + virtual nsDisplayItem* WrapItem(nsDisplayListBuilder* aBuilder, + nsDisplayItem* aItem) = 0; + + nsresult WrapLists(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + const nsDisplayListSet& aIn, const nsDisplayListSet& aOut); + nsresult WrapListsInPlace(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + const nsDisplayListSet& aLists); + + protected: + nsDisplayWrapper() = default; +}; + +/** + * The standard display item to paint a stacking context with translucency + * set by the stacking context root frame's 'opacity' style. + */ +class nsDisplayOpacity : public nsDisplayWrapList { + public: + nsDisplayOpacity(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + nsDisplayList* aList, + const ActiveScrolledRoot* aActiveScrolledRoot, + bool aForEventsAndPluginsOnly, bool aNeedsActiveLayer); + + nsDisplayOpacity(nsDisplayListBuilder* aBuilder, + const nsDisplayOpacity& aOther) + : nsDisplayWrapList(aBuilder, aOther), + mOpacity(aOther.mOpacity), + mForEventsAndPluginsOnly(aOther.mForEventsAndPluginsOnly), + mNeedsActiveLayer(aOther.mNeedsActiveLayer), + mChildOpacityState(ChildOpacityState::Unknown) { + MOZ_COUNT_CTOR(nsDisplayOpacity); + // We should not try to merge flattened opacities. + MOZ_ASSERT(aOther.mChildOpacityState != ChildOpacityState::Applied); + } + + void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect, + HitTestState* aState, nsTArray<nsIFrame*>* aOutFrames) override; + + MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayOpacity) + + NS_DISPLAY_DECL_NAME("Opacity", TYPE_OPACITY) + + bool RestoreState() override { + if (!nsDisplayWrapList::RestoreState() && mOpacity == mState.mOpacity) { + return false; + } + + mOpacity = mState.mOpacity; + return true; + } + + void InvalidateCachedChildInfo(nsDisplayListBuilder* aBuilder) override { + mChildOpacityState = ChildOpacityState::Unknown; + } + + nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder, + bool* aSnap) const override; + already_AddRefed<Layer> BuildLayer( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aContainerParameters) override; + LayerState GetLayerState( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aParameters) override; + bool ComputeVisibility(nsDisplayListBuilder* aBuilder, + nsRegion* aVisibleRegion) override; + + bool CanMerge(const nsDisplayItem* aItem) const override { + // items for the same content element should be merged into a single + // compositing group + // aItem->GetUnderlyingFrame() returns non-null because it's + // nsDisplayOpacity + return HasDifferentFrame(aItem) && HasSameTypeAndClip(aItem) && + HasSameContent(aItem); + } + + nsDisplayItemGeometry* AllocateGeometry( + nsDisplayListBuilder* aBuilder) override { + return new nsDisplayOpacityGeometry(this, aBuilder, mOpacity); + } + + void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder, + const nsDisplayItemGeometry* aGeometry, + nsRegion* aInvalidRegion) const override; + + bool IsInvalid(nsRect& aRect) const override { + if (mForEventsAndPluginsOnly) { + return false; + } + return nsDisplayWrapList::IsInvalid(aRect); + } + void ApplyOpacity(nsDisplayListBuilder* aBuilder, float aOpacity, + const DisplayItemClipChain* aClip) override; + bool CanApplyOpacity() const override; + bool ShouldFlattenAway(nsDisplayListBuilder* aBuilder) override; + + bool NeedsGeometryUpdates() const override { + // For flattened nsDisplayOpacity items, ComputeInvalidationRegion() only + // handles invalidation for changed |mOpacity|. In order to keep track of + // the current bounds of the item for invalidation, nsDisplayOpacityGeometry + // for the corresponding DisplayItemData needs to be updated, even if the + // reported invalidation region is empty. + return mChildOpacityState == ChildOpacityState::Deferred; + } + + /** + * Returns true if ShouldFlattenAway() applied opacity to children. + */ + bool OpacityAppliedToChildren() const { + return mChildOpacityState == ChildOpacityState::Applied; + } + + static bool NeedsActiveLayer(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + bool aEnforceMinimumSize = true); + void WriteDebugInfo(std::stringstream& aStream) override; + bool CanUseAsyncAnimations(nsDisplayListBuilder* aBuilder) override; + bool CreateWebRenderCommands( + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) override; + + float GetOpacity() const { return mOpacity; } + + private: + NS_DISPLAY_ALLOW_CLONING() + + bool ApplyToChildren(nsDisplayListBuilder* aBuilder); + bool ApplyToFilterOrMask(const bool aUsingLayers); + + float mOpacity; + bool mForEventsAndPluginsOnly : 1; + enum class ChildOpacityState : uint8_t { + // Our child list has changed since the last time ApplyToChildren was + // called. + Unknown, + // Our children defer opacity handling to us. + Deferred, + // Opacity is applied to our children. + Applied + }; + bool mNeedsActiveLayer : 1; +#ifndef __GNUC__ + ChildOpacityState mChildOpacityState : 2; +#else + ChildOpacityState mChildOpacityState; +#endif + + struct { + float mOpacity; + } mState; +}; + +class nsDisplayBlendMode : public nsDisplayWrapList { + public: + nsDisplayBlendMode(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + nsDisplayList* aList, mozilla::StyleBlend aBlendMode, + const ActiveScrolledRoot* aActiveScrolledRoot, + const bool aIsForBackground); + nsDisplayBlendMode(nsDisplayListBuilder* aBuilder, + const nsDisplayBlendMode& aOther) + : nsDisplayWrapList(aBuilder, aOther), + mBlendMode(aOther.mBlendMode), + mIsForBackground(aOther.mIsForBackground) { + MOZ_COUNT_CTOR(nsDisplayBlendMode); + } + + MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayBlendMode) + + NS_DISPLAY_DECL_NAME("BlendMode", TYPE_BLEND_MODE) + + nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder, + bool* aSnap) const override; + already_AddRefed<Layer> BuildLayer( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aContainerParameters) override; + void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder, + const nsDisplayItemGeometry* aGeometry, + nsRegion* aInvalidRegion) const override { + // We don't need to compute an invalidation region since we have + // LayerTreeInvalidation + } + + LayerState GetLayerState( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aParameters) override; + bool CreateWebRenderCommands( + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) override; + bool ComputeVisibility(nsDisplayListBuilder* aBuilder, + nsRegion* aVisibleRegion) override; + + bool CanMerge(const nsDisplayItem* aItem) const override; + + bool ShouldFlattenAway(nsDisplayListBuilder* aBuilder) override { + return false; + } + + mozilla::gfx::CompositionOp BlendMode(); + + protected: + mozilla::StyleBlend mBlendMode; + bool mIsForBackground; + + private: + NS_DISPLAY_ALLOW_CLONING() +}; + +class nsDisplayTableBlendMode : public nsDisplayBlendMode { + public: + nsDisplayTableBlendMode(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + nsDisplayList* aList, mozilla::StyleBlend aBlendMode, + const ActiveScrolledRoot* aActiveScrolledRoot, + nsIFrame* aAncestorFrame, const bool aIsForBackground) + : nsDisplayBlendMode(aBuilder, aFrame, aList, aBlendMode, + aActiveScrolledRoot, aIsForBackground), + mAncestorFrame(aAncestorFrame) { + if (aBuilder->IsRetainingDisplayList()) { + mAncestorFrame->AddDisplayItem(this); + } + } + + nsDisplayTableBlendMode(nsDisplayListBuilder* aBuilder, + const nsDisplayTableBlendMode& aOther) + : nsDisplayBlendMode(aBuilder, aOther), + mAncestorFrame(aOther.mAncestorFrame) { + if (aBuilder->IsRetainingDisplayList()) { + mAncestorFrame->AddDisplayItem(this); + } + } + + ~nsDisplayTableBlendMode() override { + if (mAncestorFrame) { + mAncestorFrame->RemoveDisplayItem(this); + } + } + + NS_DISPLAY_DECL_NAME("TableBlendMode", TYPE_TABLE_BLEND_MODE) + + nsIFrame* FrameForInvalidation() const override { return mAncestorFrame; } + + void RemoveFrame(nsIFrame* aFrame) override { + if (aFrame == mAncestorFrame) { + mAncestorFrame = nullptr; + SetDeletedFrame(); + } + nsDisplayBlendMode::RemoveFrame(aFrame); + } + + protected: + nsIFrame* mAncestorFrame; + + private: + NS_DISPLAY_ALLOW_CLONING() +}; + +class nsDisplayBlendContainer : public nsDisplayWrapList { + public: + static nsDisplayBlendContainer* CreateForMixBlendMode( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayList* aList, + const ActiveScrolledRoot* aActiveScrolledRoot); + + static nsDisplayBlendContainer* CreateForBackgroundBlendMode( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + nsIFrame* aSecondaryFrame, nsDisplayList* aList, + const ActiveScrolledRoot* aActiveScrolledRoot); + + MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayBlendContainer) + + NS_DISPLAY_DECL_NAME("BlendContainer", TYPE_BLEND_CONTAINER) + + already_AddRefed<Layer> BuildLayer( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aContainerParameters) override; + LayerState GetLayerState( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aParameters) override; + bool CreateWebRenderCommands( + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) override; + + bool CanMerge(const nsDisplayItem* aItem) const override { + // Items for the same content element should be merged into a single + // compositing group. + return HasDifferentFrame(aItem) && HasSameTypeAndClip(aItem) && + HasSameContent(aItem) && + mIsForBackground == + static_cast<const nsDisplayBlendContainer*>(aItem) + ->mIsForBackground; + } + + bool ShouldFlattenAway(nsDisplayListBuilder* aBuilder) override { + return false; + } + + protected: + nsDisplayBlendContainer(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + nsDisplayList* aList, + const ActiveScrolledRoot* aActiveScrolledRoot, + bool aIsForBackground); + nsDisplayBlendContainer(nsDisplayListBuilder* aBuilder, + const nsDisplayBlendContainer& aOther) + : nsDisplayWrapList(aBuilder, aOther), + mIsForBackground(aOther.mIsForBackground) { + MOZ_COUNT_CTOR(nsDisplayBlendContainer); + } + + // Used to distinguish containers created at building stacking + // context or appending background. + bool mIsForBackground; + + private: + NS_DISPLAY_ALLOW_CLONING() +}; + +class nsDisplayTableBlendContainer : public nsDisplayBlendContainer { + public: + NS_DISPLAY_DECL_NAME("TableBlendContainer", TYPE_TABLE_BLEND_CONTAINER) + + nsIFrame* FrameForInvalidation() const override { return mAncestorFrame; } + + void RemoveFrame(nsIFrame* aFrame) override { + if (aFrame == mAncestorFrame) { + mAncestorFrame = nullptr; + SetDeletedFrame(); + } + nsDisplayBlendContainer::RemoveFrame(aFrame); + } + + protected: + nsDisplayTableBlendContainer(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + nsDisplayList* aList, + const ActiveScrolledRoot* aActiveScrolledRoot, + bool aIsForBackground, nsIFrame* aAncestorFrame) + : nsDisplayBlendContainer(aBuilder, aFrame, aList, aActiveScrolledRoot, + aIsForBackground), + mAncestorFrame(aAncestorFrame) { + if (aBuilder->IsRetainingDisplayList()) { + mAncestorFrame->AddDisplayItem(this); + } + } + + nsDisplayTableBlendContainer(nsDisplayListBuilder* aBuilder, + const nsDisplayTableBlendContainer& aOther) + : nsDisplayBlendContainer(aBuilder, aOther), + mAncestorFrame(aOther.mAncestorFrame) {} + + ~nsDisplayTableBlendContainer() override { + if (mAncestorFrame) { + mAncestorFrame->RemoveDisplayItem(this); + } + } + + nsIFrame* mAncestorFrame; + + private: + NS_DISPLAY_ALLOW_CLONING() +}; + +/** + * nsDisplayOwnLayer constructor flags. If we nest this class inside + * nsDisplayOwnLayer then we can't forward-declare it up at the top of this + * file and that makes it hard to use in all the places that we need to use it. + */ +enum class nsDisplayOwnLayerFlags { + None = 0, + GenerateSubdocInvalidations = 1 << 0, + GenerateScrollableLayer = 1 << 1, +}; + +MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(nsDisplayOwnLayerFlags) + +/** + * A display item that has no purpose but to ensure its contents get + * their own layer. + */ +class nsDisplayOwnLayer : public nsDisplayWrapList { + public: + typedef mozilla::layers::ScrollbarData ScrollbarData; + + enum OwnLayerType { + OwnLayerForTransformWithRoundedClip, + OwnLayerForStackingContext, + OwnLayerForImageBoxFrame, + OwnLayerForScrollbar, + OwnLayerForScrollThumb, + OwnLayerForSubdoc, + OwnLayerForBoxFrame + }; + + /** + * @param aFlags eGenerateSubdocInvalidations : + * Add UserData to the created ContainerLayer, so that invalidations + * for this layer are send to our nsPresContext. + * eGenerateScrollableLayer : only valid on nsDisplaySubDocument (and + * subclasses), indicates this layer is to be a scrollable layer, so call + * ComputeFrameMetrics, etc. + * @param aScrollTarget when eVerticalScrollbar or eHorizontalScrollbar + * is set in the flags, this parameter should be the ViewID of the + * scrollable content this scrollbar is for. + */ + nsDisplayOwnLayer( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayList* aList, + const ActiveScrolledRoot* aActiveScrolledRoot, + nsDisplayOwnLayerFlags aFlags = nsDisplayOwnLayerFlags::None, + const ScrollbarData& aScrollbarData = ScrollbarData{}, + bool aForceActive = true, bool aClearClipChain = false); + + nsDisplayOwnLayer(nsDisplayListBuilder* aBuilder, + const nsDisplayOwnLayer& aOther) + : nsDisplayWrapList(aBuilder, aOther), + mFlags(aOther.mFlags), + mScrollbarData(aOther.mScrollbarData), + mForceActive(aOther.mForceActive), + mWrAnimationId(aOther.mWrAnimationId) { + MOZ_COUNT_CTOR(nsDisplayOwnLayer); + } + + MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayOwnLayer) + + NS_DISPLAY_DECL_NAME("OwnLayer", TYPE_OWN_LAYER) + + already_AddRefed<Layer> BuildLayer( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aContainerParameters) override; + bool CreateWebRenderCommands( + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) override; + bool UpdateScrollData( + mozilla::layers::WebRenderScrollData* aData, + mozilla::layers::WebRenderLayerScrollData* aLayerData) override; + LayerState GetLayerState( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aParameters) override; + + bool CanMerge(const nsDisplayItem* aItem) const override { + // Don't allow merging, each sublist must have its own layer + return false; + } + + bool ShouldFlattenAway(nsDisplayListBuilder* aBuilder) override { + return false; + } + + void WriteDebugInfo(std::stringstream& aStream) override; + nsDisplayOwnLayerFlags GetFlags() { return mFlags; } + bool IsScrollThumbLayer() const; + bool IsScrollbarContainer() const; + bool IsRootScrollbarContainer() const; + bool IsZoomingLayer() const; + bool IsFixedPositionLayer() const; + bool IsStickyPositionLayer() const; + bool HasDynamicToolbar() const; + + protected: + nsDisplayOwnLayerFlags mFlags; + + /** + * If this nsDisplayOwnLayer represents a scroll thumb layer or a + * scrollbar container layer, mScrollbarData stores information + * about the scrollbar. Otherwise, mScrollbarData will be + * default-constructed (in particular with mDirection == Nothing()) + * and can be ignored. + */ + ScrollbarData mScrollbarData; + bool mForceActive; + uint64_t mWrAnimationId; +}; + +/** + * A display item for subdocuments. This is more or less the same as + * nsDisplayOwnLayer, except that it always populates the FrameMetrics instance + * on the ContainerLayer it builds. + */ +class nsDisplaySubDocument : public nsDisplayOwnLayer { + public: + nsDisplaySubDocument(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + nsSubDocumentFrame* aSubDocFrame, nsDisplayList* aList, + nsDisplayOwnLayerFlags aFlags); + ~nsDisplaySubDocument() override; + + NS_DISPLAY_DECL_NAME("SubDocument", TYPE_SUBDOCUMENT) + + nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) const override; + + virtual nsSubDocumentFrame* SubDocumentFrame() { return mSubDocFrame; } + + bool ComputeVisibility(nsDisplayListBuilder* aBuilder, + nsRegion* aVisibleRegion) override; + bool ShouldFlattenAway(nsDisplayListBuilder* aBuilder) override { + return mShouldFlatten; + } + + void SetShouldFlattenAway(bool aShouldFlatten) { + mShouldFlatten = aShouldFlatten; + } + + LayerState GetLayerState( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aParameters) override { + if (mShouldFlatten) { + return mozilla::LayerState::LAYER_NONE; + } + return nsDisplayOwnLayer::GetLayerState(aBuilder, aManager, aParameters); + } + + nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder, + bool* aSnap) const override; + + nsIFrame* FrameForInvalidation() const override; + void RemoveFrame(nsIFrame* aFrame) override; + + void Disown(); + + protected: + ViewID mScrollParentId; + bool mForceDispatchToContentRegion; + bool mShouldFlatten; + nsSubDocumentFrame* mSubDocFrame; +}; + +/** + * A display item used to represent sticky position elements. The contents + * gets its own layer and creates a stacking context, and the layer will have + * position-related metadata set on it. + */ +class nsDisplayStickyPosition : public nsDisplayOwnLayer { + public: + nsDisplayStickyPosition(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + nsDisplayList* aList, + const ActiveScrolledRoot* aActiveScrolledRoot, + const ActiveScrolledRoot* aContainerASR, + bool aClippedToDisplayPort); + nsDisplayStickyPosition(nsDisplayListBuilder* aBuilder, + const nsDisplayStickyPosition& aOther) + : nsDisplayOwnLayer(aBuilder, aOther), + mContainerASR(aOther.mContainerASR), + mClippedToDisplayPort(aOther.mClippedToDisplayPort) { + MOZ_COUNT_CTOR(nsDisplayStickyPosition); + } + + MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayStickyPosition) + + void SetClipChain(const DisplayItemClipChain* aClipChain, + bool aStore) override; + bool IsClippedToDisplayPort() const { return mClippedToDisplayPort; } + + already_AddRefed<Layer> BuildLayer( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aContainerParameters) override; + NS_DISPLAY_DECL_NAME("StickyPosition", TYPE_STICKY_POSITION) + LayerState GetLayerState( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aParameters) override { + return mozilla::LayerState::LAYER_ACTIVE; + } + + bool CreateWebRenderCommands( + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) override; + + bool UpdateScrollData( + mozilla::layers::WebRenderScrollData* aData, + mozilla::layers::WebRenderLayerScrollData* aLayerData) override; + + const ActiveScrolledRoot* GetContainerASR() const { return mContainerASR; } + + private: + NS_DISPLAY_ALLOW_CLONING() + + void CalculateLayerScrollRanges( + mozilla::StickyScrollContainer* aStickyScrollContainer, + float aAppUnitsPerDevPixel, float aScaleX, float aScaleY, + mozilla::LayerRectAbsolute& aStickyOuter, + mozilla::LayerRectAbsolute& aStickyInner); + + mozilla::StickyScrollContainer* GetStickyScrollContainer(); + + // This stores the ASR that this sticky container item would have assuming it + // has no fixed descendants. This may be the same as the ASR returned by + // GetActiveScrolledRoot(), or it may be a descendant of that. + RefPtr<const ActiveScrolledRoot> mContainerASR; + // This flag tracks if this sticky item is just clipped to the enclosing + // scrollframe's displayport, or if there are additional clips in play. In + // the former case, we can skip setting the displayport clip as the scrolled- + // clip of the corresponding layer. This allows sticky items to remain + // unclipped when the enclosing scrollframe is scrolled past the displayport. + // i.e. when the rest of the scrollframe checkerboards, the sticky item will + // not. This makes sense to do because the sticky item has abnormal scrolling + // behavior and may still be visible even if the rest of the scrollframe is + // checkerboarded. Note that the sticky item will still be subject to the + // scrollport clip. + bool mClippedToDisplayPort; +}; + +class nsDisplayFixedPosition : public nsDisplayOwnLayer { + public: + nsDisplayFixedPosition(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + nsDisplayList* aList, + const ActiveScrolledRoot* aActiveScrolledRoot, + const ActiveScrolledRoot* aContainerASR); + nsDisplayFixedPosition(nsDisplayListBuilder* aBuilder, + const nsDisplayFixedPosition& aOther) + : nsDisplayOwnLayer(aBuilder, aOther), + mAnimatedGeometryRootForScrollMetadata( + aOther.mAnimatedGeometryRootForScrollMetadata), + mContainerASR(aOther.mContainerASR), + mIsFixedBackground(aOther.mIsFixedBackground) { + MOZ_COUNT_CTOR(nsDisplayFixedPosition); + } + + static nsDisplayFixedPosition* CreateForFixedBackground( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + nsIFrame* aSecondaryFrame, nsDisplayBackgroundImage* aImage, + const uint16_t aIndex); + + MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayFixedPosition) + + NS_DISPLAY_DECL_NAME("FixedPosition", TYPE_FIXED_POSITION) + + already_AddRefed<Layer> BuildLayer( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aContainerParameters) override; + + LayerState GetLayerState( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aParameters) override { + return mozilla::LayerState::LAYER_ACTIVE_FORCE; + } + + bool ShouldFixToViewport(nsDisplayListBuilder* aBuilder) const override { + return mIsFixedBackground; + } + + AnimatedGeometryRoot* AnimatedGeometryRootForScrollMetadata() const override { + return mAnimatedGeometryRootForScrollMetadata; + } + + bool CreateWebRenderCommands( + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) override; + bool UpdateScrollData( + mozilla::layers::WebRenderScrollData* aData, + mozilla::layers::WebRenderLayerScrollData* aLayerData) override; + void WriteDebugInfo(std::stringstream& aStream) override; + + protected: + // For background-attachment:fixed + nsDisplayFixedPosition(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + nsDisplayList* aList); + void Init(nsDisplayListBuilder* aBuilder); + ViewID GetScrollTargetId(); + + RefPtr<AnimatedGeometryRoot> mAnimatedGeometryRootForScrollMetadata; + RefPtr<const ActiveScrolledRoot> mContainerASR; + bool mIsFixedBackground; + + private: + NS_DISPLAY_ALLOW_CLONING() +}; + +class nsDisplayTableFixedPosition : public nsDisplayFixedPosition { + public: + NS_DISPLAY_DECL_NAME("TableFixedPosition", TYPE_TABLE_FIXED_POSITION) + + nsIFrame* FrameForInvalidation() const override { return mAncestorFrame; } + + void RemoveFrame(nsIFrame* aFrame) override { + if (aFrame == mAncestorFrame) { + mAncestorFrame = nullptr; + SetDeletedFrame(); + } + nsDisplayFixedPosition::RemoveFrame(aFrame); + } + + protected: + nsDisplayTableFixedPosition(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + nsDisplayList* aList, nsIFrame* aAncestorFrame); + + nsDisplayTableFixedPosition(nsDisplayListBuilder* aBuilder, + const nsDisplayTableFixedPosition& aOther) + : nsDisplayFixedPosition(aBuilder, aOther), + mAncestorFrame(aOther.mAncestorFrame) {} + + ~nsDisplayTableFixedPosition() override { + if (mAncestorFrame) { + mAncestorFrame->RemoveDisplayItem(this); + } + } + + nsIFrame* mAncestorFrame; + + private: + NS_DISPLAY_ALLOW_CLONING() +}; + +/** + * This creates an empty scrollable layer. It has no child layers. + * It is used to record the existence of a scrollable frame in the layer + * tree. + */ +class nsDisplayScrollInfoLayer : public nsDisplayWrapList { + public: + nsDisplayScrollInfoLayer(nsDisplayListBuilder* aBuilder, + nsIFrame* aScrolledFrame, nsIFrame* aScrollFrame, + const CompositorHitTestInfo& aHitInfo, + const nsRect& aHitArea); + + MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayScrollInfoLayer) + + NS_DISPLAY_DECL_NAME("ScrollInfoLayer", TYPE_SCROLL_INFO_LAYER) + + already_AddRefed<Layer> BuildLayer( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aContainerParameters) override; + + nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder, + bool* aSnap) const override { + *aSnap = false; + return nsRegion(); + } + + LayerState GetLayerState( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aParameters) override; + + bool ShouldFlattenAway(nsDisplayListBuilder* aBuilder) override { + return false; + } + + void WriteDebugInfo(std::stringstream& aStream) override; + mozilla::UniquePtr<ScrollMetadata> ComputeScrollMetadata( + nsDisplayListBuilder* aBuilder, LayerManager* aLayerManager, + const ContainerLayerParameters& aContainerParameters); + bool UpdateScrollData( + mozilla::layers::WebRenderScrollData* aData, + mozilla::layers::WebRenderLayerScrollData* aLayerData) override; + bool CreateWebRenderCommands( + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) override; + + protected: + nsIFrame* mScrollFrame; + nsIFrame* mScrolledFrame; + ViewID mScrollParentId; + CompositorHitTestInfo mHitInfo; + nsRect mHitArea; +}; + +/** + * nsDisplayZoom is used for subdocuments that have a different full zoom than + * their parent documents. This item creates a container layer. + */ +class nsDisplayZoom : public nsDisplaySubDocument { + public: + /** + * @param aFrame is the root frame of the subdocument. + * @param aList contains the display items for the subdocument. + * @param aAPD is the app units per dev pixel ratio of the subdocument. + * @param aParentAPD is the app units per dev pixel ratio of the parent + * document. + * @param aFlags eGenerateSubdocInvalidations : + * Add UserData to the created ContainerLayer, so that invalidations + * for this layer are send to our nsPresContext. + */ + nsDisplayZoom(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + nsSubDocumentFrame* aSubDocFrame, nsDisplayList* aList, + int32_t aAPD, int32_t aParentAPD, + nsDisplayOwnLayerFlags aFlags = nsDisplayOwnLayerFlags::None); + + MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayZoom) + + NS_DISPLAY_DECL_NAME("Zoom", TYPE_ZOOM) + + nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) const override; + void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect, + HitTestState* aState, nsTArray<nsIFrame*>* aOutFrames) override; + bool ComputeVisibility(nsDisplayListBuilder* aBuilder, + nsRegion* aVisibleRegion) override; + LayerState GetLayerState( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aParameters) override { + return mozilla::LayerState::LAYER_ACTIVE; + } + + // Get the app units per dev pixel ratio of the child document. + int32_t GetChildAppUnitsPerDevPixel() { return mAPD; } + // Get the app units per dev pixel ratio of the parent document. + int32_t GetParentAppUnitsPerDevPixel() { return mParentAPD; } + + private: + int32_t mAPD, mParentAPD; +}; + +/** + * nsDisplayAsyncZoom is used for APZ zooming. It wraps the contents of the + * root content document's scroll frame, including fixed position content. It + * does not contain the scroll frame's scrollbars. It is clipped to the scroll + * frame's scroll port clip. It is not scrolled; only its non-fixed contents + * are scrolled. This item creates a container layer. + */ +class nsDisplayAsyncZoom : public nsDisplayOwnLayer { + public: + nsDisplayAsyncZoom(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + nsDisplayList* aList, + const ActiveScrolledRoot* aActiveScrolledRoot, + mozilla::layers::FrameMetrics::ViewID aViewID); + nsDisplayAsyncZoom(nsDisplayListBuilder* aBuilder, + const nsDisplayAsyncZoom& aOther) + : nsDisplayOwnLayer(aBuilder, aOther), mViewID(aOther.mViewID) { + MOZ_COUNT_CTOR(nsDisplayAsyncZoom); + } + +#ifdef NS_BUILD_REFCNT_LOGGING + virtual ~nsDisplayAsyncZoom(); +#endif + + NS_DISPLAY_DECL_NAME("AsyncZoom", TYPE_ASYNC_ZOOM) + + void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect, + HitTestState* aState, nsTArray<nsIFrame*>* aOutFrames) override; + already_AddRefed<Layer> BuildLayer( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aContainerParameters) override; + LayerState GetLayerState( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aParameters) override { + return mozilla::LayerState::LAYER_ACTIVE_FORCE; + } + bool UpdateScrollData( + mozilla::layers::WebRenderScrollData* aData, + mozilla::layers::WebRenderLayerScrollData* aLayerData) override; + + protected: + mozilla::layers::FrameMetrics::ViewID mViewID; +}; + +/** + * A base class for different effects types. + */ +class nsDisplayEffectsBase : public nsDisplayWrapList { + public: + nsDisplayEffectsBase(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + nsDisplayList* aList, + const ActiveScrolledRoot* aActiveScrolledRoot, + bool aClearClipChain = false); + nsDisplayEffectsBase(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + nsDisplayList* aList); + + nsDisplayEffectsBase(nsDisplayListBuilder* aBuilder, + const nsDisplayEffectsBase& aOther) + : nsDisplayWrapList(aBuilder, aOther), + mEffectsBounds(aOther.mEffectsBounds), + mHandleOpacity(aOther.mHandleOpacity) { + MOZ_COUNT_CTOR(nsDisplayEffectsBase); + } + + MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayEffectsBase) + + nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder, + bool* aSnap) const override; + void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect, + HitTestState* aState, nsTArray<nsIFrame*>* aOutFrames) override; + + bool RestoreState() override { + if (!nsDisplayWrapList::RestoreState() && !mHandleOpacity) { + return false; + } + + mHandleOpacity = false; + return true; + } + + bool ShouldFlattenAway(nsDisplayListBuilder* aBuilder) override { + return false; + } + + virtual void SelectOpacityOptimization(const bool /* aUsingLayers */) { + SetHandleOpacity(); + } + + bool ShouldHandleOpacity() const { return mHandleOpacity; } + + gfxRect BBoxInUserSpace() const; + gfxPoint UserSpaceOffset() const; + + void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder, + const nsDisplayItemGeometry* aGeometry, + nsRegion* aInvalidRegion) const override; + + protected: + void SetHandleOpacity() { mHandleOpacity = true; } + bool ValidateSVGFrame(); + + // relative to mFrame + nsRect mEffectsBounds; + // True if we need to handle css opacity in this display item. + bool mHandleOpacity; +}; + +/** + * A display item to paint a stacking context with 'mask' and 'clip-path' + * effects set by the stacking context root frame's style. The 'mask' and + * 'clip-path' properties may both contain multiple masks and clip paths, + * respectively. + * + * Note that 'mask' and 'clip-path' may just contain CSS simple-images and CSS + * basic shapes, respectively. That is, they don't necessarily reference + * resources such as SVG 'mask' and 'clipPath' elements. + */ +class nsDisplayMasksAndClipPaths : public nsDisplayEffectsBase { + public: + typedef mozilla::layers::ImageLayer ImageLayer; + + nsDisplayMasksAndClipPaths(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + nsDisplayList* aList, + const ActiveScrolledRoot* aActiveScrolledRoot); + nsDisplayMasksAndClipPaths(nsDisplayListBuilder* aBuilder, + const nsDisplayMasksAndClipPaths& aOther) + : nsDisplayEffectsBase(aBuilder, aOther), + mDestRects(aOther.mDestRects.Clone()) { + MOZ_COUNT_CTOR(nsDisplayMasksAndClipPaths); + } + + MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayMasksAndClipPaths) + + NS_DISPLAY_DECL_NAME("Mask", TYPE_MASK) + + bool CanMerge(const nsDisplayItem* aItem) const override; + + void Merge(const nsDisplayItem* aItem) override { + nsDisplayWrapList::Merge(aItem); + + const nsDisplayMasksAndClipPaths* other = + static_cast<const nsDisplayMasksAndClipPaths*>(aItem); + mEffectsBounds.UnionRect( + mEffectsBounds, + other->mEffectsBounds + other->mFrame->GetOffsetTo(mFrame)); + } + + already_AddRefed<Layer> BuildLayer( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aContainerParameters) override; + LayerState GetLayerState( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aParameters) override; + bool ComputeVisibility(nsDisplayListBuilder* aBuilder, + nsRegion* aVisibleRegion) override; + + nsDisplayItemGeometry* AllocateGeometry( + nsDisplayListBuilder* aBuilder) override { + return new nsDisplayMasksAndClipPathsGeometry(this, aBuilder); + } + + void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder, + const nsDisplayItemGeometry* aGeometry, + nsRegion* aInvalidRegion) const override; +#ifdef MOZ_DUMP_PAINTING + void PrintEffects(nsACString& aTo); +#endif + + bool IsValidMask(); + + void PaintAsLayer(nsDisplayListBuilder* aBuilder, gfxContext* aCtx, + LayerManager* aManager); + + void PaintWithContentsPaintCallback( + nsDisplayListBuilder* aBuilder, gfxContext* aCtx, + const std::function<void()>& aPaintChildren); + + /* + * Paint mask onto aMaskContext in mFrame's coordinate space and + * return whether the mask layer was painted successfully. + */ + bool PaintMask(nsDisplayListBuilder* aBuilder, gfxContext* aMaskContext, + bool* aMaskPainted = nullptr); + + const nsTArray<nsRect>& GetDestRects() { return mDestRects; } + + void SelectOpacityOptimization(const bool aUsingLayers) override; + + bool CreateWebRenderCommands( + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) override; + + mozilla::Maybe<nsRect> GetClipWithRespectToASR( + nsDisplayListBuilder* aBuilder, + const ActiveScrolledRoot* aASR) const override; + + private: + NS_DISPLAY_ALLOW_CLONING() + + // According to mask property and the capability of aManager, determine + // whether we can paint the mask onto a dedicate mask layer. + bool CanPaintOnMaskLayer(LayerManager* aManager); + + nsTArray<nsRect> mDestRects; + bool mApplyOpacityWithSimpleClipPath; +}; + +class nsDisplayBackdropRootContainer : public nsDisplayWrapList { + public: + nsDisplayBackdropRootContainer(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame, nsDisplayList* aList, + const ActiveScrolledRoot* aActiveScrolledRoot) + : nsDisplayWrapList(aBuilder, aFrame, aList, aActiveScrolledRoot, true) { + MOZ_COUNT_CTOR(nsDisplayBackdropRootContainer); + } + + MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayBackdropRootContainer) + + NS_DISPLAY_DECL_NAME("BackdropRootContainer", TYPE_BACKDROP_ROOT_CONTAINER) + + already_AddRefed<Layer> BuildLayer( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aContainerParameters) override; + LayerState GetLayerState( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aParameters) override; + + bool CreateWebRenderCommands( + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) override; + + bool ShouldFlattenAway(nsDisplayListBuilder* aBuilder) override { + return !aBuilder->IsPaintingForWebRender(); + } +}; + +class nsDisplayBackdropFilters : public nsDisplayWrapList { + public: + nsDisplayBackdropFilters(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + nsDisplayList* aList, const nsRect& aBackdropRect) + : nsDisplayWrapList(aBuilder, aFrame, aList), + mBackdropRect(aBackdropRect) { + MOZ_COUNT_CTOR(nsDisplayBackdropFilters); + } + + MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayBackdropFilters) + + NS_DISPLAY_DECL_NAME("BackdropFilter", TYPE_BACKDROP_FILTER) + + bool CreateWebRenderCommands( + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) override; + + static bool CanCreateWebRenderCommands(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame); + + bool ShouldFlattenAway(nsDisplayListBuilder* aBuilder) override { + return !aBuilder->IsPaintingForWebRender(); + } + + private: + nsRect mBackdropRect; +}; + +/** + * A display item to paint a stacking context with filter effects set by the + * stacking context root frame's style. + * + * Note that the filters may just be simple CSS filter functions. That is, + * they won't necessarily be references to SVG 'filter' elements. + */ +class nsDisplayFilters : public nsDisplayEffectsBase { + public: + nsDisplayFilters(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + nsDisplayList* aList); + + nsDisplayFilters(nsDisplayListBuilder* aBuilder, + const nsDisplayFilters& aOther) + : nsDisplayEffectsBase(aBuilder, aOther), + mEffectsBounds(aOther.mEffectsBounds) { + MOZ_COUNT_CTOR(nsDisplayFilters); + } + + MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayFilters) + + NS_DISPLAY_DECL_NAME("Filter", TYPE_FILTER) + + bool CanMerge(const nsDisplayItem* aItem) const override { + // Items for the same content element should be merged into a single + // compositing group. + return HasDifferentFrame(aItem) && HasSameTypeAndClip(aItem) && + HasSameContent(aItem); + } + + void Merge(const nsDisplayItem* aItem) override { + nsDisplayWrapList::Merge(aItem); + + const nsDisplayFilters* other = static_cast<const nsDisplayFilters*>(aItem); + mEffectsBounds.UnionRect( + mEffectsBounds, + other->mEffectsBounds + other->mFrame->GetOffsetTo(mFrame)); + } + + already_AddRefed<Layer> BuildLayer( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aContainerParameters) override; + LayerState GetLayerState( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aParameters) override; + + nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) const override { + *aSnap = false; + return mEffectsBounds + ToReferenceFrame(); + } + + bool ComputeVisibility(nsDisplayListBuilder* aBuilder, + nsRegion* aVisibleRegion) override; + + nsDisplayItemGeometry* AllocateGeometry( + nsDisplayListBuilder* aBuilder) override { + return new nsDisplayFiltersGeometry(this, aBuilder); + } + + void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder, + const nsDisplayItemGeometry* aGeometry, + nsRegion* aInvalidRegion) const override; +#ifdef MOZ_DUMP_PAINTING + void PrintEffects(nsACString& aTo); +#endif + + void PaintAsLayer(nsDisplayListBuilder* aBuilder, gfxContext* aCtx, + LayerManager* aManager); + + bool CreateWebRenderCommands( + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) override; + bool CanCreateWebRenderCommands(); + + private: + NS_DISPLAY_ALLOW_CLONING() + + // relative to mFrame + nsRect mEffectsBounds; +}; + +/* A display item that applies a transformation to all of its descendant + * elements. This wrapper should only be used if there is a transform applied + * to the root element. + * + * The reason that a "bounds" rect is involved in transform calculations is + * because CSS-transforms allow percentage values for the x and y components + * of <translation-value>s, where percentages are percentages of the element's + * border box. + * + * INVARIANT: The wrapped frame is transformed or we supplied a transform getter + * function. + * INVARIANT: The wrapped frame is non-null. + */ +class nsDisplayTransform : public nsDisplayHitTestInfoBase { + using Matrix4x4 = mozilla::gfx::Matrix4x4; + using Matrix4x4Flagged = mozilla::gfx::Matrix4x4Flagged; + using Point3D = mozilla::gfx::Point3D; + using TransformReferenceBox = nsStyleTransformMatrix::TransformReferenceBox; + + public: + enum class PrerenderDecision : uint8_t { No, Full, Partial }; + + /** + * Returns a matrix (in pixels) for the current frame. The matrix should be + * relative to the current frame's coordinate space. + * + * @param aFrame The frame to compute the transform for. + * @param aAppUnitsPerPixel The number of app units per graphics unit. + */ + typedef Matrix4x4 (*ComputeTransformFunction)(nsIFrame* aFrame, + float aAppUnitsPerPixel); + + /* Constructor accepts a display list, empties it, and wraps it up. It also + * ferries the underlying frame to the nsDisplayItem constructor. + */ + nsDisplayTransform(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + nsDisplayList* aList, const nsRect& aChildrenBuildingRect); + + nsDisplayTransform(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + nsDisplayList* aList, const nsRect& aChildrenBuildingRect, + PrerenderDecision aPrerenderDecision); + + nsDisplayTransform(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + nsDisplayList* aList, const nsRect& aChildrenBuildingRect, + ComputeTransformFunction aTransformGetter); + + MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayTransform) + + NS_DISPLAY_DECL_NAME("nsDisplayTransform", TYPE_TRANSFORM) + + bool RestoreState() override { + if (!nsDisplayHitTestInfoBase::RestoreState() && !mShouldFlatten) { + return false; + } + + mShouldFlatten = false; + return true; + } + + void UpdateBounds(nsDisplayListBuilder* aBuilder) override; + + /** + * This function updates bounds for items with a frame establishing + * 3D rendering context. + */ + void UpdateBoundsFor3D(nsDisplayListBuilder* aBuilder); + + void DoUpdateBoundsPreserves3D(nsDisplayListBuilder* aBuilder) override; + + void Destroy(nsDisplayListBuilder* aBuilder) override { + GetChildren()->DeleteAll(aBuilder); + nsDisplayHitTestInfoBase::Destroy(aBuilder); + } + + nsRect GetComponentAlphaBounds(nsDisplayListBuilder* aBuilder) const override; + + RetainedDisplayList* GetChildren() const override { return &mChildren; } + + nsRect GetUntransformedBounds(nsDisplayListBuilder* aBuilder, + bool* aSnap) const override { + *aSnap = false; + return mChildBounds; + } + + const nsRect& GetUntransformedPaintRect() const override { + return mChildrenBuildingRect; + } + + bool ShouldFlattenAway(nsDisplayListBuilder* aBuilder) override; + + void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect, + HitTestState* aState, nsTArray<nsIFrame*>* aOutFrames) override; + nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) const override; + nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder, + bool* aSnap) const override; + LayerState GetLayerState( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aParameters) override; + already_AddRefed<Layer> BuildLayer( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aContainerParameters) override; + bool CreateWebRenderCommands( + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) override; + bool UpdateScrollData( + mozilla::layers::WebRenderScrollData* aData, + mozilla::layers::WebRenderLayerScrollData* aLayerData) override; + bool ComputeVisibility(nsDisplayListBuilder* aBuilder, + nsRegion* aVisibleRegion) override; + + nsDisplayItemGeometry* AllocateGeometry( + nsDisplayListBuilder* aBuilder) override { + return new nsDisplayTransformGeometry( + this, aBuilder, GetTransformForRendering(), + mFrame->PresContext()->AppUnitsPerDevPixel()); + } + + void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder, + const nsDisplayItemGeometry* aGeometry, + nsRegion* aInvalidRegion) const override { + const nsDisplayTransformGeometry* geometry = + static_cast<const nsDisplayTransformGeometry*>(aGeometry); + + // This code is only called for flattened, inactive transform items. + // Only check if the transform has changed. The bounds invalidation should + // be handled by the children themselves. + if (!geometry->mTransform.FuzzyEqual(GetTransformForRendering())) { + bool snap; + aInvalidRegion->Or(GetBounds(aBuilder, &snap), geometry->mBounds); + } + } + + bool NeedsGeometryUpdates() const override { return mShouldFlatten; } + + const nsIFrame* ReferenceFrameForChildren() const override { + // If we were created using a transform-getter, then we don't + // belong to a transformed frame, and aren't a reference frame + // for our children. + if (!mTransformGetter) { + return mFrame; + } + return nsDisplayHitTestInfoBase::ReferenceFrameForChildren(); + } + + AnimatedGeometryRoot* AnimatedGeometryRootForScrollMetadata() const override { + return mAnimatedGeometryRootForScrollMetadata; + } + + const nsRect& GetBuildingRectForChildren() const override { + return mChildrenBuildingRect; + } + + enum { INDEX_MAX = UINT32_MAX >> TYPE_BITS }; + + /** + * We include the perspective matrix from our containing block for the + * purposes of visibility calculations, but we exclude it from the transform + * we set on the layer (for rendering), since there will be an + * nsDisplayPerspective created for that. + */ + const Matrix4x4Flagged& GetTransform() const; + const Matrix4x4Flagged& GetInverseTransform() const; + + bool ShouldSkipTransform(nsDisplayListBuilder* aBuilder) const; + Matrix4x4 GetTransformForRendering( + mozilla::LayoutDevicePoint* aOutOrigin = nullptr) const; + + /** + * Return the transform that is aggregation of all transform on the + * preserves3d chain. + */ + const Matrix4x4& GetAccumulatedPreserved3DTransform( + nsDisplayListBuilder* aBuilder); + + float GetHitDepthAtPoint(nsDisplayListBuilder* aBuilder, + const nsPoint& aPoint); + + /** + * TransformRect takes in as parameters a rectangle (in aFrame's coordinate + * space) and returns the smallest rectangle (in aFrame's coordinate space) + * containing the transformed image of that rectangle. That is, it takes + * the four corners of the rectangle, transforms them according to the + * matrix associated with the specified frame, then returns the smallest + * rectangle containing the four transformed points. + * + * @param untransformedBounds The rectangle (in app units) to transform. + * @param aFrame The frame whose transformation should be applied. This + * function raises an assertion if aFrame is null or doesn't have a + * transform applied to it. + * @param aRefBox the reference box to use, which would usually be just + * TransformReferemceBox(aFrame), but callers may override it if + * needed. + */ + static nsRect TransformRect(const nsRect& aUntransformedBounds, + const nsIFrame* aFrame, + TransformReferenceBox& aRefBox); + + /* UntransformRect is like TransformRect, except that it inverts the + * transform. + */ + static bool UntransformRect(const nsRect& aTransformedBounds, + const nsRect& aChildBounds, + const nsIFrame* aFrame, nsRect* aOutRect); + + bool UntransformRect(nsDisplayListBuilder* aBuilder, const nsRect& aRect, + nsRect* aOutRect) const; + + bool UntransformBuildingRect(nsDisplayListBuilder* aBuilder, + nsRect* aOutRect) const { + return UntransformRect(aBuilder, GetBuildingRect(), aOutRect); + } + + bool UntransformPaintRect(nsDisplayListBuilder* aBuilder, + nsRect* aOutRect) const { + return UntransformRect(aBuilder, GetPaintRect(), aOutRect); + } + + static Point3D GetDeltaToTransformOrigin(const nsIFrame* aFrame, + TransformReferenceBox&, + float aAppUnitsPerPixel); + + /* + * Returns true if aFrame has perspective applied from its containing + * block. + * Returns the matrix to append to apply the persective (taking + * perspective-origin into account), relative to aFrames coordinate + * space). + * aOutMatrix is assumed to be the identity matrix, and isn't explicitly + * cleared. + */ + static bool ComputePerspectiveMatrix(const nsIFrame* aFrame, + float aAppUnitsPerPixel, + Matrix4x4& aOutMatrix); + + struct MOZ_STACK_CLASS FrameTransformProperties { + FrameTransformProperties(const nsIFrame* aFrame, + TransformReferenceBox& aRefBox, + float aAppUnitsPerPixel); + FrameTransformProperties( + const mozilla::StyleTranslate& aTranslate, + const mozilla::StyleRotate& aRotate, const mozilla::StyleScale& aScale, + const mozilla::StyleTransform& aTransform, + const mozilla::Maybe<mozilla::ResolvedMotionPathData>& aMotion, + const Point3D& aToTransformOrigin) + : mFrame(nullptr), + mTranslate(aTranslate), + mRotate(aRotate), + mScale(aScale), + mTransform(aTransform), + mMotion(aMotion), + mToTransformOrigin(aToTransformOrigin) {} + + bool HasTransform() const { + return !mTranslate.IsNone() || !mRotate.IsNone() || !mScale.IsNone() || + !mTransform.IsNone() || mMotion.isSome(); + } + + const nsIFrame* mFrame; + const mozilla::StyleTranslate& mTranslate; + const mozilla::StyleRotate& mRotate; + const mozilla::StyleScale& mScale; + const mozilla::StyleTransform& mTransform; + const mozilla::Maybe<mozilla::ResolvedMotionPathData> mMotion; + const Point3D mToTransformOrigin; + }; + + /** + * Given a frame with the transform property or an SVG transform, + * returns the transformation matrix for that frame. + * + * @param aFrame The frame to get the matrix from. + * @param aOrigin Relative to which point this transform should be applied. + * @param aAppUnitsPerPixel The number of app units per graphics unit. + * @param aBoundsOverride [optional] If this is nullptr (the default), the + * computation will use the value of TransformReferenceBox(aFrame). + * Otherwise, it will use the value of aBoundsOverride. This is + * mostly for internal use and in most cases you will not need to + * specify a value. + * @param aFlags OFFSET_BY_ORIGIN The resulting matrix will be translated + * by aOrigin. This translation is applied *before* the CSS transform. + * @param aFlags INCLUDE_PRESERVE3D_ANCESTORS The computed transform will + * include the transform of any ancestors participating in the same + * 3d rendering context. + * @param aFlags INCLUDE_PERSPECTIVE The resulting matrix will include the + * perspective transform from the containing block if applicable. + */ + enum { + OFFSET_BY_ORIGIN = 1 << 0, + INCLUDE_PRESERVE3D_ANCESTORS = 1 << 1, + INCLUDE_PERSPECTIVE = 1 << 2, + }; + static Matrix4x4 GetResultingTransformMatrix(const nsIFrame* aFrame, + const nsPoint& aOrigin, + float aAppUnitsPerPixel, + uint32_t aFlags); + static Matrix4x4 GetResultingTransformMatrix( + const FrameTransformProperties& aProperties, TransformReferenceBox&, + float aAppUnitsPerPixel); + + struct PrerenderInfo { + bool CanUseAsyncAnimations() const { + return mDecision != PrerenderDecision::No && mHasAnimations; + } + PrerenderDecision mDecision = PrerenderDecision::No; + bool mHasAnimations = true; + }; + /** + * Decide whether we should prerender some or all of the contents of the + * transformed frame even when it's not completely visible (yet). + * Return PrerenderDecision::Full if the entire contents should be + * prerendered, PrerenderDecision::Partial if some but not all of the + * contents should be prerendered, or PrerenderDecision::No if only the + * visible area should be rendered. + * |mNoAffectDecisionInPreserve3D| is set if the prerender decision should not + * affect the decision on other frames in the preserve 3d tree. + * |aDirtyRect| is updated to the area that should be prerendered. + */ + static PrerenderInfo ShouldPrerenderTransformedContent( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsRect* aDirtyRect); + + bool CanUseAsyncAnimations(nsDisplayListBuilder* aBuilder) override; + + bool MayBeAnimated(nsDisplayListBuilder* aBuilder, + bool aEnforceMinimumSize = true) const; + + void WriteDebugInfo(std::stringstream& aStream) override; + + /** + * This item is an additional item as the boundary between parent + * and child 3D rendering context. + * \see nsIFrame::BuildDisplayListForStackingContext(). + */ + bool IsTransformSeparator() const { return mIsTransformSeparator; } + /** + * This item is the boundary between parent and child 3D rendering + * context. + */ + bool IsLeafOf3DContext() const { + return (IsTransformSeparator() || + (!mFrame->Extend3DContext() && Combines3DTransformWithAncestors())); + } + /** + * The backing frame of this item participates a 3D rendering + * context. + */ + bool IsParticipating3DContext() const { + return mFrame->Extend3DContext() || Combines3DTransformWithAncestors(); + } + + bool IsPartialPrerender() const { + return mPrerenderDecision == PrerenderDecision::Partial; + } + + void AddSizeOfExcludingThis(nsWindowSizes&) const override; + + private: + void ComputeBounds(nsDisplayListBuilder* aBuilder); + nsRect TransformUntransformedBounds(nsDisplayListBuilder* aBuilder, + const Matrix4x4Flagged& aMatrix) const; + void UpdateUntransformedBounds(nsDisplayListBuilder* aBuilder); + + void SetReferenceFrameToAncestor(nsDisplayListBuilder* aBuilder); + void Init(nsDisplayListBuilder* aBuilder, nsDisplayList* aChildren); + + static Matrix4x4 GetResultingTransformMatrixInternal( + const FrameTransformProperties& aProperties, + TransformReferenceBox& aRefBox, const nsPoint& aOrigin, + float aAppUnitsPerPixel, uint32_t aFlags); + + mutable mozilla::Maybe<Matrix4x4Flagged> mTransform; + mutable mozilla::Maybe<Matrix4x4Flagged> mInverseTransform; + // Accumulated transform of ancestors on the preserves-3d chain. + mozilla::UniquePtr<Matrix4x4> mTransformPreserves3D; + ComputeTransformFunction mTransformGetter; + RefPtr<AnimatedGeometryRoot> mAnimatedGeometryRootForChildren; + RefPtr<AnimatedGeometryRoot> mAnimatedGeometryRootForScrollMetadata; + nsRect mChildrenBuildingRect; + mutable RetainedDisplayList mChildren; + + // The untransformed bounds of |mChildren|. + nsRect mChildBounds; + // The transformed bounds of this display item. + nsRect mBounds; + PrerenderDecision mPrerenderDecision : 8; + // This item is a separator between 3D rendering contexts, and + // mTransform have been presetted by the constructor. + // This also forces us not to extend the 3D context. Since we don't create a + // transform item, a container layer, for every frame in a preserves3d + // context, the transform items of a child preserves3d context may extend the + // parent context unintendedly if the root of the child preserves3d context + // doesn't create a transform item. + bool mIsTransformSeparator : 1; + // True if this nsDisplayTransform should get flattened + bool mShouldFlatten : 1; +}; + +/* A display item that applies a perspective transformation to a single + * nsDisplayTransform child item. We keep this as a separate item since the + * perspective-origin is relative to an ancestor of the transformed frame, and + * APZ can scroll the child separately. + */ +class nsDisplayPerspective : public nsDisplayHitTestInfoBase { + typedef mozilla::gfx::Point3D Point3D; + + public: + nsDisplayPerspective(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + nsDisplayList* aList); + ~nsDisplayPerspective() override = default; + + NS_DISPLAY_DECL_NAME("nsDisplayPerspective", TYPE_PERSPECTIVE) + + void Destroy(nsDisplayListBuilder* aBuilder) override { + mList.DeleteAll(aBuilder); + nsDisplayHitTestInfoBase::Destroy(aBuilder); + } + + void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect, + HitTestState* aState, nsTArray<nsIFrame*>* aOutFrames) override { + return GetChildren()->HitTest(aBuilder, aRect, aState, aOutFrames); + } + + nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) const override { + *aSnap = false; + return GetChildren()->GetClippedBoundsWithRespectToASR(aBuilder, + mActiveScrolledRoot); + } + + bool ComputeVisibility(nsDisplayListBuilder* aBuilder, + nsRegion* aVisibleRegion) override; + + void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder, + const nsDisplayItemGeometry* aGeometry, + nsRegion* aInvalidRegion) const override {} + + nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder, + bool* aSnap) const override; + + LayerState GetLayerState( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aParameters) override; + bool CreateWebRenderCommands( + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) override; + + already_AddRefed<Layer> BuildLayer( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aContainerParameters) override; + + RetainedDisplayList* GetSameCoordinateSystemChildren() const override { + return &mList; + } + + RetainedDisplayList* GetChildren() const override { return &mList; } + + nsRect GetComponentAlphaBounds( + nsDisplayListBuilder* aBuilder) const override { + return GetChildren()->GetComponentAlphaBounds(aBuilder); + } + + void DoUpdateBoundsPreserves3D(nsDisplayListBuilder* aBuilder) override { + if (GetChildren()->GetTop()) { + static_cast<nsDisplayTransform*>(GetChildren()->GetTop()) + ->DoUpdateBoundsPreserves3D(aBuilder); + } + } + + private: + mutable RetainedDisplayList mList; +}; + +/** + * This class adds basic support for limiting the rendering (in the inline axis + * of the writing mode) to the part inside the specified edges. + * The two members, mVisIStartEdge and mVisIEndEdge, are relative to the edges + * of the frame's scrollable overflow rectangle and are the amount to suppress + * on each side. + * + * Setting none, both or only one edge is allowed. + * The values must be non-negative. + * The default value for both edges is zero, which means everything is painted. + */ +class nsDisplayText final : public nsPaintedDisplayItem { + public: + nsDisplayText(nsDisplayListBuilder* aBuilder, nsTextFrame* aFrame, + const mozilla::Maybe<bool>& aIsSelected); + MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayText) + + NS_DISPLAY_DECL_NAME("Text", TYPE_TEXT) + + bool RestoreState() final { + if (!nsPaintedDisplayItem::RestoreState() && mIsFrameSelected.isNothing() && + mOpacity == 1.0f) { + return false; + } + + mIsFrameSelected.reset(); + mOpacity = 1.0f; + return true; + } + + nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) const final { + *aSnap = false; + return mBounds; + } + + void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect, + HitTestState* aState, nsTArray<nsIFrame*>* aOutFrames) final { + if (nsRect(ToReferenceFrame(), mFrame->GetSize()).Intersects(aRect)) { + aOutFrames->AppendElement(mFrame); + } + } + + bool CreateWebRenderCommands( + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) final; + void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) final; + + nsRect GetComponentAlphaBounds(nsDisplayListBuilder* aBuilder) const final { + if (gfxPlatform::GetPlatform()->RespectsFontStyleSmoothing()) { + // On OS X, web authors can turn off subpixel text rendering using the + // CSS property -moz-osx-font-smoothing. If they do that, we don't need + // to use component alpha layers for the affected text. + if (mFrame->StyleFont()->mFont.smoothing == NS_FONT_SMOOTHING_GRAYSCALE) { + return nsRect(); + } + } + bool snap; + return GetBounds(aBuilder, &snap); + } + + nsDisplayItemGeometry* AllocateGeometry(nsDisplayListBuilder* aBuilder) final; + + void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder, + const nsDisplayItemGeometry* aGeometry, + nsRegion* aInvalidRegion) const final; + + void RenderToContext(gfxContext* aCtx, nsDisplayListBuilder* aBuilder, + bool aIsRecording = false); + + bool CanApplyOpacity() const final; + + void ApplyOpacity(nsDisplayListBuilder* aBuilder, float aOpacity, + const DisplayItemClipChain* aClip) final { + NS_ASSERTION(CanApplyOpacity(), "ApplyOpacity should be allowed"); + mOpacity = aOpacity; + IntersectClip(aBuilder, aClip, false); + } + + void WriteDebugInfo(std::stringstream& aStream) final; + + static nsDisplayText* CheckCast(nsDisplayItem* aItem) { + return (aItem->GetType() == DisplayItemType::TYPE_TEXT) + ? static_cast<nsDisplayText*>(aItem) + : nullptr; + } + + bool IsSelected() const; + + struct ClipEdges { + ClipEdges(const nsIFrame* aFrame, const nsPoint& aToReferenceFrame, + nscoord aVisIStartEdge, nscoord aVisIEndEdge) { + nsRect r = aFrame->ScrollableOverflowRect() + aToReferenceFrame; + if (aFrame->GetWritingMode().IsVertical()) { + mVisIStart = aVisIStartEdge > 0 ? r.y + aVisIStartEdge : nscoord_MIN; + mVisIEnd = aVisIEndEdge > 0 + ? std::max(r.YMost() - aVisIEndEdge, mVisIStart) + : nscoord_MAX; + } else { + mVisIStart = aVisIStartEdge > 0 ? r.x + aVisIStartEdge : nscoord_MIN; + mVisIEnd = aVisIEndEdge > 0 + ? std::max(r.XMost() - aVisIEndEdge, mVisIStart) + : nscoord_MAX; + } + } + + void Intersect(nscoord* aVisIStart, nscoord* aVisISize) const { + nscoord end = *aVisIStart + *aVisISize; + *aVisIStart = std::max(*aVisIStart, mVisIStart); + *aVisISize = std::max(std::min(end, mVisIEnd) - *aVisIStart, 0); + } + + nscoord mVisIStart; + nscoord mVisIEnd; + }; + + nscoord& VisIStartEdge() { return mVisIStartEdge; } + nscoord& VisIEndEdge() { return mVisIEndEdge; } + float Opacity() const { return mOpacity; } + + private: + nsRect mBounds; + float mOpacity; + + // Lengths measured from the visual inline start and end sides + // (i.e. left and right respectively in horizontal writing modes, + // regardless of bidi directionality; top and bottom in vertical modes). + nscoord mVisIStartEdge; + nscoord mVisIEndEdge; + + // Cached result of mFrame->IsSelected(). Only initialized when needed. + mutable mozilla::Maybe<bool> mIsFrameSelected; +}; + +/** + * A display item that for webrender to handle SVG + */ +class nsDisplaySVGWrapper : public nsDisplayWrapList { + public: + nsDisplaySVGWrapper(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + nsDisplayList* aList); + + MOZ_COUNTED_DTOR_OVERRIDE(nsDisplaySVGWrapper) + + NS_DISPLAY_DECL_NAME("SVGWrapper", TYPE_SVG_WRAPPER) + + already_AddRefed<Layer> BuildLayer( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aContainerParameters) override; + LayerState GetLayerState( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aParameters) override; + bool ShouldFlattenAway(nsDisplayListBuilder* aBuilder) override; + bool CreateWebRenderCommands( + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) override; +}; + +/** + * A display item for webrender to handle SVG foreign object + */ +class nsDisplayForeignObject : public nsDisplayWrapList { + public: + nsDisplayForeignObject(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + nsDisplayList* aList); +#ifdef NS_BUILD_REFCNT_LOGGING + virtual ~nsDisplayForeignObject(); +#endif + + NS_DISPLAY_DECL_NAME("ForeignObject", TYPE_FOREIGN_OBJECT) + + virtual already_AddRefed<Layer> BuildLayer( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aContainerParameters) override; + virtual LayerState GetLayerState( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aParameters) override; + virtual bool ShouldFlattenAway(nsDisplayListBuilder* aBuilder) override; + + bool CreateWebRenderCommands( + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) override; +}; + +class FlattenedDisplayListIterator { + public: + FlattenedDisplayListIterator(nsDisplayListBuilder* aBuilder, + nsDisplayList* aList) + : FlattenedDisplayListIterator(aBuilder, aList, true) {} + + ~FlattenedDisplayListIterator() { MOZ_ASSERT(!HasNext()); } + + virtual bool HasNext() const { return mNext || !mStack.IsEmpty(); } + + nsDisplayItem* GetNextItem() { + MOZ_ASSERT(mNext); + + nsDisplayItem* next = mNext; + mNext = next->GetAbove(); + + if (mNext && next->HasChildren() && mNext->HasChildren()) { + // Since |next| and |mNext| are container items in the same list, + // merging them might be possible. + next = TryMergingFrom(next); + } + + ResolveFlattening(); + + return next; + } + + nsDisplayItem* PeekNext() { return mNext; } + + protected: + FlattenedDisplayListIterator(nsDisplayListBuilder* aBuilder, + nsDisplayList* aList, + const bool aResolveFlattening) + : mBuilder(aBuilder), mNext(aList->GetBottom()) { + if (aResolveFlattening) { + // This is done conditionally in case subclass overrides + // ShouldFlattenNextItem(). + ResolveFlattening(); + } + } + + virtual void EnterChildList(nsDisplayItem* aContainerItem) {} + virtual void ExitChildList() {} + + bool AtEndOfNestedList() const { return !mNext && mStack.Length() > 0; } + + virtual bool ShouldFlattenNextItem() { + return mNext && mNext->ShouldFlattenAway(mBuilder); + } + + void ResolveFlattening() { + // Handle the case where we reach the end of a nested list, or the current + // item should start a new nested list. Repeat this until we find an actual + // item, or the very end of the outer list. + while (AtEndOfNestedList() || ShouldFlattenNextItem()) { + if (AtEndOfNestedList()) { + ExitChildList(); + + // We reached the end of the list, pop the next item from the stack. + mNext = mStack.PopLastElement(); + } else { + EnterChildList(mNext); + + // This item wants to be flattened. Store the next item on the stack, + // and use the first item in the child list instead. + mStack.AppendElement(mNext->GetAbove()); + mNext = mNext->GetChildren()->GetBottom(); + } + } + } + + /** + * Tries to merge display items starting from |aCurrent|. + * Updates the internal pointer to the next display item. + */ + nsDisplayItem* TryMergingFrom(nsDisplayItem* aCurrent) { + MOZ_ASSERT(aCurrent); + MOZ_ASSERT(aCurrent->GetAbove()); + + nsDisplayWrapList* current = aCurrent->AsDisplayWrapList(); + nsDisplayWrapList* next = mNext->AsDisplayWrapList(); + + if (!current || !next) { + // Either the current or the next item do not support merging. + return aCurrent; + } + + // Attempt to merge |next| with |current|. + if (current->CanMerge(next)) { + // Merging is possible, collect all the successive mergeable items. + AutoTArray<nsDisplayWrapList*, 2> willMerge{current}; + + do { + willMerge.AppendElement(next); + mNext = next->GetAbove(); + next = mNext ? mNext->AsDisplayWrapList() : nullptr; + } while (next && current->CanMerge(next)); + + current = mBuilder->MergeItems(willMerge); + } + + // Here |mNext| will be either the first item that could not be merged with + // |current|, or nullptr. + return current; + } + + private: + nsDisplayListBuilder* mBuilder; + nsDisplayItem* mNext; + AutoTArray<nsDisplayItem*, 16> mStack; +}; + +namespace mozilla { + +class PaintTelemetry { + public: + enum class Metric { + DisplayList, + Layerization, + FlushRasterization, + Rasterization, + COUNT, + }; + + class AutoRecord { + public: + explicit AutoRecord(Metric aMetric); + ~AutoRecord(); + + TimeStamp GetStart() const { return mStart; } + + private: + Metric mMetric; + mozilla::TimeStamp mStart; + }; + + class AutoRecordPaint { + public: + AutoRecordPaint(); + ~AutoRecordPaint(); + + private: + mozilla::TimeStamp mStart; + }; + + private: + static uint32_t sPaintLevel; + static uint32_t sMetricLevel; + static mozilla::EnumeratedArray<Metric, Metric::COUNT, double> sMetrics; +}; + +} // namespace mozilla + +#endif /*NSDISPLAYLIST_H_*/ diff --git a/layout/painting/nsDisplayListArenaTypes.h b/layout/painting/nsDisplayListArenaTypes.h new file mode 100644 index 0000000000..6a7a262664 --- /dev/null +++ b/layout/painting/nsDisplayListArenaTypes.h @@ -0,0 +1,13 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* a list of all types that can be allocated in the display list's nsPresArena, + for preprocessing */ + +DISPLAY_LIST_ARENA_OBJECT(CLIPCHAIN) +#define DECLARE_DISPLAY_ITEM_TYPE(name_, ...) DISPLAY_LIST_ARENA_OBJECT(name_) +#include "nsDisplayItemTypesList.h" +#undef DECLARE_DISPLAY_ITEM_TYPE diff --git a/layout/painting/nsDisplayListInvalidation.cpp b/layout/painting/nsDisplayListInvalidation.cpp new file mode 100644 index 0000000000..a7a8051996 --- /dev/null +++ b/layout/painting/nsDisplayListInvalidation.cpp @@ -0,0 +1,122 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsDisplayListInvalidation.h" +#include "nsDisplayList.h" +#include "nsIFrame.h" +#include "nsTableFrame.h" + +nsDisplayItemGeometry::nsDisplayItemGeometry(nsDisplayItem* aItem, + nsDisplayListBuilder* aBuilder) { + MOZ_COUNT_CTOR(nsDisplayItemGeometry); + bool snap; + mBounds = aItem->GetBounds(aBuilder, &snap); +} + +nsDisplayItemGeometry::~nsDisplayItemGeometry() { + MOZ_COUNT_DTOR(nsDisplayItemGeometry); +} + +nsDisplayItemGenericGeometry::nsDisplayItemGenericGeometry( + nsDisplayItem* aItem, nsDisplayListBuilder* aBuilder) + : nsDisplayItemGeometry(aItem, aBuilder), + mBorderRect(aItem->GetBorderRect()) {} + +bool ShouldSyncDecodeImages(nsDisplayListBuilder* aBuilder) { + return aBuilder->ShouldSyncDecodeImages(); +} + +void nsDisplayItemGenericGeometry::MoveBy(const nsPoint& aOffset) { + nsDisplayItemGeometry::MoveBy(aOffset); + mBorderRect.MoveBy(aOffset); +} + +nsDisplayItemBoundsGeometry::nsDisplayItemBoundsGeometry( + nsDisplayItem* aItem, nsDisplayListBuilder* aBuilder) + : nsDisplayItemGeometry(aItem, aBuilder) { + nscoord radii[8]; + mHasRoundedCorners = aItem->Frame()->GetBorderRadii(radii); +} + +nsDisplayBorderGeometry::nsDisplayBorderGeometry(nsDisplayItem* aItem, + nsDisplayListBuilder* aBuilder) + : nsDisplayItemGeometry(aItem, aBuilder), + nsImageGeometryMixin(aItem, aBuilder) {} + +nsDisplayBackgroundGeometry::nsDisplayBackgroundGeometry( + nsDisplayBackgroundImage* aItem, nsDisplayListBuilder* aBuilder) + : nsDisplayItemGeometry(aItem, aBuilder), + nsImageGeometryMixin(aItem, aBuilder), + mPositioningArea(aItem->GetPositioningArea()), + mDestRect(aItem->GetDestRect()) {} + +void nsDisplayBackgroundGeometry::MoveBy(const nsPoint& aOffset) { + nsDisplayItemGeometry::MoveBy(aOffset); + mPositioningArea.MoveBy(aOffset); + mDestRect.MoveBy(aOffset); +} + +nsDisplayThemedBackgroundGeometry::nsDisplayThemedBackgroundGeometry( + nsDisplayThemedBackground* aItem, nsDisplayListBuilder* aBuilder) + : nsDisplayItemGeometry(aItem, aBuilder), + mPositioningArea(aItem->GetPositioningArea()), + mWindowIsActive(aItem->IsWindowActive()) {} + +void nsDisplayThemedBackgroundGeometry::MoveBy(const nsPoint& aOffset) { + nsDisplayItemGeometry::MoveBy(aOffset); + mPositioningArea.MoveBy(aOffset); +} + +nsDisplayBoxShadowInnerGeometry::nsDisplayBoxShadowInnerGeometry( + nsDisplayItem* aItem, nsDisplayListBuilder* aBuilder) + : nsDisplayItemGeometry(aItem, aBuilder), + mPaddingRect(aItem->GetPaddingRect()) {} + +void nsDisplayBoxShadowInnerGeometry::MoveBy(const nsPoint& aOffset) { + nsDisplayItemGeometry::MoveBy(aOffset); + mPaddingRect.MoveBy(aOffset); +} + +nsDisplayBoxShadowOuterGeometry::nsDisplayBoxShadowOuterGeometry( + nsDisplayItem* aItem, nsDisplayListBuilder* aBuilder, float aOpacity) + : nsDisplayItemGenericGeometry(aItem, aBuilder), mOpacity(aOpacity) {} + +void nsDisplaySolidColorRegionGeometry::MoveBy(const nsPoint& aOffset) { + nsDisplayItemGeometry::MoveBy(aOffset); + mRegion.MoveBy(aOffset); +} + +nsDisplaySVGEffectGeometry::nsDisplaySVGEffectGeometry( + nsDisplayEffectsBase* aItem, nsDisplayListBuilder* aBuilder) + : nsDisplayItemGeometry(aItem, aBuilder), + mBBox(aItem->BBoxInUserSpace()), + mUserSpaceOffset(aItem->UserSpaceOffset()), + mFrameOffsetToReferenceFrame(aItem->ToReferenceFrame()), + mOpacity(aItem->Frame()->StyleEffects()->mOpacity), + mHandleOpacity(aItem->ShouldHandleOpacity()) {} + +void nsDisplaySVGEffectGeometry::MoveBy(const nsPoint& aOffset) { + mBounds.MoveBy(aOffset); + mFrameOffsetToReferenceFrame += aOffset; +} + +nsDisplayMasksAndClipPathsGeometry::nsDisplayMasksAndClipPathsGeometry( + nsDisplayMasksAndClipPaths* aItem, nsDisplayListBuilder* aBuilder) + : nsDisplaySVGEffectGeometry(aItem, aBuilder), + nsImageGeometryMixin(aItem, aBuilder), + mDestRects(aItem->GetDestRects().Clone()) {} + +nsDisplayFiltersGeometry::nsDisplayFiltersGeometry( + nsDisplayFilters* aItem, nsDisplayListBuilder* aBuilder) + : nsDisplaySVGEffectGeometry(aItem, aBuilder), + nsImageGeometryMixin(aItem, aBuilder) {} + +nsDisplayTableItemGeometry::nsDisplayTableItemGeometry( + nsDisplayTableItem* aItem, nsDisplayListBuilder* aBuilder, + const nsPoint& aFrameOffsetToViewport) + : nsDisplayItemGenericGeometry(aItem, aBuilder), + nsImageGeometryMixin(aItem, aBuilder), + mFrameOffsetToViewport(aFrameOffsetToViewport) {} diff --git a/layout/painting/nsDisplayListInvalidation.h b/layout/painting/nsDisplayListInvalidation.h new file mode 100644 index 0000000000..382b5ff038 --- /dev/null +++ b/layout/painting/nsDisplayListInvalidation.h @@ -0,0 +1,369 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef NSDISPLAYLISTINVALIDATION_H_ +#define NSDISPLAYLISTINVALIDATION_H_ + +#include "mozilla/Attributes.h" +#include "FrameLayerBuilder.h" +#include "nsRect.h" +#include "nsColor.h" +#include "gfxRect.h" +#include "mozilla/gfx/MatrixFwd.h" + +class nsDisplayBackgroundImage; +class nsCharClipDisplayItem; +class nsDisplayItem; +class nsDisplayListBuilder; +class nsDisplayTableItem; +class nsDisplayThemedBackground; +class nsDisplayEffectsBase; +class nsDisplayMasksAndClipPaths; +class nsDisplayFilters; + +namespace mozilla { +namespace gfx { +struct sRGBColor; +} +} // namespace mozilla + +/** + * This stores the geometry of an nsDisplayItem, and the area + * that will be affected when painting the item. + * + * It is used to retain information about display items so they + * can be compared against new display items in the next paint. + */ +class nsDisplayItemGeometry { + public: + nsDisplayItemGeometry(nsDisplayItem* aItem, nsDisplayListBuilder* aBuilder); + virtual ~nsDisplayItemGeometry(); + + /** + * Compute the area required to be invalidated if this + * display item is removed. + */ + const nsRect& ComputeInvalidationRegion() { return mBounds; } + + /** + * Shifts all retained areas of the nsDisplayItemGeometry by the given offset. + * + * This is used to compensate for scrolling, since the destination buffer + * can scroll without requiring a full repaint. + * + * @param aOffset Offset to shift by. + */ + virtual void MoveBy(const nsPoint& aOffset) { mBounds.MoveBy(aOffset); } + + virtual bool InvalidateForSyncDecodeImages() const { return false; } + + /** + * Bounds of the display item + */ + nsRect mBounds; +}; + +/** + * A default geometry implementation, used by nsDisplayItem. Retains + * and compares the bounds, and border rect. + * + * This should be sufficient for the majority of display items. + */ +class nsDisplayItemGenericGeometry : public nsDisplayItemGeometry { + public: + nsDisplayItemGenericGeometry(nsDisplayItem* aItem, + nsDisplayListBuilder* aBuilder); + + void MoveBy(const nsPoint& aOffset) override; + + nsRect mBorderRect; +}; + +bool ShouldSyncDecodeImages(nsDisplayListBuilder* aBuilder); + +/** + * nsImageGeometryMixin is a mixin for geometry items that draw images. + * Geometry items that include this mixin can track drawing results and use + * that information to inform invalidation decisions. + * + * This mixin uses CRTP; its template parameter should be the type of the class + * that is inheriting from it. See nsDisplayItemGenericImageGeometry for an + * example. + */ +template <typename T> +class nsImageGeometryMixin { + public: + nsImageGeometryMixin(nsDisplayItem* aItem, nsDisplayListBuilder* aBuilder) + : mLastDrawResult(mozilla::image::ImgDrawResult::NOT_READY), + mWaitingForPaint(false) { + // Transfer state from the previous version of this geometry item. + auto lastGeometry = static_cast<T*>( + mozilla::FrameLayerBuilder::GetMostRecentGeometry(aItem)); + if (lastGeometry) { + mLastDrawResult = lastGeometry->mLastDrawResult; + mWaitingForPaint = lastGeometry->mWaitingForPaint; + } + + // If our display item is going to invalidate to trigger sync decoding of + // images, mark ourselves as waiting for a paint. If we actually get + // painted, UpdateDrawResult will get called, and we'll clear the flag. + if (ShouldSyncDecodeImages(aBuilder) && + ShouldInvalidateToSyncDecodeImages()) { + mWaitingForPaint = true; + } + } + + static void UpdateDrawResult(nsDisplayItem* aItem, + mozilla::image::ImgDrawResult aResult) { + MOZ_ASSERT(aResult != mozilla::image::ImgDrawResult::NOT_SUPPORTED, + "ImgDrawResult::NOT_SUPPORTED should be handled already!"); + + auto lastGeometry = static_cast<T*>( + mozilla::FrameLayerBuilder::GetMostRecentGeometry(aItem)); + if (lastGeometry) { + lastGeometry->mLastDrawResult = aResult; + lastGeometry->mWaitingForPaint = false; + } + } + + bool ShouldInvalidateToSyncDecodeImages() const { + if (mWaitingForPaint) { + // We previously invalidated for sync decoding and haven't gotten painted + // since them. This suggests that our display item is completely occluded + // and there's no point in invalidating again - and because the reftest + // harness takes a new snapshot every time we invalidate, doing so might + // lead to an invalidation loop if we're in a reftest. + return false; + } + + if (mLastDrawResult == mozilla::image::ImgDrawResult::SUCCESS || + mLastDrawResult == mozilla::image::ImgDrawResult::BAD_IMAGE) { + return false; + } + + return true; + } + + private: + mozilla::image::ImgDrawResult mLastDrawResult; + bool mWaitingForPaint; +}; + +/** + * nsDisplayItemGenericImageGeometry is a generic geometry item class that + * includes nsImageGeometryMixin. + * + * This should be sufficient for most display items that draw images. + */ +class nsDisplayItemGenericImageGeometry + : public nsDisplayItemGenericGeometry, + public nsImageGeometryMixin<nsDisplayItemGenericImageGeometry> { + public: + nsDisplayItemGenericImageGeometry(nsDisplayItem* aItem, + nsDisplayListBuilder* aBuilder) + : nsDisplayItemGenericGeometry(aItem, aBuilder), + nsImageGeometryMixin(aItem, aBuilder) {} + + bool InvalidateForSyncDecodeImages() const override { + return ShouldInvalidateToSyncDecodeImages(); + } +}; + +class nsDisplayItemBoundsGeometry : public nsDisplayItemGeometry { + public: + nsDisplayItemBoundsGeometry(nsDisplayItem* aItem, + nsDisplayListBuilder* aBuilder); + + bool mHasRoundedCorners; +}; + +class nsDisplayBorderGeometry + : public nsDisplayItemGeometry, + public nsImageGeometryMixin<nsDisplayBorderGeometry> { + public: + nsDisplayBorderGeometry(nsDisplayItem* aItem, nsDisplayListBuilder* aBuilder); + + bool InvalidateForSyncDecodeImages() const override { + return ShouldInvalidateToSyncDecodeImages(); + } +}; + +class nsDisplayBackgroundGeometry + : public nsDisplayItemGeometry, + public nsImageGeometryMixin<nsDisplayBackgroundGeometry> { + public: + nsDisplayBackgroundGeometry(nsDisplayBackgroundImage* aItem, + nsDisplayListBuilder* aBuilder); + + void MoveBy(const nsPoint& aOffset) override; + + bool InvalidateForSyncDecodeImages() const override { + return ShouldInvalidateToSyncDecodeImages(); + } + + nsRect mPositioningArea; + nsRect mDestRect; +}; + +class nsDisplayThemedBackgroundGeometry : public nsDisplayItemGeometry { + public: + nsDisplayThemedBackgroundGeometry(nsDisplayThemedBackground* aItem, + nsDisplayListBuilder* aBuilder); + + void MoveBy(const nsPoint& aOffset) override; + + nsRect mPositioningArea; + bool mWindowIsActive; +}; + +class nsDisplayTreeBodyGeometry + : public nsDisplayItemGenericGeometry, + public nsImageGeometryMixin<nsDisplayTreeBodyGeometry> { + public: + nsDisplayTreeBodyGeometry(nsDisplayItem* aItem, + nsDisplayListBuilder* aBuilder, + bool aWindowIsActive) + : nsDisplayItemGenericGeometry(aItem, aBuilder), + nsImageGeometryMixin(aItem, aBuilder), + mWindowIsActive(aWindowIsActive) {} + + bool InvalidateForSyncDecodeImages() const override { + return ShouldInvalidateToSyncDecodeImages(); + } + + bool mWindowIsActive = false; +}; + +class nsDisplayBoxShadowInnerGeometry : public nsDisplayItemGeometry { + public: + nsDisplayBoxShadowInnerGeometry(nsDisplayItem* aItem, + nsDisplayListBuilder* aBuilder); + + void MoveBy(const nsPoint& aOffset) override; + + nsRect mPaddingRect; +}; + +class nsDisplayBoxShadowOuterGeometry : public nsDisplayItemGenericGeometry { + public: + nsDisplayBoxShadowOuterGeometry(nsDisplayItem* aItem, + nsDisplayListBuilder* aBuilder, + float aOpacity); + + float mOpacity; +}; + +class nsDisplaySolidColorGeometry : public nsDisplayItemBoundsGeometry { + public: + nsDisplaySolidColorGeometry(nsDisplayItem* aItem, + nsDisplayListBuilder* aBuilder, nscolor aColor) + : nsDisplayItemBoundsGeometry(aItem, aBuilder), mColor(aColor) {} + + nscolor mColor; +}; + +class nsDisplaySolidColorRegionGeometry : public nsDisplayItemBoundsGeometry { + public: + nsDisplaySolidColorRegionGeometry(nsDisplayItem* aItem, + nsDisplayListBuilder* aBuilder, + const nsRegion& aRegion, + mozilla::gfx::sRGBColor aColor) + : nsDisplayItemBoundsGeometry(aItem, aBuilder), + mRegion(aRegion), + mColor(aColor) {} + + void MoveBy(const nsPoint& aOffset) override; + + nsRegion mRegion; + mozilla::gfx::sRGBColor mColor; +}; + +class nsDisplaySVGEffectGeometry : public nsDisplayItemGeometry { + public: + nsDisplaySVGEffectGeometry(nsDisplayEffectsBase* aItem, + nsDisplayListBuilder* aBuilder); + + void MoveBy(const nsPoint& aOffset) override; + + gfxRect mBBox; + gfxPoint mUserSpaceOffset; + nsPoint mFrameOffsetToReferenceFrame; + float mOpacity; + bool mHandleOpacity; +}; + +class nsDisplayMasksAndClipPathsGeometry + : public nsDisplaySVGEffectGeometry, + public nsImageGeometryMixin<nsDisplayMasksAndClipPathsGeometry> { + public: + nsDisplayMasksAndClipPathsGeometry(nsDisplayMasksAndClipPaths* aItem, + nsDisplayListBuilder* aBuilder); + + bool InvalidateForSyncDecodeImages() const override { + return ShouldInvalidateToSyncDecodeImages(); + } + + nsTArray<nsRect> mDestRects; +}; + +class nsDisplayFiltersGeometry + : public nsDisplaySVGEffectGeometry, + public nsImageGeometryMixin<nsDisplayFiltersGeometry> { + public: + nsDisplayFiltersGeometry(nsDisplayFilters* aItem, + nsDisplayListBuilder* aBuilder); + + bool InvalidateForSyncDecodeImages() const override { + return ShouldInvalidateToSyncDecodeImages(); + } +}; + +class nsDisplayTableItemGeometry + : public nsDisplayItemGenericGeometry, + public nsImageGeometryMixin<nsDisplayTableItemGeometry> { + public: + nsDisplayTableItemGeometry(nsDisplayTableItem* aItem, + nsDisplayListBuilder* aBuilder, + const nsPoint& aFrameOffsetToViewport); + + bool InvalidateForSyncDecodeImages() const override { + return ShouldInvalidateToSyncDecodeImages(); + } + + nsPoint mFrameOffsetToViewport; +}; + +class nsDisplayOpacityGeometry : public nsDisplayItemGenericGeometry { + public: + nsDisplayOpacityGeometry(nsDisplayItem* aItem, nsDisplayListBuilder* aBuilder, + float aOpacity) + : nsDisplayItemGenericGeometry(aItem, aBuilder), mOpacity(aOpacity) {} + + float mOpacity; +}; + +class nsDisplayTransformGeometry : public nsDisplayItemGeometry { + public: + nsDisplayTransformGeometry(nsDisplayItem* aItem, + nsDisplayListBuilder* aBuilder, + const mozilla::gfx::Matrix4x4Flagged& aTransform, + int32_t aAppUnitsPerDevPixel) + : nsDisplayItemGeometry(aItem, aBuilder), + mTransform(aTransform), + mAppUnitsPerDevPixel(aAppUnitsPerDevPixel) {} + + void MoveBy(const nsPoint& aOffset) override { + nsDisplayItemGeometry::MoveBy(aOffset); + mTransform.PostTranslate( + NSAppUnitsToFloatPixels(aOffset.x, mAppUnitsPerDevPixel), + NSAppUnitsToFloatPixels(aOffset.y, mAppUnitsPerDevPixel), 0.0f); + } + + mozilla::gfx::Matrix4x4Flagged mTransform; + int32_t mAppUnitsPerDevPixel; +}; + +#endif /*NSDISPLAYLISTINVALIDATION_H_*/ diff --git a/layout/painting/nsImageRenderer.cpp b/layout/painting/nsImageRenderer.cpp new file mode 100644 index 0000000000..a23a900860 --- /dev/null +++ b/layout/painting/nsImageRenderer.cpp @@ -0,0 +1,1060 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* utility code for drawing images as CSS borders, backgrounds, and shapes. */ + +#include "nsImageRenderer.h" + +#include "mozilla/webrender/WebRenderAPI.h" + +#include "gfxContext.h" +#include "gfxDrawable.h" +#include "ImageOps.h" +#include "ImageRegion.h" +#include "mozilla/layers/RenderRootStateManager.h" +#include "mozilla/layers/StackingContextHelper.h" +#include "mozilla/layers/WebRenderLayerManager.h" +#include "nsContentUtils.h" +#include "nsCSSRendering.h" +#include "nsCSSRenderingGradients.h" +#include "nsDeviceContext.h" +#include "nsIFrame.h" +#include "nsLayoutUtils.h" +#include "nsStyleStructInlines.h" +#include "mozilla/ISVGDisplayableFrame.h" +#include "mozilla/SVGIntegrationUtils.h" +#include "mozilla/SVGPaintServerFrame.h" +#include "mozilla/SVGObserverUtils.h" + +using namespace mozilla; +using namespace mozilla::gfx; +using namespace mozilla::image; +using namespace mozilla::layers; + +nsSize CSSSizeOrRatio::ComputeConcreteSize() const { + NS_ASSERTION(CanComputeConcreteSize(), "Cannot compute"); + if (mHasWidth && mHasHeight) { + return nsSize(mWidth, mHeight); + } + if (mHasWidth) { + return nsSize(mWidth, mRatio.Inverted().ApplyTo(mWidth)); + } + + MOZ_ASSERT(mHasHeight); + return nsSize(mRatio.ApplyTo(mHeight), mHeight); +} + +nsImageRenderer::nsImageRenderer(nsIFrame* aForFrame, const StyleImage* aImage, + uint32_t aFlags) + : mForFrame(aForFrame), + mImage(&aImage->FinalImage()), + mType(mImage->tag), + mImageContainer(nullptr), + mGradientData(nullptr), + mPaintServerFrame(nullptr), + mPrepareResult(ImgDrawResult::NOT_READY), + mSize(0, 0), + mFlags(aFlags), + mExtendMode(ExtendMode::CLAMP), + mMaskOp(StyleMaskMode::MatchSource) {} + +bool nsImageRenderer::PrepareImage() { + if (mImage->IsNone()) { + mPrepareResult = ImgDrawResult::BAD_IMAGE; + return false; + } + + const bool isImageRequest = mImage->IsImageRequestType(); + MOZ_ASSERT_IF(!isImageRequest, !mImage->GetImageRequest()); + imgRequestProxy* request = nullptr; + if (isImageRequest) { + request = mImage->GetImageRequest(); + if (!request) { + // request could be null here if the StyleImage refused + // to load a same-document URL, or the url was invalid, for example. + mPrepareResult = ImgDrawResult::BAD_IMAGE; + return false; + } + } + + if (!mImage->IsComplete()) { + MOZ_DIAGNOSTIC_ASSERT(isImageRequest); + + // Make sure the image is actually decoding. + bool frameComplete = + request->StartDecodingWithResult(imgIContainer::FLAG_ASYNC_NOTIFY); + + // Boost the loading priority since we know we want to draw the image. + if (mFlags & nsImageRenderer::FLAG_PAINTING_TO_WINDOW) { + request->BoostPriority(imgIRequest::CATEGORY_DISPLAY); + } + + // Check again to see if we finished. + // We cannot prepare the image for rendering if it is not fully loaded. + if (!frameComplete && !mImage->IsComplete()) { + uint32_t imageStatus = 0; + request->GetImageStatus(&imageStatus); + if (imageStatus & imgIRequest::STATUS_ERROR) { + mPrepareResult = ImgDrawResult::BAD_IMAGE; + return false; + } + + // Special case: If not errored, and we requested a sync decode, and the + // image has loaded, push on through because the Draw() will do a sync + // decode then. + const bool syncDecodeWillComplete = + (mFlags & FLAG_SYNC_DECODE_IMAGES) && + (imageStatus & imgIRequest::STATUS_LOAD_COMPLETE); + if (!syncDecodeWillComplete) { + mPrepareResult = ImgDrawResult::NOT_READY; + return false; + } + } + } + + if (isImageRequest) { + nsCOMPtr<imgIContainer> srcImage; + DebugOnly<nsresult> rv = request->GetImage(getter_AddRefs(srcImage)); + MOZ_ASSERT(NS_SUCCEEDED(rv) && srcImage, + "If GetImage() is failing, mImage->IsComplete() " + "should have returned false"); + + if (srcImage) { + srcImage = nsLayoutUtils::OrientImage( + srcImage, mForFrame->StyleVisibility()->mImageOrientation); + } + + if (!mImage->IsRect()) { + mImageContainer.swap(srcImage); + } else { + auto croprect = mImage->ComputeActualCropRect(); + if (!croprect || croprect->mRect.IsEmpty()) { + // The cropped image has zero size + mPrepareResult = ImgDrawResult::BAD_IMAGE; + return false; + } + if (croprect->mIsEntireImage) { + // The cropped image is identical to the source image + mImageContainer.swap(srcImage); + } else { + nsCOMPtr<imgIContainer> subImage = + ImageOps::Clip(srcImage, croprect->mRect, Nothing()); + mImageContainer.swap(subImage); + } + } + mPrepareResult = ImgDrawResult::SUCCESS; + } else if (mImage->IsGradient()) { + mGradientData = &*mImage->AsGradient(); + mPrepareResult = ImgDrawResult::SUCCESS; + } else if (mImage->IsElement()) { + dom::Element* paintElement = // may be null + SVGObserverUtils::GetAndObserveBackgroundImage( + mForFrame->FirstContinuation(), mImage->AsElement().AsAtom()); + // If the referenced element is an <img>, <canvas>, or <video> element, + // prefer SurfaceFromElement as it's more reliable. + mImageElementSurface = nsLayoutUtils::SurfaceFromElement(paintElement); + + if (!mImageElementSurface.GetSourceSurface()) { + nsIFrame* paintServerFrame = + paintElement ? paintElement->GetPrimaryFrame() : nullptr; + // If there's no referenced frame, or the referenced frame is + // non-displayable SVG, then we have nothing valid to paint. + if (!paintServerFrame || + (paintServerFrame->IsFrameOfType(nsIFrame::eSVG) && + !static_cast<SVGPaintServerFrame*>( + do_QueryFrame(paintServerFrame)) && + !static_cast<ISVGDisplayableFrame*>( + do_QueryFrame(paintServerFrame)))) { + mPrepareResult = ImgDrawResult::BAD_IMAGE; + return false; + } + mPaintServerFrame = paintServerFrame; + } + + mPrepareResult = ImgDrawResult::SUCCESS; + } else if (mImage->IsCrossFade()) { + // See bug 546052 - cross-fade implementation still being worked + // on. + mPrepareResult = ImgDrawResult::BAD_IMAGE; + return false; + } else { + MOZ_ASSERT(mImage->IsNone(), "Unknown image type?"); + } + + return IsReady(); +} + +CSSSizeOrRatio nsImageRenderer::ComputeIntrinsicSize() { + NS_ASSERTION(IsReady(), + "Ensure PrepareImage() has returned true " + "before calling me"); + + CSSSizeOrRatio result; + switch (mType) { + case StyleImage::Tag::Rect: + case StyleImage::Tag::Url: { + bool haveWidth, haveHeight; + CSSIntSize imageIntSize; + nsLayoutUtils::ComputeSizeForDrawing( + mImageContainer, imageIntSize, result.mRatio, haveWidth, haveHeight); + if (haveWidth) { + result.SetWidth(nsPresContext::CSSPixelsToAppUnits(imageIntSize.width)); + } + if (haveHeight) { + result.SetHeight( + nsPresContext::CSSPixelsToAppUnits(imageIntSize.height)); + } + + // If we know the aspect ratio and one of the dimensions, + // we can compute the other missing width or height. + if (!haveHeight && haveWidth && result.mRatio) { + nscoord intrinsicHeight = + result.mRatio.Inverted().ApplyTo(imageIntSize.width); + result.SetHeight(nsPresContext::CSSPixelsToAppUnits(intrinsicHeight)); + } else if (haveHeight && !haveWidth && result.mRatio) { + nscoord intrinsicWidth = result.mRatio.ApplyTo(imageIntSize.height); + result.SetWidth(nsPresContext::CSSPixelsToAppUnits(intrinsicWidth)); + } + + break; + } + case StyleImage::Tag::Element: { + // XXX element() should have the width/height of the referenced element, + // and that element's ratio, if it matches. If it doesn't match, it + // should have no width/height or ratio. See element() in CSS images: + // <http://dev.w3.org/csswg/css-images-4/#element-notation>. + // Make sure to change nsStyleImageLayers::Size::DependsOnFrameSize + // when fixing this! + if (mPaintServerFrame) { + // SVG images have no intrinsic size + if (!mPaintServerFrame->IsFrameOfType(nsIFrame::eSVG)) { + // The intrinsic image size for a generic nsIFrame paint server is + // the union of the border-box rects of all of its continuations, + // rounded to device pixels. + int32_t appUnitsPerDevPixel = + mForFrame->PresContext()->AppUnitsPerDevPixel(); + result.SetSize(IntSizeToAppUnits( + SVGIntegrationUtils::GetContinuationUnionSize(mPaintServerFrame) + .ToNearestPixels(appUnitsPerDevPixel), + appUnitsPerDevPixel)); + } + } else { + NS_ASSERTION(mImageElementSurface.GetSourceSurface(), + "Surface should be ready."); + IntSize surfaceSize = mImageElementSurface.mSize; + result.SetSize( + nsSize(nsPresContext::CSSPixelsToAppUnits(surfaceSize.width), + nsPresContext::CSSPixelsToAppUnits(surfaceSize.height))); + } + break; + } + case StyleImage::Tag::ImageSet: + MOZ_FALLTHROUGH_ASSERT("image-set should be resolved already"); + // Bug 546052 cross-fade not yet implemented. + case StyleImage::Tag::CrossFade: + // Per <http://dev.w3.org/csswg/css3-images/#gradients>, gradients have no + // intrinsic dimensions. + case StyleImage::Tag::Gradient: + case StyleImage::Tag::None: + break; + } + + return result; +} + +/* static */ +nsSize nsImageRenderer::ComputeConcreteSize( + const CSSSizeOrRatio& aSpecifiedSize, const CSSSizeOrRatio& aIntrinsicSize, + const nsSize& aDefaultSize) { + // The specified size is fully specified, just use that + if (aSpecifiedSize.IsConcrete()) { + return aSpecifiedSize.ComputeConcreteSize(); + } + + MOZ_ASSERT(!aSpecifiedSize.mHasWidth || !aSpecifiedSize.mHasHeight); + + if (!aSpecifiedSize.mHasWidth && !aSpecifiedSize.mHasHeight) { + // no specified size, try using the intrinsic size + if (aIntrinsicSize.CanComputeConcreteSize()) { + return aIntrinsicSize.ComputeConcreteSize(); + } + + if (aIntrinsicSize.mHasWidth) { + return nsSize(aIntrinsicSize.mWidth, aDefaultSize.height); + } + if (aIntrinsicSize.mHasHeight) { + return nsSize(aDefaultSize.width, aIntrinsicSize.mHeight); + } + + // couldn't use the intrinsic size either, revert to using the default size + return ComputeConstrainedSize(aDefaultSize, aIntrinsicSize.mRatio, CONTAIN); + } + + MOZ_ASSERT(aSpecifiedSize.mHasWidth || aSpecifiedSize.mHasHeight); + + // The specified height is partial, try to compute the missing part. + if (aSpecifiedSize.mHasWidth) { + nscoord height; + if (aIntrinsicSize.HasRatio()) { + height = aIntrinsicSize.mRatio.Inverted().ApplyTo(aSpecifiedSize.mWidth); + } else if (aIntrinsicSize.mHasHeight) { + height = aIntrinsicSize.mHeight; + } else { + height = aDefaultSize.height; + } + return nsSize(aSpecifiedSize.mWidth, height); + } + + MOZ_ASSERT(aSpecifiedSize.mHasHeight); + nscoord width; + if (aIntrinsicSize.HasRatio()) { + width = aIntrinsicSize.mRatio.ApplyTo(aSpecifiedSize.mHeight); + } else if (aIntrinsicSize.mHasWidth) { + width = aIntrinsicSize.mWidth; + } else { + width = aDefaultSize.width; + } + return nsSize(width, aSpecifiedSize.mHeight); +} + +/* static */ +nsSize nsImageRenderer::ComputeConstrainedSize( + const nsSize& aConstrainingSize, const AspectRatio& aIntrinsicRatio, + FitType aFitType) { + if (!aIntrinsicRatio) { + return aConstrainingSize; + } + + // Suppose we're doing a "contain" fit. If the image's aspect ratio has a + // "fatter" shape than the constraint area, then we need to use the + // constraint area's full width, and we need to use the aspect ratio to + // produce a height. On the other hand, if the aspect ratio is "skinnier", we + // use the constraint area's full height, and we use the aspect ratio to + // produce a width. (If instead we're doing a "cover" fit, then it can easily + // be seen that we should do precisely the opposite.) + // + // We check if the image's aspect ratio is "fatter" than the constraint area + // by simply applying the aspect ratio to the constraint area's height, to + // produce a "hypothetical width", and we check whether that + // aspect-ratio-provided "hypothetical width" is wider than the constraint + // area's actual width. If it is, then the aspect ratio is fatter than the + // constraint area. + // + // This is equivalent to the more descriptive alternative: + // + // AspectRatio::FromSize(aConstrainingSize) < aIntrinsicRatio + // + // But gracefully handling the case where one of the two dimensions from + // aConstrainingSize is zero. This is easy to prove since: + // + // aConstrainingSize.width / aConstrainingSize.height < aIntrinsicRatio + // + // Is trivially equivalent to: + // + // aIntrinsicRatio.width < aIntrinsicRatio * aConstrainingSize.height + // + // For the cases where height is not zero. + // + // We use float math here to avoid losing precision for very large backgrounds + // since we use saturating nscoord math otherwise. + const float constraintWidth = float(aConstrainingSize.width); + const float hypotheticalWidth = + aIntrinsicRatio.ApplyToFloat(aConstrainingSize.height); + + nsSize size; + if ((aFitType == CONTAIN) == (constraintWidth < hypotheticalWidth)) { + size.width = aConstrainingSize.width; + size.height = aIntrinsicRatio.Inverted().ApplyTo(aConstrainingSize.width); + // If we're reducing the size by less than one css pixel, then just use the + // constraining size. + if (aFitType == CONTAIN && + aConstrainingSize.height - size.height < AppUnitsPerCSSPixel()) { + size.height = aConstrainingSize.height; + } + } else { + size.height = aConstrainingSize.height; + size.width = aIntrinsicRatio.ApplyTo(aConstrainingSize.height); + if (aFitType == CONTAIN && + aConstrainingSize.width - size.width < AppUnitsPerCSSPixel()) { + size.width = aConstrainingSize.width; + } + } + return size; +} + +/** + * mSize is the image's "preferred" size for this particular rendering, while + * the drawn (aka concrete) size is the actual rendered size after accounting + * for background-size etc.. The preferred size is most often the image's + * intrinsic dimensions. But for images with incomplete intrinsic dimensions, + * the preferred size varies, depending on the specified and default sizes, see + * nsImageRenderer::Compute*Size. + * + * This distinction is necessary because the components of a vector image are + * specified with respect to its preferred size for a rendering situation, not + * to its actual rendered size. For example, consider a 4px wide background + * vector image with no height which contains a left-aligned + * 2px wide black rectangle with height 100%. If the background-size width is + * auto (or 4px), the vector image will render 4px wide, and the black rectangle + * will be 2px wide. If the background-size width is 8px, the vector image will + * render 8px wide, and the black rectangle will be 4px wide -- *not* 2px wide. + * In both cases mSize.width will be 4px; but in the first case the returned + * width will be 4px, while in the second case the returned width will be 8px. + */ +void nsImageRenderer::SetPreferredSize(const CSSSizeOrRatio& aIntrinsicSize, + const nsSize& aDefaultSize) { + mSize.width = + aIntrinsicSize.mHasWidth ? aIntrinsicSize.mWidth : aDefaultSize.width; + mSize.height = + aIntrinsicSize.mHasHeight ? aIntrinsicSize.mHeight : aDefaultSize.height; +} + +// Convert from nsImageRenderer flags to the flags we want to use for drawing in +// the imgIContainer namespace. +static uint32_t ConvertImageRendererToDrawFlags(uint32_t aImageRendererFlags) { + uint32_t drawFlags = imgIContainer::FLAG_NONE; + if (aImageRendererFlags & nsImageRenderer::FLAG_SYNC_DECODE_IMAGES) { + drawFlags |= imgIContainer::FLAG_SYNC_DECODE; + } + if (aImageRendererFlags & (nsImageRenderer::FLAG_PAINTING_TO_WINDOW | + nsImageRenderer::FLAG_HIGH_QUALITY_SCALING)) { + drawFlags |= imgIContainer::FLAG_HIGH_QUALITY_SCALING; + } + return drawFlags; +} + +ImgDrawResult nsImageRenderer::Draw(nsPresContext* aPresContext, + gfxContext& aRenderingContext, + const nsRect& aDirtyRect, + const nsRect& aDest, const nsRect& aFill, + const nsPoint& aAnchor, + const nsSize& aRepeatSize, + const CSSIntRect& aSrc, float aOpacity) { + if (!IsReady()) { + MOZ_ASSERT_UNREACHABLE( + "Ensure PrepareImage() has returned true before " + "calling me"); + return ImgDrawResult::TEMPORARY_ERROR; + } + + if (aDest.IsEmpty() || aFill.IsEmpty() || mSize.width <= 0 || + mSize.height <= 0) { + return ImgDrawResult::SUCCESS; + } + + SamplingFilter samplingFilter = + nsLayoutUtils::GetSamplingFilterForFrame(mForFrame); + ImgDrawResult result = ImgDrawResult::SUCCESS; + RefPtr<gfxContext> ctx = &aRenderingContext; + IntRect tmpDTRect; + + if (ctx->CurrentOp() != CompositionOp::OP_OVER || + mMaskOp == StyleMaskMode::Luminance) { + gfxRect clipRect = ctx->GetClipExtents(gfxContext::eDeviceSpace); + tmpDTRect = RoundedOut(ToRect(clipRect)); + if (tmpDTRect.IsEmpty()) { + return ImgDrawResult::SUCCESS; + } + RefPtr<DrawTarget> tempDT = + gfxPlatform::GetPlatform()->CreateSimilarSoftwareDrawTarget( + ctx->GetDrawTarget(), tmpDTRect.Size(), SurfaceFormat::B8G8R8A8); + if (!tempDT || !tempDT->IsValid()) { + gfxDevCrash(LogReason::InvalidContext) + << "ImageRenderer::Draw problem " << gfx::hexa(tempDT); + return ImgDrawResult::TEMPORARY_ERROR; + } + tempDT->SetTransform(ctx->GetDrawTarget()->GetTransform() * + Matrix::Translation(-tmpDTRect.TopLeft())); + ctx = gfxContext::CreatePreservingTransformOrNull(tempDT); + if (!ctx) { + gfxDevCrash(LogReason::InvalidContext) + << "ImageRenderer::Draw problem " << gfx::hexa(tempDT); + return ImgDrawResult::TEMPORARY_ERROR; + } + } + + switch (mType) { + case StyleImage::Tag::Rect: + case StyleImage::Tag::Url: { + result = nsLayoutUtils::DrawBackgroundImage( + *ctx, mForFrame, aPresContext, mImageContainer, samplingFilter, aDest, + aFill, aRepeatSize, aAnchor, aDirtyRect, + ConvertImageRendererToDrawFlags(mFlags), mExtendMode, aOpacity); + break; + } + case StyleImage::Tag::Gradient: { + nsCSSGradientRenderer renderer = nsCSSGradientRenderer::Create( + aPresContext, mForFrame->Style(), *mGradientData, mSize); + + renderer.Paint(*ctx, aDest, aFill, aRepeatSize, aSrc, aDirtyRect, + aOpacity); + break; + } + case StyleImage::Tag::Element: { + RefPtr<gfxDrawable> drawable = DrawableForElement(aDest, *ctx); + if (!drawable) { + NS_WARNING("Could not create drawable for element"); + return ImgDrawResult::TEMPORARY_ERROR; + } + + nsCOMPtr<imgIContainer> image(ImageOps::CreateFromDrawable(drawable)); + result = nsLayoutUtils::DrawImage( + *ctx, mForFrame->Style(), aPresContext, image, samplingFilter, aDest, + aFill, aAnchor, aDirtyRect, ConvertImageRendererToDrawFlags(mFlags), + aOpacity); + break; + } + case StyleImage::Tag::ImageSet: + MOZ_FALLTHROUGH_ASSERT("image-set should be resolved already"); + // See bug 546052 - cross-fade implementation still being worked + // on. + case StyleImage::Tag::CrossFade: + case StyleImage::Tag::None: + break; + } + + if (!tmpDTRect.IsEmpty()) { + DrawTarget* dt = aRenderingContext.GetDrawTarget(); + Matrix oldTransform = dt->GetTransform(); + dt->SetTransform(Matrix()); + if (mMaskOp == StyleMaskMode::Luminance) { + RefPtr<SourceSurface> surf = ctx->GetDrawTarget()->IntoLuminanceSource( + LuminanceType::LUMINANCE, 1.0f); + dt->MaskSurface(ColorPattern(DeviceColor(0, 0, 0, 1.0f)), surf, + tmpDTRect.TopLeft(), + DrawOptions(1.0f, aRenderingContext.CurrentOp())); + } else { + RefPtr<SourceSurface> surf = ctx->GetDrawTarget()->Snapshot(); + dt->DrawSurface( + surf, + Rect(tmpDTRect.x, tmpDTRect.y, tmpDTRect.width, tmpDTRect.height), + Rect(0, 0, tmpDTRect.width, tmpDTRect.height), + DrawSurfaceOptions(SamplingFilter::POINT), + DrawOptions(1.0f, aRenderingContext.CurrentOp())); + } + + dt->SetTransform(oldTransform); + } + + if (!mImage->IsComplete()) { + result &= ImgDrawResult::SUCCESS_NOT_COMPLETE; + } + + return result; +} + +ImgDrawResult nsImageRenderer::BuildWebRenderDisplayItems( + nsPresContext* aPresContext, mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const mozilla::layers::StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, nsDisplayItem* aItem, + const nsRect& aDirtyRect, const nsRect& aDest, const nsRect& aFill, + const nsPoint& aAnchor, const nsSize& aRepeatSize, const CSSIntRect& aSrc, + float aOpacity) { + if (!IsReady()) { + MOZ_ASSERT_UNREACHABLE( + "Ensure PrepareImage() has returned true before " + "calling me"); + return ImgDrawResult::NOT_READY; + } + + if (aDest.IsEmpty() || aFill.IsEmpty() || mSize.width <= 0 || + mSize.height <= 0) { + return ImgDrawResult::SUCCESS; + } + + ImgDrawResult drawResult = ImgDrawResult::SUCCESS; + switch (mType) { + case StyleImage::Tag::Gradient: { + nsCSSGradientRenderer renderer = nsCSSGradientRenderer::Create( + aPresContext, mForFrame->Style(), *mGradientData, mSize); + + renderer.BuildWebRenderDisplayItems(aBuilder, aSc, aDest, aFill, + aRepeatSize, aSrc, + !aItem->BackfaceIsHidden(), aOpacity); + break; + } + case StyleImage::Tag::Rect: + case StyleImage::Tag::Url: { + uint32_t containerFlags = imgIContainer::FLAG_ASYNC_NOTIFY; + if (mFlags & (nsImageRenderer::FLAG_PAINTING_TO_WINDOW | + nsImageRenderer::FLAG_HIGH_QUALITY_SCALING)) { + containerFlags |= imgIContainer::FLAG_HIGH_QUALITY_SCALING; + } + if (mFlags & nsImageRenderer::FLAG_SYNC_DECODE_IMAGES) { + containerFlags |= imgIContainer::FLAG_SYNC_DECODE; + } + + CSSIntSize destCSSSize{ + nsPresContext::AppUnitsToIntCSSPixels(aDest.width), + nsPresContext::AppUnitsToIntCSSPixels(aDest.height)}; + + Maybe<SVGImageContext> svgContext( + Some(SVGImageContext(Some(destCSSSize)))); + + const int32_t appUnitsPerDevPixel = + mForFrame->PresContext()->AppUnitsPerDevPixel(); + LayoutDeviceRect destRect = + LayoutDeviceRect::FromAppUnits(aDest, appUnitsPerDevPixel); + auto stretchSize = wr::ToLayoutSize(destRect.Size()); + + gfx::IntSize decodeSize = + nsLayoutUtils::ComputeImageContainerDrawingParameters( + mImageContainer, mForFrame, destRect, aSc, containerFlags, + svgContext); + + RefPtr<layers::ImageContainer> container; + drawResult = mImageContainer->GetImageContainerAtSize( + aManager->LayerManager(), decodeSize, svgContext, containerFlags, + getter_AddRefs(container)); + if (!container) { + NS_WARNING("Failed to get image container"); + break; + } + + mozilla::wr::ImageRendering rendering = wr::ToImageRendering( + nsLayoutUtils::GetSamplingFilterForFrame(aItem->Frame())); + gfx::IntSize size; + Maybe<wr::ImageKey> key = aManager->CommandBuilder().CreateImageKey( + aItem, container, aBuilder, aResources, rendering, aSc, size, + Nothing()); + + if (key.isNothing()) { + break; + } + + wr::LayoutRect dest = wr::ToLayoutRect(destRect); + + wr::LayoutRect clip = wr::ToLayoutRect( + LayoutDeviceRect::FromAppUnits(aFill, appUnitsPerDevPixel)); + + if (mExtendMode == ExtendMode::CLAMP) { + // The image is not repeating. Just push as a regular image. + aBuilder.PushImage(dest, clip, !aItem->BackfaceIsHidden(), rendering, + key.value()); + } else { + nsPoint firstTilePos = nsLayoutUtils::GetBackgroundFirstTilePos( + aDest.TopLeft(), aFill.TopLeft(), aRepeatSize); + LayoutDeviceRect fillRect = LayoutDeviceRect::FromAppUnits( + nsRect(firstTilePos.x, firstTilePos.y, + aFill.XMost() - firstTilePos.x, + aFill.YMost() - firstTilePos.y), + appUnitsPerDevPixel); + wr::LayoutRect fill = wr::ToLayoutRect(fillRect); + + switch (mExtendMode) { + case ExtendMode::REPEAT_Y: + fill.origin.x = dest.origin.x; + fill.size.width = dest.size.width; + stretchSize.width = dest.size.width; + break; + case ExtendMode::REPEAT_X: + fill.origin.y = dest.origin.y; + fill.size.height = dest.size.height; + stretchSize.height = dest.size.height; + break; + default: + break; + } + + LayoutDeviceSize gapSize = LayoutDeviceSize::FromAppUnits( + aRepeatSize - aDest.Size(), appUnitsPerDevPixel); + + aBuilder.PushRepeatingImage(fill, clip, !aItem->BackfaceIsHidden(), + stretchSize, wr::ToLayoutSize(gapSize), + rendering, key.value()); + } + break; + } + default: + break; + } + + if (!mImage->IsComplete() && drawResult == ImgDrawResult::SUCCESS) { + return ImgDrawResult::SUCCESS_NOT_COMPLETE; + } + return drawResult; +} + +already_AddRefed<gfxDrawable> nsImageRenderer::DrawableForElement( + const nsRect& aImageRect, gfxContext& aContext) { + NS_ASSERTION(mType == StyleImage::Tag::Element, + "DrawableForElement only makes sense if backed by an element"); + if (mPaintServerFrame) { + // XXX(seth): In order to not pass FLAG_SYNC_DECODE_IMAGES here, + // DrawableFromPaintServer would have to return a ImgDrawResult indicating + // whether any images could not be painted because they weren't fully + // decoded. Even always passing FLAG_SYNC_DECODE_IMAGES won't eliminate all + // problems, as it won't help if there are image which haven't finished + // loading, but it's better than nothing. + int32_t appUnitsPerDevPixel = + mForFrame->PresContext()->AppUnitsPerDevPixel(); + nsRect destRect = aImageRect - aImageRect.TopLeft(); + nsIntSize roundedOut = destRect.ToOutsidePixels(appUnitsPerDevPixel).Size(); + IntSize imageSize(roundedOut.width, roundedOut.height); + + RefPtr<gfxDrawable> drawable; + + SurfaceFormat format = aContext.GetDrawTarget()->GetFormat(); + // Don't allow creating images that are too big + if (aContext.GetDrawTarget()->CanCreateSimilarDrawTarget(imageSize, + format)) { + drawable = SVGIntegrationUtils::DrawableFromPaintServer( + mPaintServerFrame, mForFrame, mSize, imageSize, + aContext.GetDrawTarget(), aContext.CurrentMatrixDouble(), + SVGIntegrationUtils::FLAG_SYNC_DECODE_IMAGES); + } + + return drawable.forget(); + } + NS_ASSERTION(mImageElementSurface.GetSourceSurface(), + "Surface should be ready."); + RefPtr<gfxDrawable> drawable = + new gfxSurfaceDrawable(mImageElementSurface.GetSourceSurface().get(), + mImageElementSurface.mSize); + return drawable.forget(); +} + +ImgDrawResult nsImageRenderer::DrawLayer( + nsPresContext* aPresContext, gfxContext& aRenderingContext, + const nsRect& aDest, const nsRect& aFill, const nsPoint& aAnchor, + const nsRect& aDirty, const nsSize& aRepeatSize, float aOpacity) { + if (!IsReady()) { + MOZ_ASSERT_UNREACHABLE( + "Ensure PrepareImage() has returned true before " + "calling me"); + return ImgDrawResult::TEMPORARY_ERROR; + } + + if (aDest.IsEmpty() || aFill.IsEmpty() || mSize.width <= 0 || + mSize.height <= 0) { + return ImgDrawResult::SUCCESS; + } + + return Draw( + aPresContext, aRenderingContext, aDirty, aDest, aFill, aAnchor, + aRepeatSize, + CSSIntRect(0, 0, nsPresContext::AppUnitsToIntCSSPixels(mSize.width), + nsPresContext::AppUnitsToIntCSSPixels(mSize.height)), + aOpacity); +} + +ImgDrawResult nsImageRenderer::BuildWebRenderDisplayItemsForLayer( + nsPresContext* aPresContext, mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const mozilla::layers::StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, nsDisplayItem* aItem, + const nsRect& aDest, const nsRect& aFill, const nsPoint& aAnchor, + const nsRect& aDirty, const nsSize& aRepeatSize, float aOpacity) { + if (!IsReady()) { + MOZ_ASSERT_UNREACHABLE( + "Ensure PrepareImage() has returned true before " + "calling me"); + return mPrepareResult; + } + + CSSIntRect srcRect(0, 0, nsPresContext::AppUnitsToIntCSSPixels(mSize.width), + nsPresContext::AppUnitsToIntCSSPixels(mSize.height)); + + if (aDest.IsEmpty() || aFill.IsEmpty() || srcRect.IsEmpty()) { + return ImgDrawResult::SUCCESS; + } + return BuildWebRenderDisplayItems(aPresContext, aBuilder, aResources, aSc, + aManager, aItem, aDirty, aDest, aFill, + aAnchor, aRepeatSize, srcRect, aOpacity); +} + +/** + * Compute the size and position of the master copy of the image. I.e., a single + * tile used to fill the dest rect. + * aFill The destination rect to be filled + * aHFill and aVFill are the repeat patterns for the component - + * StyleBorderImageRepeat - i.e., how a tiling unit is used to fill aFill + * aUnitSize The size of the source rect in dest coords. + */ +static nsRect ComputeTile(nsRect& aFill, StyleBorderImageRepeat aHFill, + StyleBorderImageRepeat aVFill, + const nsSize& aUnitSize, nsSize& aRepeatSize) { + nsRect tile; + switch (aHFill) { + case StyleBorderImageRepeat::Stretch: + tile.x = aFill.x; + tile.width = aFill.width; + aRepeatSize.width = tile.width; + break; + case StyleBorderImageRepeat::Repeat: + tile.x = aFill.x + aFill.width / 2 - aUnitSize.width / 2; + tile.width = aUnitSize.width; + aRepeatSize.width = tile.width; + break; + case StyleBorderImageRepeat::Round: + tile.x = aFill.x; + tile.width = + nsCSSRendering::ComputeRoundedSize(aUnitSize.width, aFill.width); + aRepeatSize.width = tile.width; + break; + case StyleBorderImageRepeat::Space: { + nscoord space; + aRepeatSize.width = nsCSSRendering::ComputeBorderSpacedRepeatSize( + aUnitSize.width, aFill.width, space); + tile.x = aFill.x + space; + tile.width = aUnitSize.width; + aFill.x = tile.x; + aFill.width = aFill.width - space * 2; + } break; + default: + MOZ_ASSERT_UNREACHABLE("unrecognized border-image fill style"); + } + + switch (aVFill) { + case StyleBorderImageRepeat::Stretch: + tile.y = aFill.y; + tile.height = aFill.height; + aRepeatSize.height = tile.height; + break; + case StyleBorderImageRepeat::Repeat: + tile.y = aFill.y + aFill.height / 2 - aUnitSize.height / 2; + tile.height = aUnitSize.height; + aRepeatSize.height = tile.height; + break; + case StyleBorderImageRepeat::Round: + tile.y = aFill.y; + tile.height = + nsCSSRendering::ComputeRoundedSize(aUnitSize.height, aFill.height); + aRepeatSize.height = tile.height; + break; + case StyleBorderImageRepeat::Space: { + nscoord space; + aRepeatSize.height = nsCSSRendering::ComputeBorderSpacedRepeatSize( + aUnitSize.height, aFill.height, space); + tile.y = aFill.y + space; + tile.height = aUnitSize.height; + aFill.y = tile.y; + aFill.height = aFill.height - space * 2; + } break; + default: + MOZ_ASSERT_UNREACHABLE("unrecognized border-image fill style"); + } + + return tile; +} + +/** + * Returns true if the given set of arguments will require the tiles which fill + * the dest rect to be scaled from the source tile. See comment on ComputeTile + * for argument descriptions. + */ +static bool RequiresScaling(const nsRect& aFill, StyleBorderImageRepeat aHFill, + StyleBorderImageRepeat aVFill, + const nsSize& aUnitSize) { + // If we have no tiling in either direction, we can skip the intermediate + // scaling step. + return (aHFill != StyleBorderImageRepeat::Stretch || + aVFill != StyleBorderImageRepeat::Stretch) && + (aUnitSize.width != aFill.width || aUnitSize.height != aFill.height); +} + +ImgDrawResult nsImageRenderer::DrawBorderImageComponent( + nsPresContext* aPresContext, gfxContext& aRenderingContext, + const nsRect& aDirtyRect, const nsRect& aFill, const CSSIntRect& aSrc, + StyleBorderImageRepeat aHFill, StyleBorderImageRepeat aVFill, + const nsSize& aUnitSize, uint8_t aIndex, + const Maybe<nsSize>& aSVGViewportSize, const bool aHasIntrinsicRatio) { + if (!IsReady()) { + MOZ_ASSERT_UNREACHABLE( + "Ensure PrepareImage() has returned true before " + "calling me"); + return ImgDrawResult::BAD_ARGS; + } + + if (aFill.IsEmpty() || aSrc.IsEmpty()) { + return ImgDrawResult::SUCCESS; + } + + const bool isRequestBacked = + mType == StyleImage::Tag::Url || mType == StyleImage::Tag::Rect; + MOZ_ASSERT(isRequestBacked == mImage->IsImageRequestType()); + + if (isRequestBacked || mType == StyleImage::Tag::Element) { + nsCOMPtr<imgIContainer> subImage; + + // To draw one portion of an image into a border component, we stretch that + // portion to match the size of that border component and then draw onto. + // However, preserveAspectRatio attribute of a SVG image may break this + // rule. To get correct rendering result, we add + // FLAG_FORCE_PRESERVEASPECTRATIO_NONE flag here, to tell mImage to ignore + // preserveAspectRatio attribute, and always do non-uniform stretch. + uint32_t drawFlags = ConvertImageRendererToDrawFlags(mFlags) | + imgIContainer::FLAG_FORCE_PRESERVEASPECTRATIO_NONE; + // For those SVG image sources which don't have fixed aspect ratio (i.e. + // without viewport size and viewBox), we should scale the source uniformly + // after the viewport size is decided by "Default Sizing Algorithm". + if (!aHasIntrinsicRatio) { + drawFlags = drawFlags | imgIContainer::FLAG_FORCE_UNIFORM_SCALING; + } + // Retrieve or create the subimage we'll draw. + nsIntRect srcRect(aSrc.x, aSrc.y, aSrc.width, aSrc.height); + if (isRequestBacked) { + CachedBorderImageData* cachedData = + mForFrame->GetProperty(nsIFrame::CachedBorderImageDataProperty()); + if (!cachedData) { + cachedData = new CachedBorderImageData(); + mForFrame->AddProperty(nsIFrame::CachedBorderImageDataProperty(), + cachedData); + } + if (!(subImage = cachedData->GetSubImage(aIndex))) { + subImage = ImageOps::Clip(mImageContainer, srcRect, aSVGViewportSize); + cachedData->SetSubImage(aIndex, subImage); + } + } else { + // This path, for eStyleImageType_Element, is currently slower than it + // needs to be because we don't cache anything. (In particular, if we have + // to draw to a temporary surface inside ClippedImage, we don't cache that + // temporary surface since we immediately throw the ClippedImage we create + // here away.) However, if we did cache, we'd need to know when to + // invalidate that cache, and it's not clear that it's worth the trouble + // since using border-image with -moz-element is rare. + + RefPtr<gfxDrawable> drawable = + DrawableForElement(nsRect(nsPoint(), mSize), aRenderingContext); + if (!drawable) { + NS_WARNING("Could not create drawable for element"); + return ImgDrawResult::TEMPORARY_ERROR; + } + + nsCOMPtr<imgIContainer> image(ImageOps::CreateFromDrawable(drawable)); + subImage = ImageOps::Clip(image, srcRect, aSVGViewportSize); + } + + MOZ_ASSERT(!aSVGViewportSize || + subImage->GetType() == imgIContainer::TYPE_VECTOR); + + SamplingFilter samplingFilter = + nsLayoutUtils::GetSamplingFilterForFrame(mForFrame); + + if (!RequiresScaling(aFill, aHFill, aVFill, aUnitSize)) { + ImgDrawResult result = nsLayoutUtils::DrawSingleImage( + aRenderingContext, aPresContext, subImage, samplingFilter, aFill, + aDirtyRect, + /* no SVGImageContext */ Nothing(), drawFlags); + + if (!mImage->IsComplete()) { + result &= ImgDrawResult::SUCCESS_NOT_COMPLETE; + } + + return result; + } + + nsSize repeatSize; + nsRect fillRect(aFill); + nsRect tile = ComputeTile(fillRect, aHFill, aVFill, aUnitSize, repeatSize); + + ImgDrawResult result = nsLayoutUtils::DrawBackgroundImage( + aRenderingContext, mForFrame, aPresContext, subImage, samplingFilter, + tile, fillRect, repeatSize, tile.TopLeft(), aDirtyRect, drawFlags, + ExtendMode::CLAMP, 1.0); + + if (!mImage->IsComplete()) { + result &= ImgDrawResult::SUCCESS_NOT_COMPLETE; + } + + return result; + } + + nsSize repeatSize(aFill.Size()); + nsRect fillRect(aFill); + nsRect destTile = + RequiresScaling(fillRect, aHFill, aVFill, aUnitSize) + ? ComputeTile(fillRect, aHFill, aVFill, aUnitSize, repeatSize) + : fillRect; + + return Draw(aPresContext, aRenderingContext, aDirtyRect, destTile, fillRect, + destTile.TopLeft(), repeatSize, aSrc); +} + +ImgDrawResult nsImageRenderer::DrawShapeImage(nsPresContext* aPresContext, + gfxContext& aRenderingContext) { + if (!IsReady()) { + MOZ_ASSERT_UNREACHABLE( + "Ensure PrepareImage() has returned true before " + "calling me"); + return ImgDrawResult::NOT_READY; + } + + if (mSize.width <= 0 || mSize.height <= 0) { + return ImgDrawResult::SUCCESS; + } + + if (mImage->IsImageRequestType()) { + uint32_t drawFlags = + ConvertImageRendererToDrawFlags(mFlags) | imgIContainer::FRAME_FIRST; + nsRect dest(nsPoint(0, 0), mSize); + // We have a tricky situation in our choice of SamplingFilter. Shape + // images define a float area based on the alpha values in the rendered + // pixels. When multiple device pixels are used for one css pixel, the + // sampling can change crisp edges into aliased edges. For visual pixels, + // that's usually the right choice. For defining a float area, it can + // cause problems. If a style is using a shape-image-threshold value that + // is less than the alpha of the edge pixels, any filtering may smear the + // alpha into adjacent pixels and expand the float area in a confusing + // way. Since the alpha threshold can be set precisely in CSS, and since a + // web author may be counting on that threshold to define a precise float + // area from an image, it is least confusing to have the rendered pixels + // have unfiltered alpha. We use SamplingFilter::POINT to ensure that each + // rendered pixel has an alpha that precisely matches the alpha of the + // closest pixel in the image. + return nsLayoutUtils::DrawSingleImage( + aRenderingContext, aPresContext, mImageContainer, SamplingFilter::POINT, + dest, dest, Nothing(), drawFlags, nullptr, nullptr); + } + + if (mImage->IsGradient()) { + nsCSSGradientRenderer renderer = nsCSSGradientRenderer::Create( + aPresContext, mForFrame->Style(), *mGradientData, mSize); + nsRect dest(nsPoint(0, 0), mSize); + renderer.Paint(aRenderingContext, dest, dest, mSize, + CSSIntRect::FromAppUnitsRounded(dest), dest, 1.0); + return ImgDrawResult::SUCCESS; + } + + // Unsupported image type. + return ImgDrawResult::BAD_IMAGE; +} + +bool nsImageRenderer::IsRasterImage() { + return mImageContainer && + mImageContainer->GetType() == imgIContainer::TYPE_RASTER; +} + +bool nsImageRenderer::IsAnimatedImage() { + bool animated = false; + return mImageContainer && + NS_SUCCEEDED(mImageContainer->GetAnimated(&animated)) && animated; +} + +already_AddRefed<imgIContainer> nsImageRenderer::GetImage() { + return do_AddRef(mImageContainer); +} + +bool nsImageRenderer::IsImageContainerAvailable(layers::LayerManager* aManager, + uint32_t aFlags) { + return mImageContainer && + mImageContainer->IsImageContainerAvailable(aManager, aFlags); +} + +void nsImageRenderer::PurgeCacheForViewportChange( + const Maybe<nsSize>& aSVGViewportSize, const bool aHasIntrinsicRatio) { + // Check if we should flush the cached data - only vector images need to do + // the check since they might not have fixed ratio. + if (mImageContainer && + mImageContainer->GetType() == imgIContainer::TYPE_VECTOR) { + if (auto* cachedData = + mForFrame->GetProperty(nsIFrame::CachedBorderImageDataProperty())) { + cachedData->PurgeCacheForViewportChange(aSVGViewportSize, + aHasIntrinsicRatio); + } + } +} diff --git a/layout/painting/nsImageRenderer.h b/layout/painting/nsImageRenderer.h new file mode 100644 index 0000000000..6d5843a73f --- /dev/null +++ b/layout/painting/nsImageRenderer.h @@ -0,0 +1,316 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsImageRenderer_h__ +#define nsImageRenderer_h__ + +#include "nsStyleStruct.h" +#include "Units.h" +#include "mozilla/AspectRatio.h" +#include "mozilla/SurfaceFromElementResult.h" + +class gfxDrawable; +class nsDisplayItem; +namespace mozilla { + +namespace layers { +class StackingContextHelper; +class WebRenderParentCommand; +class RenderRootStateManager; +} // namespace layers + +namespace wr { +class DisplayListBuilder; +class IpcResourceUpdateQueue; +} // namespace wr + +// A CSSSizeOrRatio represents a (possibly partially specified) size for use +// in computing image sizes. Either or both of the width and height might be +// given. A ratio of width to height may also be given. If we at least two +// of these then we can compute a concrete size, that is a width and height. +struct CSSSizeOrRatio { + CSSSizeOrRatio() + : mWidth(0), mHeight(0), mHasWidth(false), mHasHeight(false) {} + + bool CanComputeConcreteSize() const { + return mHasWidth + mHasHeight + HasRatio() >= 2; + } + bool IsConcrete() const { return mHasWidth && mHasHeight; } + bool HasRatio() const { return !!mRatio; } + bool IsEmpty() const { + return (mHasWidth && mWidth <= 0) || (mHasHeight && mHeight <= 0) || + !mRatio; + } + + // CanComputeConcreteSize must return true when ComputeConcreteSize is + // called. + nsSize ComputeConcreteSize() const; + + void SetWidth(nscoord aWidth) { + mWidth = aWidth; + mHasWidth = true; + if (mHasHeight) { + mRatio = AspectRatio::FromSize(mWidth, mHeight); + } + } + void SetHeight(nscoord aHeight) { + mHeight = aHeight; + mHasHeight = true; + if (mHasWidth) { + mRatio = AspectRatio::FromSize(mWidth, mHeight); + } + } + void SetSize(const nsSize& aSize) { + mWidth = aSize.width; + mHeight = aSize.height; + mHasWidth = true; + mHasHeight = true; + mRatio = AspectRatio::FromSize(mWidth, mHeight); + } + void SetRatio(const AspectRatio& aRatio) { + MOZ_ASSERT( + !mHasWidth || !mHasHeight, + "Probably shouldn't be setting a ratio if we have a concrete size"); + mRatio = aRatio; + } + + AspectRatio mRatio; + nscoord mWidth; + nscoord mHeight; + bool mHasWidth; + bool mHasHeight; +}; + +/** + * This is a small wrapper class to encapsulate image drawing that can draw an + * StyleImage image, which may internally be a real image, a sub image, or a CSS + * gradient, etc... + * + * @note Always call the member functions in the order of PrepareImage(), + * SetSize(), and Draw*(). + */ +class nsImageRenderer { + public: + typedef mozilla::image::ImgDrawResult ImgDrawResult; + typedef mozilla::layers::LayerManager LayerManager; + typedef mozilla::layers::ImageContainer ImageContainer; + + enum { + FLAG_SYNC_DECODE_IMAGES = 0x01, + FLAG_PAINTING_TO_WINDOW = 0x02, + FLAG_HIGH_QUALITY_SCALING = 0x04 + }; + enum FitType { CONTAIN, COVER }; + + nsImageRenderer(nsIFrame* aForFrame, const mozilla::StyleImage* aImage, + uint32_t aFlags); + ~nsImageRenderer() = default; + /** + * Populates member variables to get ready for rendering. + * @return true iff the image is ready, and there is at least a pixel to + * draw. + */ + bool PrepareImage(); + + /** + * The three Compute*Size functions correspond to the sizing algorthms and + * definitions from the CSS Image Values and Replaced Content spec. See + * http://dev.w3.org/csswg/css-images-3/#sizing . + */ + + /** + * Compute the intrinsic size of the image as defined in the CSS Image Values + * spec. The intrinsic size is the unscaled size which the image would ideally + * like to be in app units. + */ + mozilla::CSSSizeOrRatio ComputeIntrinsicSize(); + + /** + * Computes the placement for a background image, or for the image data + * inside of a replaced element. + * + * @param aPos The CSS <position> value that specifies the image's position. + * @param aOriginBounds The box to which the tiling position should be + * relative. For background images, this should correspond to + * 'background-origin' for the frame, except when painting on the + * canvas, in which case the origin bounds should be the bounds + * of the root element's frame. For a replaced element, this should + * be the element's content-box. + * @param aTopLeft [out] The top-left corner where an image tile should be + * drawn. + * @param aAnchorPoint [out] A point which should be pixel-aligned by + * nsLayoutUtils::DrawImage. This is the same as aTopLeft, unless + * CSS specifies a percentage (including 'right' or 'bottom'), in + * which case it's that percentage within of aOriginBounds. So + * 'right' would set aAnchorPoint.x to aOriginBounds.XMost(). + * + * Points are returned relative to aOriginBounds. + */ + static void ComputeObjectAnchorPoint(const mozilla::Position& aPos, + const nsSize& aOriginBounds, + const nsSize& aImageSize, + nsPoint* aTopLeft, + nsPoint* aAnchorPoint); + + /** + * Compute the size of the rendered image using either the 'cover' or + * 'contain' constraints (aFitType). + */ + static nsSize ComputeConstrainedSize( + const nsSize& aConstrainingSize, + const mozilla::AspectRatio& aIntrinsicRatio, FitType aFitType); + /** + * Compute the size of the rendered image (the concrete size) where no cover/ + * contain constraints are given. The 'default algorithm' from the CSS Image + * Values spec. + */ + static nsSize ComputeConcreteSize( + const mozilla::CSSSizeOrRatio& aSpecifiedSize, + const mozilla::CSSSizeOrRatio& aIntrinsicSize, + const nsSize& aDefaultSize); + + /** + * Set this image's preferred size. This will be its intrinsic size where + * specified and the default size where it is not. Used as the unscaled size + * when rendering the image. + */ + void SetPreferredSize(const mozilla::CSSSizeOrRatio& aIntrinsicSize, + const nsSize& aDefaultSize); + + /** + * Draws the image to the target rendering context using + * {background|mask}-specific arguments. + * @see nsLayoutUtils::DrawImage() for parameters. + */ + ImgDrawResult DrawLayer(nsPresContext* aPresContext, + gfxContext& aRenderingContext, const nsRect& aDest, + const nsRect& aFill, const nsPoint& aAnchor, + const nsRect& aDirty, const nsSize& aRepeatSize, + float aOpacity); + + /** + * Builds WebRender DisplayItems for an image using + * {background|mask}-specific arguments. + * @see nsLayoutUtils::DrawImage() for parameters. + */ + ImgDrawResult BuildWebRenderDisplayItemsForLayer( + nsPresContext* aPresContext, mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResource, + const mozilla::layers::StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, nsDisplayItem* aItem, + const nsRect& aDest, const nsRect& aFill, const nsPoint& aAnchor, + const nsRect& aDirty, const nsSize& aRepeatSize, float aOpacity); + + /** + * Draw the image to a single component of a border-image style rendering. + * aFill The destination rect to be drawn into + * aSrc is the part of the image to be rendered into a tile (aUnitSize in + * aFill), if aSrc and the dest tile are different sizes, the image will be + * scaled to map aSrc onto the dest tile. + * aHFill and aVFill are the repeat patterns for the component - + * NS_STYLE_BORDER_IMAGE_REPEAT_* + * aUnitSize The scaled size of a single source rect (in destination coords) + * aIndex identifies the component: 0 1 2 + * 3 4 5 + * 6 7 8 + * aSVGViewportSize The image size evaluated by default sizing algorithm. + * Pass Nothing() if we can read a valid viewport size or aspect-ratio from + * the drawing image directly, otherwise, pass Some() with viewport size + * evaluated from default sizing algorithm. + * aHasIntrinsicRatio is used to record if the source image has fixed + * intrinsic ratio. + */ + ImgDrawResult DrawBorderImageComponent( + nsPresContext* aPresContext, gfxContext& aRenderingContext, + const nsRect& aDirtyRect, const nsRect& aFill, + const mozilla::CSSIntRect& aSrc, mozilla::StyleBorderImageRepeat aHFill, + mozilla::StyleBorderImageRepeat aVFill, const nsSize& aUnitSize, + uint8_t aIndex, const mozilla::Maybe<nsSize>& aSVGViewportSize, + const bool aHasIntrinsicRatio); + + /** + * Draw the image to aRenderingContext which can be used to define the + * float area in the presence of "shape-outside: <image>". + */ + ImgDrawResult DrawShapeImage(nsPresContext* aPresContext, + gfxContext& aRenderingContext); + + bool IsRasterImage(); + bool IsAnimatedImage(); + + /// Retrieves the image associated with this nsImageRenderer, if there is one. + already_AddRefed<imgIContainer> GetImage(); + + bool IsImageContainerAvailable(layers::LayerManager* aManager, + uint32_t aFlags); + bool IsReady() const { return mPrepareResult == ImgDrawResult::SUCCESS; } + ImgDrawResult PrepareResult() const { return mPrepareResult; } + void SetExtendMode(mozilla::gfx::ExtendMode aMode) { mExtendMode = aMode; } + void SetMaskOp(mozilla::StyleMaskMode aMaskOp) { mMaskOp = aMaskOp; } + void PurgeCacheForViewportChange( + const mozilla::Maybe<nsSize>& aSVGViewportSize, const bool aHasRatio); + const nsSize& GetSize() const { return mSize; } + mozilla::StyleImage::Tag GetType() const { return mType; } + const mozilla::StyleGradient* GetGradientData() const { + return mGradientData; + } + + private: + /** + * Draws the image to the target rendering context. + * aSrc is a rect on the source image which will be mapped to aDest; it's + * currently only used for gradients. + * + * @see nsLayoutUtils::DrawImage() for other parameters. + */ + ImgDrawResult Draw(nsPresContext* aPresContext, gfxContext& aRenderingContext, + const nsRect& aDirtyRect, const nsRect& aDest, + const nsRect& aFill, const nsPoint& aAnchor, + const nsSize& aRepeatSize, const mozilla::CSSIntRect& aSrc, + float aOpacity = 1.0); + + /** + * Builds WebRender DisplayItems for the image. + * aSrc is a rect on the source image which will be mapped to aDest; it's + * currently only used for gradients. + * + * @see nsLayoutUtils::DrawImage() for other parameters. + */ + ImgDrawResult BuildWebRenderDisplayItems( + nsPresContext* aPresContext, mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const mozilla::layers::StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, nsDisplayItem* aItem, + const nsRect& aDirtyRect, const nsRect& aDest, const nsRect& aFill, + const nsPoint& aAnchor, const nsSize& aRepeatSize, + const mozilla::CSSIntRect& aSrc, float aOpacity = 1.0); + + /** + * Helper method for creating a gfxDrawable from mPaintServerFrame or + * mImageElementSurface. + * Requires mType to be Element. + * Returns null if we cannot create the drawable. + */ + already_AddRefed<gfxDrawable> DrawableForElement(const nsRect& aImageRect, + gfxContext& aContext); + + nsIFrame* mForFrame; + const mozilla::StyleImage* mImage; + mozilla::StyleImage::Tag mType; + nsCOMPtr<imgIContainer> mImageContainer; + const mozilla::StyleGradient* mGradientData; + nsIFrame* mPaintServerFrame; + SurfaceFromElementResult mImageElementSurface; + ImgDrawResult mPrepareResult; + nsSize mSize; // unscaled size of the image, in app units + uint32_t mFlags; + mozilla::gfx::ExtendMode mExtendMode; + mozilla::StyleMaskMode mMaskOp; +}; + +} // namespace mozilla + +#endif /* nsImageRenderer_h__ */ |