From 0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 03:47:29 +0200 Subject: Adding upstream version 115.8.0esr. Signed-off-by: Daniel Baumann --- layout/painting/ActiveLayerTracker.cpp | 414 ++ layout/painting/ActiveLayerTracker.h | 102 + layout/painting/BorderCache.h | 67 + layout/painting/BorderConsts.h | 22 + layout/painting/DashedCornerFinder.cpp | 416 ++ layout/painting/DashedCornerFinder.h | 274 + layout/painting/DisplayItemClip.cpp | 486 ++ layout/painting/DisplayItemClip.h | 193 + layout/painting/DisplayItemClipChain.cpp | 88 + layout/painting/DisplayItemClipChain.h | 117 + layout/painting/DisplayListClipState.cpp | 149 + layout/painting/DisplayListClipState.h | 301 + layout/painting/DottedCornerFinder.cpp | 539 ++ layout/painting/DottedCornerFinder.h | 432 ++ layout/painting/HitTestInfo.cpp | 74 + layout/painting/HitTestInfo.h | 59 + layout/painting/MaskLayerImageCache.cpp | 64 + layout/painting/MaskLayerImageCache.h | 257 + layout/painting/MatrixStack.h | 61 + layout/painting/PaintTracker.cpp | 13 + layout/painting/PaintTracker.h | 31 + layout/painting/RetainedDisplayListBuilder.cpp | 1732 +++++ layout/painting/RetainedDisplayListBuilder.h | 287 + layout/painting/RetainedDisplayListHelpers.h | 187 + layout/painting/TransformClipNode.h | 138 + layout/painting/WindowRenderer.cpp | 229 + layout/painting/WindowRenderer.h | 284 + layout/painting/crashtests/1402183-1.html | 20 + layout/painting/crashtests/1405881-1.html | 24 + layout/painting/crashtests/1407470-1.html | 19 + layout/painting/crashtests/1413073-1.html | 15 + layout/painting/crashtests/1413073-2.html | 18 + layout/painting/crashtests/1418177-1.html | 34 + layout/painting/crashtests/1418722-1.html | 17 + layout/painting/crashtests/1419917.html | 15 + layout/painting/crashtests/1425271-1.html | 54 + layout/painting/crashtests/1428906-1.html | 16 + layout/painting/crashtests/1430589-1.html | 55 + layout/painting/crashtests/1454105-1.html | 22 + layout/painting/crashtests/1455944-1.html | 14 + layout/painting/crashtests/1458145.html | 11 + layout/painting/crashtests/1465305-1.html | 8 + layout/painting/crashtests/1468124-1.html | 27 + layout/painting/crashtests/1469472.html | 7 + layout/painting/crashtests/1477831-1.html | 11 + layout/painting/crashtests/1504033.html | 13 + layout/painting/crashtests/1514544-1.html | 15 + layout/painting/crashtests/1547420-1.html | 20 + layout/painting/crashtests/1549909.html | 9 + layout/painting/crashtests/1551389-1.html | 6 + layout/painting/crashtests/1555819-1.html | 12 + layout/painting/crashtests/1574392.html | 1 + layout/painting/crashtests/1589800-1.html | 47 + layout/painting/crashtests/1667503-1.html | 16 + layout/painting/crashtests/1713880-1.html | 10 + layout/painting/crashtests/1714584-1.html | 5 + layout/painting/crashtests/1717655-1.html | 19 + layout/painting/crashtests/1763006-1.html | 23 + layout/painting/crashtests/1819957-1.html | 14 + layout/painting/crashtests/crashtests.list | 32 + layout/painting/moz.build | 72 + layout/painting/nsCSSRendering.cpp | 4944 ++++++++++++++ layout/painting/nsCSSRendering.h | 924 +++ layout/painting/nsCSSRenderingBorders.cpp | 3891 +++++++++++ layout/painting/nsCSSRenderingBorders.h | 357 + layout/painting/nsCSSRenderingGradients.cpp | 1299 ++++ layout/painting/nsCSSRenderingGradients.h | 125 + layout/painting/nsDisplayItemTypes.h | 73 + layout/painting/nsDisplayItemTypesList.h | 120 + layout/painting/nsDisplayList.cpp | 8708 ++++++++++++++++++++++++ layout/painting/nsDisplayList.h | 6863 +++++++++++++++++++ layout/painting/nsDisplayListArenaTypes.h | 15 + layout/painting/nsDisplayListInvalidation.cpp | 124 + layout/painting/nsDisplayListInvalidation.h | 236 + layout/painting/nsImageRenderer.cpp | 1054 +++ layout/painting/nsImageRenderer.h | 313 + 76 files changed, 36763 insertions(+) create mode 100644 layout/painting/ActiveLayerTracker.cpp create mode 100644 layout/painting/ActiveLayerTracker.h create mode 100644 layout/painting/BorderCache.h create mode 100644 layout/painting/BorderConsts.h create mode 100644 layout/painting/DashedCornerFinder.cpp create mode 100644 layout/painting/DashedCornerFinder.h create mode 100644 layout/painting/DisplayItemClip.cpp create mode 100644 layout/painting/DisplayItemClip.h create mode 100644 layout/painting/DisplayItemClipChain.cpp create mode 100644 layout/painting/DisplayItemClipChain.h create mode 100644 layout/painting/DisplayListClipState.cpp create mode 100644 layout/painting/DisplayListClipState.h create mode 100644 layout/painting/DottedCornerFinder.cpp create mode 100644 layout/painting/DottedCornerFinder.h create mode 100644 layout/painting/HitTestInfo.cpp create mode 100644 layout/painting/HitTestInfo.h create mode 100644 layout/painting/MaskLayerImageCache.cpp create mode 100644 layout/painting/MaskLayerImageCache.h create mode 100644 layout/painting/MatrixStack.h create mode 100644 layout/painting/PaintTracker.cpp create mode 100644 layout/painting/PaintTracker.h create mode 100644 layout/painting/RetainedDisplayListBuilder.cpp create mode 100644 layout/painting/RetainedDisplayListBuilder.h create mode 100644 layout/painting/RetainedDisplayListHelpers.h create mode 100644 layout/painting/TransformClipNode.h create mode 100644 layout/painting/WindowRenderer.cpp create mode 100644 layout/painting/WindowRenderer.h create mode 100644 layout/painting/crashtests/1402183-1.html create mode 100644 layout/painting/crashtests/1405881-1.html create mode 100644 layout/painting/crashtests/1407470-1.html create mode 100644 layout/painting/crashtests/1413073-1.html create mode 100644 layout/painting/crashtests/1413073-2.html create mode 100644 layout/painting/crashtests/1418177-1.html create mode 100644 layout/painting/crashtests/1418722-1.html create mode 100644 layout/painting/crashtests/1419917.html create mode 100644 layout/painting/crashtests/1425271-1.html create mode 100644 layout/painting/crashtests/1428906-1.html create mode 100644 layout/painting/crashtests/1430589-1.html create mode 100644 layout/painting/crashtests/1454105-1.html create mode 100644 layout/painting/crashtests/1455944-1.html create mode 100644 layout/painting/crashtests/1458145.html create mode 100644 layout/painting/crashtests/1465305-1.html create mode 100644 layout/painting/crashtests/1468124-1.html create mode 100644 layout/painting/crashtests/1469472.html create mode 100644 layout/painting/crashtests/1477831-1.html create mode 100644 layout/painting/crashtests/1504033.html create mode 100644 layout/painting/crashtests/1514544-1.html create mode 100644 layout/painting/crashtests/1547420-1.html create mode 100644 layout/painting/crashtests/1549909.html create mode 100644 layout/painting/crashtests/1551389-1.html create mode 100644 layout/painting/crashtests/1555819-1.html create mode 100644 layout/painting/crashtests/1574392.html create mode 100644 layout/painting/crashtests/1589800-1.html create mode 100644 layout/painting/crashtests/1667503-1.html create mode 100644 layout/painting/crashtests/1713880-1.html create mode 100644 layout/painting/crashtests/1714584-1.html create mode 100644 layout/painting/crashtests/1717655-1.html create mode 100644 layout/painting/crashtests/1763006-1.html create mode 100644 layout/painting/crashtests/1819957-1.html create mode 100644 layout/painting/crashtests/crashtests.list create mode 100644 layout/painting/moz.build create mode 100644 layout/painting/nsCSSRendering.cpp create mode 100644 layout/painting/nsCSSRendering.h create mode 100644 layout/painting/nsCSSRenderingBorders.cpp create mode 100644 layout/painting/nsCSSRenderingBorders.h create mode 100644 layout/painting/nsCSSRenderingGradients.cpp create mode 100644 layout/painting/nsCSSRenderingGradients.h create mode 100644 layout/painting/nsDisplayItemTypes.h create mode 100644 layout/painting/nsDisplayItemTypesList.h create mode 100644 layout/painting/nsDisplayList.cpp create mode 100644 layout/painting/nsDisplayList.h create mode 100644 layout/painting/nsDisplayListArenaTypes.h create mode 100644 layout/painting/nsDisplayListInvalidation.cpp create mode 100644 layout/painting/nsDisplayListInvalidation.h create mode 100644 layout/painting/nsImageRenderer.cpp create mode 100644 layout/painting/nsImageRenderer.h (limited to 'layout/painting') diff --git a/layout/painting/ActiveLayerTracker.cpp b/layout/painting/ActiveLayerTracker.cpp new file mode 100644 index 0000000000..fd36734c3c --- /dev/null +++ b/layout/painting/ActiveLayerTracker.cpp @@ -0,0 +1,414 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ActiveLayerTracker.h" + +#include "mozilla/AnimationUtils.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/gfx/gfxVars.h" +#include "mozilla/gfx/Matrix.h" +#include "mozilla/EffectSet.h" +#include "mozilla/MotionPathUtils.h" +#include "mozilla/PodOperations.h" +#include "mozilla/StaticPtr.h" +#include "gfx2DGlue.h" +#include "nsExpirationTracker.h" +#include "nsContainerFrame.h" +#include "nsIContent.h" +#include "nsRefreshDriver.h" +#include "nsPIDOMWindow.h" +#include "mozilla/dom/Document.h" +#include "nsAnimationManager.h" +#include "nsStyleTransformMatrix.h" +#include "nsTransitionManager.h" +#include "nsDisplayList.h" +#include "nsDOMCSSDeclaration.h" +#include "nsLayoutUtils.h" + +namespace mozilla { + +using namespace gfx; + +/** + * This tracks the state of a frame that may need active layers due to + * ongoing content changes or style changes that indicate animation. + * + * When no changes of *any* kind are detected after 75-100ms we remove this + * object. Because we only track all kinds of activity with a single + * nsExpirationTracker, it's possible a frame might remain active somewhat + * spuriously if different kinds of changes kept happening, but that almost + * certainly doesn't matter. + */ +class LayerActivity { + public: + enum ActivityIndex { + ACTIVITY_OPACITY, + ACTIVITY_TRANSFORM, + + ACTIVITY_SCALE, + ACTIVITY_TRIGGERED_REPAINT, + + // keep as last item + ACTIVITY_COUNT + }; + + explicit LayerActivity(nsIFrame* aFrame) : mFrame(aFrame), mContent(nullptr) { + PodArrayZero(mRestyleCounts); + } + ~LayerActivity(); + nsExpirationState* GetExpirationState() { return &mState; } + uint8_t& RestyleCountForProperty(nsCSSPropertyID aProperty) { + return mRestyleCounts[GetActivityIndexForProperty(aProperty)]; + } + + static ActivityIndex GetActivityIndexForProperty(nsCSSPropertyID aProperty) { + switch (aProperty) { + case eCSSProperty_opacity: + return ACTIVITY_OPACITY; + case eCSSProperty_transform: + case eCSSProperty_translate: + case eCSSProperty_rotate: + case eCSSProperty_scale: + case eCSSProperty_offset_path: + case eCSSProperty_offset_distance: + case eCSSProperty_offset_rotate: + case eCSSProperty_offset_anchor: + // TODO: Bug 1559232: Add offset-position. + return ACTIVITY_TRANSFORM; + default: + MOZ_ASSERT(false); + return ACTIVITY_OPACITY; + } + } + + static ActivityIndex GetActivityIndexForPropertySet( + const nsCSSPropertyIDSet& aPropertySet) { + if (aPropertySet.IsSubsetOf( + nsCSSPropertyIDSet::TransformLikeProperties())) { + return ACTIVITY_TRANSFORM; + } + MOZ_ASSERT( + aPropertySet.IsSubsetOf(nsCSSPropertyIDSet::OpacityProperties())); + return ACTIVITY_OPACITY; + } + + // While tracked, exactly one of mFrame or mContent is non-null, depending + // on whether this property is stored on a frame or on a content node. + // When this property is expired by the layer activity tracker, both mFrame + // and mContent are nulled-out and the property is deleted. + nsIFrame* mFrame; + nsIContent* mContent; + + nsExpirationState mState; + + // Previous scale due to the CSS transform property. + Maybe mPreviousTransformScale; + + // Number of restyle operations detected + uint8_t mRestyleCounts[ACTIVITY_COUNT]; +}; + +class LayerActivityTracker final + : public nsExpirationTracker { + public: + // 75-100ms is a good timeout period. We use 4 generations of 25ms each. + enum { GENERATION_MS = 100 }; + + explicit LayerActivityTracker(nsIEventTarget* aEventTarget) + : nsExpirationTracker( + GENERATION_MS, "LayerActivityTracker", aEventTarget) {} + ~LayerActivityTracker() override { AgeAllGenerations(); } + + void NotifyExpired(LayerActivity* aObject) override; +}; + +static StaticAutoPtr gLayerActivityTracker; + +LayerActivity::~LayerActivity() { + if (mFrame || mContent) { + NS_ASSERTION(gLayerActivityTracker, "Should still have a tracker"); + gLayerActivityTracker->RemoveObject(this); + } +} + +// Frames with this property have NS_FRAME_HAS_LAYER_ACTIVITY_PROPERTY set +NS_DECLARE_FRAME_PROPERTY_DELETABLE(LayerActivityProperty, LayerActivity) + +void LayerActivityTracker::NotifyExpired(LayerActivity* aObject) { + RemoveObject(aObject); + + nsIFrame* f = aObject->mFrame; + nsIContent* c = aObject->mContent; + aObject->mFrame = nullptr; + aObject->mContent = nullptr; + + MOZ_ASSERT((f == nullptr) != (c == nullptr), + "A LayerActivity object should always have a reference to either " + "its frame or its content"); + + if (f) { + f->RemoveStateBits(NS_FRAME_HAS_LAYER_ACTIVITY_PROPERTY); + f->RemoveProperty(LayerActivityProperty()); + } else { + c->RemoveProperty(nsGkAtoms::LayerActivity); + } +} + +static LayerActivity* GetLayerActivity(nsIFrame* aFrame) { + if (!aFrame->HasAnyStateBits(NS_FRAME_HAS_LAYER_ACTIVITY_PROPERTY)) { + return nullptr; + } + return aFrame->GetProperty(LayerActivityProperty()); +} + +static LayerActivity* GetLayerActivityForUpdate(nsIFrame* aFrame) { + LayerActivity* layerActivity = GetLayerActivity(aFrame); + if (layerActivity) { + gLayerActivityTracker->MarkUsed(layerActivity); + } else { + if (!gLayerActivityTracker) { + gLayerActivityTracker = + new LayerActivityTracker(GetMainThreadSerialEventTarget()); + } + layerActivity = new LayerActivity(aFrame); + gLayerActivityTracker->AddObject(layerActivity); + aFrame->AddStateBits(NS_FRAME_HAS_LAYER_ACTIVITY_PROPERTY); + aFrame->SetProperty(LayerActivityProperty(), layerActivity); + } + return layerActivity; +} + +static void IncrementMutationCount(uint8_t* aCount) { + *aCount = uint8_t(std::min(0xFF, *aCount + 1)); +} + +/* static */ +void ActiveLayerTracker::TransferActivityToContent(nsIFrame* aFrame, + nsIContent* aContent) { + if (!aFrame->HasAnyStateBits(NS_FRAME_HAS_LAYER_ACTIVITY_PROPERTY)) { + return; + } + LayerActivity* layerActivity = aFrame->TakeProperty(LayerActivityProperty()); + aFrame->RemoveStateBits(NS_FRAME_HAS_LAYER_ACTIVITY_PROPERTY); + if (!layerActivity) { + return; + } + layerActivity->mFrame = nullptr; + layerActivity->mContent = aContent; + aContent->SetProperty(nsGkAtoms::LayerActivity, layerActivity, + nsINode::DeleteProperty, true); +} + +/* static */ +void ActiveLayerTracker::TransferActivityToFrame(nsIContent* aContent, + nsIFrame* aFrame) { + auto* layerActivity = static_cast( + aContent->TakeProperty(nsGkAtoms::LayerActivity)); + if (!layerActivity) { + return; + } + layerActivity->mContent = nullptr; + layerActivity->mFrame = aFrame; + aFrame->AddStateBits(NS_FRAME_HAS_LAYER_ACTIVITY_PROPERTY); + aFrame->SetProperty(LayerActivityProperty(), layerActivity); +} + +static void IncrementScaleRestyleCountIfNeeded(nsIFrame* aFrame, + LayerActivity* aActivity) { + const nsStyleDisplay* display = aFrame->StyleDisplay(); + if (!display->HasTransformProperty() && !display->HasIndividualTransform() && + display->mOffsetPath.IsNone()) { + // The transform was removed. + aActivity->mPreviousTransformScale = Nothing(); + IncrementMutationCount( + &aActivity->mRestyleCounts[LayerActivity::ACTIVITY_SCALE]); + return; + } + + // Compute the new scale due to the CSS transform property. + // Note: Motion path doesn't contribute to scale factor. (It only has 2d + // translate and 2d rotate, so we use Nothing() for it.) + nsStyleTransformMatrix::TransformReferenceBox refBox(aFrame); + Matrix4x4 transform = nsStyleTransformMatrix::ReadTransforms( + display->mTranslate, display->mRotate, display->mScale, Nothing(), + display->mTransform, refBox, AppUnitsPerCSSPixel()); + Matrix transform2D; + if (!transform.Is2D(&transform2D)) { + // We don't attempt to handle 3D transforms; just assume the scale changed. + aActivity->mPreviousTransformScale = Nothing(); + IncrementMutationCount( + &aActivity->mRestyleCounts[LayerActivity::ACTIVITY_SCALE]); + return; + } + + MatrixScales scale = transform2D.ScaleFactors(); + if (aActivity->mPreviousTransformScale == Some(scale)) { + return; // Nothing changed. + } + + aActivity->mPreviousTransformScale = Some(scale); + IncrementMutationCount( + &aActivity->mRestyleCounts[LayerActivity::ACTIVITY_SCALE]); +} + +/* static */ +void ActiveLayerTracker::NotifyRestyle(nsIFrame* aFrame, + nsCSSPropertyID aProperty) { + LayerActivity* layerActivity = GetLayerActivityForUpdate(aFrame); + uint8_t& mutationCount = layerActivity->RestyleCountForProperty(aProperty); + IncrementMutationCount(&mutationCount); + + if (nsCSSPropertyIDSet::TransformLikeProperties().HasProperty(aProperty)) { + IncrementScaleRestyleCountIfNeeded(aFrame, layerActivity); + } +} + +static bool IsPresContextInScriptAnimationCallback( + nsPresContext* aPresContext) { + if (aPresContext->RefreshDriver()->IsInRefresh()) { + return true; + } + // Treat timeouts/setintervals as scripted animation callbacks for our + // purposes. + nsPIDOMWindowInner* win = aPresContext->Document()->GetInnerWindow(); + return win && win->IsRunningTimeout(); +} + +/* static */ +void ActiveLayerTracker::NotifyInlineStyleRuleModified( + nsIFrame* aFrame, nsCSSPropertyID aProperty) { + if (IsPresContextInScriptAnimationCallback(aFrame->PresContext())) { + LayerActivity* layerActivity = GetLayerActivityForUpdate(aFrame); + // We know this is animated, so just hack the mutation count. + layerActivity->RestyleCountForProperty(aProperty) = 0xff; + } +} + +/* static */ +void ActiveLayerTracker::NotifyNeedsRepaint(nsIFrame* aFrame) { + LayerActivity* layerActivity = GetLayerActivityForUpdate(aFrame); + if (IsPresContextInScriptAnimationCallback(aFrame->PresContext())) { + // This is mirroring NotifyInlineStyleRuleModified's NotifyAnimated logic. + // Just max out the restyle count if we're in an animation callback. + layerActivity->mRestyleCounts[LayerActivity::ACTIVITY_TRIGGERED_REPAINT] = + 0xFF; + } else { + IncrementMutationCount( + &layerActivity + ->mRestyleCounts[LayerActivity::ACTIVITY_TRIGGERED_REPAINT]); + } +} + +static bool IsMotionPathAnimated(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame) { + return ActiveLayerTracker::IsStyleAnimated( + aBuilder, aFrame, nsCSSPropertyIDSet{eCSSProperty_offset_path}) || + (!aFrame->StyleDisplay()->mOffsetPath.IsNone() && + ActiveLayerTracker::IsStyleAnimated( + aBuilder, aFrame, + nsCSSPropertyIDSet{eCSSProperty_offset_distance, + eCSSProperty_offset_rotate, + eCSSProperty_offset_anchor})); +} + +/* static */ +bool ActiveLayerTracker::IsTransformAnimated(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame) { + return IsStyleAnimated(aBuilder, aFrame, + nsCSSPropertyIDSet::CSSTransformProperties()) || + IsMotionPathAnimated(aBuilder, aFrame); +} + +/* static */ +bool ActiveLayerTracker::IsTransformMaybeAnimated(nsIFrame* aFrame) { + return IsStyleAnimated(nullptr, aFrame, + nsCSSPropertyIDSet::CSSTransformProperties()) || + IsMotionPathAnimated(nullptr, aFrame); +} + +/* static */ +bool ActiveLayerTracker::IsStyleAnimated( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + const nsCSSPropertyIDSet& aPropertySet) { + MOZ_ASSERT( + aPropertySet.IsSubsetOf(nsCSSPropertyIDSet::TransformLikeProperties()) || + aPropertySet.IsSubsetOf(nsCSSPropertyIDSet::OpacityProperties()), + "Only subset of opacity or transform-like properties set calls this"); + + // For display:table content, transforms are applied to the table wrapper + // (primary frame) but their will-change style will be specified on the style + // frame and, unlike other transform properties, not inherited. + // As a result, for transform properties only we need to be careful to look up + // the will-change style on the _style_ frame. + const nsIFrame* styleFrame = nsLayoutUtils::GetStyleFrame(aFrame); + const nsCSSPropertyIDSet transformSet = + nsCSSPropertyIDSet::TransformLikeProperties(); + if ((styleFrame && (styleFrame->StyleDisplay()->mWillChange.bits & + StyleWillChangeBits::TRANSFORM)) && + aPropertySet.Intersects(transformSet) && + (!aBuilder || + aBuilder->IsInWillChangeBudget(aFrame, aFrame->GetSize()))) { + return true; + } + if ((aFrame->StyleDisplay()->mWillChange.bits & + StyleWillChangeBits::OPACITY) && + aPropertySet.Intersects(nsCSSPropertyIDSet::OpacityProperties()) && + (!aBuilder || + aBuilder->IsInWillChangeBudget(aFrame, aFrame->GetSize()))) { + return !StaticPrefs::gfx_will_change_ignore_opacity(); + } + + LayerActivity* layerActivity = GetLayerActivity(aFrame); + if (layerActivity) { + LayerActivity::ActivityIndex activityIndex = + LayerActivity::GetActivityIndexForPropertySet(aPropertySet); + if (layerActivity->mRestyleCounts[activityIndex] >= 2) { + // If the frame needs to be repainted frequently, we probably don't get + // much from treating the property as animated, *unless* this frame's + // 'scale' (which includes the bounds changes of a rotation) is changing. + // Marking a scaling transform as animating allows us to avoid resizing + // the texture, even if we have to repaint the contents of that texture. + if (layerActivity + ->mRestyleCounts[LayerActivity::ACTIVITY_TRIGGERED_REPAINT] < + 2 || + (aPropertySet.Intersects(transformSet) && + IsScaleSubjectToAnimation(aFrame))) { + return true; + } + } + } + + if (nsLayoutUtils::HasEffectiveAnimation(aFrame, aPropertySet)) { + return true; + } + + if (!aPropertySet.Intersects(transformSet) || + !aFrame->Combines3DTransformWithAncestors()) { + return false; + } + + // For preserve-3d, we check if there is any transform animation on its parent + // frames in the 3d rendering context. If there is one, this function will + // return true. + return IsStyleAnimated(aBuilder, aFrame->GetParent(), aPropertySet); +} + +/* static */ +bool ActiveLayerTracker::IsScaleSubjectToAnimation(nsIFrame* aFrame) { + // Check whether JavaScript is animating this frame's scale. + LayerActivity* layerActivity = GetLayerActivity(aFrame); + if (layerActivity && + layerActivity->mRestyleCounts[LayerActivity::ACTIVITY_SCALE] >= 2) { + return true; + } + + return AnimationUtils::FrameHasAnimatedScale(aFrame); +} + +/* static */ +void ActiveLayerTracker::Shutdown() { gLayerActivityTracker = nullptr; } + +} // namespace mozilla diff --git a/layout/painting/ActiveLayerTracker.h b/layout/painting/ActiveLayerTracker.h new file mode 100644 index 0000000000..7a6005f284 --- /dev/null +++ b/layout/painting/ActiveLayerTracker.h @@ -0,0 +1,102 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef ACTIVELAYERTRACKER_H_ +#define ACTIVELAYERTRACKER_H_ + +#include "nsCSSPropertyID.h" + +class nsIFrame; +class nsIContent; +class nsCSSPropertyIDSet; +class nsDOMCSSDeclaration; + +namespace mozilla { + +class nsDisplayListBuilder; + +/** + * This class receives various notifications about style changes and content + * changes that affect layerization decisions, and implements the heuristics + * that drive those decisions. It manages per-frame state to support those + * heuristics. + */ +class ActiveLayerTracker { + public: + static void Shutdown(); + + /* + * We track style changes to selected styles: + * eCSSProperty_transform, eCSSProperty_translate, + * eCSSProperty_rotate, eCSSProperty_scale + * eCSSProperty_offset_path, eCSSProperty_offset_distance, + * eCSSProperty_offset_rotate, eCSSProperty_offset_anchor, + * eCSSProperty_opacity + * and use that information to guess whether style changes are animated. + */ + + /** + * Notify aFrame's style property as having changed due to a restyle, + * and therefore possibly wanting an active layer to render that style. + * Any such marking will time out after a short period. + * @param aProperty the property that has changed + */ + static void NotifyRestyle(nsIFrame* aFrame, nsCSSPropertyID aProperty); + + /** + * Notify that a property in the inline style rule of aFrame's element + * has been modified. + */ + static void NotifyInlineStyleRuleModified(nsIFrame* aFrame, + nsCSSPropertyID aProperty); + /** + * Notify that a frame needs to be repainted. This is important for layering + * decisions where, say, aFrame's transform is updated from JS, but we need + * to repaint aFrame anyway, so we get no benefit from giving it its own + * layer. + */ + static void NotifyNeedsRepaint(nsIFrame* aFrame); + /** + * Return true if aFrame's property style in |aPropertySet| should be + * considered as being animated for constructing active layers. + */ + static bool IsStyleAnimated(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + const nsCSSPropertyIDSet& aPropertySet); + /** + * Return true if aFrame's transform-like property, + * i.e. transform/translate/rotate/scale, is animated. + */ + static bool IsTransformAnimated(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame); + /** + * Return true if aFrame's transform style should be considered as being + * animated for pre-rendering. + */ + static bool IsTransformMaybeAnimated(nsIFrame* aFrame); + /** + * Return true if aFrame either has an animated scale now, or is likely to + * have one in the future because it has a CSS animation or transition + * (which may not be playing right now) that affects its scale. + */ + static bool IsScaleSubjectToAnimation(nsIFrame* aFrame); + + /** + * Transfer the LayerActivity property to the frame's content node when the + * frame is about to be destroyed so that layer activity can be tracked + * throughout reframes of an element. Only call this when aFrame is the + * primary frame of aContent. + */ + static void TransferActivityToContent(nsIFrame* aFrame, nsIContent* aContent); + /** + * Transfer the LayerActivity property back to the content node's primary + * frame after the frame has been created. + */ + static void TransferActivityToFrame(nsIContent* aContent, nsIFrame* aFrame); +}; + +} // namespace mozilla + +#endif /* ACTIVELAYERTRACKER_H_ */ diff --git a/layout/painting/BorderCache.h b/layout/painting/BorderCache.h new file mode 100644 index 0000000000..bb6bf7e4ed --- /dev/null +++ b/layout/painting/BorderCache.h @@ -0,0 +1,67 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_BorderCache_h_ +#define mozilla_BorderCache_h_ + +#include "mozilla/gfx/2D.h" +#include "mozilla/HashFunctions.h" +#include "PLDHashTable.h" + +namespace mozilla { +// Cache for best overlap and best dashLength. + +struct FourFloats { + typedef mozilla::gfx::Float Float; + + Float n[4]; + + FourFloats() { + n[0] = 0.0f; + n[1] = 0.0f; + n[2] = 0.0f; + n[3] = 0.0f; + } + + FourFloats(Float a, Float b, Float c, Float d) { + n[0] = a; + n[1] = b; + n[2] = c; + n[3] = d; + } + + bool operator==(const FourFloats& aOther) const { + return n[0] == aOther.n[0] && n[1] == aOther.n[1] && n[2] == aOther.n[2] && + n[3] == aOther.n[3]; + } +}; + +class FourFloatsHashKey : public PLDHashEntryHdr { + public: + typedef const FourFloats& KeyType; + typedef const FourFloats* KeyTypePointer; + + explicit FourFloatsHashKey(KeyTypePointer aKey) : mValue(*aKey) {} + FourFloatsHashKey(const FourFloatsHashKey& aToCopy) + : mValue(aToCopy.mValue) {} + ~FourFloatsHashKey() = default; + + KeyType GetKey() const { return mValue; } + bool KeyEquals(KeyTypePointer aKey) const { return *aKey == mValue; } + + static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; } + static PLDHashNumber HashKey(KeyTypePointer aKey) { + return HashBytes(aKey->n, sizeof(mozilla::gfx::Float) * 4); + } + enum { ALLOW_MEMMOVE = true }; + + private: + const FourFloats mValue; +}; + +} // namespace mozilla + +#endif /* mozilla_BorderCache_h_ */ diff --git a/layout/painting/BorderConsts.h b/layout/painting/BorderConsts.h new file mode 100644 index 0000000000..e25c697101 --- /dev/null +++ b/layout/painting/BorderConsts.h @@ -0,0 +1,22 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_BorderConsts_h_ +#define mozilla_BorderConsts_h_ + +// thickness of dashed line relative to dotted line +#define DOT_LENGTH 1 // square +#define DASH_LENGTH 3 // 3 times longer than dot + +#define C_TL mozilla::eCornerTopLeft +#define C_TR mozilla::eCornerTopRight +#define C_BR mozilla::eCornerBottomRight +#define C_BL mozilla::eCornerBottomLeft + +#define BORDER_SEGMENT_COUNT_MAX 100 +#define BORDER_DOTTED_CORNER_MAX_RADIUS 100000 + +#endif /* mozilla_BorderConsts_h_ */ diff --git a/layout/painting/DashedCornerFinder.cpp b/layout/painting/DashedCornerFinder.cpp new file mode 100644 index 0000000000..c892179df9 --- /dev/null +++ b/layout/painting/DashedCornerFinder.cpp @@ -0,0 +1,416 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "DashedCornerFinder.h" + +#include + +#include "BorderCache.h" +#include "BorderConsts.h" +#include "nsTHashMap.h" + +namespace mozilla { + +using namespace gfx; + +struct BestDashLength { + typedef mozilla::gfx::Float Float; + + Float dashLength; + size_t count; + + BestDashLength() : dashLength(0.0f), count(0) {} + + BestDashLength(Float aDashLength, size_t aCount) + : dashLength(aDashLength), count(aCount) {} +}; + +static const size_t DashedCornerCacheSize = 256; +nsTHashMap DashedCornerCache; + +DashedCornerFinder::DashedCornerFinder(const Bezier& aOuterBezier, + const Bezier& aInnerBezier, + Float aBorderWidthH, Float aBorderWidthV, + const Size& aCornerDim) + : mOuterBezier(aOuterBezier), + mInnerBezier(aInnerBezier), + mLastOuterP(aOuterBezier.mPoints[0]), + mLastInnerP(aInnerBezier.mPoints[0]), + mLastOuterT(0.0f), + mLastInnerT(0.0f), + mBestDashLength(DOT_LENGTH * DASH_LENGTH), + mHasZeroBorderWidth(false), + mHasMore(true), + mMaxCount(aCornerDim.width + aCornerDim.height), + mType(OTHER), + mI(0), + mCount(0) { + NS_ASSERTION(aBorderWidthH > 0.0f || aBorderWidthV > 0.0f, + "At least one side should have non-zero width."); + + DetermineType(aBorderWidthH, aBorderWidthV); + + Reset(); +} + +void DashedCornerFinder::DetermineType(Float aBorderWidthH, + Float aBorderWidthV) { + if (aBorderWidthH < aBorderWidthV) { + // Always draw from wider side to thinner side. + std::swap(mInnerBezier.mPoints[0], mInnerBezier.mPoints[3]); + std::swap(mInnerBezier.mPoints[1], mInnerBezier.mPoints[2]); + std::swap(mOuterBezier.mPoints[0], mOuterBezier.mPoints[3]); + std::swap(mOuterBezier.mPoints[1], mOuterBezier.mPoints[2]); + mLastOuterP = mOuterBezier.mPoints[0]; + mLastInnerP = mInnerBezier.mPoints[0]; + } + + // See the comment at mType declaration for each condition. + + Float borderRadiusA = + fabs(mOuterBezier.mPoints[0].x - mOuterBezier.mPoints[3].x); + Float borderRadiusB = + fabs(mOuterBezier.mPoints[0].y - mOuterBezier.mPoints[3].y); + if (aBorderWidthH == aBorderWidthV && borderRadiusA == borderRadiusB && + borderRadiusA > aBorderWidthH * 2.0f) { + Float curveHeight = borderRadiusA - aBorderWidthH / 2.0; + + mType = PERFECT; + Float borderLength = M_PI * curveHeight / 2.0f; + + Float dashWidth = aBorderWidthH * DOT_LENGTH * DASH_LENGTH; + size_t count = ceil(borderLength / dashWidth); + if (count % 2) { + count++; + } + mCount = count / 2 + 1; + mBestDashLength = borderLength / (aBorderWidthH * count); + } + + Float minBorderWidth = std::min(aBorderWidthH, aBorderWidthV); + if (minBorderWidth == 0.0f) { + mHasZeroBorderWidth = true; + } + + if (mType == OTHER && !mHasZeroBorderWidth) { + Float minBorderRadius = std::min(borderRadiusA, borderRadiusB); + Float maxBorderRadius = std::max(borderRadiusA, borderRadiusB); + Float maxBorderWidth = std::max(aBorderWidthH, aBorderWidthV); + + FindBestDashLength(minBorderWidth, maxBorderWidth, minBorderRadius, + maxBorderRadius); + } +} + +bool DashedCornerFinder::HasMore(void) const { + if (mHasZeroBorderWidth) { + return mI < mMaxCount && mHasMore; + } + + return mI < mCount; +} + +DashedCornerFinder::Result DashedCornerFinder::Next(void) { + Float lastOuterT, lastInnerT, outerT, innerT; + + if (mI == 0) { + lastOuterT = 0.0f; + lastInnerT = 0.0f; + } else { + if (mType == PERFECT) { + lastOuterT = lastInnerT = (mI * 2.0f - 0.5f) / ((mCount - 1) * 2.0f); + } else { + Float last2OuterT = mLastOuterT; + Float last2InnerT = mLastInnerT; + + (void)FindNext(mBestDashLength); + + // + // mLastOuterT lastOuterT + // | | + // v v + // +---+---+---+---+ <- last2OuterT + // | |###|###| | + // | |###|###| | + // | |###|###| | + // +---+---+---+---+ <- last2InnerT + // ^ ^ + // | | + // mLastInnerT lastInnerT + lastOuterT = (mLastOuterT + last2OuterT) / 2.0f; + lastInnerT = (mLastInnerT + last2InnerT) / 2.0f; + } + } + + if ((!mHasZeroBorderWidth && mI == mCount - 1) || + (mHasZeroBorderWidth && !mHasMore)) { + outerT = 1.0f; + innerT = 1.0f; + } else { + if (mType == PERFECT) { + outerT = innerT = (mI * 2.0f + 0.5f) / ((mCount - 1) * 2.0f); + } else { + Float last2OuterT = mLastOuterT; + Float last2InnerT = mLastInnerT; + + (void)FindNext(mBestDashLength); + + // + // outerT last2OuterT + // | | + // v v + // mLastOuterT -> +---+---+---+---+ + // | |###|###| | + // | |###|###| | + // | |###|###| | + // mLastInnerT -> +---+---+---+---+ + // ^ ^ + // | | + // innerT last2InnerT + outerT = (mLastOuterT + last2OuterT) / 2.0f; + innerT = (mLastInnerT + last2InnerT) / 2.0f; + } + } + + mI++; + + Bezier outerSectionBezier; + Bezier innerSectionBezier; + GetSubBezier(&outerSectionBezier, mOuterBezier, lastOuterT, outerT); + GetSubBezier(&innerSectionBezier, mInnerBezier, lastInnerT, innerT); + return DashedCornerFinder::Result(outerSectionBezier, innerSectionBezier); +} + +void DashedCornerFinder::Reset(void) { + mLastOuterP = mOuterBezier.mPoints[0]; + mLastInnerP = mInnerBezier.mPoints[0]; + mLastOuterT = 0.0f; + mLastInnerT = 0.0f; + mHasMore = true; +} + +Float DashedCornerFinder::FindNext(Float dashLength) { + Float upper = 1.0f; + Float lower = mLastOuterT; + + Point OuterP, InnerP; + // Start from upper bound to check if this is the last segment. + Float outerT = upper; + Float innerT; + Float W = 0.0f; + Float L = 0.0f; + + const Float LENGTH_MARGIN = 0.1f; + for (size_t i = 0; i < MAX_LOOP; i++) { + OuterP = GetBezierPoint(mOuterBezier, outerT); + InnerP = FindBezierNearestPoint(mInnerBezier, OuterP, outerT, &innerT); + + // Calculate approximate dash length. + // + // W = (W1 + W2) / 2 + // L = (OuterL + InnerL) / 2 + // dashLength = L / W + // + // ____----+----____ + // OuterP ___--- | ---___ mLastOuterP + // +--- | ---+ + // | | | + // | | | + // | W | W1 | + // | | | + // W2 | | | + // | | ______------+ + // | ____+---- mLastInnerP + // | ___--- + // | __--- + // +-- + // InnerP + // OuterL + // ____---------____ + // OuterP ___--- ---___ mLastOuterP + // +--- ---+ + // | L | + // | ___----------______ | + // | __--- -----+ + // | __-- | + // +-- | + // | InnerL ______------+ + // | ____----- mLastInnerP + // | ___--- + // | __--- + // +-- + // InnerP + Float W1 = (mLastOuterP - mLastInnerP).Length(); + Float W2 = (OuterP - InnerP).Length(); + Float OuterL = GetBezierLength(mOuterBezier, mLastOuterT, outerT); + Float InnerL = GetBezierLength(mInnerBezier, mLastInnerT, innerT); + W = (W1 + W2) / 2.0f; + L = (OuterL + InnerL) / 2.0f; + if (L > W * dashLength + LENGTH_MARGIN) { + if (i > 0) { + upper = outerT; + } + } else if (L < W * dashLength - LENGTH_MARGIN) { + if (i == 0) { + // This is the last segment with shorter dashLength. + mHasMore = false; + break; + } + lower = outerT; + } else { + break; + } + + outerT = (upper + lower) / 2.0f; + } + + mLastOuterP = OuterP; + mLastInnerP = InnerP; + mLastOuterT = outerT; + mLastInnerT = innerT; + + if (W == 0.0f) { + return 1.0f; + } + + return L / W; +} + +void DashedCornerFinder::FindBestDashLength(Float aMinBorderWidth, + Float aMaxBorderWidth, + Float aMinBorderRadius, + Float aMaxBorderRadius) { + // If dashLength is not calculateable, find it with binary search, + // such that there exists i that OuterP_i == OuterP_n and + // InnerP_i == InnerP_n with given dashLength. + + FourFloats key(aMinBorderWidth, aMaxBorderWidth, aMinBorderRadius, + aMaxBorderRadius); + BestDashLength best; + if (DashedCornerCache.Get(key, &best)) { + mCount = best.count; + mBestDashLength = best.dashLength; + return; + } + + Float lower = 1.0f; + Float upper = DOT_LENGTH * DASH_LENGTH; + Float dashLength = upper; + size_t targetCount = 0; + + const Float LENGTH_MARGIN = 0.1f; + for (size_t j = 0; j < MAX_LOOP; j++) { + size_t count; + Float actualDashLength; + if (!GetCountAndLastDashLength(dashLength, &count, &actualDashLength)) { + if (j == 0) { + mCount = mMaxCount; + break; + } + } + + if (j == 0) { + if (count == 1) { + // If only 1 segment fits, fill entire region + // + // count = 1 + // mCount = 1 + // | 1 | + // +---+---+ + // |###|###| + // |###|###| + // |###|###| + // +---+---+ + // 1 + mCount = 1; + break; + } + + // targetCount should be 2n. + // + // targetCount = 2 + // mCount = 2 + // | 1 | 2 | + // +---+---+---+---+ + // |###| | |###| + // |###| | |###| + // |###| | |###| + // +---+---+---+---+ + // 1 2 + // + // targetCount = 6 + // mCount = 4 + // | 1 | 2 | 3 | 4 | 5 | 6 | + // +---+---+---+---+---+---+---+---+---+---+---+---+ + // |###| | |###|###| | |###|###| | |###| + // |###| | |###|###| | |###|###| | |###| + // |###| | |###|###| | |###|###| | |###| + // +---+---+---+---+---+---+---+---+---+---+---+---+ + // 1 2 3 4 + if (count % 2) { + targetCount = count + 1; + } else { + targetCount = count; + } + + mCount = targetCount / 2 + 1; + } + + if (count == targetCount) { + mBestDashLength = dashLength; + + // actualDashLength won't be greater than dashLength. + if (actualDashLength > dashLength - LENGTH_MARGIN) { + break; + } + + // We started from upper bound, no need to update range when j == 0. + if (j > 0) { + upper = dashLength; + } + } else { + // |j == 0 && count != targetCount| means that |targetCount = count + 1|, + // and we started from upper bound, no need to update range when j == 0. + if (j > 0) { + if (count > targetCount) { + lower = dashLength; + } else { + upper = dashLength; + } + } + } + + dashLength = (upper + lower) / 2.0f; + } + + if (DashedCornerCache.Count() > DashedCornerCacheSize) { + DashedCornerCache.Clear(); + } + DashedCornerCache.InsertOrUpdate(key, + BestDashLength(mBestDashLength, mCount)); +} + +bool DashedCornerFinder::GetCountAndLastDashLength(Float aDashLength, + size_t* aCount, + Float* aActualDashLength) { + // Return the number of segments and the last segment's dashLength for + // the given dashLength. + + Reset(); + + for (size_t i = 0; i < mMaxCount; i++) { + Float actualDashLength = FindNext(aDashLength); + if (mLastOuterT >= 1.0f) { + *aCount = i + 1; + *aActualDashLength = actualDashLength; + return true; + } + } + + return false; +} + +} // namespace mozilla diff --git a/layout/painting/DashedCornerFinder.h b/layout/painting/DashedCornerFinder.h new file mode 100644 index 0000000000..3a409d19f7 --- /dev/null +++ b/layout/painting/DashedCornerFinder.h @@ -0,0 +1,274 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_DashedCornerFinder_h_ +#define mozilla_DashedCornerFinder_h_ + +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/BezierUtils.h" + +namespace mozilla { + +// Calculate {OuterT_i, InnerT_i} for each 1 < i < n, that +// (OuterL_i + InnerL_i) / 2 == dashLength * (W_i + W_{i-1}) / 2 +// where +// OuterP_i: OuterCurve(OuterT_i) +// InnerP_i: InnerCurve(OuterT_i) +// OuterL_i: Elliptic arc length between OuterP_i - OuterP_{i-1} +// InnerL_i: Elliptic arc length between InnerP_i - InnerP_{i-1} +// W_i = |OuterP_i - InnerP_i| +// 1.0 < dashLength < 3.0 +// +// OuterP_1 OuterP_0 +// _+__-----------+ OuterCurve +// OuterP_2 __---- | OuterL_1 | +// __+--- | | +// __--- | OuterL_2 | | +// OuterP_3 _-- | | W_1 | W_0 +// _+ | | | +// / \ W_2 | | | +// / \ | | InnerL_1 | +// | \ | InnerL_2|____-------+ InnerCurve +// | \ |____----+ InnerP_0 +// | . \ __---+ InnerP_1 +// | \ / InnerP_2 +// | . /+ InnerP_3 +// | | +// | . | +// | | +// | | +// | | +// OuterP_{n-1} +--------+ InnerP_{n-1} +// | | +// | | +// | | +// | | +// | | +// OuterP_n +--------+ InnerP_n +// +// Returns region with [OuterCurve((OuterT_{2j} + OuterT_{2j-1}) / 2), +// OuterCurve((OuterT_{2j} + OuterT_{2j-1}) / 2), +// InnerCurve((OuterT_{2j} + OuterT_{2j+1}) / 2), +// InnerCurve((OuterT_{2j} + OuterT_{2j+1}) / 2)], +// to start and end with half segment. +// +// _+__----+------+ OuterCurve +// _+---- | |######| +// __+---#| | |######| +// _+---##|####| | |######| +// _-- |#####|#####| | |#####| +// _+ |#####|#####| | |#####| +// / \ |#####|####| | |#####| +// / \ |####|#####| | |#####| +// | \ |####|####| |____-+-----+ InnerCurve +// | \ |####|____+---+ +// | . \ __+---+ +// | \ / +// | . /+ +// | | +// | . | +// | | +// | | +// | | +// +--------+ +// | | +// | | +// +--------+ +// |########| +// |########| +// +--------+ + +class DashedCornerFinder { + typedef mozilla::gfx::Bezier Bezier; + typedef mozilla::gfx::Float Float; + typedef mozilla::gfx::Point Point; + typedef mozilla::gfx::Size Size; + + public: + struct Result { + // Control points for the outer curve and the inner curve. + // + // outerSectionBezier + // | + // v _+ 3 + // ___---#| + // 0 +---#######| + // |###########| + // |###########| + // |##########| + // |##########| + // |#########| + // |#####____+ 3 + // 0 +---- + // ^ + // | + // innerSectionBezier + Bezier outerSectionBezier; + Bezier innerSectionBezier; + + Result(const Bezier& aOuterSectionBezier, const Bezier& aInnerSectionBezier) + : outerSectionBezier(aOuterSectionBezier), + innerSectionBezier(aInnerSectionBezier) {} + }; + + // aCornerDim.width + // |<----------------->| + // | | + // --+-------------___---+-- + // ^ | __-- | ^ + // | | _- | | + // | | / | | aBorderWidthH + // | | / | | + // | | | | v + // | | | __--+-- + // aCornerDim.height | || _- + // | || / + // | | / + // | | | + // | | | + // | | | + // | | | + // v | | + // --+---------+ + // | | + // |<------->| + // aBorderWidthV + DashedCornerFinder(const Bezier& aOuterBezier, const Bezier& aInnerBezier, + Float aBorderWidthH, Float aBorderWidthV, + const Size& aCornerDim); + + bool HasMore(void) const; + Result Next(void); + + private: + static const size_t MAX_LOOP = 32; + + // Bezier control points for the outer curve and the inner curve. + // + // ___---+ outer curve + // __-- | + // _- | + // / | + // / | + // | | + // | __--+ inner curve + // | _- + // | / + // | / + // | | + // | | + // | | + // | | + // | | + // +---------+ + Bezier mOuterBezier; + Bezier mInnerBezier; + + Point mLastOuterP; + Point mLastInnerP; + Float mLastOuterT; + Float mLastInnerT; + + // Length for each segment, ratio of the border width at that point. + Float mBestDashLength; + + // If one of border-widths is 0, do not calculate mBestDashLength, and draw + // segments until it reaches the other side or exceeds mMaxCount. + bool mHasZeroBorderWidth; + bool mHasMore; + + // The maximum number of segments. + size_t mMaxCount; + + enum { + // radius.width + // |<----------------->| + // | | + // --+-------------___---+-- + // ^ | __-- | ^ + // | | _- | | + // | | / + | top-width + // | | / | | + // | | | | v + // | | | __--+-- + // radius.height | || _- + // | || / + // | | / + // | | | + // | | | + // | | | + // | | | + // v | | + // --+----+----+ + // | | + // |<------->| + // left-width + + // * top-width == left-width + // * radius.width == radius.height + // * top-width < radius.width * 2 + // + // Split the perfect circle's arc into 2n segments, each segment's length is + // top-width * dashLength. Then split the inner curve and the outer curve + // with same angles. + // + // radius.width + // |<---------------------->| + // | | v + // --+------------------------+-- + // ^ | | | top-width / 2 + // | | perfect | | + // | | circle's ___---+-- + // | | arc __-+ | ^ + // | | | _- | | + // radius.height | | | + | +-- + // | | | / \ | | + // | | | | \ | | + // | | | | \ | | + // | | +->| \ | | + // | | +---__ \ | | + // | | | --__ \ | | + // | | | ---__ \ | | + // v | | --_\|| + // --+----+----+--------------+ + // | | | + // |<-->| | + // left-width / 2 + PERFECT, + + // Other cases. + // + // Split the outer curve and the inner curve into 2n segments, each segment + // satisfies following: + // (OuterL_i + InnerL_i) / 2 == dashLength * (W_i + W_{i-1}) / 2 + OTHER + } mType; + + size_t mI; + size_t mCount; + + // Determine mType from parameters. + void DetermineType(Float aBorderWidthH, Float aBorderWidthV); + + // Reset calculation. + void Reset(void); + + // Find next segment. + Float FindNext(Float dashLength); + + // Find mBestDashLength for parameters. + void FindBestDashLength(Float aMinBorderWidth, Float aMaxBorderWidth, + Float aMinBorderRadius, Float aMaxBorderRadius); + + // Fill corner with dashes with given dash length, and return the number of + // segments and last segment's dash length. + bool GetCountAndLastDashLength(Float aDashLength, size_t* aCount, + Float* aActualDashLength); +}; + +} // namespace mozilla + +#endif /* mozilla_DashedCornerFinder_h_ */ diff --git a/layout/painting/DisplayItemClip.cpp b/layout/painting/DisplayItemClip.cpp new file mode 100644 index 0000000000..78aae2e1ec --- /dev/null +++ b/layout/painting/DisplayItemClip.cpp @@ -0,0 +1,486 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "DisplayItemClip.h" + +#include "gfxContext.h" +#include "gfxUtils.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/PathHelpers.h" +#include "mozilla/layers/StackingContextHelper.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/webrender/WebRenderTypes.h" +#include "nsPresContext.h" +#include "nsCSSRendering.h" +#include "nsLayoutUtils.h" +#include "nsRegion.h" + +using namespace mozilla::gfx; + +namespace mozilla { + +void DisplayItemClip::SetTo(const nsRect& aRect) { SetTo(aRect, nullptr); } + +void DisplayItemClip::SetTo(const nsRect& aRect, const nscoord* aRadii) { + mHaveClipRect = true; + mClipRect = aRect; + if (aRadii) { + mRoundedClipRects.SetLength(1); + mRoundedClipRects[0].mRect = aRect; + memcpy(mRoundedClipRects[0].mRadii, aRadii, sizeof(nscoord) * 8); + } else { + mRoundedClipRects.Clear(); + } +} + +void DisplayItemClip::SetTo(const nsRect& aRect, const nsRect& aRoundedRect, + const nscoord* aRadii) { + mHaveClipRect = true; + mClipRect = aRect; + mRoundedClipRects.SetLength(1); + mRoundedClipRects[0].mRect = aRoundedRect; + memcpy(mRoundedClipRects[0].mRadii, aRadii, sizeof(nscoord) * 8); +} + +bool DisplayItemClip::MayIntersect(const nsRect& aRect) const { + if (!mHaveClipRect) { + return !aRect.IsEmpty(); + } + nsRect r = aRect.Intersect(mClipRect); + if (r.IsEmpty()) { + return false; + } + for (uint32_t i = 0; i < mRoundedClipRects.Length(); ++i) { + const RoundedRect& rr = mRoundedClipRects[i]; + if (!nsLayoutUtils::RoundedRectIntersectsRect(rr.mRect, rr.mRadii, r)) { + return false; + } + } + return true; +} + +void DisplayItemClip::IntersectWith(const DisplayItemClip& aOther) { + if (!aOther.mHaveClipRect) { + return; + } + if (!mHaveClipRect) { + *this = aOther; + return; + } + if (!mClipRect.IntersectRect(mClipRect, aOther.mClipRect)) { + mRoundedClipRects.Clear(); + return; + } + mRoundedClipRects.AppendElements(aOther.mRoundedClipRects); +} + +void DisplayItemClip::ApplyTo(gfxContext* aContext, int32_t A2D) const { + ApplyRectTo(aContext, A2D); + ApplyRoundedRectClipsTo(aContext, A2D, 0, mRoundedClipRects.Length()); +} + +void DisplayItemClip::ApplyRectTo(gfxContext* aContext, int32_t A2D) const { + aContext->NewPath(); + gfxRect clip = nsLayoutUtils::RectToGfxRect(mClipRect, A2D); + aContext->SnappedRectangle(clip); + aContext->Clip(); +} + +void DisplayItemClip::ApplyRoundedRectClipsTo(gfxContext* aContext, int32_t A2D, + uint32_t aBegin, + uint32_t aEnd) const { + DrawTarget& aDrawTarget = *aContext->GetDrawTarget(); + + aEnd = std::min(aEnd, mRoundedClipRects.Length()); + + for (uint32_t i = aBegin; i < aEnd; ++i) { + RefPtr 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 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 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* aArray) const { + aArray->AppendElements(mRoundedClipRects.Elements(), + mRoundedClipRects.Length()); +} + +bool DisplayItemClip::ComputeRegionInClips(const DisplayItemClip* aOldClip, + const nsPoint& aShift, + nsRegion* aCombined) const { + if (!mHaveClipRect || (aOldClip && !aOldClip->mHaveClipRect)) { + return false; + } + + if (aOldClip) { + *aCombined = aOldClip->NonRoundedIntersection(); + aCombined->MoveBy(aShift); + aCombined->Or(*aCombined, NonRoundedIntersection()); + } else { + *aCombined = NonRoundedIntersection(); + } + return true; +} + +void DisplayItemClip::MoveBy(const nsPoint& aPoint) { + if (!mHaveClipRect) { + return; + } + mClipRect += aPoint; + for (uint32_t i = 0; i < mRoundedClipRects.Length(); ++i) { + mRoundedClipRects[i].mRect += aPoint; + } +} + +static StaticAutoPtr gNoClip; + +const DisplayItemClip& DisplayItemClip::NoClip() { + if (!gNoClip) { + gNoClip = new DisplayItemClip(); + } + return *gNoClip; +} + +void DisplayItemClip::Shutdown() { gNoClip = nullptr; } + +nsCString DisplayItemClip::ToString() const { + nsAutoCString str; + if (mHaveClipRect) { + str.AppendPrintf("%d,%d,%d,%d", mClipRect.x, mClipRect.y, mClipRect.width, + mClipRect.height); + for (uint32_t i = 0; i < mRoundedClipRects.Length(); ++i) { + const RoundedRect& r = mRoundedClipRects[i]; + str.AppendPrintf(" [%d,%d,%d,%d corners %d,%d,%d,%d,%d,%d,%d,%d]", + r.mRect.x, r.mRect.y, r.mRect.width, r.mRect.height, + r.mRadii[0], r.mRadii[1], r.mRadii[2], r.mRadii[3], + r.mRadii[4], r.mRadii[5], r.mRadii[6], r.mRadii[7]); + } + } + return std::move(str); +} + +void DisplayItemClip::ToComplexClipRegions( + int32_t aAppUnitsPerDevPixel, + nsTArray& 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 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* aArray) const; + + void ToComplexClipRegions(int32_t aAppUnitsPerDevPixel, + nsTArray& aOutArray) const; + + static const DisplayItemClip& NoClip(); + + static void Shutdown(); + + private: + nsRect mClipRect; + CopyableTArray 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 mParent; + uint32_t mRefCount = 0; + DisplayItemClipChain* mNextClipChainToDestroy; +#ifdef DEBUG + bool mOnStack; +#endif +}; + +struct DisplayItemClipChainHasher { + typedef const DisplayItemClipChain* Key; + + std::size_t operator()(const Key& aKey) const { + return DisplayItemClipChain::Hash(aKey); + } +}; + +struct DisplayItemClipChainEqualer { + typedef const DisplayItemClipChain* Key; + + bool operator()(const Key& lhs, const Key& rhs) const { + return DisplayItemClipChain::Equal(lhs, rhs); + } +}; + +} // namespace mozilla + +#endif /* DISPLAYITEMCLIPCHAIN_H_ */ diff --git a/layout/painting/DisplayListClipState.cpp b/layout/painting/DisplayListClipState.cpp new file mode 100644 index 0000000000..8a87e2906b --- /dev/null +++ b/layout/painting/DisplayListClipState.cpp @@ -0,0 +1,149 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "DisplayListClipState.h" + +#include "nsDisplayList.h" + +namespace mozilla { + +const DisplayItemClipChain* DisplayListClipState::GetCurrentCombinedClipChain( + nsDisplayListBuilder* aBuilder) { + if (mCurrentCombinedClipChainIsValid) { + return mCurrentCombinedClipChain; + } + if (!mClipChainContentDescendants && !mClipChainContainingBlockDescendants) { + mCurrentCombinedClipChain = nullptr; + mCurrentCombinedClipChainIsValid = true; + return nullptr; + } + + mCurrentCombinedClipChain = aBuilder->CreateClipChainIntersection( + mCurrentCombinedClipChain, mClipChainContentDescendants, + mClipChainContainingBlockDescendants); + mCurrentCombinedClipChainIsValid = true; + return mCurrentCombinedClipChain; +} + +static void ApplyClip(nsDisplayListBuilder* aBuilder, + const DisplayItemClipChain*& aClipToModify, + const ActiveScrolledRoot* aASR, + DisplayItemClipChain& aClipChainOnStack) { + aClipChainOnStack.mASR = aASR; + if (aClipToModify && aClipToModify->mASR == aASR) { + // Intersect with aClipToModify and replace the clip chain item. + aClipChainOnStack.mClip.IntersectWith(aClipToModify->mClip); + aClipChainOnStack.mParent = aClipToModify->mParent; + aClipToModify = &aClipChainOnStack; + } else if (!aClipToModify || + ActiveScrolledRoot::IsAncestor(aClipToModify->mASR, aASR)) { + // Add a new clip chain item at the bottom. + aClipChainOnStack.mParent = aClipToModify; + aClipToModify = &aClipChainOnStack; + } else { + // We need to insert / intersect a DisplayItemClipChain in the middle of the + // aClipToModify chain. This is a very rare case. + // Find the common ancestor and have the builder create the + // DisplayItemClipChain intersection. This will create new + // DisplayItemClipChain objects for all descendants of ancestorSC and we + // will not hold on to a pointer to aClipChainOnStack. + const DisplayItemClipChain* ancestorSC = aClipToModify; + while (ancestorSC && + ActiveScrolledRoot::IsAncestor(aASR, ancestorSC->mASR)) { + ancestorSC = ancestorSC->mParent; + } + ancestorSC = aBuilder->CopyWholeChain(ancestorSC); + aClipChainOnStack.mParent = nullptr; + aClipToModify = aBuilder->CreateClipChainIntersection( + ancestorSC, aClipToModify, &aClipChainOnStack); + } +} + +void DisplayListClipState::ClipContainingBlockDescendants( + nsDisplayListBuilder* aBuilder, const nsRect& aRect, const nscoord* aRadii, + DisplayItemClipChain& aClipChainOnStack) { + if (aRadii) { + aClipChainOnStack.mClip.SetTo(aRect, aRadii); + } else { + aClipChainOnStack.mClip.SetTo(aRect); + } + const ActiveScrolledRoot* asr = aBuilder->CurrentActiveScrolledRoot(); + ApplyClip(aBuilder, mClipChainContainingBlockDescendants, asr, + aClipChainOnStack); + InvalidateCurrentCombinedClipChain(asr); +} + +void DisplayListClipState::ClipContentDescendants( + nsDisplayListBuilder* aBuilder, const nsRect& aRect, const nscoord* aRadii, + DisplayItemClipChain& aClipChainOnStack) { + if (aRadii) { + aClipChainOnStack.mClip.SetTo(aRect, aRadii); + } else { + aClipChainOnStack.mClip.SetTo(aRect); + } + const ActiveScrolledRoot* asr = aBuilder->CurrentActiveScrolledRoot(); + ApplyClip(aBuilder, mClipChainContentDescendants, asr, aClipChainOnStack); + InvalidateCurrentCombinedClipChain(asr); +} + +void DisplayListClipState::ClipContentDescendants( + nsDisplayListBuilder* aBuilder, const nsRect& aRect, + const nsRect& aRoundedRect, const nscoord* aRadii, + DisplayItemClipChain& aClipChainOnStack) { + if (aRadii) { + aClipChainOnStack.mClip.SetTo(aRect, aRoundedRect, aRadii); + } else { + nsRect intersect = aRect.Intersect(aRoundedRect); + aClipChainOnStack.mClip.SetTo(intersect); + } + const ActiveScrolledRoot* asr = aBuilder->CurrentActiveScrolledRoot(); + ApplyClip(aBuilder, mClipChainContentDescendants, asr, aClipChainOnStack); + InvalidateCurrentCombinedClipChain(asr); +} + +void DisplayListClipState::InvalidateCurrentCombinedClipChain( + const ActiveScrolledRoot* aInvalidateUpTo) { + mClippedToDisplayPort = false; + mCurrentCombinedClipChainIsValid = false; + while (mCurrentCombinedClipChain && + ActiveScrolledRoot::IsAncestor(aInvalidateUpTo, + mCurrentCombinedClipChain->mASR)) { + mCurrentCombinedClipChain = mCurrentCombinedClipChain->mParent; + } +} + +void DisplayListClipState::ClipContainingBlockDescendantsToContentBox( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + DisplayItemClipChain& aClipChainOnStack, uint32_t aFlags) { + nscoord radii[8]; + bool hasBorderRadius = aFrame->GetContentBoxBorderRadii(radii); + if (!hasBorderRadius && + (aFlags & ASSUME_DRAWING_RESTRICTED_TO_CONTENT_RECT)) { + return; + } + + nsRect clipRect = aFrame->GetContentRectRelativeToSelf() + + aBuilder->ToReferenceFrame(aFrame); + // If we have a border-radius, we have to clip our content to that + // radius. + ClipContainingBlockDescendants( + aBuilder, clipRect, hasBorderRadius ? radii : nullptr, aClipChainOnStack); +} + +DisplayListClipState::AutoSaveRestore::AutoSaveRestore( + nsDisplayListBuilder* aBuilder) + : mBuilder(aBuilder), + mState(aBuilder->ClipState()), + mSavedState(aBuilder->ClipState()) +#ifdef DEBUG + , + mClipUsed(false), + mRestored(false) +#endif +{ +} + +} // namespace mozilla diff --git a/layout/painting/DisplayListClipState.h b/layout/painting/DisplayListClipState.h new file mode 100644 index 0000000000..0f1df2ca58 --- /dev/null +++ b/layout/painting/DisplayListClipState.h @@ -0,0 +1,301 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DISPLAYLISTCLIPSTATE_H_ +#define DISPLAYLISTCLIPSTATE_H_ + +#include "DisplayItemClip.h" +#include "DisplayItemClipChain.h" + +#include "mozilla/DebugOnly.h" + +class nsIFrame; +class nsIScrollableFrame; + +namespace mozilla { + +class nsDisplayListBuilder; + +/** + * All clip coordinates are in appunits relative to the reference frame + * for the display item we're building. + */ +class DisplayListClipState { + public: + DisplayListClipState() + : mClipChainContentDescendants(nullptr), + mClipChainContainingBlockDescendants(nullptr), + mCurrentCombinedClipChain(nullptr), + mCurrentCombinedClipChainIsValid(false), + mClippedToDisplayPort(false) {} + + void SetClippedToDisplayPort() { mClippedToDisplayPort = true; } + bool IsClippedToDisplayPort() const { return mClippedToDisplayPort; } + + /** + * Returns intersection of mClipChainContainingBlockDescendants and + * mClipChainContentDescendants, allocated on aBuilder's arena. + */ + const DisplayItemClipChain* GetCurrentCombinedClipChain( + nsDisplayListBuilder* aBuilder); + + const DisplayItemClipChain* GetClipChainForContainingBlockDescendants() + const { + return mClipChainContainingBlockDescendants; + } + const DisplayItemClipChain* GetClipChainForContentDescendants() const { + return mClipChainContentDescendants; + } + + const ActiveScrolledRoot* GetContentClipASR() const { + return mClipChainContentDescendants ? mClipChainContentDescendants->mASR + : nullptr; + } + + class AutoSaveRestore; + + class AutoClipContainingBlockDescendantsToContentBox; + + class AutoClipMultiple; + + enum { ASSUME_DRAWING_RESTRICTED_TO_CONTENT_RECT = 0x01 }; + + private: + void Clear() { + mClipChainContentDescendants = nullptr; + mClipChainContainingBlockDescendants = nullptr; + mCurrentCombinedClipChain = nullptr; + mCurrentCombinedClipChainIsValid = false; + mClippedToDisplayPort = false; + } + + void SetClipChainForContainingBlockDescendants( + const DisplayItemClipChain* aClipChain) { + mClipChainContainingBlockDescendants = aClipChain; + InvalidateCurrentCombinedClipChain(aClipChain ? aClipChain->mASR : nullptr); + } + + /** + * Intersects the given clip rect (with optional aRadii) with the current + * mClipContainingBlockDescendants and sets mClipContainingBlockDescendants to + * the result, stored in aClipOnStack. + */ + void ClipContainingBlockDescendants(nsDisplayListBuilder* aBuilder, + const nsRect& aRect, + const nscoord* aRadii, + DisplayItemClipChain& aClipChainOnStack); + + void ClipContentDescendants(nsDisplayListBuilder* aBuilder, + const nsRect& aRect, const nscoord* aRadii, + DisplayItemClipChain& aClipChainOnStack); + void ClipContentDescendants(nsDisplayListBuilder* aBuilder, + const nsRect& aRect, const nsRect& aRoundedRect, + const nscoord* aRadii, + DisplayItemClipChain& aClipChainOnStack); + + void InvalidateCurrentCombinedClipChain( + const ActiveScrolledRoot* aInvalidateUpTo); + + /** + * Clips containing-block descendants to the frame's content-box, + * taking border-radius into account. + * If aFlags contains ASSUME_DRAWING_RESTRICTED_TO_CONTENT_RECT then + * we assume display items will not draw outside the content rect, so + * clipping is only required if there is a border-radius. This is an + * optimization to reduce the amount of clipping required. + */ + void ClipContainingBlockDescendantsToContentBox( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + DisplayItemClipChain& aClipChainOnStack, uint32_t aFlags); + + /** + * All content descendants (i.e. following placeholder frames to their + * out-of-flows if necessary) should be clipped by + * mClipChainContentDescendants. Null if no clipping applies. + */ + const DisplayItemClipChain* mClipChainContentDescendants; + /** + * All containing-block descendants (i.e. frame descendants), including + * display items for the current frame, should be clipped by + * mClipChainContainingBlockDescendants. + * Null if no clipping applies. + */ + const DisplayItemClipChain* mClipChainContainingBlockDescendants; + /** + * The intersection of mClipChainContentDescendants and + * mClipChainContainingBlockDescendants. + * Allocated in the nsDisplayListBuilder arena. Null if none has been + * allocated or both mClipChainContentDescendants and + * mClipChainContainingBlockDescendants are null. + */ + const DisplayItemClipChain* mCurrentCombinedClipChain; + bool mCurrentCombinedClipChainIsValid; + /** + * A flag that is used by sticky positioned items to know if the clip applied + * to them is just the displayport clip or if there is additional clipping. + */ + bool mClippedToDisplayPort; +}; + +/** + * A class to automatically save and restore the current clip state. Also + * offers methods for modifying the clip state. Only one modification is allowed + * to be in scope at a time using one of these objects; multiple modifications + * require nested objects. The interface is written this way to prevent + * dangling pointers to DisplayItemClips. + */ +class DisplayListClipState::AutoSaveRestore { + public: + explicit AutoSaveRestore(nsDisplayListBuilder* aBuilder); + void Restore() { + mState = mSavedState; +#ifdef DEBUG + mRestored = true; +#endif + } + ~AutoSaveRestore() { Restore(); } + + void Clear() { + NS_ASSERTION(!mRestored, "Already restored!"); + mState.Clear(); +#ifdef DEBUG + mClipUsed = false; +#endif + } + + void SetClipChainForContainingBlockDescendants( + const DisplayItemClipChain* aClipChain) { + mState.SetClipChainForContainingBlockDescendants(aClipChain); + } + + /** + * Intersects the given clip rect (with optional aRadii) with the current + * mClipContainingBlockDescendants and sets mClipContainingBlockDescendants to + * the result, stored in aClipOnStack. + */ + void ClipContainingBlockDescendants(const nsRect& aRect, + const nscoord* aRadii = nullptr) { + NS_ASSERTION(!mRestored, "Already restored!"); + NS_ASSERTION(!mClipUsed, "mClip already used"); +#ifdef DEBUG + mClipUsed = true; +#endif + mState.ClipContainingBlockDescendants(mBuilder, aRect, aRadii, mClipChain); + } + + void ClipContentDescendants(const nsRect& aRect, + const nscoord* aRadii = nullptr) { + NS_ASSERTION(!mRestored, "Already restored!"); + NS_ASSERTION(!mClipUsed, "mClip already used"); +#ifdef DEBUG + mClipUsed = true; +#endif + mState.ClipContentDescendants(mBuilder, aRect, aRadii, mClipChain); + } + + void ClipContentDescendants(const nsRect& aRect, const nsRect& aRoundedRect, + const nscoord* aRadii = nullptr) { + NS_ASSERTION(!mRestored, "Already restored!"); + NS_ASSERTION(!mClipUsed, "mClip already used"); +#ifdef DEBUG + mClipUsed = true; +#endif + mState.ClipContentDescendants(mBuilder, aRect, aRoundedRect, aRadii, + mClipChain); + } + + /** + * Clips containing-block descendants to the frame's content-box, + * taking border-radius into account. + * If aFlags contains ASSUME_DRAWING_RESTRICTED_TO_CONTENT_RECT then + * we assume display items will not draw outside the content rect, so + * clipping is only required if there is a border-radius. This is an + * optimization to reduce the amount of clipping required. + */ + void ClipContainingBlockDescendantsToContentBox( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, uint32_t aFlags = 0) { + NS_ASSERTION(!mRestored, "Already restored!"); + NS_ASSERTION(!mClipUsed, "mClip already used"); +#ifdef DEBUG + mClipUsed = true; +#endif + mState.ClipContainingBlockDescendantsToContentBox(aBuilder, aFrame, + mClipChain, aFlags); + } + + void SetClippedToDisplayPort() { mState.SetClippedToDisplayPort(); } + bool IsClippedToDisplayPort() const { + return mState.IsClippedToDisplayPort(); + } + + protected: + nsDisplayListBuilder* mBuilder; + DisplayListClipState& mState; + DisplayListClipState mSavedState; + DisplayItemClipChain mClipChain; +#ifdef DEBUG + bool mClipUsed; + bool mRestored; +#endif +}; + +class DisplayListClipState::AutoClipContainingBlockDescendantsToContentBox + : public AutoSaveRestore { + public: + AutoClipContainingBlockDescendantsToContentBox(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame, + uint32_t aFlags = 0) + : AutoSaveRestore(aBuilder) { +#ifdef DEBUG + mClipUsed = true; +#endif + mState.ClipContainingBlockDescendantsToContentBox(aBuilder, aFrame, + mClipChain, aFlags); + } +}; + +/** + * Do not use this outside of nsIFrame::BuildDisplayListForChild, use + * multiple AutoSaveRestores instead. We provide this class just to ensure + * BuildDisplayListForChild is as efficient as possible. + */ +class DisplayListClipState::AutoClipMultiple : public AutoSaveRestore { + public: + explicit AutoClipMultiple(nsDisplayListBuilder* aBuilder) + : AutoSaveRestore(aBuilder) +#ifdef DEBUG + , + mExtraClipUsed(false) +#endif + { + } + + /** + * Intersects the given clip rect (with optional aRadii) with the current + * mClipContainingBlockDescendants and sets mClipContainingBlockDescendants to + * the result, stored in aClipOnStack. + */ + void ClipContainingBlockDescendantsExtra(const nsRect& aRect, + const nscoord* aRadii) { + NS_ASSERTION(!mRestored, "Already restored!"); + NS_ASSERTION(!mExtraClipUsed, "mExtraClip already used"); +#ifdef DEBUG + mExtraClipUsed = true; +#endif + mState.ClipContainingBlockDescendants(mBuilder, aRect, aRadii, + mExtraClipChain); + } + + protected: + DisplayItemClipChain mExtraClipChain; +#ifdef DEBUG + bool mExtraClipUsed; +#endif +}; + +} // namespace mozilla + +#endif /* DISPLAYLISTCLIPSTATE_H_ */ diff --git a/layout/painting/DottedCornerFinder.cpp b/layout/painting/DottedCornerFinder.cpp new file mode 100644 index 0000000000..2e8408d80b --- /dev/null +++ b/layout/painting/DottedCornerFinder.cpp @@ -0,0 +1,539 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "DottedCornerFinder.h" + +#include + +#include "BorderCache.h" +#include "BorderConsts.h" +#include "nsTHashMap.h" + +namespace mozilla { + +using namespace gfx; + +static inline Float Square(Float x) { return x * x; } + +static Point PointRotateCCW90(const Point& aP) { return Point(aP.y, -aP.x); } + +struct BestOverlap { + Float overlap; + size_t count; + + BestOverlap() : overlap(0.0f), count(0) {} + + BestOverlap(Float aOverlap, size_t aCount) + : overlap(aOverlap), count(aCount) {} +}; + +static const size_t DottedCornerCacheSize = 256; +nsTHashMap DottedCornerCache; + +DottedCornerFinder::DottedCornerFinder(const Bezier& aOuterBezier, + const Bezier& aInnerBezier, + Corner aCorner, Float aBorderRadiusX, + Float aBorderRadiusY, const Point& aC0, + Float aR0, const Point& aCn, Float aRn, + const Size& aCornerDim) + : mOuterBezier(aOuterBezier), + mInnerBezier(aInnerBezier), + mCorner(aCorner), + mNormalSign((aCorner == C_TL || aCorner == C_BR) ? -1.0f : 1.0f), + mC0(aC0), + mCn(aCn), + mR0(aR0), + mRn(aRn), + mMaxR(std::max(aR0, aRn)), + mCenterCurveOrigin(mC0.x, mCn.y), + mCenterCurveR(0.0), + mInnerCurveOrigin(mInnerBezier.mPoints[0].x, mInnerBezier.mPoints[3].y), + mBestOverlap(0.0f), + mHasZeroBorderWidth(false), + mHasMore(true), + mMaxCount(aCornerDim.width + aCornerDim.height), + mType(OTHER), + mI(0), + mCount(0) { + NS_ASSERTION(mR0 > 0.0f || mRn > 0.0f, + "At least one side should have non-zero radius."); + + mInnerWidth = fabs(mInnerBezier.mPoints[0].x - mInnerBezier.mPoints[3].x); + mInnerHeight = fabs(mInnerBezier.mPoints[0].y - mInnerBezier.mPoints[3].y); + + DetermineType(aBorderRadiusX, aBorderRadiusY); + + Reset(); +} + +static bool IsSingleCurve(Float aMinR, Float aMaxR, Float aMinBorderRadius, + Float aMaxBorderRadius) { + return aMinR > 0.0f && aMinBorderRadius > aMaxR * 4.0f && + aMinBorderRadius / aMaxBorderRadius > 0.5f; +} + +void DottedCornerFinder::DetermineType(Float aBorderRadiusX, + Float aBorderRadiusY) { + // Calculate parameters for the center curve before swap. + Float centerCurveWidth = fabs(mC0.x - mCn.x); + Float centerCurveHeight = fabs(mC0.y - mCn.y); + Point cornerPoint(mCn.x, mC0.y); + + bool swapped = false; + if (mR0 < mRn) { + // Always draw from wider side to thinner side. + std::swap(mC0, mCn); + std::swap(mR0, mRn); + std::swap(mInnerBezier.mPoints[0], mInnerBezier.mPoints[3]); + std::swap(mInnerBezier.mPoints[1], mInnerBezier.mPoints[2]); + std::swap(mOuterBezier.mPoints[0], mOuterBezier.mPoints[3]); + std::swap(mOuterBezier.mPoints[1], mOuterBezier.mPoints[2]); + mNormalSign = -mNormalSign; + swapped = true; + } + + // See the comment at mType declaration for each condition. + + Float minR = std::min(mR0, mRn); + Float minBorderRadius = std::min(aBorderRadiusX, aBorderRadiusY); + Float maxBorderRadius = std::max(aBorderRadiusX, aBorderRadiusY); + if (IsSingleCurve(minR, mMaxR, minBorderRadius, maxBorderRadius)) { + if (mR0 == mRn) { + Float borderLength; + if (minBorderRadius == maxBorderRadius) { + mType = PERFECT; + borderLength = M_PI * centerCurveHeight / 2.0f; + + mCenterCurveR = centerCurveWidth; + } else { + mType = SINGLE_CURVE_AND_RADIUS; + borderLength = + GetQuarterEllipticArcLength(centerCurveWidth, centerCurveHeight); + } + + Float diameter = mR0 * 2.0f; + size_t count = round(borderLength / diameter); + if (count % 2) { + count++; + } + mCount = count / 2 - 1; + if (mCount > 0) { + mBestOverlap = 1.0f - borderLength / (diameter * count); + } + } else { + mType = SINGLE_CURVE; + } + } + + if (mType == SINGLE_CURVE_AND_RADIUS || mType == SINGLE_CURVE) { + Size cornerSize(centerCurveWidth, centerCurveHeight); + GetBezierPointsForCorner(&mCenterBezier, mCorner, cornerPoint, cornerSize); + if (swapped) { + std::swap(mCenterBezier.mPoints[0], mCenterBezier.mPoints[3]); + std::swap(mCenterBezier.mPoints[1], mCenterBezier.mPoints[2]); + } + } + + if (minR == 0.0f) { + mHasZeroBorderWidth = true; + } + + if ((mType == SINGLE_CURVE || mType == OTHER) && !mHasZeroBorderWidth) { + FindBestOverlap(minR, minBorderRadius, maxBorderRadius); + } +} + +bool DottedCornerFinder::HasMore(void) const { + if (mHasZeroBorderWidth) { + return mI < mMaxCount && mHasMore; + } + + return mI < mCount; +} + +DottedCornerFinder::Result DottedCornerFinder::Next(void) { + mI++; + + if (mType == PERFECT) { + Float phi = mI * 4.0f * mR0 * (1 - mBestOverlap) / mCenterCurveR; + if (mCorner == C_TL) { + phi = -M_PI / 2.0f - phi; + } else if (mCorner == C_TR) { + phi = -M_PI / 2.0f + phi; + } else if (mCorner == C_BR) { + phi = M_PI / 2.0f - phi; + } else { + phi = M_PI / 2.0f + phi; + } + + Point C(mCenterCurveOrigin.x + mCenterCurveR * cos(phi), + mCenterCurveOrigin.y + mCenterCurveR * sin(phi)); + return DottedCornerFinder::Result(C, mR0); + } + + // Find unfilled and filled circles. + (void)FindNext(mBestOverlap); + if (mHasMore) { + (void)FindNext(mBestOverlap); + } + + return Result(mLastC, mLastR); +} + +void DottedCornerFinder::Reset(void) { + mLastC = mC0; + mLastR = mR0; + mLastT = 0.0f; + mHasMore = true; +} + +void DottedCornerFinder::FindPointAndRadius(Point& C, Float& r, + const Point& innerTangent, + const Point& normal, Float t) { + // Find radius for the given tangent point on the inner curve such that the + // circle is also tangent to the outer curve. + + NS_ASSERTION(mType == OTHER, "Wrong mType"); + + Float lower = 0.0f; + Float upper = mMaxR; + const Float DIST_MARGIN = 0.1f; + for (size_t i = 0; i < MAX_LOOP; i++) { + r = (upper + lower) / 2.0f; + C = innerTangent + normal * r; + + Point Near = FindBezierNearestPoint(mOuterBezier, C, t); + Float distSquare = (C - Near).LengthSquare(); + + if (distSquare > Square(r + DIST_MARGIN)) { + lower = r; + } else if (distSquare < Square(r - DIST_MARGIN)) { + upper = r; + } else { + break; + } + } +} + +Float DottedCornerFinder::FindNext(Float overlap) { + Float lower = mLastT; + Float upper = 1.0f; + Float t; + + Point C = mLastC; + Float r = 0.0f; + + Float factor = (1.0f - overlap); + + Float circlesDist = 0.0f; + Float expectedDist = 0.0f; + + const Float DIST_MARGIN = 0.1f; + if (mType == SINGLE_CURVE_AND_RADIUS) { + r = mR0; + + expectedDist = (r + mLastR) * factor; + + // Find C_i on the center curve. + for (size_t i = 0; i < MAX_LOOP; i++) { + t = (upper + lower) / 2.0f; + C = GetBezierPoint(mCenterBezier, t); + + // Check overlap along arc. + circlesDist = GetBezierLength(mCenterBezier, mLastT, t); + if (circlesDist < expectedDist - DIST_MARGIN) { + lower = t; + } else if (circlesDist > expectedDist + DIST_MARGIN) { + upper = t; + } else { + break; + } + } + } else if (mType == SINGLE_CURVE) { + // Find C_i on the center curve, and calculate r_i. + for (size_t i = 0; i < MAX_LOOP; i++) { + t = (upper + lower) / 2.0f; + C = GetBezierPoint(mCenterBezier, t); + + Point Diff = GetBezierDifferential(mCenterBezier, t); + Float DiffLength = Diff.Length(); + if (DiffLength == 0.0f) { + // Basically this shouldn't happen. + // If differential is 0, we cannot calculate tangent circle, + // skip this point. + t = (t + upper) / 2.0f; + continue; + } + + Point normal = PointRotateCCW90(Diff / DiffLength) * (-mNormalSign); + r = CalculateDistanceToEllipticArc(C, normal, mInnerCurveOrigin, + mInnerWidth, mInnerHeight); + + // Check overlap along arc. + circlesDist = GetBezierLength(mCenterBezier, mLastT, t); + expectedDist = (r + mLastR) * factor; + if (circlesDist < expectedDist - DIST_MARGIN) { + lower = t; + } else if (circlesDist > expectedDist + DIST_MARGIN) { + upper = t; + } else { + break; + } + } + } else { + Float distSquareMax = Square(mMaxR * 3.0f); + Float circlesDistSquare = 0.0f; + + // Find C_i and r_i. + for (size_t i = 0; i < MAX_LOOP; i++) { + t = (upper + lower) / 2.0f; + Point innerTangent = GetBezierPoint(mInnerBezier, t); + if ((innerTangent - mLastC).LengthSquare() > distSquareMax) { + // It's clear that this tangent point is too far, skip it. + upper = t; + continue; + } + + Point Diff = GetBezierDifferential(mInnerBezier, t); + Float DiffLength = Diff.Length(); + if (DiffLength == 0.0f) { + // Basically this shouldn't happen. + // If differential is 0, we cannot calculate tangent circle, + // skip this point. + t = (t + upper) / 2.0f; + continue; + } + + Point normal = PointRotateCCW90(Diff / DiffLength) * mNormalSign; + FindPointAndRadius(C, r, innerTangent, normal, t); + + // Check overlap with direct distance. + circlesDistSquare = (C - mLastC).LengthSquare(); + expectedDist = (r + mLastR) * factor; + if (circlesDistSquare < Square(expectedDist - DIST_MARGIN)) { + lower = t; + } else if (circlesDistSquare > Square(expectedDist + DIST_MARGIN)) { + upper = t; + } else { + break; + } + } + + circlesDist = sqrt(circlesDistSquare); + } + + if (mHasZeroBorderWidth) { + // When calculating circle around r=0, it may result in wrong radius that + // is bigger than previous circle. Detect it and stop calculating. + const Float R_MARGIN = 0.1f; + if (mLastR < R_MARGIN && r > mLastR) { + mHasMore = false; + mLastR = 0.0f; + return 0.0f; + } + } + + mLastT = t; + mLastC = C; + mLastR = r; + + if (mHasZeroBorderWidth) { + const Float T_MARGIN = 0.001f; + if (mLastT >= 1.0f - T_MARGIN || + (mLastC - mCn).LengthSquare() < Square(mLastR)) { + mHasMore = false; + } + } + + if (expectedDist == 0.0f) { + return 0.0f; + } + + return 1.0f - circlesDist * factor / expectedDist; +} + +void DottedCornerFinder::FindBestOverlap(Float aMinR, Float aMinBorderRadius, + Float aMaxBorderRadius) { + // If overlap is not calculateable, find it with binary search, + // such that there exists i that C_i == C_n with the given overlap. + + FourFloats key(aMinR, mMaxR, aMinBorderRadius, aMaxBorderRadius); + BestOverlap best; + if (DottedCornerCache.Get(key, &best)) { + mCount = best.count; + mBestOverlap = best.overlap; + return; + } + + Float lower = 0.0f; + Float upper = 0.5f; + // Start from lower bound to find the minimum number of circles. + Float overlap = 0.0f; + mBestOverlap = overlap; + size_t targetCount = 0; + + const Float OVERLAP_MARGIN = 0.1f; + for (size_t j = 0; j < MAX_LOOP; j++) { + Reset(); + + size_t count; + Float actualOverlap; + if (!GetCountAndLastOverlap(overlap, &count, &actualOverlap)) { + if (j == 0) { + mCount = mMaxCount; + break; + } + } + + if (j == 0) { + if (count < 3 || (count == 3 && actualOverlap > 0.5f)) { + // |count == 3 && actualOverlap > 0.5f| means there could be + // a circle but it is too near from both ends. + // + // if actualOverlap == 0.0 + // 1 2 3 + // +-------+-------+-------+-------+ + // | ##### | ***** | ##### | ##### | + // |#######|*******|#######|#######| + // |###+###|***+***|###+###|###+###| + // |# C_0 #|* C_1 *|# C_2 #|# C_n #| + // | ##### | ***** | ##### | ##### | + // +-------+-------+-------+-------+ + // | + // V + // +-------+---+-------+---+-------+ + // | ##### | | ##### | | ##### | + // |#######| |#######| |#######| + // |###+###| |###+###| |###+###| Find the best overlap to place + // |# C_0 #| |# C_1 #| |# C_n #| C_1 at the middle of them + // | ##### | | ##### | | ##### | + // +-------+---+-------+---|-------+ + // + // if actualOverlap == 0.5 + // 1 2 3 + // +-------+-------+-------+---+ + // | ##### | ***** | ##### |## | + // |#######|*******|##### C_n #| + // |###+###|***+***|###+###+###| + // |# C_0 #|* C_1 *|# C_2 #|###| + // | ##### | ***** | ##### |## | + // +-------+-------+-------+---+ + // | + // V + // +-------+-+-------+-+-------+ + // | ##### | | ##### | | ##### | + // |#######| |#######| |#######| + // |###+###| |###+###| |###+###| Even if we place C_1 at the middle + // |# C_0 #| |# C_1 #| |# C_n #| of them, it's too near from them + // | ##### | | ##### | | ##### | + // +-------+-+-------+-|-------+ + // | + // V + // +-------+-----------+-------+ + // | ##### | | ##### | + // |#######| |#######| + // |###+###| |###+###| Do not draw any circle + // |# C_0 #| |# C_n #| + // | ##### | | ##### | + // +-------+-----------+-------+ + mCount = 0; + break; + } + + // targetCount should be 2n, as we're searching C_1 to C_n. + // + // targetCount = 4 + // mCount = 1 + // 1 2 3 4 + // +-------+-------+-------+-------+-------+ + // | ##### | ***** | ##### | ***** | ##### | + // |#######|*******|#######|*******|#######| + // |###+###|***+***|###+###|***+***|###+###| + // |# C_0 #|* C_1 *|# C_2 #|* C_3 *|# C_n #| + // | ##### | ***** | ##### | ***** | ##### | + // +-------+-------+-------+-------+-------+ + // 1 + // + // targetCount = 6 + // mCount = 2 + // 1 2 3 4 5 6 + // +-------+-------+-------+-------+-------+-------+-------+ + // | ##### | ***** | ##### | ***** | ##### | ***** | ##### | + // |#######|*******|#######|*******|#######|*******|#######| + // |###+###|***+***|###+###|***+***|###+###|***+***|###+###| + // |# C_0 #|* C_1 *|# C_2 #|* C_3 *|# C_4 #|* C_5 *|# C_n #| + // | ##### | ***** | ##### | ***** | ##### | ***** | ##### | + // +-------+-------+-------+-------+-------+-------+-------+ + // 1 2 + if (count % 2) { + targetCount = count + 1; + } else { + targetCount = count; + } + + mCount = targetCount / 2 - 1; + } + + if (count == targetCount) { + mBestOverlap = overlap; + + if (fabs(actualOverlap - overlap) < OVERLAP_MARGIN) { + break; + } + + // We started from upper bound, no need to update range when j == 0. + if (j > 0) { + if (actualOverlap > overlap) { + lower = overlap; + } else { + upper = overlap; + } + } + } else { + // |j == 0 && count != targetCount| means that |targetCount = count + 1|, + // and we started from upper bound, no need to update range when j == 0. + if (j > 0) { + if (count > targetCount) { + upper = overlap; + } else { + lower = overlap; + } + } + } + + overlap = (upper + lower) / 2.0f; + } + + if (DottedCornerCache.Count() > DottedCornerCacheSize) { + DottedCornerCache.Clear(); + } + DottedCornerCache.InsertOrUpdate(key, BestOverlap(mBestOverlap, mCount)); +} + +bool DottedCornerFinder::GetCountAndLastOverlap(Float aOverlap, size_t* aCount, + Float* aActualOverlap) { + // Return the number of circles and the last circles' overlap for the + // given overlap. + + Reset(); + + const Float T_MARGIN = 0.001f; + const Float DIST_MARGIN = 0.1f; + const Float DIST_MARGIN_SQUARE = Square(DIST_MARGIN); + for (size_t i = 0; i < mMaxCount; i++) { + Float actualOverlap = FindNext(aOverlap); + if (mLastT >= 1.0f - T_MARGIN || + (mLastC - mCn).LengthSquare() < DIST_MARGIN_SQUARE) { + *aCount = i + 1; + *aActualOverlap = actualOverlap; + return true; + } + } + + return false; +} + +} // namespace mozilla diff --git a/layout/painting/DottedCornerFinder.h b/layout/painting/DottedCornerFinder.h new file mode 100644 index 0000000000..fd3a9e5d04 --- /dev/null +++ b/layout/painting/DottedCornerFinder.h @@ -0,0 +1,432 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_DottedCornerFinder_h_ +#define mozilla_DottedCornerFinder_h_ + +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/BezierUtils.h" +#include "gfxRect.h" + +namespace mozilla { + +// Calculate C_i and r_i for each filled/unfilled circles in dotted corner. +// Returns circle with C_{2j} and r_{2j} where 0 < 2j < n. +// +// ____-----------+ +// __---- ***** ###| +// __---- ********* ####| +// __--- ##### ***********#####| +// _-- ######### *****+*****#####+ C_0 +// _- ########### *** C_1****#####| +// / #####+##### ********* ####| +// / . ### C_2 ### ***** ###| +// | ######### ____-------+ +// | . #####____----- +// | __---- +// | . / +// | / +// | ***** | +// | ******* | +// |*********| +// |****+****| +// | C_{n-1} | +// | ******* | +// | ***** | +// | ##### | +// | ####### | +// |#########| +// +----+----+ +// C_n + +class DottedCornerFinder { + typedef mozilla::gfx::Bezier Bezier; + typedef mozilla::gfx::Float Float; + typedef mozilla::gfx::Point Point; + typedef mozilla::gfx::Size Size; + + public: + struct Result { + // Center point of dot and its radius. + Point C; + Float r; + + Result(const Point& aC, Float aR) : C(aC), r(aR) { MOZ_ASSERT(aR >= 0); } + }; + + // aBorderRadiusX + // aCornerDim.width + // |<----------------->| + // | | v + // --+-------------___---+-- + // ^ | __-- | | + // | | _- | | aR0 + // | | / aC0 +-- + // | | / | ^ + // | | | | + // aBorderRadiusY | | | __--+ + // aCornerDim.height | || _- + // | || / + // | | / + // | | | + // | | | + // | | | + // | | | + // v | aCn | + // --+----+----+ + // | | + // |<-->| + // aRn + // + // aCornerDim and (aBorderRadiusX, aBorderRadiusY) can be different when + // aBorderRadiusX is smaller than aRn*2 or + // aBorderRadiusY is smaller than aR0*2. + // + // aCornerDim.width + // |<----------------->| + // | | + // | aBorderRadiusX | + // |<--------->| | + // | | | + // -------------------+-------__--+-------+-- + // ^ ^ | _- | ^ + // | | | / | | + // | | | / | | + // | aBorderRadiusY | | | | | aR0 + // | | || | | + // | | | | | + // aCornerDim.height | v | | v + // | --+ aC0 +-- + // | | | + // | | | + // | | | + // | | | + // | | | + // v | aCn | + // -------------------+---------+---------+ + // | | + // |<------->| + // aRn + DottedCornerFinder(const Bezier& aOuterBezier, const Bezier& aInnerBezier, + mozilla::Corner aCorner, Float aBorderRadiusX, + Float aBorderRadiusY, const Point& aC0, Float aR0, + const Point& aCn, Float aRn, const Size& aCornerDim); + + bool HasMore(void) const; + Result Next(void); + + private: + static const size_t MAX_LOOP = 32; + + // Bezier control points for the outer curve, the inner curve, and a curve + // that center points of circles are on (center curve). + // + // ___---+ outer curve + // __-- | + // _- | + // / __---+ center curve + // / __-- | + // | / | + // | / __--+ inner curve + // | | _- + // | | / + // | | / + // | | | + // | | | + // | | | + // | | | + // | | | + // +----+----+ + Bezier mOuterBezier; + Bezier mInnerBezier; + Bezier mCenterBezier; + + mozilla::Corner mCorner; + + // Sign of the normal vector used in radius calculation, flipped depends on + // corner and start and end radii. + Float mNormalSign; + + // Center points and raii for start and end circles, mR0 >= mRn. + // mMaxR = max(mR0, mRn) + // + // v + // ___---+------ + // __-- #|# | mRn + // _- ##|## | + // / ##+## --- + // / mCn ^ + // | #|# + // | __--+ + // | _- + // | / + // | / + // | | + // | | + // | ##### | + // | ####### | + // |#########| + // +----+----+ + // |## mC0 ##| + // | ####### | + // | ##### | + // | | + // |<-->| + // + // mR0 + // + Point mC0; + Point mCn; + Float mR0; + Float mRn; + Float mMaxR; + + // Parameters for the center curve with perfect circle and the inner curve. + // The center curve doesn't necessarily share the origin with others. + // + // ___---+ + // __-- | + // _- | + // / __-+ | + // / __-- | + // | / | + // | / __--+-- + // | | _- | ^ + // | | / | | + // | | / | | + // | | | | | + // | | | | | mInnerHeight + // | | | | | + // | + | | | + // | | | v + // +---------+---------+ + // | | mInnerCurveOrigin + // |<------->| + // mInnerWidth + // + // ___---+ + // __-- + // _- + // / __-+ + // / __-- | + // | / | + // | / __--+ + // | | _- | + // | | / | + // | | / | + // | | | | + // | | | | + // | | | | + // | +--- | ------+ + // | | | | mCenterCurveOrigin + // + | + | + // | | + // | | + // | | + // | | + // |<---------->| + // mCenterCurveR + // + Point mCenterCurveOrigin; + Float mCenterCurveR; + Point mInnerCurveOrigin; + Float mInnerWidth; + Float mInnerHeight; + + Point mLastC; + Float mLastR; + Float mLastT; + + // Overlap between two circles. + // It uses arc length on PERFECT, SINGLE_CURVE_AND_RADIUS, and SINGLE_CURVE, + // and direct distance on OTHER. + Float mBestOverlap; + + // If one of border-widths is 0, do not calculate overlap, and draw circles + // until it reaches the other side or exceeds mMaxCount. + bool mHasZeroBorderWidth; + bool mHasMore; + + // The maximum number of filled/unfilled circles. + size_t mMaxCount; + + enum { + // radius.width + // |<----------------->| + // | | + // --+-------------___---+---- + // ^ | __-- #|# ^ + // | | _- ##|## | + // | | / ##+## | top-width + // | | / ##|## | + // | | | #|# v + // | | | __--+---- + // radius.height | || _- + // | || / + // | | / + // | | | + // | | | + // | | ##### | + // | | ####### | + // v |#########| + // --+----+----+ + // |#########| + // | ####### | + // | ##### | + // | | + // |<------->| + // left-width + + // * top-width == left-width + // * radius.width == radius.height + // * top-width < radius.width * 2 + // + // All circles has same radii and are on single perfect circle's arc. + // Overlap is known. + // + // Split the perfect circle's arc into 2n segments, each segment's length is + // top-width * (1 - overlap). Place each circle's center point C_i on each + // end of the segment, each circle's radius r_i is top-width / 2 + // + // ##### + // ####### + // perfect ######### + // circle's ___---+#### + // arc ##### __-- ## C_0 ## + // | #####_- ###|### + // | ####+#### ##|## + // | ##/C_i ## | + // | |###### | + // | | ##### | + // +->| | + // | | + // ##|## | + // ###|### | + // ####|#### | + // ####+-------------------+ + // ## C_n ## + // ####### + // ##### + PERFECT, + + // * top-width == left-width + // * 0.5 < radius.width / radius.height < 2.0 + // * top-width < min(radius.width, radius.height) * 2 + // + // All circles has same radii and are on single elliptic arc. + // Overlap is known. + // + // Split the elliptic arc into 2n segments, each segment's length is + // top-width * (1 - overlap). Place each circle's center point C_i on each + // end of the segment, each circle's radius r_i is top-width / 2 + // + // ##### + // ####### + // ##### ######### + // ####### ____----+#### + // elliptic ######__--- ## C_0 ## + // arc ##__+-### ###|### + // | / # C_i # ##|## + // +--> / ##### | + // | | + // ###|# | + // ###|### | + // ####|#### | + // ####+------------------------+ + // ## C_n ## + // ####### + // ##### + SINGLE_CURVE_AND_RADIUS, + + // * top-width != left-width + // * 0 < min(top-width, left-width) + // * 0.5 < radius.width / radius.height < 2.0 + // * max(top-width, left-width) < min(radius.width, radius.height) * 2 + // + // All circles are on single elliptic arc. + // Overlap is unknown. + // + // Place each circle's center point C_i on elliptic arc, each circle's + // radius r_i is the distance between the center point and the inner curve. + // The arc segment's length between C_i and C_{i-1} is + // (r_i + r_{i-1}) * (1 - overlap). + // + // outer curve + // / + // / + // / / center curve + // / ####### / + // /## /# + // +# / # + // /# / # + // / # C_i / # + // / # + # / + // / # / \ # / inner curve + // # / \ #/ + // # / r_i \+ + // #/ ##/ + // / ####### / + // / + SINGLE_CURVE, + + // Other cases. + // Circles are not on single elliptic arc. + // Overlap are unknown. + // + // Place tangent point innerTangent on the inner curve and find circle's + // center point C_i and radius r_i where the circle is also tangent to the + // outer curve. + // Distance between C_i and C_{i-1} is (r_i + r_{i-1}) * (1 - overlap). + // + // outer curve + // / + // / + // / + // / ####### + // /## ## + // +# # + // /# \ # + // / # \ # + // / # + # / + // / # C_i \ # / inner curve + // # \ #/ + // # r_i \+ + // ## ##/ innerTangent + // ####### / + // / + OTHER + } mType; + + size_t mI; + size_t mCount; + + // Determine mType from parameters. + void DetermineType(Float aBorderRadiusX, Float aBorderRadiusY); + + // Reset calculation. + void Reset(void); + + // Find radius for the given tangent point on the inner curve such that the + // circle is also tangent to the outer curve. + void FindPointAndRadius(Point& C, Float& r, const Point& innerTangent, + const Point& normal, Float t); + + // Find next dot. + Float FindNext(Float overlap); + + // Find mBestOverlap for parameters. + void FindBestOverlap(Float aMinR, Float aMinBorderRadius, + Float aMaxBorderRadius); + + // Fill corner with dots with given overlap, and return the number of dots + // and last two dots's overlap. + bool GetCountAndLastOverlap(Float aOverlap, size_t* aCount, + Float* aActualOverlap); +}; + +} // namespace mozilla + +#endif /* mozilla_DottedCornerFinder_h_ */ diff --git a/layout/painting/HitTestInfo.cpp b/layout/painting/HitTestInfo.cpp new file mode 100644 index 0000000000..872773c32f --- /dev/null +++ b/layout/painting/HitTestInfo.cpp @@ -0,0 +1,74 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "HitTestInfo.h" + +#include "mozilla/StaticPtr.h" +#include "mozilla/webrender/WebRenderAPI.h" +#include "nsDisplayList.h" +#include "nsIFrame.h" +#include "mozilla/layers/ScrollableLayerGuid.h" + +using namespace mozilla::gfx; + +namespace mozilla { + +static StaticAutoPtr gEmptyHitTestInfo; + +const HitTestInfo& HitTestInfo::Empty() { + if (!gEmptyHitTestInfo) { + gEmptyHitTestInfo = new HitTestInfo(); + } + + return *gEmptyHitTestInfo; +} + +void HitTestInfo::Shutdown() { gEmptyHitTestInfo = nullptr; } + +using ViewID = layers::ScrollableLayerGuid::ViewID; + +ViewID HitTestInfo::GetViewId(wr::DisplayListBuilder& aBuilder, + const ActiveScrolledRoot* aASR) const { + if (mScrollTarget) { + return *mScrollTarget; + } + + Maybe fixedTarget = aBuilder.GetContainingFixedPosScrollTarget(aASR); + + if (fixedTarget) { + return *fixedTarget; + } + + if (aASR) { + return aASR->GetViewId(); + } + + return layers::ScrollableLayerGuid::NULL_SCROLL_ID; +} + +void HitTestInfo::Initialize(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame) { + if (!aBuilder->BuildCompositorHitTestInfo()) { + return; + } + + mInfo = aFrame->GetCompositorHitTestInfo(aBuilder); + if (mInfo != gfx::CompositorHitTestInvisibleToHit) { + mArea = aFrame->GetCompositorHitTestArea(aBuilder); + InitializeScrollTarget(aBuilder); + } +} + +void HitTestInfo::InitializeScrollTarget(nsDisplayListBuilder* aBuilder) { + if (aBuilder->GetCurrentScrollbarDirection().isSome()) { + // In the case of scrollbar frames, we use the scrollbar's target + // scrollframe instead of the scrollframe with which the scrollbar actually + // moves. + MOZ_ASSERT(Info().contains(CompositorHitTestFlags::eScrollbar)); + mScrollTarget = Some(aBuilder->GetCurrentScrollbarTarget()); + } +} + +} // namespace mozilla diff --git a/layout/painting/HitTestInfo.h b/layout/painting/HitTestInfo.h new file mode 100644 index 0000000000..95438b810c --- /dev/null +++ b/layout/painting/HitTestInfo.h @@ -0,0 +1,59 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_HITTESTINFO_H +#define GFX_HITTESTINFO_H + +#include "mozilla/gfx/CompositorHitTestInfo.h" +#include "mozilla/layers/ScrollableLayerGuid.h" +#include "nsRect.h" + +class nsIFrame; + +namespace mozilla { + +class nsDisplayListBuilder; +struct ActiveScrolledRoot; + +namespace wr { +class DisplayListBuilder; +} // namespace wr + +/** + * A helper class that manages compositor hit testing information. + */ +class HitTestInfo { + public: + using CompositorHitTestInfo = gfx::CompositorHitTestInfo; + using ViewID = layers::ScrollableLayerGuid::ViewID; + + ViewID GetViewId(wr::DisplayListBuilder& aBuilder, + const ActiveScrolledRoot* aASR) const; + + void Initialize(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame); + void InitializeScrollTarget(nsDisplayListBuilder* aBuilder); + + void SetAreaAndInfo(const nsRect& aArea, const CompositorHitTestInfo& aInfo) { + mArea = aArea; + mInfo = aInfo; + } + + static void Shutdown(); + + const nsRect& Area() const { return mArea; } + const CompositorHitTestInfo& Info() const { return mInfo; } + + static const HitTestInfo& Empty(); + + private: + nsRect mArea; + CompositorHitTestInfo mInfo; + mozilla::Maybe mScrollTarget; +}; + +} // namespace mozilla + +#endif diff --git a/layout/painting/MaskLayerImageCache.cpp b/layout/painting/MaskLayerImageCache.cpp new file mode 100644 index 0000000000..2b3b63dc57 --- /dev/null +++ b/layout/painting/MaskLayerImageCache.cpp @@ -0,0 +1,64 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "MaskLayerImageCache.h" + +#include "ImageContainer.h" +#include "mozilla/layers/KnowsCompositor.h" + +using namespace mozilla::layers; + +namespace mozilla { + +MaskLayerImageCache::MaskLayerImageCache() { + MOZ_COUNT_CTOR(MaskLayerImageCache); +} +MaskLayerImageCache::~MaskLayerImageCache() { + MOZ_COUNT_DTOR(MaskLayerImageCache); +} + +void MaskLayerImageCache::Sweep() { + for (auto iter = mMaskImageContainers.Iter(); !iter.Done(); iter.Next()) { + const auto& key = iter.Get()->mKey; + if (key->HasZeroLayerCount()) { + iter.Remove(); + } + } +} + +ImageContainer* MaskLayerImageCache::FindImageFor( + const MaskLayerImageKey** aKey) { + if (MaskLayerImageEntry* entry = mMaskImageContainers.GetEntry(**aKey)) { + *aKey = entry->mKey.get(); + return entry->mContainer; + } + + return nullptr; +} + +void MaskLayerImageCache::PutImage(const MaskLayerImageKey* aKey, + ImageContainer* aContainer) { + MaskLayerImageEntry* entry = mMaskImageContainers.PutEntry(*aKey); + entry->mContainer = aContainer; +} + +MaskLayerImageCache::MaskLayerImageKey::MaskLayerImageKey() + : mRoundedClipRects(), mLayerCount(0) { + MOZ_COUNT_CTOR(MaskLayerImageKey); +} + +MaskLayerImageCache::MaskLayerImageKey::MaskLayerImageKey( + const MaskLayerImageKey& aKey) + : mRoundedClipRects(aKey.mRoundedClipRects.Clone()), + mLayerCount(aKey.mLayerCount) { + MOZ_COUNT_CTOR(MaskLayerImageKey); +} + +MaskLayerImageCache::MaskLayerImageKey::~MaskLayerImageKey() { + MOZ_COUNT_DTOR(MaskLayerImageKey); +} + +} // namespace mozilla diff --git a/layout/painting/MaskLayerImageCache.h b/layout/painting/MaskLayerImageCache.h new file mode 100644 index 0000000000..7e20f72a20 --- /dev/null +++ b/layout/painting/MaskLayerImageCache.h @@ -0,0 +1,257 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MASKLAYERIMAGECACHE_H_ +#define MASKLAYERIMAGECACHE_H_ + +#include "DisplayItemClip.h" +#include "nsPresContext.h" +#include "mozilla/gfx/Matrix.h" +#include "mozilla/UniquePtr.h" + +namespace mozilla { + +namespace layers { +class ImageContainer; +class KnowsCompositor; +} // namespace layers + +/** + * Keeps a record of image containers for mask layers, containers are mapped + * from the rounded rects used to create them. + * The cache stores MaskLayerImageEntries indexed by MaskLayerImageKeys. + * Each MaskLayerImageEntry owns a heap-allocated MaskLayerImageKey + * (heap-allocated so that a mask layer's userdata can keep a pointer to the + * key for its image, in spite of the hashtable moving its entries around). + * The key consists of the rounded rects used to create the mask, + * an nsRefPtr to the ImageContainer containing the image, and a count + * of the number of layers currently using this ImageContainer. + * When the key's layer count is zero, the cache + * may remove the entry, which deletes the key object. + */ +class MaskLayerImageCache { + typedef mozilla::layers::ImageContainer ImageContainer; + typedef mozilla::layers::KnowsCompositor KnowsCompositor; + + public: + MaskLayerImageCache(); + ~MaskLayerImageCache(); + + /** + * Representation of a rounded rectangle in device pixel coordinates, in + * contrast to DisplayItemClip::RoundedRect, which uses app units. + * In particular, our internal representation uses a gfxRect, rather than + * an nsRect, so this class is easier to use with transforms. + */ + struct PixelRoundedRect { + PixelRoundedRect() = delete; + + PixelRoundedRect(const DisplayItemClip::RoundedRect& aRRect, + nsPresContext* aPresContext) + : mRect(aPresContext->AppUnitsToGfxUnits(aRRect.mRect.x), + aPresContext->AppUnitsToGfxUnits(aRRect.mRect.y), + aPresContext->AppUnitsToGfxUnits(aRRect.mRect.width), + aPresContext->AppUnitsToGfxUnits(aRRect.mRect.height)) { + MOZ_COUNT_CTOR(PixelRoundedRect); + for (const auto corner : mozilla::AllPhysicalHalfCorners()) { + mRadii[corner] = + aPresContext->AppUnitsToGfxUnits(aRRect.mRadii[corner]); + } + } + + PixelRoundedRect(const PixelRoundedRect& aPRR) : mRect(aPRR.mRect) { + MOZ_COUNT_CTOR(PixelRoundedRect); + for (const auto corner : mozilla::AllPhysicalHalfCorners()) { + mRadii[corner] = aPRR.mRadii[corner]; + } + } + + MOZ_COUNTED_DTOR(PixelRoundedRect) + + // Applies the scale and translate components of aTransform. + // It is an error to pass a matrix which does more than just scale + // and translate. + void ScaleAndTranslate(const gfx::Matrix& aTransform) { + NS_ASSERTION(aTransform._12 == 0 && aTransform._21 == 0, + "Transform has a component other than scale and translate"); + + mRect = aTransform.TransformBounds(mRect); + + for (size_t i = 0; i < ArrayLength(mRadii); i += 2) { + mRadii[i] *= aTransform._11; + mRadii[i + 1] *= aTransform._22; + } + } + + bool operator==(const PixelRoundedRect& aOther) const { + if (!mRect.IsEqualInterior(aOther.mRect)) { + return false; + } + + for (const auto corner : mozilla::AllPhysicalHalfCorners()) { + if (mRadii[corner] != aOther.mRadii[corner]) { + return false; + } + } + return true; + } + bool operator!=(const PixelRoundedRect& aOther) const { + return !(*this == aOther); + } + + // Create a hash for this object. + PLDHashNumber Hash() const { + PLDHashNumber hash = HashBytes(&mRect.x, 4 * sizeof(gfxFloat)); + hash = AddToHash(hash, HashBytes(mRadii, 8 * sizeof(gfxFloat))); + + return hash; + } + + gfx::Rect mRect; + // Indices into mRadii are the enum HalfCorner constants in gfx/2d/Types.h + gfxFloat mRadii[8]; + }; + + struct MaskLayerImageKeyRef; + + /** + * A key to identify cached image containers. + * The const-ness of this class is with respect to its use as a key into a + * hashtable, so anything not used to create the hash is mutable. + * mLayerCount counts the number of mask layers which have a reference to + * MaskLayerImageEntry::mContainer; it is maintained by MaskLayerUserData, + * which keeps a reference to the key. There will usually be mLayerCount + 1 + * pointers to a key object (the +1 being from the hashtable entry), but this + * invariant may be temporarily broken. + */ + struct MaskLayerImageKey { + friend struct MaskLayerImageKeyRef; + + MaskLayerImageKey(); + MaskLayerImageKey(const MaskLayerImageKey& aKey); + + ~MaskLayerImageKey(); + + bool HasZeroLayerCount() const { return mLayerCount == 0; } + + PLDHashNumber Hash() const { + PLDHashNumber hash = 0; + + for (uint32_t i = 0; i < mRoundedClipRects.Length(); ++i) { + hash = AddToHash(hash, mRoundedClipRects[i].Hash()); + } + hash = AddToHash(hash, mKnowsCompositor.get()); + + return hash; + } + + bool operator==(const MaskLayerImageKey& aOther) const { + return mKnowsCompositor == aOther.mKnowsCompositor && + mRoundedClipRects == aOther.mRoundedClipRects; + } + + nsTArray mRoundedClipRects; + RefPtr 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 mKey; + RefPtr mContainer; + }; + + nsTHashtable 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 +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 mMatrices; +}; + +typedef MatrixStack MatrixStack4x4; + +} // namespace mozilla + +#endif /* MOZILLA_PAINTING_MATRIXSTACK_H */ diff --git a/layout/painting/PaintTracker.cpp b/layout/painting/PaintTracker.cpp new file mode 100644 index 0000000000..208f037e7a --- /dev/null +++ b/layout/painting/PaintTracker.cpp @@ -0,0 +1,13 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/PaintTracker.h" + +namespace mozilla { + +int PaintTracker::gPaintTracker; + +} // namespace mozilla diff --git a/layout/painting/PaintTracker.h b/layout/painting/PaintTracker.h new file mode 100644 index 0000000000..1f5554f983 --- /dev/null +++ b/layout/painting/PaintTracker.h @@ -0,0 +1,31 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_PaintTracker_h +#define mozilla_PaintTracker_h + +#include "mozilla/Attributes.h" +#include "nsDebug.h" + +namespace mozilla { + +class MOZ_STACK_CLASS PaintTracker { + public: + PaintTracker() { ++gPaintTracker; } + ~PaintTracker() { + NS_ASSERTION(gPaintTracker > 0, "Mismatched constructor/destructor"); + --gPaintTracker; + } + + static bool IsPainting() { return !!gPaintTracker; } + + private: + static int gPaintTracker; +}; + +} // namespace mozilla + +#endif // mozilla_PaintTracker_h diff --git a/layout/painting/RetainedDisplayListBuilder.cpp b/layout/painting/RetainedDisplayListBuilder.cpp new file mode 100644 index 0000000000..3e3b8e9a94 --- /dev/null +++ b/layout/painting/RetainedDisplayListBuilder.cpp @@ -0,0 +1,1732 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include "RetainedDisplayListBuilder.h" + +#include "mozilla/Attributes.h" +#include "mozilla/StaticPrefs_layout.h" +#include "nsIFrame.h" +#include "nsIFrameInlines.h" +#include "nsIScrollableFrame.h" +#include "nsPlaceholderFrame.h" +#include "nsSubDocumentFrame.h" +#include "nsViewManager.h" +#include "nsCanvasFrame.h" +#include "mozilla/AutoRestore.h" +#include "mozilla/DisplayPortUtils.h" +#include "mozilla/PresShell.h" +#include "mozilla/ProfilerLabels.h" + +/** + * Code for doing display list building for a modified subset of the window, + * and then merging it into the existing display list (for the full window). + * + * The approach primarily hinges on the observation that the 'true' ordering + * of display items is represented by a DAG (only items that intersect in 2d + * space have a defined ordering). Our display list is just one of a many + * possible linear representations of this ordering. + * + * Each time a frame changes (gets a new ComputedStyle, or has a size/position + * change), we schedule a paint (as we do currently), but also reord the frame + * that changed. + * + * When the next paint occurs we union the overflow areas (in screen space) of + * the changed frames, and compute a rect/region that contains all changed + * items. We then build a display list just for this subset of the screen and + * merge it into the display list from last paint. + * + * Any items that exist in one list and not the other must not have a defined + * ordering in the DAG, since they need to intersect to have an ordering and + * we would have built both in the new list if they intersected. Given that, we + * can align items that appear in both lists, and any items that appear between + * matched items can be inserted into the merged list in any order. + * + * Frames that are a stacking context, containing blocks for position:fixed + * descendants, and don't have any continuations (see + * CanStoreDisplayListBuildingRect) trigger recursion into the algorithm with + * separate retaining decisions made. + * + * RDL defines the concept of an AnimatedGeometryRoot (AGR), the nearest + * ancestor frame which can be moved asynchronously on the compositor thread. + * These are currently nsDisplayItems which return true from CanMoveAsync + * (animated nsDisplayTransform and nsDisplayStickyPosition) and + * ActiveScrolledRoots. + * + * For each context that we run the retaining algorithm, there can only be + * mutations to one AnimatedGeometryRoot. This is because we are unable to + * reason about intersections of items that might then move relative to each + * other without RDL running again. If there are mutations to multiple + * AnimatedGeometryRoots, then we bail out and rebuild all the items in the + * context. + * + * Otherwise, when mutations are restricted to a single AGR, we pre-process the + * old display list and mark the frames for all existing (unmodified!) items + * that belong to a different AGR and ensure that we rebuild those items for + * correct sorting with the modified ones. + */ + +namespace mozilla { + +RetainedDisplayListData::RetainedDisplayListData() + : mModifiedFrameLimit( + StaticPrefs::layout_display_list_rebuild_frame_limit()) {} + +void RetainedDisplayListData::AddModifiedFrame(nsIFrame* aFrame) { + MOZ_ASSERT(!aFrame->IsFrameModified()); + Flags(aFrame) += RetainedDisplayListData::FrameFlag::Modified; + aFrame->SetFrameIsModified(true); + mModifiedFrameCount++; +} + +static void MarkFramesWithItemsAndImagesModified(nsDisplayList* aList) { + for (nsDisplayItem* i : *aList) { + if (!i->HasDeletedFrame() && i->CanBeReused() && + !i->Frame()->IsFrameModified()) { + // If we have existing cached geometry for this item, then check that for + // whether we need to invalidate for a sync decode. If we don't, then + // use the item's flags. + // XXX: handle webrender case by looking up retained data for the item + // and checking InvalidateForSyncDecodeImages + bool invalidate = false; + if (!(i->GetFlags() & TYPE_RENDERS_NO_IMAGES)) { + invalidate = true; + } + + if (invalidate) { + DL_LOGV("RDL - Invalidating item %p (%s)", i, i->Name()); + i->FrameForInvalidation()->MarkNeedsDisplayItemRebuild(); + if (i->GetDependentFrame()) { + i->GetDependentFrame()->MarkNeedsDisplayItemRebuild(); + } + } + } + if (i->GetChildren()) { + MarkFramesWithItemsAndImagesModified(i->GetChildren()); + } + } +} + +static nsIFrame* SelectAGRForFrame(nsIFrame* aFrame, nsIFrame* aParentAGR) { + if (!aFrame->IsStackingContext() || !aFrame->IsFixedPosContainingBlock()) { + return aParentAGR; + } + + if (!aFrame->HasOverrideDirtyRegion()) { + return nullptr; + } + + nsDisplayListBuilder::DisplayListBuildingData* data = + aFrame->GetProperty(nsDisplayListBuilder::DisplayListBuildingRect()); + + return data && data->mModifiedAGR ? data->mModifiedAGR : nullptr; +} + +void RetainedDisplayListBuilder::AddSizeOfIncludingThis( + nsWindowSizes& aSizes) const { + aSizes.mLayoutRetainedDisplayListSize += aSizes.mState.mMallocSizeOf(this); + mBuilder.AddSizeOfExcludingThis(aSizes); + mList.AddSizeOfExcludingThis(aSizes); +} + +bool AnyContentAncestorModified(nsIFrame* aFrame, nsIFrame* aStopAtFrame) { + nsIFrame* f = aFrame; + while (f) { + if (f->IsFrameModified()) { + return true; + } + + if (aStopAtFrame && f == aStopAtFrame) { + break; + } + + f = nsLayoutUtils::GetDisplayListParent(f); + } + + return false; +} + +// Removes any display items that belonged to a frame that was deleted, +// and mark frames that belong to a different AGR so that get their +// items built again. +// TODO: We currently descend into all children even if we don't have an AGR +// to mark, as child stacking contexts might. It would be nice if we could +// jump into those immediately rather than walking the entire thing. +bool RetainedDisplayListBuilder::PreProcessDisplayList( + RetainedDisplayList* aList, nsIFrame* aAGR, PartialUpdateResult& aUpdated, + nsIFrame* aAsyncAncestor, const ActiveScrolledRoot* aAsyncAncestorASR, + nsIFrame* aOuterFrame, uint32_t aCallerKey, uint32_t aNestingDepth, + bool aKeepLinked) { + // The DAG merging algorithm does not have strong mechanisms in place to keep + // the complexity of the resulting DAG under control. In some cases we can + // build up edges very quickly. Detect those cases and force a full display + // list build if we hit them. + static const uint32_t kMaxEdgeRatio = 5; + const bool initializeDAG = !aList->mDAG.Length(); + if (!aKeepLinked && !initializeDAG && + aList->mDAG.mDirectPredecessorList.Length() > + (aList->mDAG.mNodesInfo.Length() * kMaxEdgeRatio)) { + return false; + } + + // If we had aKeepLinked=true for this list on the previous paint, then + // mOldItems will already be initialized as it won't have been consumed during + // a merge. + const bool initializeOldItems = aList->mOldItems.IsEmpty(); + if (initializeOldItems) { + aList->mOldItems.SetCapacity(aList->Length()); + } else { + MOZ_RELEASE_ASSERT(!initializeDAG); + } + + MOZ_RELEASE_ASSERT( + initializeDAG || + aList->mDAG.Length() == + (initializeOldItems ? aList->Length() : aList->mOldItems.Length())); + + nsDisplayList out(Builder()); + + size_t i = 0; + while (nsDisplayItem* item = aList->RemoveBottom()) { +#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + item->SetMergedPreProcessed(false, true); +#endif + + // If we have a previously initialized old items list, then it can differ + // from the current list due to items removed for having a deleted frame. + // We can't easily remove these, since the DAG has entries for those indices + // and it's hard to rewrite in-place. + // Skip over entries with no current item to keep the iterations in sync. + if (!initializeOldItems) { + while (!aList->mOldItems[i].mItem) { + i++; + } + } + + if (initializeDAG) { + if (i == 0) { + aList->mDAG.AddNode(Span()); + } else { + MergedListIndex previous(i - 1); + aList->mDAG.AddNode(Span(&previous, 1)); + } + } + + if (!item->CanBeReused() || item->HasDeletedFrame() || + AnyContentAncestorModified(item->FrameForInvalidation(), aOuterFrame)) { + if (initializeOldItems) { + aList->mOldItems.AppendElement(OldItemInfo(nullptr)); + } else { + MOZ_RELEASE_ASSERT(aList->mOldItems[i].mItem == item); + aList->mOldItems[i].mItem = nullptr; + } + + if (item->IsGlassItem() && item == mBuilder.GetGlassDisplayItem()) { + mBuilder.ClearGlassDisplayItem(); + } + + item->Destroy(&mBuilder); + Metrics()->mRemovedItems++; + + i++; + aUpdated = PartialUpdateResult::Updated; + continue; + } + + if (initializeOldItems) { + aList->mOldItems.AppendElement(OldItemInfo(item)); + } + + // If we're not going to keep the list linked, then this old item entry + // is the only pointer to the item. Let it know that it now strongly + // owns the item, so it can destroy it if it goes away. + aList->mOldItems[i].mOwnsItem = !aKeepLinked; + + item->SetOldListIndex(aList, OldListIndex(i), aCallerKey, aNestingDepth); + + nsIFrame* f = item->Frame(); + + if (item->GetChildren()) { + // If children inside this list were invalid, then we'd have walked the + // ancestors and set ForceDescendIntoVisible on the current frame. If an + // ancestor is modified, then we'll throw this away entirely. Either way, + // we won't need to run merging on this sublist, and we can keep the items + // linked into their display list. + // The caret can move without invalidating, but we always set the force + // descend into frame state bit on that frame, so check for that too. + // TODO: AGR marking below can call MarkFrameForDisplayIfVisible and make + // us think future siblings need to be merged, even though we don't really + // need to. + bool keepLinked = aKeepLinked; + nsIFrame* invalid = item->FrameForInvalidation(); + if (!invalid->ForceDescendIntoIfVisible() && + !invalid->HasAnyStateBits(NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO)) { + keepLinked = true; + } + + // If this item's frame is an AGR (can be moved asynchronously by the + // compositor), then use that frame for descendants. Also pass the ASR + // for that item, so that descendants can compare to see if any new + // ASRs have been pushed since. + nsIFrame* asyncAncestor = aAsyncAncestor; + const ActiveScrolledRoot* asyncAncestorASR = aAsyncAncestorASR; + if (item->CanMoveAsync()) { + asyncAncestor = item->Frame(); + asyncAncestorASR = item->GetActiveScrolledRoot(); + } + + if (!PreProcessDisplayList( + item->GetChildren(), SelectAGRForFrame(f, aAGR), aUpdated, + asyncAncestor, asyncAncestorASR, item->Frame(), + item->GetPerFrameKey(), aNestingDepth + 1, keepLinked)) { + MOZ_RELEASE_ASSERT( + !aKeepLinked, + "Can't early return since we need to move the out list back"); + return false; + } + } + + // TODO: We should be able to check the clipped bounds relative + // to the common AGR (of both the existing item and the invalidated + // frame) and determine if they can ever intersect. + // TODO: We only really need to build the ancestor container item that is a + // sibling of the changed thing to get correct ordering. The changed content + // is a frame though, and it's hard to map that to container items in this + // list. + // If an ancestor display item is an AGR, and our ASR matches the ASR + // of that item, then there can't have been any new ASRs pushed since that + // item, so that item is our AGR. Otherwise, our AGR is our ASR. + // TODO: If aAsyncAncestorASR is non-null, then item->GetActiveScrolledRoot + // should be the same or a descendant and also non-null. Unfortunately an + // RDL bug means this can be wrong for sticky items after a partial update, + // so we have to work around it. Bug 1730749 and bug 1730826 should resolve + // this. + nsIFrame* agrFrame = nullptr; + if (aAsyncAncestorASR == item->GetActiveScrolledRoot() || + !item->GetActiveScrolledRoot()) { + agrFrame = aAsyncAncestor; + } else { + agrFrame = + item->GetActiveScrolledRoot()->mScrollableFrame->GetScrolledFrame(); + } + + if (aAGR && agrFrame != aAGR) { + mBuilder.MarkFrameForDisplayIfVisible(f, RootReferenceFrame()); + } + + // If we're going to keep this linked list and not merge it, then mark the + // item as used and put it back into the list. + if (aKeepLinked) { + item->SetReused(true); + if (item->GetChildren()) { + item->UpdateBounds(Builder()); + } + if (item->GetType() == DisplayItemType::TYPE_SUBDOCUMENT) { + IncrementSubDocPresShellPaintCount(item); + } + out.AppendToTop(item); + } + i++; + } + + MOZ_RELEASE_ASSERT(aList->mOldItems.Length() == aList->mDAG.Length()); + + if (aKeepLinked) { + aList->AppendToTop(&out); + } + + return true; +} + +void IncrementPresShellPaintCount(nsDisplayListBuilder* aBuilder, + nsDisplayItem* aItem) { + MOZ_ASSERT(aItem->GetType() == DisplayItemType::TYPE_SUBDOCUMENT); + + nsSubDocumentFrame* subDocFrame = + static_cast(aItem)->SubDocumentFrame(); + MOZ_ASSERT(subDocFrame); + + PresShell* presShell = subDocFrame->GetSubdocumentPresShellForPainting(0); + MOZ_ASSERT(presShell); + + aBuilder->IncrementPresShellPaintCount(presShell); +} + +void RetainedDisplayListBuilder::IncrementSubDocPresShellPaintCount( + nsDisplayItem* aItem) { + IncrementPresShellPaintCount(&mBuilder, aItem); +} + +static Maybe SelectContainerASR( + const DisplayItemClipChain* aClipChain, const ActiveScrolledRoot* aItemASR, + Maybe& 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& aContainerASR) { + if (!aContainerASR) { + return; + } + + nsDisplayWrapList* wrapList = aItem->AsDisplayWrapList(); + if (!wrapList) { + aItem->SetActiveScrolledRoot(*aContainerASR); + return; + } + + wrapList->SetActiveScrolledRoot(ActiveScrolledRoot::PickAncestor( + wrapList->GetFrameActiveScrolledRoot(), *aContainerASR)); +} + +static void CopyASR(nsDisplayItem* aOld, nsDisplayItem* aNew) { + aNew->SetActiveScrolledRoot(aOld->GetActiveScrolledRoot()); +} + +OldItemInfo::OldItemInfo(nsDisplayItem* aItem) + : mItem(aItem), mUsed(false), mDiscarded(false), mOwnsItem(false) { + if (mItem) { + // Clear cached modified frame state when adding an item to the old list. + mItem->SetModifiedFrame(false); + } +} + +void OldItemInfo::AddedMatchToMergedList(RetainedDisplayListBuilder* aBuilder, + MergedListIndex aIndex) { + AddedToMergedList(aIndex); +} + +void OldItemInfo::Discard(RetainedDisplayListBuilder* aBuilder, + nsTArray&& 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*>( + &aOldList.mDAG))), + mMergedItems(aBuilder->Builder()), + mOuterItem(aOuterItem), + mResultIsModified(false) { + mMergedDAG.EnsureCapacityFor(mOldDAG); + MOZ_RELEASE_ASSERT(mOldItems.Length() == mOldDAG.Length()); + } + + Maybe ProcessItemFromNewList( + nsDisplayItem* aNewItem, const Maybe& 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 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(), + aPreviousItem)); + } + + void MergeChildLists(nsDisplayItem* aNewItem, nsDisplayItem* aOldItem, + nsDisplayItem* aOutItem) { + if (!aOutItem->GetChildren()) { + return; + } + + Maybe containerASRForChildren; + nsDisplayList empty(mBuilder->Builder()); + const bool modified = mBuilder->MergeDisplayLists( + aNewItem ? aNewItem->GetChildren() : &empty, aOldItem->GetChildren(), + aOutItem->GetChildren(), containerASRForChildren, aOutItem); + if (modified) { + aOutItem->InvalidateCachedChildInfo(mBuilder->Builder()); + UpdateASR(aOutItem, containerASRForChildren); + mResultIsModified = true; + } else if (aOutItem == aNewItem) { + // If nothing changed, but we copied the contents across to + // the new item, then also copy the ASR data. + CopyASR(aOldItem, aNewItem); + } + // Ideally we'd only UpdateBounds if something changed, but + // nsDisplayWrapList also uses this to update the clip chain for the + // current ASR, which gets reset during RestoreState(), so we always need + // to run it again. + aOutItem->UpdateBounds(mBuilder->Builder()); + } + + bool ShouldUseNewItem(nsDisplayItem* aNewItem) { + // Generally we want to use the old item when the frame isn't marked as + // modified so that any cached information on the item (or referencing the + // item) gets retained. Quite a few FrameLayerBuilder performance + // improvements benefit by this. Sometimes, however, we can end up where the + // new item paints something different from the old item, even though we + // haven't modified the frame, and it's hard to fix. In these cases we just + // always use the new item to be safe. + DisplayItemType type = aNewItem->GetType(); + if (type == DisplayItemType::TYPE_CANVAS_BACKGROUND_COLOR || + type == DisplayItemType::TYPE_SOLID_COLOR) { + // The canvas background color item can paint the color from another + // frame, and even though we schedule a paint, we don't mark the canvas + // frame as invalid. + return true; + } + + if (type == DisplayItemType::TYPE_TABLE_BORDER_COLLAPSE) { + // We intentionally don't mark the root table frame as modified when a + // subframe changes, even though the border collapse item for the root + // frame is what paints the changed border. Marking the root frame as + // modified would rebuild display items for the whole table area, and we + // don't want that. + return true; + } + + if (type == DisplayItemType::TYPE_TEXT_OVERFLOW) { + // Text overflow marker items are created with the wrapping block as their + // frame, and have an index value to note which line they are created for. + // Their rendering can change if the items on that line change, which may + // not mark the block as modified. We rebuild them if we build any item on + // the line, so we should always get new items if they might have changed + // rendering, and it's easier to just use the new items rather than + // computing if we actually need them. + return true; + } + + if (type == DisplayItemType::TYPE_SUBDOCUMENT || + type == DisplayItemType::TYPE_STICKY_POSITION) { + // nsDisplaySubDocument::mShouldFlatten can change without an invalidation + // (and is the reason we unconditionally build the subdocument item), so + // always use the new one to make sure we get the right value. + // Same for |nsDisplayStickyPosition::mShouldFlatten|. + return true; + } + + if (type == DisplayItemType::TYPE_CARET) { + // The caret can change position while still being owned by the same frame + // and we don't invalidate in that case. Use the new version since the + // changed bounds are needed for DLBI. + return true; + } + + if (type == DisplayItemType::TYPE_MASK || + type == DisplayItemType::TYPE_FILTER || + type == DisplayItemType::TYPE_SVG_WRAPPER) { + // SVG items have some invalidation issues, see bugs 1494110 and 1494663. + return true; + } + + if (type == DisplayItemType::TYPE_TRANSFORM) { + // Prerendering of transforms can change without frame invalidation. + return true; + } + + return false; + } + + RetainedDisplayList Finalize() { + for (size_t i = 0; i < mOldDAG.Length(); i++) { + if (mOldItems[i].IsUsed()) { + continue; + } + + AutoTArray directPredecessors = + ResolveNodeIndexesOldToMerged( + mOldDAG.GetDirectPredecessors(OldListIndex(i))); + ProcessOldNode(OldListIndex(i), std::move(directPredecessors)); + } + + RetainedDisplayList result(mBuilder->Builder()); + result.AppendToTop(&mMergedItems); + result.mDAG = std::move(mMergedDAG); + MOZ_RELEASE_ASSERT(result.mDAG.Length() == result.Length()); + return result; + } + + bool HasMatchingItemInOldList(nsDisplayItem* aItem, OldListIndex* aOutIndex) { + // Look for an item that matches aItem's frame and per-frame-key, but isn't + // the same item. + uint32_t outerKey = mOuterItem ? mOuterItem->GetPerFrameKey() : 0; + nsIFrame* frame = aItem->Frame(); + for (nsDisplayItem* i : frame->DisplayItems()) { + if (i != aItem && i->Frame() == frame && + i->GetPerFrameKey() == aItem->GetPerFrameKey()) { + if (i->GetOldListIndex(mOldList, outerKey, aOutIndex)) { + return true; + } + } + } + return false; + } + +#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + bool HasModifiedFrame(nsDisplayItem* aItem) { + nsIFrame* stopFrame = mOuterItem ? mOuterItem->Frame() : nullptr; + return AnyContentAncestorModified(aItem->FrameForInvalidation(), stopFrame); + } +#endif + + void UpdateContainerASR(nsDisplayItem* aItem) { + mContainerASR = SelectContainerASR( + aItem->GetClipChain(), aItem->GetActiveScrolledRoot(), mContainerASR); + } + + MergedListIndex AddNewNode( + nsDisplayItem* aItem, const Maybe& aOldIndex, + Span aDirectPredecessors, + const Maybe& aExtraDirectPredecessor) { + UpdateContainerASR(aItem); + aItem->NotifyUsed(mBuilder->Builder()); + +#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + for (nsDisplayItem* i : aItem->Frame()->DisplayItems()) { + if (i->Frame() == aItem->Frame() && + i->GetPerFrameKey() == aItem->GetPerFrameKey()) { + MOZ_DIAGNOSTIC_ASSERT(!i->IsMergedItem()); + } + } + + aItem->SetMergedPreProcessed(true, false); +#endif + + mMergedItems.AppendToTop(aItem); + mBuilder->Metrics()->mTotalItems++; + + MergedListIndex newIndex = + mMergedDAG.AddNode(aDirectPredecessors, aExtraDirectPredecessor); + return newIndex; + } + + void ProcessOldNode(OldListIndex aNode, + nsTArray&& 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 aPredecessors) + : mNode(aNode), + mDirectPredecessors(aPredecessors), + mCurrentPredecessorIndex(0) {} + + bool IsFinished() { + return mCurrentPredecessorIndex == mDirectPredecessors.Length(); + } + + OldListIndex GetAndIncrementCurrentPredecessor() { + return mDirectPredecessors[mCurrentPredecessorIndex++]; + } + + OldListIndex mNode; + Span mDirectPredecessors; + size_t mCurrentPredecessorIndex; + }; + + AutoTArray ProcessPredecessorsOfOldNode( + OldListIndex aNode) { + AutoTArray 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 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 ResolveNodeIndexesOldToMerged( + Span aDirectPredecessors) { + AutoTArray 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 mContainerASR; + nsTArray mOldItems; + DirectedAcyclicGraph 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 mMergedDAG; + nsDisplayItem* mOuterItem; + bool mResultIsModified; +}; + +#ifdef DEBUG +void VerifyNotModified(nsDisplayList* aList) { + for (nsDisplayItem* item : *aList) { + MOZ_ASSERT(!AnyContentAncestorModified(item->FrameForInvalidation())); + + if (item->GetChildren()) { + VerifyNotModified(item->GetChildren()); + } + } +} +#endif + +/** + * Takes two display lists and merges them into an output list. + * + * Display lists wthout an explicit DAG are interpreted as linear DAGs (with a + * maximum of one direct predecessor and one direct successor per node). We add + * the two DAGs together, and then output the topological sorted ordering as the + * final display list. + * + * Once we've merged a list, we then retain the DAG (as part of the + * RetainedDisplayList object) to use for future merges. + */ +bool RetainedDisplayListBuilder::MergeDisplayLists( + nsDisplayList* aNewList, RetainedDisplayList* aOldList, + RetainedDisplayList* aOutList, + mozilla::Maybe& 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 previousItemIndex; + for (nsDisplayItem* item : aNewList->TakeItems()) { + Metrics()->mNewItems++; + previousItemIndex = merge.ProcessItemFromNewList(item, previousItemIndex); + } + + *aOutList = merge.Finalize(); + aOutContainerASR = merge.mContainerASR; + return merge.mResultIsModified; +} + +void RetainedDisplayListBuilder::GetModifiedAndFramesWithProps( + nsTArray* aOutModifiedFrames, + nsTArray* aOutFramesWithProps) { + for (auto it = Data()->ConstIterator(); !it.Done(); it.Next()) { + nsIFrame* frame = it.Key(); + const RetainedDisplayListData::FrameFlags& flags = it.Data(); + + if (flags.contains(RetainedDisplayListData::FrameFlag::Modified)) { + aOutModifiedFrames->AppendElement(frame); + } + + if (flags.contains(RetainedDisplayListData::FrameFlag::HasProps)) { + aOutFramesWithProps->AppendElement(frame); + } + + if (flags.contains(RetainedDisplayListData::FrameFlag::HadWillChange)) { + Builder()->RemoveFromWillChangeBudgets(frame); + } + } + + Data()->Clear(); +} + +// ComputeRebuildRegion debugging +// #define CRR_DEBUG 1 +#if CRR_DEBUG +# define CRR_LOG(...) printf_stderr(__VA_ARGS__) +#else +# define CRR_LOG(...) +#endif + +static nsDisplayItem* GetFirstDisplayItemWithChildren(nsIFrame* aFrame) { + for (nsDisplayItem* i : aFrame->DisplayItems()) { + if (i->HasDeletedFrame() || i->Frame() != aFrame) { + // The main frame for the display item has been deleted or the display + // item belongs to another frame. + continue; + } + + if (i->HasChildren()) { + return static_cast(i); + } + } + return nullptr; +} + +static bool IsInPreserve3DContext(const nsIFrame* aFrame) { + return aFrame->Extend3DContext() || + aFrame->Combines3DTransformWithAncestors(); +} + +// Returns true if |aFrame| can store a display list building rect. +// These limitations are necessary to guarantee that +// 1) Just enough items are rebuilt to properly update display list +// 2) Modified frames will be visited during a partial display list build. +static bool CanStoreDisplayListBuildingRect(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame) { + return aFrame != aBuilder->RootReferenceFrame() && + aFrame->IsStackingContext() && aFrame->IsFixedPosContainingBlock() && + // Split frames might have placeholders for modified frames in their + // unmodified continuation frame. + !aFrame->GetPrevContinuation() && !aFrame->GetNextContinuation(); +} + +static bool ProcessFrameInternal(nsIFrame* aFrame, + nsDisplayListBuilder* aBuilder, + nsIFrame** aAGR, nsRect& aOverflow, + const nsIFrame* aStopAtFrame, + nsTArray& aOutFramesWithProps, + const bool aStopAtStackingContext) { + nsIFrame* currentFrame = aFrame; + + while (currentFrame != aStopAtFrame) { + CRR_LOG("currentFrame: %p (placeholder=%d), aOverflow: %d %d %d %d\n", + currentFrame, !aStopAtStackingContext, aOverflow.x, aOverflow.y, + aOverflow.width, aOverflow.height); + + // If the current frame is an OOF frame, DisplayListBuildingData needs to be + // set on all the ancestor stacking contexts of the placeholder frame, up + // to the containing block of the OOF frame. This is done to ensure that the + // content that might be behind the OOF frame is built for merging. + nsIFrame* placeholder = currentFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW) + ? currentFrame->GetPlaceholderFrame() + : nullptr; + + if (placeholder) { + nsRect placeholderOverflow = aOverflow; + auto rv = nsLayoutUtils::TransformRect(currentFrame, placeholder, + placeholderOverflow); + if (rv != nsLayoutUtils::TRANSFORM_SUCCEEDED) { + placeholderOverflow = nsRect(); + } + + CRR_LOG("Processing placeholder %p for OOF frame %p\n", placeholder, + currentFrame); + + CRR_LOG("OOF frame draw area: %d %d %d %d\n", placeholderOverflow.x, + placeholderOverflow.y, placeholderOverflow.width, + placeholderOverflow.height); + + // Tracking AGRs for the placeholder processing is not necessary, as the + // goal is to only modify the DisplayListBuildingData rect. + nsIFrame* dummyAGR = nullptr; + + // Find a common ancestor frame to handle frame continuations. + // TODO: It might be possible to write a more specific and efficient + // function for this. + const nsIFrame* ancestor = nsLayoutUtils::FindNearestCommonAncestorFrame( + currentFrame->GetParent(), placeholder->GetParent()); + + if (!ProcessFrameInternal(placeholder, aBuilder, &dummyAGR, + placeholderOverflow, ancestor, + aOutFramesWithProps, false)) { + return false; + } + } + + // Convert 'aOverflow' into the coordinate space of the nearest stacking + // context or display port ancestor and update 'currentFrame' to point to + // that frame. + aOverflow = nsLayoutUtils::TransformFrameRectToAncestor( + currentFrame, aOverflow, aStopAtFrame, nullptr, nullptr, + /* aStopAtStackingContextAndDisplayPortAndOOFFrame = */ true, + ¤tFrame); + if (IsInPreserve3DContext(currentFrame)) { + return false; + } + + MOZ_ASSERT(currentFrame); + + // Check whether the current frame is a scrollable frame with display port. + nsRect displayPort; + nsIScrollableFrame* sf = do_QueryFrame(currentFrame); + nsIContent* content = sf ? currentFrame->GetContent() : nullptr; + + if (content && DisplayPortUtils::GetDisplayPort(content, &displayPort)) { + CRR_LOG("Frame belongs to displayport frame %p\n", currentFrame); + + // Get overflow relative to the scrollport (from the scrollframe) + nsRect r = aOverflow - sf->GetScrollPortRect().TopLeft(); + r.IntersectRect(r, displayPort); + if (!r.IsEmpty()) { + nsRect* rect = currentFrame->GetProperty( + nsDisplayListBuilder::DisplayListBuildingDisplayPortRect()); + if (!rect) { + rect = new nsRect(); + currentFrame->SetProperty( + nsDisplayListBuilder::DisplayListBuildingDisplayPortRect(), rect); + currentFrame->SetHasOverrideDirtyRegion(true); + aOutFramesWithProps.AppendElement(currentFrame); + } + rect->UnionRect(*rect, r); + CRR_LOG("Adding area to displayport draw area: %d %d %d %d\n", r.x, r.y, + r.width, r.height); + + // TODO: Can we just use MarkFrameForDisplayIfVisible, plus + // MarkFramesForDifferentAGR to ensure that this displayport, plus any + // items that move relative to it get rebuilt, and then not contribute + // to the root dirty area? + aOverflow = sf->GetScrollPortRect(); + } else { + // Don't contribute to the root dirty area at all. + aOverflow.SetEmpty(); + } + } else { + aOverflow.IntersectRect(aOverflow, + currentFrame->InkOverflowRectRelativeToSelf()); + } + + if (aOverflow.IsEmpty()) { + break; + } + + if (CanStoreDisplayListBuildingRect(aBuilder, currentFrame)) { + CRR_LOG("Frame belongs to stacking context frame %p\n", currentFrame); + // If we found an intermediate stacking context with an existing display + // item then we can store the dirty rect there and stop. If we couldn't + // find one then we need to keep bubbling up to the next stacking context. + nsDisplayItem* wrapperItem = + GetFirstDisplayItemWithChildren(currentFrame); + if (!wrapperItem) { + continue; + } + + // Store the stacking context relative dirty area such + // that display list building will pick it up when it + // gets to it. + nsDisplayListBuilder::DisplayListBuildingData* data = + currentFrame->GetProperty( + nsDisplayListBuilder::DisplayListBuildingRect()); + if (!data) { + data = new nsDisplayListBuilder::DisplayListBuildingData(); + currentFrame->SetProperty( + nsDisplayListBuilder::DisplayListBuildingRect(), data); + currentFrame->SetHasOverrideDirtyRegion(true); + aOutFramesWithProps.AppendElement(currentFrame); + } + CRR_LOG("Adding area to stacking context draw area: %d %d %d %d\n", + aOverflow.x, aOverflow.y, aOverflow.width, aOverflow.height); + data->mDirtyRect.UnionRect(data->mDirtyRect, aOverflow); + + if (!aStopAtStackingContext) { + // Continue ascending the frame tree until we reach aStopAtFrame. + continue; + } + + // Grab the visible (display list building) rect for children of this + // wrapper item and convert into into coordinate relative to the current + // frame. + nsRect previousVisible = wrapperItem->GetBuildingRectForChildren(); + if (wrapperItem->ReferenceFrameForChildren() != wrapperItem->Frame()) { + previousVisible -= wrapperItem->ToReferenceFrame(); + } + + if (!previousVisible.Contains(aOverflow)) { + // If the overflow area of the changed frame isn't contained within the + // old item, then we might change the size of the item and need to + // update its sorting accordingly. Keep propagating the overflow area up + // so that we build intersecting items for sorting. + continue; + } + + if (!data->mModifiedAGR) { + data->mModifiedAGR = *aAGR; + } else if (data->mModifiedAGR != *aAGR) { + data->mDirtyRect = currentFrame->InkOverflowRectRelativeToSelf(); + CRR_LOG( + "Found multiple modified AGRs within this stacking context, " + "giving up\n"); + } + + // Don't contribute to the root dirty area at all. + aOverflow.SetEmpty(); + *aAGR = nullptr; + + break; + } + } + return true; +} + +bool RetainedDisplayListBuilder::ProcessFrame( + nsIFrame* aFrame, nsDisplayListBuilder* aBuilder, nsIFrame* aStopAtFrame, + nsTArray& aOutFramesWithProps, const bool aStopAtStackingContext, + nsRect* aOutDirty, nsIFrame** aOutModifiedAGR) { + if (aFrame->HasOverrideDirtyRegion()) { + aOutFramesWithProps.AppendElement(aFrame); + } + + if (aFrame->HasAnyStateBits(NS_FRAME_IN_POPUP)) { + return true; + } + + // TODO: There is almost certainly a faster way of doing this, probably can be + // combined with the ancestor walk for TransformFrameRectToAncestor. + nsIFrame* agrFrame = aBuilder->FindAnimatedGeometryRootFrameFor(aFrame); + + CRR_LOG("Processing frame %p with agr %p\n", aFrame, agr->mFrame); + + // Convert the frame's overflow rect into the coordinate space + // of the nearest stacking context that has an existing display item. + // We store that as a dirty rect on that stacking context so that we build + // all items that intersect the changed frame within the stacking context, + // and then we use MarkFrameForDisplayIfVisible to make sure the stacking + // context itself gets built. We don't need to build items that intersect + // outside of the stacking context, since we know the stacking context item + // exists in the old list, so we can trivially merge without needing other + // items. + nsRect overflow = aFrame->InkOverflowRectRelativeToSelf(); + + // If the modified frame is also a caret frame, include the caret area. + // This is needed because some frames (for example text frames without text) + // might have an empty overflow rect. + if (aFrame == aBuilder->GetCaretFrame()) { + overflow.UnionRect(overflow, aBuilder->GetCaretRect()); + } + + if (!ProcessFrameInternal(aFrame, aBuilder, &agrFrame, overflow, aStopAtFrame, + aOutFramesWithProps, aStopAtStackingContext)) { + return false; + } + + if (!overflow.IsEmpty()) { + aOutDirty->UnionRect(*aOutDirty, overflow); + CRR_LOG("Adding area to root draw area: %d %d %d %d\n", overflow.x, + overflow.y, overflow.width, overflow.height); + + // If we get changed frames from multiple AGRS, then just give up as it gets + // really complex to track which items would need to be marked in + // MarkFramesForDifferentAGR. + if (!*aOutModifiedAGR) { + CRR_LOG("Setting %p as root stacking context AGR\n", agrFrame); + *aOutModifiedAGR = agrFrame; + } else if (agrFrame && *aOutModifiedAGR != agrFrame) { + CRR_LOG("Found multiple AGRs in root stacking context, giving up\n"); + return false; + } + } + return true; +} + +static void AddFramesForContainingBlock(nsIFrame* aBlock, + const nsFrameList& aFrames, + nsTArray& 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& aExtraFrames) { + for (nsIFrame* f = aFrame; f; f = nsLayoutUtils::GetDisplayListParent(f)) { + if (f->ForceDescendIntoIfVisible()) { + return; + } + f->SetForceDescendIntoIfVisible(true); + CRR_LOG("Considering OOFs for %p\n", f); + + AddFramesForContainingBlock(f, f->GetChildList(FrameChildListID::Float), + aExtraFrames); + AddFramesForContainingBlock(f, f->GetChildList(f->GetAbsoluteListID()), + aExtraFrames); + + // This condition must match the condition in + // nsLayoutUtils::GetParentOrPlaceholderFor which is used by + // nsLayoutUtils::GetDisplayListParent + if (f->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW) && !f->GetPrevInFlow()) { + nsIFrame* parent = f->GetParent(); + if (parent && !parent->ForceDescendIntoIfVisible()) { + // If the GetDisplayListParent call is going to walk to a placeholder, + // in rare cases the placeholder might be contained in a different + // continuation from the oof. So we have to make sure to mark the oofs + // parent. In the common case this doesn't make us do any extra work, + // just changes the order in which we visit the frames since walking + // through placeholders will walk through the parent, and we stop when + // we find a ForceDescendIntoIfVisible bit set. + FindContainingBlocks(parent, aExtraFrames); + } + } + } +} + +/** + * Given a list of frames that has been modified, computes the region that we + * need to do display list building for in order to build all modified display + * items. + * + * When a modified frame is within a stacking context (with an existing display + * item), then we only contribute to the build area within the stacking context, + * as well as forcing display list building to descend to the stacking context. + * We don't need to add build area outside of the stacking context (and force + * items above/below the stacking context container item to be built), since + * just matching the position of the stacking context container item is + * sufficient to ensure correct ordering during merging. + * + * We need to rebuild all items that might intersect with the modified frame, + * both now and during async changes on the compositor. We do this by rebuilding + * the area covered by the changed frame, as well as rebuilding all items that + * have a different (async) AGR to the changed frame. If we have changes to + * multiple AGRs (within a stacking context), then we rebuild that stacking + * context entirely. + * + * @param aModifiedFrames The list of modified frames. + * @param aOutDirty The result region to use for display list building. + * @param aOutModifiedAGR The modified AGR for the root stacking context. + * @param aOutFramesWithProps The list of frames to which we attached partial + * build data so that it can be cleaned up. + * + * @return true if we succesfully computed a partial rebuild region, false if a + * full build is required. + */ +bool RetainedDisplayListBuilder::ComputeRebuildRegion( + nsTArray& aModifiedFrames, nsRect* aOutDirty, + nsIFrame** aOutModifiedAGR, nsTArray& aOutFramesWithProps) { + CRR_LOG("Computing rebuild regions for %zu frames:\n", + aModifiedFrames.Length()); + nsTArray extraFrames; + for (nsIFrame* f : aModifiedFrames) { + MOZ_ASSERT(f); + + mBuilder.AddFrameMarkedForDisplayIfVisible(f); + FindContainingBlocks(f, extraFrames); + + if (!ProcessFrame(f, &mBuilder, RootReferenceFrame(), aOutFramesWithProps, + true, aOutDirty, aOutModifiedAGR)) { + return false; + } + } + + // Since we set modified to true on the extraFrames, add them to + // aModifiedFrames so that it will get reverted. + aModifiedFrames.AppendElements(extraFrames); + + for (nsIFrame* f : extraFrames) { + f->SetFrameIsModified(true); + + if (!ProcessFrame(f, &mBuilder, RootReferenceFrame(), aOutFramesWithProps, + true, aOutDirty, aOutModifiedAGR)) { + return false; + } + } + + return true; +} + +bool RetainedDisplayListBuilder::ShouldBuildPartial( + nsTArray& aModifiedFrames) { + if (mList.IsEmpty()) { + // Partial builds without a previous display list do not make sense. + Metrics()->mPartialUpdateFailReason = PartialUpdateFailReason::EmptyList; + return false; + } + + if (aModifiedFrames.Length() > + StaticPrefs::layout_display_list_rebuild_frame_limit()) { + // Computing a dirty rect with too many modified frames can be slow. + Metrics()->mPartialUpdateFailReason = PartialUpdateFailReason::RebuildLimit; + return false; + } + + // We don't support retaining with overlay scrollbars, since they require + // us to look at the display list and pick the highest z-index, which + // we can't do during partial building. + if (mBuilder.DisablePartialUpdates()) { + mBuilder.SetDisablePartialUpdates(false); + Metrics()->mPartialUpdateFailReason = PartialUpdateFailReason::Disabled; + return false; + } + + for (nsIFrame* f : aModifiedFrames) { + MOZ_ASSERT(f); + + const LayoutFrameType type = f->Type(); + + // If we have any modified frames of the following types, it is likely that + // doing a partial rebuild of the display list will be slower than doing a + // full rebuild. + // This is because these frames either intersect or may intersect with most + // of the page content. This is either due to display port size or different + // async AGR. + if (type == LayoutFrameType::Viewport || + type == LayoutFrameType::PageContent || + type == LayoutFrameType::Canvas || type == LayoutFrameType::Scrollbar) { + Metrics()->mPartialUpdateFailReason = PartialUpdateFailReason::FrameType; + return false; + } + + // Detect root scroll frame and do a full rebuild for them too for the same + // reasons as above, but also because top layer items should to be marked + // modified if the root scroll frame is modified. Putting this check here + // means we don't need to check everytime a frame is marked modified though. + if (type == LayoutFrameType::Scroll && f->GetParent() && + !f->GetParent()->GetParent()) { + Metrics()->mPartialUpdateFailReason = PartialUpdateFailReason::FrameType; + return false; + } + } + + return true; +} + +void RetainedDisplayListBuilder::InvalidateCaretFramesIfNeeded() { + if (mPreviousCaret == mBuilder.GetCaretFrame()) { + // The current caret frame is the same as the previous one. + return; + } + + if (mPreviousCaret) { + mPreviousCaret->MarkNeedsDisplayItemRebuild(); + } + + if (mBuilder.GetCaretFrame()) { + mBuilder.GetCaretFrame()->MarkNeedsDisplayItemRebuild(); + } + + mPreviousCaret = mBuilder.GetCaretFrame(); +} + +static void ClearFrameProps(nsTArray& aFrames) { + for (nsIFrame* f : aFrames) { + DL_LOGV("RDL - Clearing modified flags for frame %p", f); + if (f->HasOverrideDirtyRegion()) { + f->SetHasOverrideDirtyRegion(false); + f->RemoveProperty(nsDisplayListBuilder::DisplayListBuildingRect()); + f->RemoveProperty( + nsDisplayListBuilder::DisplayListBuildingDisplayPortRect()); + } + + f->SetFrameIsModified(false); + f->SetHasModifiedDescendants(false); + } +} + +class AutoClearFramePropsArray { + public: + explicit AutoClearFramePropsArray(size_t aCapacity) : mFrames(aCapacity) {} + AutoClearFramePropsArray() = default; + ~AutoClearFramePropsArray() { ClearFrameProps(mFrames); } + + nsTArray& Frames() { return mFrames; } + bool IsEmpty() const { return mFrames.IsEmpty(); } + + private: + nsTArray mFrames; +}; + +void RetainedDisplayListBuilder::ClearFramesWithProps() { + AutoClearFramePropsArray modifiedFrames; + AutoClearFramePropsArray framesWithProps; + GetModifiedAndFramesWithProps(&modifiedFrames.Frames(), + &framesWithProps.Frames()); +} + +void RetainedDisplayListBuilder::ClearRetainedData() { + DL_LOGI("(%p) RDL - Clearing retained display list builder data", this); + List()->DeleteAll(Builder()); + ClearFramesWithProps(); + ClearReuseableDisplayItems(); +} + +namespace RDLUtils { + +MOZ_NEVER_INLINE_DEBUG void AssertFrameSubtreeUnmodified( + const nsIFrame* aFrame) { + MOZ_ASSERT(!aFrame->IsFrameModified()); + MOZ_ASSERT(!aFrame->HasModifiedDescendants()); + + for (const auto& childList : aFrame->ChildLists()) { + for (nsIFrame* child : childList.mList) { + AssertFrameSubtreeUnmodified(child); + } + } +} + +MOZ_NEVER_INLINE_DEBUG void AssertDisplayListUnmodified(nsDisplayList* aList) { + for (nsDisplayItem* item : *aList) { + AssertDisplayItemUnmodified(item); + } +} + +MOZ_NEVER_INLINE_DEBUG void AssertDisplayItemUnmodified(nsDisplayItem* aItem) { + MOZ_ASSERT(!aItem->HasDeletedFrame()); + MOZ_ASSERT(!AnyContentAncestorModified(aItem->FrameForInvalidation())); + + if (aItem->GetChildren()) { + AssertDisplayListUnmodified(aItem->GetChildren()); + } +} + +} // namespace RDLUtils + +namespace RDL { + +void MarkAncestorFrames(nsIFrame* aFrame, + nsTArray& aOutFramesWithProps) { + nsIFrame* frame = nsLayoutUtils::GetDisplayListParent(aFrame); + while (frame && !frame->HasModifiedDescendants()) { + aOutFramesWithProps.AppendElement(frame); + frame->SetHasModifiedDescendants(true); + frame = nsLayoutUtils::GetDisplayListParent(frame); + } +} + +/** + * Iterates over the modified frames array and updates the frame tree flags + * so that container frames know whether they have modified descendant frames. + * Frames that were marked modified are added to |aOutFramesWithProps|, so that + * the modified status can be cleared after the display list build. + */ +void MarkAllAncestorFrames(const nsTArray& aModifiedFrames, + nsTArray& aOutFramesWithProps) { + nsAutoString frameName; + DL_LOGI("RDL - Modified frames: %zu", aModifiedFrames.Length()); + for (nsIFrame* frame : aModifiedFrames) { +#ifdef DEBUG + frame->GetFrameName(frameName); +#endif + DL_LOGV("RDL - Processing modified frame: %p (%s)", frame, + NS_ConvertUTF16toUTF8(frameName).get()); + + MarkAncestorFrames(frame, aOutFramesWithProps); + } +} + +/** + * Marks the given display item |aItem| as reuseable container, and updates the + * bounds in case some child items were destroyed. + */ +MOZ_NEVER_INLINE_DEBUG void ReuseStackingContextItem( + nsDisplayListBuilder* aBuilder, nsDisplayItem* aItem) { + aItem->SetPreProcessed(); + + if (aItem->HasChildren()) { + aItem->UpdateBounds(aBuilder); + } + + aBuilder->AddReusableDisplayItem(aItem); + DL_LOGD("Reusing display item %p", aItem); +} + +bool IsSupportedFrameType(const nsIFrame* aFrame) { + // The way table backgrounds are handled makes these frames incompatible with + // this retained display list approach. + if (aFrame->IsTableColFrame()) { + return false; + } + + if (aFrame->IsTableColGroupFrame()) { + return false; + } + + if (aFrame->IsTableRowFrame()) { + return false; + } + + if (aFrame->IsTableRowGroupFrame()) { + return false; + } + + if (aFrame->IsTableCellFrame()) { + return false; + } + + // Everything else should work. + return true; +} + +bool IsReuseableStackingContextItem(nsDisplayItem* aItem) { + if (!IsSupportedFrameType(aItem->Frame())) { + return false; + } + + if (!aItem->IsReusable()) { + return false; + } + + const nsIFrame* frame = aItem->FrameForInvalidation(); + return !frame->HasModifiedDescendants() && !frame->GetPrevContinuation() && + !frame->GetNextContinuation(); +} + +/** + * Recursively visits every display item of the display list and destroys all + * display items that depend on deleted or modified frames. + * The stacking context display items for unmodified frame subtrees are kept + * linked and collected in given |aOutItems| array. + */ +void CollectStackingContextItems(nsDisplayListBuilder* aBuilder, + nsDisplayList* aList, nsIFrame* aOuterFrame, + int aDepth = 0, bool aParentReused = false) { + for (nsDisplayItem* item : aList->TakeItems()) { + if (DL_LOG_TEST(LogLevel::Debug)) { + DL_LOGD( + "%*s Preprocessing item %p (%s) (frame: %p) " + "(children: %zu) (depth: %d) (parentReused: %d)", + aDepth, "", item, item->Name(), + item->HasDeletedFrame() ? nullptr : item->Frame(), + item->GetChildren() ? item->GetChildren()->Length() : 0, aDepth, + aParentReused); + } + + if (!item->CanBeReused() || item->HasDeletedFrame() || + AnyContentAncestorModified(item->FrameForInvalidation(), aOuterFrame)) { + DL_LOGD("%*s Deleted modified or temporary item %p", aDepth, "", item); + item->Destroy(aBuilder); + continue; + } + + MOZ_ASSERT(!AnyContentAncestorModified(item->FrameForInvalidation())); + MOZ_ASSERT(!item->IsPreProcessed()); + item->InvalidateCachedChildInfo(aBuilder); +#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + item->SetMergedPreProcessed(false, true); +#endif + item->SetReused(true); + + const bool isStackingContextItem = IsReuseableStackingContextItem(item); + + if (item->GetChildren()) { + CollectStackingContextItems(aBuilder, item->GetChildren(), item->Frame(), + aDepth + 1, + aParentReused || isStackingContextItem); + } + + if (aParentReused) { + // Keep the contents of the current container item linked. +#ifdef DEBUG + RDLUtils::AssertDisplayItemUnmodified(item); +#endif + aList->AppendToTop(item); + } else if (isStackingContextItem) { + // |item| is a stacking context item that can be reused. + ReuseStackingContextItem(aBuilder, item); + } else { + // |item| is inside a container item that will be destroyed later. + DL_LOGD("%*s Deleted unused item %p", aDepth, "", item); + item->Destroy(aBuilder); + continue; + } + + if (item->GetType() == DisplayItemType::TYPE_SUBDOCUMENT) { + IncrementPresShellPaintCount(aBuilder, item); + } + } +} + +} // namespace RDL + +bool RetainedDisplayListBuilder::TrySimpleUpdate( + const nsTArray& aModifiedFrames, + nsTArray& aOutFramesWithProps) { + if (!mBuilder.IsReusingStackingContextItems()) { + return false; + } + + RDL::MarkAllAncestorFrames(aModifiedFrames, aOutFramesWithProps); + RDL::CollectStackingContextItems(&mBuilder, &mList, RootReferenceFrame()); + + return true; +} + +PartialUpdateResult RetainedDisplayListBuilder::AttemptPartialUpdate( + nscolor aBackstop) { + DL_LOGI("(%p) RDL - AttemptPartialUpdate, root frame: %p", this, + RootReferenceFrame()); + + mBuilder.RemoveModifiedWindowRegions(); + + if (mBuilder.ShouldSyncDecodeImages()) { + DL_LOGI("RDL - Sync decoding images"); + MarkFramesWithItemsAndImagesModified(&mList); + } + + InvalidateCaretFramesIfNeeded(); + + // We set the override dirty regions during ComputeRebuildRegion or in + // DisplayPortUtils::InvalidateForDisplayPortChange. The display port change + // also marks the frame modified, so those regions are cleared here as well. + AutoClearFramePropsArray modifiedFrames(64); + AutoClearFramePropsArray framesWithProps(64); + GetModifiedAndFramesWithProps(&modifiedFrames.Frames(), + &framesWithProps.Frames()); + + if (!ShouldBuildPartial(modifiedFrames.Frames())) { + // Do not allow partial builds if the |ShouldBuildPartial()| heuristic + // fails. + mBuilder.SetPartialBuildFailed(true); + return PartialUpdateResult::Failed; + } + + nsRect modifiedDirty; + nsDisplayList modifiedDL(&mBuilder); + nsIFrame* modifiedAGR = nullptr; + PartialUpdateResult result = PartialUpdateResult::NoChange; + const bool simpleUpdate = + TrySimpleUpdate(modifiedFrames.Frames(), framesWithProps.Frames()); + + mBuilder.EnterPresShell(RootReferenceFrame()); + + if (!simpleUpdate) { + if (!ComputeRebuildRegion(modifiedFrames.Frames(), &modifiedDirty, + &modifiedAGR, framesWithProps.Frames()) || + !PreProcessDisplayList(&mList, modifiedAGR, result, + RootReferenceFrame(), nullptr)) { + DL_LOGI("RDL - Partial update aborted"); + mBuilder.SetPartialBuildFailed(true); + mBuilder.LeavePresShell(RootReferenceFrame(), nullptr); + mList.DeleteAll(&mBuilder); + return PartialUpdateResult::Failed; + } + } else { + modifiedDirty = mBuilder.GetVisibleRect(); + } + + // This is normally handled by EnterPresShell, but we skipped it so that we + // didn't call MarkFrameForDisplayIfVisible before ComputeRebuildRegion. + nsIScrollableFrame* sf = + RootReferenceFrame()->PresShell()->GetRootScrollFrameAsScrollable(); + if (sf) { + nsCanvasFrame* canvasFrame = do_QueryFrame(sf->GetScrolledFrame()); + if (canvasFrame) { + mBuilder.MarkFrameForDisplayIfVisible(canvasFrame, RootReferenceFrame()); + } + } + + nsRect rootOverflow = RootOverflowRect(); + modifiedDirty.IntersectRect(modifiedDirty, rootOverflow); + + mBuilder.SetDirtyRect(modifiedDirty); + mBuilder.SetPartialUpdate(true); + mBuilder.SetPartialBuildFailed(false); + + DL_LOGI("RDL - Starting display list build"); + RootReferenceFrame()->BuildDisplayListForStackingContext(&mBuilder, + &modifiedDL); + DL_LOGI("RDL - Finished display list build"); + + if (!modifiedDL.IsEmpty()) { + nsLayoutUtils::AddExtraBackgroundItems( + &mBuilder, &modifiedDL, RootReferenceFrame(), + nsRect(nsPoint(0, 0), rootOverflow.Size()), rootOverflow, aBackstop); + } + mBuilder.SetPartialUpdate(false); + + if (mBuilder.PartialBuildFailed()) { + DL_LOGI("RDL - Partial update failed!"); + mBuilder.LeavePresShell(RootReferenceFrame(), nullptr); + mBuilder.ClearReuseableDisplayItems(); + mList.DeleteAll(&mBuilder); + modifiedDL.DeleteAll(&mBuilder); + Metrics()->mPartialUpdateFailReason = PartialUpdateFailReason::Content; + return PartialUpdateResult::Failed; + } + + // printf_stderr("Painting --- Modified list (dirty %d,%d,%d,%d):\n", + // modifiedDirty.x, modifiedDirty.y, modifiedDirty.width, + // modifiedDirty.height); + // nsIFrame::PrintDisplayList(&mBuilder, modifiedDL); + + // |modifiedDL| can sometimes be empty here. We still perform the + // display list merging to prune unused items (for example, items that + // are not visible anymore) from the old list. + // TODO: Optimization opportunity. In this case, MergeDisplayLists() + // unnecessarily creates a hashtable of the old items. + // TODO: Ideally we could skip this if result is NoChange, but currently when + // we call RestoreState on nsDisplayWrapList it resets the clip to the base + // clip, and we need the UpdateBounds call (within MergeDisplayLists) to + // move it to the correct inner clip. + if (!simpleUpdate) { + Maybe dummy; + if (MergeDisplayLists(&modifiedDL, &mList, &mList, dummy)) { + result = PartialUpdateResult::Updated; + } + } else { + MOZ_ASSERT(mList.IsEmpty()); + mList = std::move(modifiedDL); + mBuilder.ClearReuseableDisplayItems(); + result = PartialUpdateResult::Updated; + } + +#if 0 + if (DL_LOG_TEST(LogLevel::Verbose)) { + printf_stderr("Painting --- Display list:\n"); + nsIFrame::PrintDisplayList(&mBuilder, mList); + } +#endif + + mBuilder.LeavePresShell(RootReferenceFrame(), List()); + return result; +} + +nsRect RetainedDisplayListBuilder::RootOverflowRect() const { + const nsIFrame* rootReferenceFrame = RootReferenceFrame(); + nsRect rootOverflowRect = rootReferenceFrame->InkOverflowRectRelativeToSelf(); + const nsPresContext* presContext = rootReferenceFrame->PresContext(); + if (!rootReferenceFrame->GetParent() && + presContext->IsRootContentDocumentCrossProcess() && + presContext->HasDynamicToolbar()) { + rootOverflowRect.SizeTo(nsLayoutUtils::ExpandHeightForDynamicToolbar( + presContext, rootOverflowRect.Size())); + } + + return rootOverflowRect; +} + +} // namespace mozilla diff --git a/layout/painting/RetainedDisplayListBuilder.h b/layout/painting/RetainedDisplayListBuilder.h new file mode 100644 index 0000000000..98c085fee0 --- /dev/null +++ b/layout/painting/RetainedDisplayListBuilder.h @@ -0,0 +1,287 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef RETAINEDDISPLAYLISTBUILDER_H_ +#define RETAINEDDISPLAYLISTBUILDER_H_ + +#include "nsDisplayList.h" +#include "mozilla/Maybe.h" +#include "mozilla/EnumSet.h" + +class nsWindowSizes; + +namespace mozilla { + +class nsDisplayItem; +class nsDisplayList; + +/** + * RetainedDisplayListData contains frame invalidation information. + * Currently this is implemented as a map of frame pointers to flags. + */ +struct RetainedDisplayListData { + enum class FrameFlag : uint8_t { Modified, HasProps, HadWillChange }; + using FrameFlags = mozilla::EnumSet; + + RetainedDisplayListData(); + + /** + * Adds the frame to modified frames list. + */ + void AddModifiedFrame(nsIFrame* aFrame); + + /** + * Removes all the frames from this RetainedDisplayListData. + */ + void Clear() { + mFrames.Clear(); + mModifiedFrameCount = 0; + } + + /** + * Returns a mutable reference to flags set for the given |aFrame|. + */ + FrameFlags& Flags(nsIFrame* aFrame) { return mFrames.LookupOrInsert(aFrame); } + + /** + * Returns flags set for the given |aFrame|, or FrameFlags::None if the frame + * is not in this RetainedDisplayListData. + */ + FrameFlags GetFlags(nsIFrame* aFrame) const { return mFrames.Get(aFrame); } + + bool IsModified(nsIFrame* aFrame) const { + return GetFlags(aFrame).contains(FrameFlag::Modified); + } + + bool HasProps(nsIFrame* aFrame) const { + return GetFlags(aFrame).contains(FrameFlag::HasProps); + } + + bool HadWillChange(nsIFrame* aFrame) const { + return GetFlags(aFrame).contains(FrameFlag::HadWillChange); + } + + /** + * Returns an iterator to the underlying frame storage. + */ + auto ConstIterator() { return mFrames.ConstIter(); } + + /** + * Returns true if the modified frame limit has been reached. + */ + bool AtModifiedFrameLimit() { + return mModifiedFrameCount >= mModifiedFrameLimit; + } + + /** + * Removes the given |aFrame| from this RetainedDisplayListData. + */ + bool Remove(nsIFrame* aFrame) { return mFrames.Remove(aFrame); } + + private: + nsTHashMap, FrameFlags> mFrames; + uint32_t mModifiedFrameCount = 0; + uint32_t mModifiedFrameLimit; // initialized to a pref value in constructor +}; + +enum class PartialUpdateResult { Failed, NoChange, Updated }; + +enum class PartialUpdateFailReason { + NA, + EmptyList, + RebuildLimit, + FrameType, + Disabled, + Content, + VisibleRect, +}; + +struct RetainedDisplayListMetrics { + RetainedDisplayListMetrics() { Reset(); } + + void Reset() { + mNewItems = 0; + mRebuiltItems = 0; + mRemovedItems = 0; + mReusedItems = 0; + mTotalItems = 0; + mPartialBuildDuration = 0; + mFullBuildDuration = 0; + mPartialUpdateFailReason = PartialUpdateFailReason::NA; + mPartialUpdateResult = PartialUpdateResult::NoChange; + } + + void StartBuild() { mStartTime = mozilla::TimeStamp::Now(); } + + void EndFullBuild() { mFullBuildDuration = Elapsed(); } + + void EndPartialBuild(PartialUpdateResult aResult) { + mPartialBuildDuration = Elapsed(); + mPartialUpdateResult = aResult; + } + + double Elapsed() { + return (mozilla::TimeStamp::Now() - mStartTime).ToMilliseconds(); + } + + const char* FailReasonString() const { + switch (mPartialUpdateFailReason) { + case PartialUpdateFailReason::NA: + return "N/A"; + case PartialUpdateFailReason::EmptyList: + return "Empty list"; + case PartialUpdateFailReason::RebuildLimit: + return "Rebuild limit"; + case PartialUpdateFailReason::FrameType: + return "Frame type"; + case PartialUpdateFailReason::Disabled: + return "Disabled"; + case PartialUpdateFailReason::Content: + return "Content"; + case PartialUpdateFailReason::VisibleRect: + return "VisibleRect"; + default: + MOZ_ASSERT_UNREACHABLE("Enum value not handled!"); + } + } + + unsigned int mNewItems; + unsigned int mRebuiltItems; + unsigned int mRemovedItems; + unsigned int mReusedItems; + unsigned int mTotalItems; + + mozilla::TimeStamp mStartTime; + double mPartialBuildDuration; + double mFullBuildDuration; + PartialUpdateFailReason mPartialUpdateFailReason; + PartialUpdateResult mPartialUpdateResult; +}; + +class RetainedDisplayListBuilder { + public: + RetainedDisplayListBuilder(nsIFrame* aReferenceFrame, + nsDisplayListBuilderMode aMode, bool aBuildCaret) + : mBuilder(aReferenceFrame, aMode, aBuildCaret, true), mList(&mBuilder) {} + ~RetainedDisplayListBuilder() { mList.DeleteAll(&mBuilder); } + + nsDisplayListBuilder* Builder() { return &mBuilder; } + + nsDisplayList* List() { return &mList; } + + RetainedDisplayListMetrics* Metrics() { return &mMetrics; } + + RetainedDisplayListData* Data() { return &mData; } + + PartialUpdateResult AttemptPartialUpdate(nscolor aBackstop); + + /** + * Clears the modified state for frames in the retained display list data. + */ + void ClearFramesWithProps(); + + void ClearRetainedData(); + + void ClearReuseableDisplayItems() { mBuilder.ClearReuseableDisplayItems(); } + + void AddSizeOfIncludingThis(nsWindowSizes&) const; + + NS_DECLARE_FRAME_PROPERTY_DELETABLE(Cached, RetainedDisplayListBuilder) + + private: + void GetModifiedAndFramesWithProps(nsTArray* aOutModifiedFrames, + nsTArray* aOutFramesWithProps); + + void IncrementSubDocPresShellPaintCount(nsDisplayItem* aItem); + + /** + * Invalidates the current and previous caret frame if they have changed. + */ + void InvalidateCaretFramesIfNeeded(); + + /** + * A simple early exit heuristic to avoid slow partial display list rebuilds. + * Returns true if a partial display list build should be attempted. + */ + bool ShouldBuildPartial(nsTArray& aModifiedFrames); + + /** + * Recursively pre-processes the old display list tree before building the + * new partial display lists, and serializes the old list into an array, + * recording indices on items for fast lookup during merging. Builds an + * initial linear DAG for the list if we don't have an existing one. Finds + * items that have a different AGR from the specified one, and marks them to + * also be built so that we get relative ordering correct. Passes + * aKeepLinked=true internally for sub-lists that can't be changed to keep the + * original list structure linked for fast re-use. + */ + bool PreProcessDisplayList( + RetainedDisplayList* aList, nsIFrame* aAGR, PartialUpdateResult& aUpdated, + nsIFrame* aAsyncAncestor, const ActiveScrolledRoot* aAsyncAncestorASR, + nsIFrame* aOuterFrame = nullptr, uint32_t aCallerKey = 0, + uint32_t aNestingDepth = 0, bool aKeepLinked = false); + + /** + * Merges items from aNewList into non-invalidated items from aOldList and + * stores the result in aOutList. + * + * aOuterItem is a pointer to an item that owns one of the lists, if + * available. If both lists are populated, then both outer items must not be + * invalidated, and identical, so either can be passed here. + * + * Returns true if changes were made, and the resulting display list (in + * aOutList) is different from aOldList. + */ + bool MergeDisplayLists( + nsDisplayList* aNewList, RetainedDisplayList* aOldList, + RetainedDisplayList* aOutList, + mozilla::Maybe& aOutContainerASR, + nsDisplayItem* aOuterItem = nullptr); + + bool ComputeRebuildRegion(nsTArray& aModifiedFrames, + nsRect* aOutDirty, nsIFrame** aOutModifiedAGR, + nsTArray& aOutFramesWithProps); + + bool ProcessFrame(nsIFrame* aFrame, nsDisplayListBuilder* aBuilder, + nsIFrame* aStopAtFrame, + nsTArray& aOutFramesWithProps, + const bool aStopAtStackingContext, nsRect* aOutDirty, + nsIFrame** aOutModifiedAGR); + + nsIFrame* RootReferenceFrame() { return mBuilder.RootReferenceFrame(); } + const nsIFrame* RootReferenceFrame() const { + return mBuilder.RootReferenceFrame(); + } + + nsRect RootOverflowRect() const; + + /** + * Tries to perform a simple partial display list build without display list + * merging. In this mode, only the top-level stacking context items and their + * contents are reused, when the frame subtree has not been modified. + */ + bool TrySimpleUpdate(const nsTArray& aModifiedFrames, + nsTArray& aOutFramesWithProps); + + friend class MergeState; + + nsDisplayListBuilder mBuilder; + RetainedDisplayList mList; + WeakFrame mPreviousCaret; + RetainedDisplayListMetrics mMetrics; + RetainedDisplayListData mData; +}; + +namespace RDLUtils { + +void AssertFrameSubtreeUnmodified(const nsIFrame* aFrame); +void AssertDisplayItemUnmodified(nsDisplayItem* aItem); +void AssertDisplayListUnmodified(nsDisplayList* aList); + +} // namespace RDLUtils +} // namespace mozilla + +#endif // RETAINEDDISPLAYLISTBUILDER_H_ diff --git a/layout/painting/RetainedDisplayListHelpers.h b/layout/painting/RetainedDisplayListHelpers.h new file mode 100644 index 0000000000..933ab31a7a --- /dev/null +++ b/layout/painting/RetainedDisplayListHelpers.h @@ -0,0 +1,187 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef RETAINEDDISPLAYLISTHELPERS_H_ +#define RETAINEDDISPLAYLISTHELPERS_H_ + +#include "mozilla/Span.h" +#include "PLDHashTable.h" + +class nsIFrame; + +namespace mozilla { + +struct DisplayItemKey { + bool operator==(const DisplayItemKey& aOther) const { + return mFrame == aOther.mFrame && mPerFrameKey == aOther.mPerFrameKey; + } + + nsIFrame* mFrame; + uint32_t mPerFrameKey; +}; + +class DisplayItemHashEntry : public PLDHashEntryHdr { + public: + typedef DisplayItemKey KeyType; + typedef const DisplayItemKey* KeyTypePointer; + + explicit DisplayItemHashEntry(KeyTypePointer aKey) : mKey(*aKey) {} + DisplayItemHashEntry(DisplayItemHashEntry&&) = default; + + ~DisplayItemHashEntry() = default; + + KeyType GetKey() const { return mKey; } + bool KeyEquals(KeyTypePointer aKey) const { return mKey == *aKey; } + + static KeyTypePointer KeyToPointer(KeyType& aKey) { return &aKey; } + static PLDHashNumber HashKey(KeyTypePointer aKey) { + if (!aKey) { + return 0; + } + + return mozilla::HashGeneric(aKey->mFrame, aKey->mPerFrameKey); + } + enum { ALLOW_MEMMOVE = true }; + + DisplayItemKey mKey; +}; + +template +bool SpanContains(mozilla::Span& aSpan, T aItem) { + for (const T& i : aSpan) { + if (i == aItem) { + return true; + } + } + return false; +} + +class OldListUnits {}; +class MergedListUnits {}; + +template +struct Index { + Index() : val(0) {} + explicit Index(size_t aVal) : val(aVal) { + MOZ_RELEASE_ASSERT(aVal < std::numeric_limits::max(), + "List index overflowed"); + } + + bool operator==(const Index& aOther) const { + return val == aOther.val; + } + + uint32_t val; +}; +typedef Index OldListIndex; +typedef Index MergedListIndex; + +template +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 AddNode( + mozilla::Span> aDirectPredecessors, + const mozilla::Maybe>& 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(index); + } + + size_t Length() { return mNodesInfo.Length(); } + + mozilla::Span> GetDirectPredecessors(Index aNodeIndex) { + NodeInfo& node = mNodesInfo[aNodeIndex.val]; + const auto span = mozilla::Span{mDirectPredecessorList}; + return span.Subspan(node.mIndexInDirectPredecessorList, + node.mDirectPredecessorCount); + } + + template + void EnsureCapacityFor(const DirectedAcyclicGraph& 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 mNodesInfo; + nsTArray> mDirectPredecessorList; +}; + +class RetainedDisplayListBuilder; +class nsDisplayItem; + +struct OldItemInfo { + explicit OldItemInfo(nsDisplayItem* aItem); + + void AddedToMergedList(MergedListIndex aIndex) { + MOZ_ASSERT(!IsUsed()); + mUsed = true; + mIndex = aIndex; + mItem = nullptr; + } + + void AddedMatchToMergedList(RetainedDisplayListBuilder* aBuilder, + MergedListIndex aIndex); + void Discard(RetainedDisplayListBuilder* aBuilder, + nsTArray&& aDirectPredecessors); + bool IsUsed() { return mUsed; } + + bool IsDiscarded() { + MOZ_ASSERT(IsUsed()); + return mDiscarded; + } + + bool IsChanged(); + + nsDisplayItem* mItem; + nsTArray mDirectPredecessors; + MergedListIndex mIndex; + bool mUsed; + bool mDiscarded; + bool mOwnsItem; +}; + +bool AnyContentAncestorModified(nsIFrame* aFrame, + nsIFrame* aStopAtFrame = nullptr); + +} // namespace mozilla + +#endif // RETAINEDDISPLAYLISTHELPERS_H_ diff --git a/layout/painting/TransformClipNode.h b/layout/painting/TransformClipNode.h new file mode 100644 index 0000000000..a28dc3dbc0 --- /dev/null +++ b/layout/painting/TransformClipNode.h @@ -0,0 +1,138 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_PAINTING_TRANSFORMCLIPNODE_H +#define MOZILLA_PAINTING_TRANSFORMCLIPNODE_H + +#include "mozilla/gfx/MatrixFwd.h" +#include "mozilla/gfx/Rect.h" +#include "mozilla/Maybe.h" +#include "nsISupports.h" +#include "nsRegionFwd.h" + +namespace mozilla { + +/** + * TransformClipNode stores a transformation matrix and a post-transform + * clip rect. + * They can be used to transform and clip a display item inside a flattened + * nsDisplayTransform to the coordinate space of that nsDisplayTransform. + */ +class TransformClipNode { + NS_INLINE_DECL_REFCOUNTING(TransformClipNode); + + public: + TransformClipNode(const RefPtr& aParent, + const gfx::Matrix4x4Flagged& aTransform, + const Maybe& 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& 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& 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 mParent; + const gfx::Matrix4x4Flagged mTransform; + const Maybe mClip; +}; + +} // namespace mozilla + +#endif /* MOZILLA_PAINTING_TRANSFORMCLIPNODE_H */ diff --git a/layout/painting/WindowRenderer.cpp b/layout/painting/WindowRenderer.cpp new file mode 100644 index 0000000000..d695ef8eba --- /dev/null +++ b/layout/painting/WindowRenderer.cpp @@ -0,0 +1,229 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "WindowRenderer.h" + +#include "gfxPlatform.h" +#include "mozilla/dom/Animation.h" // for Animation +#include "mozilla/dom/AnimationEffect.h" +#include "mozilla/EffectSet.h" +#include "mozilla/layers/PersistentBufferProvider.h" // for PersistentBufferProviderBasic, PersistentBufferProvider (ptr only) +#include "nsDisplayList.h" + +using namespace mozilla::gfx; +using namespace mozilla::layers; + +namespace mozilla { + +/** + * StartFrameTimeRecording, together with StopFrameTimeRecording + * enable recording of frame intervals. + * + * To allow concurrent consumers, a cyclic array is used which serves all + * consumers, practically stateless with regard to consumers. + * + * To save resources, the buffer is allocated on first call to + * StartFrameTimeRecording and recording is paused if no consumer which called + * StartFrameTimeRecording is able to get valid results (because the cyclic + * buffer was overwritten since that call). + * + * To determine availability of the data upon StopFrameTimeRecording: + * - mRecording.mNextIndex increases on each RecordFrame, and never resets. + * - Cyclic buffer position is realized as mNextIndex % bufferSize. + * - StartFrameTimeRecording returns mNextIndex. When StopFrameTimeRecording is + * called, the required start index is passed as an arg, and we're able to + * calculate the required length. If this length is bigger than bufferSize, it + * means data was overwritten. otherwise, we can return the entire sequence. + * - To determine if we need to pause, mLatestStartIndex is updated to + * mNextIndex on each call to StartFrameTimeRecording. If this index gets + * overwritten, it means that all earlier start indices obtained via + * StartFrameTimeRecording were also overwritten, hence, no point in + * recording, so pause. + * - mCurrentRunStartIndex indicates the oldest index of the recording after + * which the recording was not paused. If StopFrameTimeRecording is invoked + * with a start index older than this, it means that some frames were not + * recorded, so data is invalid. + */ +uint32_t FrameRecorder::StartFrameTimeRecording(int32_t aBufferSize) { + if (mRecording.mIsPaused) { + mRecording.mIsPaused = false; + + if (!mRecording.mIntervals.Length()) { // Initialize recording buffers + mRecording.mIntervals.SetLength(aBufferSize); + } + + // After being paused, recent values got invalid. Update them to now. + mRecording.mLastFrameTime = TimeStamp::Now(); + + // Any recording which started before this is invalid, since we were paused. + mRecording.mCurrentRunStartIndex = mRecording.mNextIndex; + } + + // If we'll overwrite this index, there are no more consumers with aStartIndex + // for which we're able to provide the full recording, so no point in keep + // recording. + mRecording.mLatestStartIndex = mRecording.mNextIndex; + return mRecording.mNextIndex; +} + +void FrameRecorder::RecordFrame() { + if (!mRecording.mIsPaused) { + TimeStamp now = TimeStamp::Now(); + uint32_t i = mRecording.mNextIndex % mRecording.mIntervals.Length(); + mRecording.mIntervals[i] = + static_cast((now - mRecording.mLastFrameTime).ToMilliseconds()); + mRecording.mNextIndex++; + mRecording.mLastFrameTime = now; + + if (mRecording.mNextIndex > + (mRecording.mLatestStartIndex + mRecording.mIntervals.Length())) { + // We've just overwritten the most recent recording start -> pause. + mRecording.mIsPaused = true; + } + } +} + +void FrameRecorder::StopFrameTimeRecording(uint32_t aStartIndex, + nsTArray& aFrameIntervals) { + uint32_t bufferSize = mRecording.mIntervals.Length(); + uint32_t length = mRecording.mNextIndex - aStartIndex; + if (mRecording.mIsPaused || length > bufferSize || + aStartIndex < mRecording.mCurrentRunStartIndex) { + // aStartIndex is too old. Also if aStartIndex was issued before + // mRecordingNextIndex overflowed (uint32_t) + // and stopped after the overflow (would happen once every 828 days of + // constant 60fps). + length = 0; + } + + if (!length) { + aFrameIntervals.Clear(); + return; // empty recording, return empty arrays. + } + // Set length in advance to avoid possibly repeated reallocations + aFrameIntervals.SetLength(length); + + uint32_t cyclicPos = aStartIndex % bufferSize; + for (uint32_t i = 0; i < length; i++, cyclicPos++) { + if (cyclicPos == bufferSize) { + cyclicPos = 0; + } + aFrameIntervals[i] = mRecording.mIntervals[cyclicPos]; + } +} + +already_AddRefed +WindowRenderer::CreatePersistentBufferProvider( + const mozilla::gfx::IntSize& aSize, mozilla::gfx::SurfaceFormat aFormat) { + RefPtr bufferProvider; + // If we are using remote canvas we don't want to use acceleration in + // non-remote layer managers, so we always use the fallback software one. + if (!gfxPlatform::UseRemoteCanvas() || + !gfxPlatform::IsBackendAccelerated( + gfxPlatform::GetPlatform()->GetPreferredCanvasBackend())) { + bufferProvider = PersistentBufferProviderBasic::Create( + aSize, aFormat, + gfxPlatform::GetPlatform()->GetPreferredCanvasBackend()); + } + + if (!bufferProvider) { + bufferProvider = PersistentBufferProviderBasic::Create( + aSize, aFormat, gfxPlatform::GetPlatform()->GetFallbackCanvasBackend()); + } + + return bufferProvider.forget(); +} + +void WindowRenderer::AddPartialPrerenderedAnimation( + uint64_t aCompositorAnimationId, dom::Animation* aAnimation) { + mPartialPrerenderedAnimations.InsertOrUpdate(aCompositorAnimationId, + RefPtr{aAnimation}); + aAnimation->SetPartialPrerendered(aCompositorAnimationId); +} +void WindowRenderer::RemovePartialPrerenderedAnimation( + uint64_t aCompositorAnimationId, dom::Animation* aAnimation) { + MOZ_ASSERT(aAnimation); +#ifdef DEBUG + RefPtr animation; + if (mPartialPrerenderedAnimations.Remove(aCompositorAnimationId, + getter_AddRefs(animation)) && + // It may be possible that either animation's effect has already been + // nulled out via Animation::SetEffect() so ignore such cases. + aAnimation->GetEffect() && aAnimation->GetEffect()->AsKeyframeEffect() && + animation->GetEffect() && animation->GetEffect()->AsKeyframeEffect()) { + MOZ_ASSERT( + EffectSet::GetForEffect(aAnimation->GetEffect()->AsKeyframeEffect()) == + EffectSet::GetForEffect(animation->GetEffect()->AsKeyframeEffect())); + } +#else + mPartialPrerenderedAnimations.Remove(aCompositorAnimationId); +#endif + aAnimation->ResetPartialPrerendered(); +} +void WindowRenderer::UpdatePartialPrerenderedAnimations( + const nsTArray& aJankedAnimations) { + for (uint64_t id : aJankedAnimations) { + RefPtr animation; + if (mPartialPrerenderedAnimations.Remove(id, getter_AddRefs(animation))) { + animation->UpdatePartialPrerendered(); + } + } +} + +void FallbackRenderer::SetTarget(gfxContext* aTarget, + layers::BufferMode aDoubleBuffering) { + mTarget = aTarget; + mBufferMode = aDoubleBuffering; +} + +bool FallbackRenderer::BeginTransaction(const nsCString& aURL) { + if (!mTarget) { + return false; + } + + return true; +} + +void FallbackRenderer::EndTransactionWithColor(const nsIntRect& aRect, + const gfx::DeviceColor& aColor) { + mTarget->GetDrawTarget()->FillRect(Rect(aRect), ColorPattern(aColor)); + mAnimationReadyTime = TimeStamp::Now(); +} + +void FallbackRenderer::EndTransactionWithList(nsDisplayListBuilder* aBuilder, + nsDisplayList* aList, + int32_t aAppUnitsPerDevPixel, + EndTransactionFlags aFlags) { + if (aFlags & EndTransactionFlags::END_NO_COMPOSITE) { + return; + } + + DrawTarget* dt = mTarget->GetDrawTarget(); + + BackendType backend = gfxPlatform::GetPlatform()->GetContentBackendFor( + LayersBackend::LAYERS_NONE); + RefPtr dest = + gfxPlatform::GetPlatform()->CreateDrawTargetForBackend( + backend, dt->GetSize(), dt->GetFormat()); + if (dest) { + gfxContext ctx(dest, /* aPreserveTransform */ true); + + nsRegion opaque = aList->GetOpaqueRegion(aBuilder); + if (opaque.Contains(aList->GetComponentAlphaBounds(aBuilder))) { + dest->SetPermitSubpixelAA(true); + } + + aList->Paint(aBuilder, &ctx, aAppUnitsPerDevPixel); + + RefPtr snapshot = dest->Snapshot(); + dt->DrawSurface(snapshot, Rect(dest->GetRect()), Rect(dest->GetRect()), + DrawSurfaceOptions(), + DrawOptions(1.0f, CompositionOp::OP_SOURCE)); + } + mAnimationReadyTime = TimeStamp::Now(); +} + +} // namespace mozilla diff --git a/layout/painting/WindowRenderer.h b/layout/painting/WindowRenderer.h new file mode 100644 index 0000000000..e018a4cf56 --- /dev/null +++ b/layout/painting/WindowRenderer.h @@ -0,0 +1,284 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_PAINTING_WINDOWRENDERER_H +#define MOZILLA_PAINTING_WINDOWRENDERER_H + +#include "mozilla/webrender/webrender_ffi.h" +#include "mozilla/layers/LayersTypes.h" +#include "mozilla/dom/Animation.h" // for Animation +#include "mozilla/layers/ScrollableLayerGuid.h" // for ScrollableLayerGuid, ScrollableLayerGuid::ViewID +#include "mozilla/ScrollPositionUpdate.h" // for ScrollPositionUpdate +#include "nsRefPtrHashtable.h" // for nsRefPtrHashtable +#include "gfxContext.h" + +namespace mozilla { +namespace layers { +class LayerManager; +class WebRenderLayerManager; +class KnowsCompositor; +class CompositorBridgeChild; +class FrameUniformityData; +class PersistentBufferProvider; +} // namespace layers +class FallbackRenderer; +class nsDisplayListBuilder; +class nsDisplayList; + +class FrameRecorder { + public: + /** + * Record (and return) frame-intervals and paint-times for frames which were + * presented between calling StartFrameTimeRecording and + * StopFrameTimeRecording. + * + * - Uses a cyclic buffer and serves concurrent consumers, so if Stop is + * called too late + * (elements were overwritten since Start), result is considered invalid + * and hence empty.) + * - Buffer is capable of holding 10 seconds @ 60fps (or more if frames were + * less frequent). + * Can be changed (up to 1 hour) via pref: + * toolkit.framesRecording.bufferSize. + * - Note: the first frame-interval may be longer than expected because last + * frame + * might have been presented some time before calling + * StartFrameTimeRecording. + */ + + /** + * Returns a handle which represents current recording start position. + */ + virtual uint32_t StartFrameTimeRecording(int32_t aBufferSize); + + /** + * Clears, then populates aFrameIntervals with the recorded frame timing + * data. The array will be empty if data was overwritten since + * aStartIndex was obtained. + */ + virtual void StopFrameTimeRecording(uint32_t aStartIndex, + nsTArray& aFrameIntervals); + + void RecordFrame(); + + private: + struct FramesTimingRecording { + // Stores state and data for frame intervals and paint times recording. + // see LayerManager::StartFrameTimeRecording() at Layers.cpp for more + // details. + FramesTimingRecording() + : mNextIndex(0), + mLatestStartIndex(0), + mCurrentRunStartIndex(0), + mIsPaused(true) {} + nsTArray mIntervals; + TimeStamp mLastFrameTime; + uint32_t mNextIndex; + uint32_t mLatestStartIndex; + uint32_t mCurrentRunStartIndex; + bool mIsPaused; + }; + FramesTimingRecording mRecording; +}; + +/** + * WindowRenderer is the retained rendering object owned by an nsIWidget for + * drawing the contents of that window, the role previously handled by + * LayerManager. + * + * It can be WebRender, (deprecated) Layers, or an immediate-mode + * FallbackRenderer. + * + * The intention is for LayerManager to be removed entirely in the near future, + * with WebRender inheriting directly from this class. It is likely that more + * cleanup can be done once that happens. + */ +class WindowRenderer : public FrameRecorder { + NS_INLINE_DECL_REFCOUNTING(WindowRenderer) + + public: + // Cast to implementation types. + virtual layers::WebRenderLayerManager* AsWebRender() { return nullptr; } + virtual FallbackRenderer* AsFallback() { return nullptr; } + + // Required functionality + + /** + * Start a new transaction. Nested transactions are not allowed so + * there must be no transaction currently in progress. + * This transaction will update the state of the window from which + * this LayerManager was obtained. + */ + virtual bool BeginTransaction(const nsCString& aURL = nsCString()) = 0; + + enum EndTransactionFlags { + END_DEFAULT = 0, + END_NO_IMMEDIATE_REDRAW = 1 << 0, // Do not perform the drawing phase + END_NO_COMPOSITE = + 1 << 1, // Do not composite after drawing painted layer contents. + END_NO_REMOTE_COMPOSITE = 1 << 2 // Do not schedule a composition with a + // remote Compositor, if one exists. + }; + + /** + * Attempts to end an "empty transaction". There must have been no + * changes to the layer tree since the BeginTransaction(). + * It's possible for this to fail; PaintedLayers may need to be updated + * due to VRAM data being lost, for example. In such cases this method + * returns false, and the caller must proceed with a normal layer tree + * update and EndTransaction. + */ + virtual bool EndEmptyTransaction( + EndTransactionFlags aFlags = END_DEFAULT) = 0; + + virtual void Destroy() {} + + /** + * Type of layer manager this is. This is to be used sparsely in order to + * avoid a lot of Layers backend specific code. It should be used only when + * Layers backend specific functionality is necessary. + */ + virtual layers::LayersBackend GetBackendType() = 0; + + /** + * Type of layers backend that will be used to composite this layer tree. + * When compositing is done remotely, then this returns the layers type + * of the compositor. + */ + virtual layers::LayersBackend GetCompositorBackendType() { + return GetBackendType(); + } + + /** + * Checks if we need to invalidate the OS widget to trigger + * painting when updating this renderer. + */ + virtual bool NeedsWidgetInvalidation() { return true; } + + /** + * Make sure that the previous transaction has been entirely + * completed. + * + * Note: This may sychronously wait on a remote compositor + * to complete rendering. + */ + virtual void FlushRendering(wr::RenderReasons aReasons) {} + + /** + * Make sure that the previous transaction has been + * received. This will synchronsly wait on a remote compositor. + */ + virtual void WaitOnTransactionProcessed() {} + + virtual bool IsCompositingCheap() { return true; } + + /** + * returns the maximum texture size on this layer backend, or INT32_MAX + * if there is no maximum + */ + virtual int32_t GetMaxTextureSize() const { return INT32_MAX; } + + /** + * Return the name of the layer manager's backend. + */ + virtual void GetBackendName(nsAString& aName) = 0; + + virtual void GetFrameUniformity(layers::FrameUniformityData* aOutData) {} + + virtual bool AddPendingScrollUpdateForNextTransaction( + layers::ScrollableLayerGuid::ViewID aScrollId, + const ScrollPositionUpdate& aUpdateInfo) { + return false; + } + + /** + * Creates a PersistentBufferProvider for use with canvas which is optimized + * for inter-operating with this layermanager. + */ + virtual already_AddRefed + CreatePersistentBufferProvider(const mozilla::gfx::IntSize& aSize, + mozilla::gfx::SurfaceFormat aFormat); + + // Helper wrappers around cast to impl and then cast again. + + virtual layers::KnowsCompositor* AsKnowsCompositor() { return nullptr; } + + virtual layers::CompositorBridgeChild* GetCompositorBridgeChild() { + return nullptr; + } + + // Provided functionality + + void AddPartialPrerenderedAnimation(uint64_t aCompositorAnimationId, + dom::Animation* aAnimation); + void RemovePartialPrerenderedAnimation(uint64_t aCompositorAnimationId, + dom::Animation* aAnimation); + void UpdatePartialPrerenderedAnimations( + const nsTArray& aJankedAnimations); + + const TimeStamp& GetAnimationReadyTime() const { return mAnimationReadyTime; } + + protected: + virtual ~WindowRenderer() = default; + + // Transform animations which are not fully pre-rendered because it's on a + // large frame. We need to update the pre-rendered area once after we tried + // to composite area which is outside of the pre-rendered area on the + // compositor. + nsRefPtrHashtable + mPartialPrerenderedAnimations; + + // The time when painting most recently finished. This is recorded so that + // we can time any play-pending animations from this point. + TimeStamp mAnimationReadyTime; +}; + +/** + * FallbackRenderer is non-retained renderer that acts as a direct wrapper + * around calling Paint on the provided DisplayList. This is used for cases + * where initializing WebRender is too costly, and we don't need + * retaining/invalidation (like small popup windows). + * + * It doesn't support any sort of EmptyTransaction, and only draws during + * EndTransaction if a composite is requested (no END_NO_COMPOSITE flag + * provided) + */ +class FallbackRenderer : public WindowRenderer { + public: + FallbackRenderer* AsFallback() override { return this; } + + void SetTarget(gfxContext* aContext, layers::BufferMode aDoubleBuffering); + + bool BeginTransaction(const nsCString& aURL = nsCString()) override; + + bool EndEmptyTransaction(EndTransactionFlags aFlags = END_DEFAULT) override { + return false; + } + + layers::LayersBackend GetBackendType() override { + return layers::LayersBackend::LAYERS_NONE; + } + + virtual void GetBackendName(nsAString& name) override { + name.AssignLiteral("Fallback"); + } + + bool IsCompositingCheap() override { return false; } + + void EndTransactionWithColor(const nsIntRect& aRect, + const gfx::DeviceColor& aColor); + void EndTransactionWithList(nsDisplayListBuilder* aBuilder, + nsDisplayList* aList, + int32_t aAppUnitsPerDevPixel, + EndTransactionFlags aFlags); + + gfxContext* mTarget; + layers::BufferMode mBufferMode; +}; + +} // namespace mozilla + +#endif /* MOZILLA_PAINTING_WINDOWRENDERER_H */ diff --git a/layout/painting/crashtests/1402183-1.html b/layout/painting/crashtests/1402183-1.html new file mode 100644 index 0000000000..ea54512894 --- /dev/null +++ b/layout/painting/crashtests/1402183-1.html @@ -0,0 +1,20 @@ + + + +Bug 1402183 + + + + +

+r6O#i/q] +

+
+ + 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 @@ + +
+
+
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 @@ + + \ 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 @@ + + +
+
+
+ + + 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 @@ + + +
+
+
+
+
+
+ + + 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 @@ + + + + + + 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 @@ + + +
+
+
+
+ + + 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 @@ + + + + + + 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 @@ + + + + + + + + + +
+
+ +
+
+
Text
+
+
+
+
+ + + + + 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 @@ + + + + + +
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 @@ + + + + + + +
+ +
+ + + + + +
+ + + +
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 @@ + + + + + + + + + +
+
+
+ + 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 @@ + + + + + + + + +
+ +
+ + + 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 @@ + + +
#_k
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 @@ + +> + 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 @@ + + + + +| +