From 15674fd75a6215d9a3c67f475ea73af1f52063d7 Mon Sep 17 00:00:00 2001 From: longsonr Date: Thu, 7 Nov 2024 11:03:46 +0000 Subject: [PATCH] Bug 1734476 - Don't run on compositor when content may contain non-scaling-stroke r=emilio Differential Revision: https://phabricator.services.mozilla.com/D227400 --- dom/animation/AnimationPerformanceWarning.cpp | 3 +++ dom/animation/AnimationPerformanceWarning.h | 1 + dom/animation/KeyframeEffect.cpp | 9 ++++++++ dom/animation/test/mozilla/file_restyles.html | 21 +++++++++++++++++++ .../chrome/layout/layout_errors.properties | 4 +++- layout/generic/nsBlockFrame.cpp | 9 ++++++++ layout/generic/nsFrameStateBits.h | 4 ++++ layout/svg/SVGGeometryFrame.cpp | 5 +++++ layout/svg/SVGTextFrame.cpp | 9 ++++++++ layout/svg/SVGTextFrame.h | 2 ++ layout/svg/SVGUtils.cpp | 15 +++++++++++++ layout/svg/SVGUtils.h | 6 ++++++ 12 files changed, 87 insertions(+), 1 deletion(-) diff --git a/dom/animation/AnimationPerformanceWarning.cpp b/dom/animation/AnimationPerformanceWarning.cpp index 25ee1b529cc7..4cc466d91105 100644 --- a/dom/animation/AnimationPerformanceWarning.cpp +++ b/dom/animation/AnimationPerformanceWarning.cpp @@ -41,6 +41,9 @@ bool AnimationPerformanceWarning::ToLocalizedString( return NS_SUCCEEDED(ToLocalizedStringWithIntParams<2>( "CompositorAnimationWarningContentTooLargeArea", aLocalizedString)); + case Type::NonScalingStroke: + key = "CompositorAnimationWarningNonScalingStroke"; + break; case Type::TransformSVG: key = "CompositorAnimationWarningTransformSVG"; break; diff --git a/dom/animation/AnimationPerformanceWarning.h b/dom/animation/AnimationPerformanceWarning.h index 4e37f6c59a0a..637bd96973a7 100644 --- a/dom/animation/AnimationPerformanceWarning.h +++ b/dom/animation/AnimationPerformanceWarning.h @@ -21,6 +21,7 @@ struct AnimationPerformanceWarning { None, ContentTooLarge, ContentTooLargeArea, + NonScalingStroke, TransformSVG, TransformFrameInactive, TransformIsBlockedByImportantRules, diff --git a/dom/animation/KeyframeEffect.cpp b/dom/animation/KeyframeEffect.cpp index 9fb979cfccbd..0e8e9205ac59 100644 --- a/dom/animation/KeyframeEffect.cpp +++ b/dom/animation/KeyframeEffect.cpp @@ -1632,6 +1632,15 @@ bool KeyframeEffect::CanAnimateTransformOnCompositor( return false; } + // If there's any content that might have non-scaling stroke then we can't + // run in the compositor. + if (primaryFrame->IsSVGFrame() && + primaryFrame->HasAnyStateBits( + NS_STATE_SVG_MAY_CONTAIN_NON_SCALING_STROKE)) { + aPerformanceWarning = AnimationPerformanceWarning::Type::NonScalingStroke; + return false; + } + return true; } diff --git a/dom/animation/test/mozilla/file_restyles.html b/dom/animation/test/mozilla/file_restyles.html index 15d097862845..7a44d8612fbb 100644 --- a/dom/animation/test/mozilla/file_restyles.html +++ b/dom/animation/test/mozilla/file_restyles.html @@ -1377,6 +1377,27 @@ waitForAllPaints(async () => { await ensureElementRemoval(div); }); + add_task_if_omta_enabled(async function svg_non_scaling_stroke_animation() { + const div = addDiv(null, { style: 'overflow: scroll;' + + 'height: 100px; width: 100px;' }); + const svg = addSVGElement(div, 'svg', { viewBox: '0 0 250 250', + width: '40px', + height: '40px' }); + const rect = addSVGElement(svg, 'rect', { x: '0', + y: '0', + width: '250', + height: '250', + fill: 'red', + style: 'vector-effect: non-scaling-stroke; animation: rotate 100s infinite;'}); + const animation = rect.getAnimations()[0]; + await waitForAnimationReadyToRestyle(animation); + + ok(!SpecialPowers.wrap(animation).isRunningOnCompositor, + 'The animation of a non-scaling-stroke element is not running on the compositor'); + + await ensureElementRemoval(div); + }); + add_task(async function no_throttling_animations_in_transformed_parent() { const div = addDiv(null, { style: 'overflow: scroll;' + 'transform: translateX(50px);' }); diff --git a/dom/locales/en-US/chrome/layout/layout_errors.properties b/dom/locales/en-US/chrome/layout/layout_errors.properties index 7d77523a28b5..43aa84d406c4 100644 --- a/dom/locales/en-US/chrome/layout/layout_errors.properties +++ b/dom/locales/en-US/chrome/layout/layout_errors.properties @@ -19,10 +19,12 @@ CompositorAnimationWarningContentTooLargeArea=Animation cannot be run on the com ## (%3$S, %4$S) is a pair of integer values of a limit based on the viewport size ## (%5$S, %6$S) is a pair of integer values of an absolute limit CompositorAnimationWarningContentTooLarge2=Animation cannot be run on the compositor because the frame size (%1$S, %2$S) is too large relative to the viewport (larger than (%3$S, %4$S)) or larger than the maximum allowed value (%5$S, %6$S) -## LOCALIZATION NOTE(CompositorAnimationWarningTransformSVG, +## LOCALIZATION NOTE(CompositorAnimationWarningNonScalingStroke, +## CompositorAnimationWarningTransformSVG, ## CompositorAnimationWarningTransformFrameInactive, ## CompositorAnimationWarningOpacityFrameInactive): ## 'transform' and 'opacity' mean CSS property names, don't translate it. +CompositorAnimationWarningNonScalingStroke=Animations of ‘transform’ on content containing non-scaling-stroke elements cannot be run on the compositor CompositorAnimationWarningTransformSVG=Animations of ‘transform’ on elements with SVG transforms cannot be run on the compositor CompositorAnimationWarningTransformFrameInactive=Animation cannot be run on the compositor because the frame was not marked active for ‘transform’ animation CompositorAnimationWarningTransformIsBlockedByImportantRules=Transform animation cannot be run on the compositor because transform-related properties are overridden by !important rules diff --git a/layout/generic/nsBlockFrame.cpp b/layout/generic/nsBlockFrame.cpp index 7a5a2dca103b..c18e687208fb 100644 --- a/layout/generic/nsBlockFrame.cpp +++ b/layout/generic/nsBlockFrame.cpp @@ -22,6 +22,7 @@ #include "mozilla/ScrollContainerFrame.h" #include "mozilla/StaticPrefs_browser.h" #include "mozilla/StaticPrefs_layout.h" +#include "mozilla/SVGUtils.h" #include "mozilla/ToString.h" #include "mozilla/UniquePtr.h" @@ -6681,6 +6682,14 @@ static bool EstablishesBFC(const nsBlockFrame* aFrame) { void nsBlockFrame::DidSetComputedStyle(ComputedStyle* aOldStyle) { nsContainerFrame::DidSetComputedStyle(aOldStyle); + if (IsInSVGTextSubtree() && + (StyleSVGReset()->HasNonScalingStroke() && + (!aOldStyle || !aOldStyle->StyleSVGReset()->HasNonScalingStroke()))) { + nsIFrame* textFrame = + nsLayoutUtils::GetClosestFrameOfType(this, LayoutFrameType::SVGText); + MOZ_ASSERT(textFrame, "Expecting to find an SVG text frame"); + SVGUtils::UpdateNonScalingStrokeStateBit(textFrame); + } if (!aOldStyle) { return; } diff --git a/layout/generic/nsFrameStateBits.h b/layout/generic/nsFrameStateBits.h index e8362ae317c2..3fac8576d05e 100644 --- a/layout/generic/nsFrameStateBits.h +++ b/layout/generic/nsFrameStateBits.h @@ -437,6 +437,10 @@ FRAME_STATE_BIT(SVG, 23, NS_STATE_SVG_TEXT_IN_REFLOW) // to update the cached nsTextNode indexes that they correspond to. FRAME_STATE_BIT(SVG, 24, NS_STATE_SVG_TEXT_CORRESPONDENCE_DIRTY) +// Set on svg frames when they or their descendants may contain non-scaling +// stroke contents. +FRAME_STATE_BIT(SVG, 25, NS_STATE_SVG_MAY_CONTAIN_NON_SCALING_STROKE) + // == Frame state bits that apply to text frames ============================== FRAME_STATE_GROUP(Text, nsTextFrame) diff --git a/layout/svg/SVGGeometryFrame.cpp b/layout/svg/SVGGeometryFrame.cpp index 6d8fd34be03d..91bd374a5878 100644 --- a/layout/svg/SVGGeometryFrame.cpp +++ b/layout/svg/SVGGeometryFrame.cpp @@ -84,6 +84,11 @@ nsresult SVGGeometryFrame::AttributeChanged(int32_t aNameSpaceID, /* virtual */ void SVGGeometryFrame::DidSetComputedStyle(ComputedStyle* aOldComputedStyle) { nsIFrame::DidSetComputedStyle(aOldComputedStyle); + if (StyleSVGReset()->HasNonScalingStroke() && + (!aOldComputedStyle || + !aOldComputedStyle->StyleSVGReset()->HasNonScalingStroke())) { + SVGUtils::UpdateNonScalingStrokeStateBit(this); + } auto* element = static_cast(GetContent()); if (!aOldComputedStyle) { element->ClearAnyCachedPath(); diff --git a/layout/svg/SVGTextFrame.cpp b/layout/svg/SVGTextFrame.cpp index 5129e1007e50..7b280f9ec7f6 100644 --- a/layout/svg/SVGTextFrame.cpp +++ b/layout/svg/SVGTextFrame.cpp @@ -2787,6 +2787,15 @@ void SVGTextFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, aLists.Content()->AppendNewToTop(aBuilder, this); } +void SVGTextFrame::DidSetComputedStyle(ComputedStyle* aOldComputedStyle) { + SVGDisplayContainerFrame::DidSetComputedStyle(aOldComputedStyle); + if (StyleSVGReset()->HasNonScalingStroke() && + (!aOldComputedStyle || + !aOldComputedStyle->StyleSVGReset()->HasNonScalingStroke())) { + SVGUtils::UpdateNonScalingStrokeStateBit(this); + } +} + nsresult SVGTextFrame::AttributeChanged(int32_t aNameSpaceID, nsAtom* aAttribute, int32_t aModType) { if (aNameSpaceID != kNameSpaceID_None) { diff --git a/layout/svg/SVGTextFrame.h b/layout/svg/SVGTextFrame.h index c55d97a1868a..aacb0c67e20c 100644 --- a/layout/svg/SVGTextFrame.h +++ b/layout/svg/SVGTextFrame.h @@ -199,6 +199,8 @@ class SVGTextFrame final : public SVGDisplayContainerFrame { void Init(nsIContent* aContent, nsContainerFrame* aParent, nsIFrame* aPrevInFlow) override; + void DidSetComputedStyle(ComputedStyle* aOldComputedStyle) override; + nsresult AttributeChanged(int32_t aNamespaceID, nsAtom* aAttribute, int32_t aModType) override; diff --git a/layout/svg/SVGUtils.cpp b/layout/svg/SVGUtils.cpp index 837df8bd2fc5..ed1a7d7cabcf 100644 --- a/layout/svg/SVGUtils.cpp +++ b/layout/svg/SVGUtils.cpp @@ -1071,6 +1071,21 @@ bool SVGUtils::GetNonScalingStrokeTransform(const nsIFrame* aFrame, return aUserToOuterSVG->HasNonTranslation() && !aUserToOuterSVG->IsSingular(); } +void SVGUtils::UpdateNonScalingStrokeStateBit(nsIFrame* aFrame) { + MOZ_ASSERT(aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT), + "Called on invalid frame type"); + MOZ_ASSERT(aFrame->StyleSVGReset()->HasNonScalingStroke(), + "Expecting initial frame to have non-scaling-stroke style"); + + do { + MOZ_ASSERT(aFrame->IsSVGFrame(), "Unexpected frame type"); + aFrame->AddStateBits(NS_STATE_SVG_MAY_CONTAIN_NON_SCALING_STROKE); + if (aFrame->IsSVGOuterSVGFrame()) { + return; + } + } while (aFrame = aFrame->GetParent()); +} + // The logic here comes from _cairo_stroke_style_max_distance_from_path static gfxRect PathExtentsToMaxStrokeExtents(const gfxRect& aPathExtents, const nsIFrame* aFrame, diff --git a/layout/svg/SVGUtils.h b/layout/svg/SVGUtils.h index d61972a9e1f5..089632af5b8d 100644 --- a/layout/svg/SVGUtils.h +++ b/layout/svg/SVGUtils.h @@ -422,6 +422,12 @@ class SVGUtils final { static bool GetNonScalingStrokeTransform(const nsIFrame* aFrame, gfxMatrix* aUserToOuterSVG); + /** + * We need to track whether content has non-scaling-stroke because we can't + * asynchronously animate it with a scaling transform. + */ + static void UpdateNonScalingStrokeStateBit(nsIFrame* aFrame); + /** * Compute the maximum possible device space stroke extents of a path given * the path's device space path extents, its stroke style and its ctm.