diff options
Diffstat (limited to 'chromium/cc/trees')
46 files changed, 3570 insertions, 1077 deletions
diff --git a/chromium/cc/trees/damage_tracker_unittest.cc b/chromium/cc/trees/damage_tracker_unittest.cc index cc784ab1213..db9fcc1fcf6 100644 --- a/chromium/cc/trees/damage_tracker_unittest.cc +++ b/chromium/cc/trees/damage_tracker_unittest.cc @@ -236,6 +236,23 @@ class DamageTrackerTest : public LayerTreeImplTestBase, public testing::Test { return root; } + LayerImpl* CreateAndSetUpTestTreeWithTwoSurfacesDrawingFullyVisible() { + LayerImpl* root = CreateTestTreeWithTwoSurfaces(); + // Make sure render surface takes content outside visible rect into + // consideration. + root->layer_tree_impl() + ->property_trees() + ->effect_tree.Node(child1_->effect_tree_index()) + ->backdrop_filters.Append( + FilterOperation::CreateZoomFilter(2.f /* zoom */, 0 /* inset */)); + + // Setup includes going past the first frame which always damages + // everything, so that we can actually perform specific tests. + EmulateDrawingOneFrame(root); + + return root; + } + LayerImpl* CreateAndSetUpTestTreeWithFourSurfaces() { LayerImpl* root = CreateTestTreeWithFourSurfaces(); @@ -1833,7 +1850,7 @@ TEST_F(DamageTrackerTest, DamageRectTooBigWithFilter) { } TEST_F(DamageTrackerTest, DamageRectTooBigInRenderSurface) { - LayerImpl* root = CreateAndSetUpTestTreeWithTwoSurfaces(); + LayerImpl* root = CreateAndSetUpTestTreeWithTwoSurfacesDrawingFullyVisible(); // Really far left. grand_child1_->SetOffsetToTransformParent( diff --git a/chromium/cc/trees/draw_properties_unittest.cc b/chromium/cc/trees/draw_properties_unittest.cc index 6abe4863df5..b19a402d142 100644 --- a/chromium/cc/trees/draw_properties_unittest.cc +++ b/chromium/cc/trees/draw_properties_unittest.cc @@ -12,6 +12,7 @@ #include "base/memory/ptr_util.h" #include "base/stl_util.h" +#include "base/test/scoped_feature_list.h" #include "cc/animation/animation.h" #include "cc/animation/animation_host.h" #include "cc/animation/animation_id_provider.h" @@ -1415,10 +1416,13 @@ TEST_F(DrawPropertiesTest, DrawableContentRectForLayers) { UpdateActiveTreeDrawProperties(); - EXPECT_EQ(gfx::Rect(5, 5, 10, 10), grand_child1->drawable_content_rect()); - EXPECT_EQ(gfx::Rect(15, 15, 5, 5), grand_child3->drawable_content_rect()); - EXPECT_EQ(gfx::Rect(15, 15, 5, 5), grand_child3->drawable_content_rect()); - EXPECT_TRUE(grand_child4->drawable_content_rect().IsEmpty()); + EXPECT_EQ(gfx::Rect(5, 5, 10, 10), + grand_child1->visible_drawable_content_rect()); + EXPECT_EQ(gfx::Rect(15, 15, 5, 5), + grand_child3->visible_drawable_content_rect()); + EXPECT_EQ(gfx::Rect(15, 15, 5, 5), + grand_child3->visible_drawable_content_rect()); + EXPECT_TRUE(grand_child4->visible_drawable_content_rect().IsEmpty()); } TEST_F(DrawPropertiesTest, ClipRectIsPropagatedCorrectlyToSurfaces) { @@ -1775,7 +1779,7 @@ TEST_F(DrawPropertiesDrawRectsTest, DrawRectsForIdentityTransform) { target_surface_rect, layer_to_surface_transform, layer_content_rect); EXPECT_EQ(expected_visible_layer_rect, drawing_layer->visible_layer_rect()); EXPECT_EQ(expected_drawable_content_rect, - drawing_layer->drawable_content_rect()); + drawing_layer->visible_drawable_content_rect()); // Case 2: Layer is outside the surface rect. layer_content_rect = gfx::Rect(120, 120, 30, 30); @@ -1785,7 +1789,7 @@ TEST_F(DrawPropertiesDrawRectsTest, DrawRectsForIdentityTransform) { target_surface_rect, layer_to_surface_transform, layer_content_rect); EXPECT_EQ(expected_visible_layer_rect, drawing_layer->visible_layer_rect()); EXPECT_EQ(expected_drawable_content_rect, - drawing_layer->drawable_content_rect()); + drawing_layer->visible_drawable_content_rect()); // Case 3: Layer is partially overlapping the surface rect. layer_content_rect = gfx::Rect(80, 80, 30, 30); @@ -1795,7 +1799,7 @@ TEST_F(DrawPropertiesDrawRectsTest, DrawRectsForIdentityTransform) { target_surface_rect, layer_to_surface_transform, layer_content_rect); EXPECT_EQ(expected_visible_layer_rect, drawing_layer->visible_layer_rect()); EXPECT_EQ(expected_drawable_content_rect, - drawing_layer->drawable_content_rect()); + drawing_layer->visible_drawable_content_rect()); } // Test visible layer rect and drawable content rect are calculated correctly @@ -1815,7 +1819,7 @@ TEST_F(DrawPropertiesDrawRectsTest, DrawRectsFor2DRotations) { target_surface_rect, layer_to_surface_transform, layer_content_rect); EXPECT_EQ(expected_visible_layer_rect, drawing_layer->visible_layer_rect()); EXPECT_EQ(expected_drawable_content_rect, - drawing_layer->drawable_content_rect()); + drawing_layer->visible_drawable_content_rect()); // Case 2: Layer is outside the surface rect. layer_to_surface_transform.MakeIdentity(); @@ -1827,7 +1831,7 @@ TEST_F(DrawPropertiesDrawRectsTest, DrawRectsFor2DRotations) { target_surface_rect, layer_to_surface_transform, layer_content_rect); EXPECT_EQ(expected_visible_layer_rect, drawing_layer->visible_layer_rect()); EXPECT_EQ(expected_drawable_content_rect, - drawing_layer->drawable_content_rect()); + drawing_layer->visible_drawable_content_rect()); // Case 3: The layer is rotated about its top-left corner. In surface space, // the layer is oriented diagonally, with the left half outside of the render @@ -1842,7 +1846,7 @@ TEST_F(DrawPropertiesDrawRectsTest, DrawRectsFor2DRotations) { target_surface_rect, layer_to_surface_transform, layer_content_rect); EXPECT_EQ(expected_visible_layer_rect, drawing_layer->visible_layer_rect()); EXPECT_EQ(expected_drawable_content_rect, - drawing_layer->drawable_content_rect()); + drawing_layer->visible_drawable_content_rect()); // Case 4: The layer is rotated about its top-left corner, and translated // upwards. In surface space, the layer is oriented diagonally, with only the @@ -1859,7 +1863,7 @@ TEST_F(DrawPropertiesDrawRectsTest, DrawRectsFor2DRotations) { target_surface_rect, layer_to_surface_transform, layer_content_rect); EXPECT_EQ(expected_visible_layer_rect, drawing_layer->visible_layer_rect()); EXPECT_EQ(expected_drawable_content_rect, - drawing_layer->drawable_content_rect()); + drawing_layer->visible_drawable_content_rect()); } // Test visible layer rect and drawable content rect are calculated correctly @@ -1880,7 +1884,7 @@ TEST_F(DrawPropertiesDrawRectsTest, DrawRectsFor3dOrthographicTransform) { target_surface_rect, layer_to_surface_transform, layer_content_rect); EXPECT_EQ(expected_visible_layer_rect, drawing_layer->visible_layer_rect()); EXPECT_EQ(expected_drawable_content_rect, - drawing_layer->drawable_content_rect()); + drawing_layer->visible_drawable_content_rect()); // Case 2: Orthographic projection of a layer rotated about y-axis by 45 // degrees, but shifted to the side so only the right-half the layer would be @@ -1899,7 +1903,7 @@ TEST_F(DrawPropertiesDrawRectsTest, DrawRectsFor3dOrthographicTransform) { target_surface_rect, layer_to_surface_transform, layer_content_rect); EXPECT_EQ(expected_visible_layer_rect, drawing_layer->visible_layer_rect()); EXPECT_EQ(expected_drawable_content_rect, - drawing_layer->drawable_content_rect()); + drawing_layer->visible_drawable_content_rect()); } // Test visible layer rect and drawable content rect are calculated correctly @@ -1931,7 +1935,7 @@ TEST_F(DrawPropertiesDrawRectsTest, DrawRectsFor3dPerspectiveTransform) { target_surface_rect, layer_to_surface_transform, layer_content_rect); EXPECT_EQ(expected_visible_layer_rect, drawing_layer->visible_layer_rect()); EXPECT_EQ(expected_drawable_content_rect, - drawing_layer->drawable_content_rect()); + drawing_layer->visible_drawable_content_rect()); // Case 2: same projection as before, except that the layer is also translated // to the side, so that only the right half of the layer should be visible. @@ -1951,7 +1955,7 @@ TEST_F(DrawPropertiesDrawRectsTest, DrawRectsFor3dPerspectiveTransform) { target_surface_rect, layer_to_surface_transform, layer_content_rect); EXPECT_EQ(expected_visible_layer_rect, drawing_layer->visible_layer_rect()); EXPECT_EQ(expected_drawable_content_rect, - drawing_layer->drawable_content_rect()); + drawing_layer->visible_drawable_content_rect()); } // There is currently no explicit concept of an orthographic projection plane @@ -1979,7 +1983,7 @@ TEST_F(DrawPropertiesDrawRectsTest, target_surface_rect, layer_to_surface_transform, layer_content_rect); EXPECT_EQ(expected_visible_layer_rect, drawing_layer->visible_layer_rect()); EXPECT_EQ(expected_drawable_content_rect, - drawing_layer->drawable_content_rect()); + drawing_layer->visible_drawable_content_rect()); } // Test visible layer rect and drawable content rect are calculated correctly @@ -2018,7 +2022,7 @@ TEST_F(DrawPropertiesDrawRectsTest, DrawRectsFor3dPerspectiveWhenClippedByW) { target_surface_rect, layer_to_surface_transform, layer_content_rect); EXPECT_EQ(expected_visible_layer_rect, drawing_layer->visible_layer_rect()); EXPECT_EQ(expected_drawable_content_rect, - drawing_layer->drawable_content_rect()); + drawing_layer->visible_drawable_content_rect()); } static bool ProjectionClips(const gfx::Transform& map_transform, @@ -2070,7 +2074,7 @@ TEST_F(DrawPropertiesDrawRectsTest, DrawRectsForPerspectiveUnprojection) { target_surface_rect, layer_to_surface_transform, layer_content_rect); EXPECT_EQ(expected_visible_layer_rect, drawing_layer->visible_layer_rect()); EXPECT_EQ(expected_drawable_content_rect, - drawing_layer->drawable_content_rect()); + drawing_layer->visible_drawable_content_rect()); } TEST_F(DrawPropertiesTest, DrawableAndVisibleContentRectsForSimpleLayers) { @@ -2106,10 +2110,14 @@ TEST_F(DrawPropertiesTest, DrawableAndVisibleContentRectsForSimpleLayers) { EXPECT_EQ(gfx::Rect(0, 0, 25, 25), child2_layer->visible_layer_rect()); EXPECT_TRUE(child3_layer->visible_layer_rect().IsEmpty()); - // layer drawable_content_rects are not clipped. - EXPECT_EQ(gfx::Rect(0, 0, 50, 50), child1_layer->drawable_content_rect()); - EXPECT_EQ(gfx::Rect(75, 75, 50, 50), child2_layer->drawable_content_rect()); - EXPECT_EQ(gfx::Rect(125, 125, 50, 50), child3_layer->drawable_content_rect()); + // layer visible_drawable_content_rects are in target space, but still only + // the visible part. + EXPECT_EQ(gfx::Rect(0, 0, 50, 50), + child1_layer->visible_drawable_content_rect()); + EXPECT_EQ(gfx::Rect(75, 75, 25, 25), + child2_layer->visible_drawable_content_rect()); + EXPECT_EQ(gfx::Rect(125, 125, 0, 0), + child3_layer->visible_drawable_content_rect()); } TEST_F(DrawPropertiesTest, @@ -2153,9 +2161,11 @@ TEST_F(DrawPropertiesTest, EXPECT_TRUE(grand_child3->visible_layer_rect().IsEmpty()); // All grandchild DrawableContentRects should also be clipped by child. - EXPECT_EQ(gfx::Rect(5, 5, 50, 50), grand_child1->drawable_content_rect()); - EXPECT_EQ(gfx::Rect(75, 75, 25, 25), grand_child2->drawable_content_rect()); - EXPECT_TRUE(grand_child3->drawable_content_rect().IsEmpty()); + EXPECT_EQ(gfx::Rect(5, 5, 50, 50), + grand_child1->visible_drawable_content_rect()); + EXPECT_EQ(gfx::Rect(75, 75, 25, 25), + grand_child2->visible_drawable_content_rect()); + EXPECT_TRUE(grand_child3->visible_drawable_content_rect().IsEmpty()); } TEST_F(DrawPropertiesTest, VisibleContentRectWithClippingAndScaling) { @@ -2286,7 +2296,7 @@ TEST_F(DrawPropertiesTest, // An unclipped surface grows its DrawableContentRect to include all drawable // regions of the subtree. - EXPECT_EQ(gfx::RectF(5.f, 5.f, 170.f, 170.f), + EXPECT_EQ(gfx::RectF(5.f, 5.f, 95.f, 95.f), GetRenderSurface(render_surface)->DrawableContentRect()); // All layers that draw content into the unclipped surface are also unclipped. @@ -2295,9 +2305,9 @@ TEST_F(DrawPropertiesTest, EXPECT_EQ(gfx::Rect(0, 0, 25, 25), child2->visible_layer_rect()); EXPECT_EQ(gfx::Rect(0, 0, 0, 0), child3->visible_layer_rect()); - EXPECT_EQ(gfx::Rect(5, 5, 50, 50), child1->drawable_content_rect()); - EXPECT_EQ(gfx::Rect(75, 75, 50, 50), child2->drawable_content_rect()); - EXPECT_EQ(gfx::Rect(125, 125, 50, 50), child3->drawable_content_rect()); + EXPECT_EQ(gfx::Rect(5, 5, 50, 50), child1->visible_drawable_content_rect()); + EXPECT_EQ(gfx::Rect(75, 75, 25, 25), child2->visible_drawable_content_rect()); + EXPECT_EQ(gfx::Rect(125, 125, 0, 0), child3->visible_drawable_content_rect()); } TEST_F(DrawPropertiesTest, VisibleContentRectsForClippedSurfaceWithEmptyClip) { @@ -2361,7 +2371,7 @@ TEST_F(DrawPropertiesTest, UpdateActiveTreeDrawProperties(); EXPECT_TRUE(child->visible_layer_rect().IsEmpty()); - EXPECT_TRUE(child->drawable_content_rect().IsEmpty()); + EXPECT_TRUE(child->visible_drawable_content_rect().IsEmpty()); // Case 2: a matrix with flattened z, uninvertible and not visible according // to the CSS spec. @@ -2373,7 +2383,7 @@ TEST_F(DrawPropertiesTest, UpdateActiveTreeDrawProperties(); EXPECT_TRUE(child->visible_layer_rect().IsEmpty()); - EXPECT_TRUE(child->drawable_content_rect().IsEmpty()); + EXPECT_TRUE(child->visible_drawable_content_rect().IsEmpty()); // Case 3: a matrix with flattened z, also uninvertible and not visible. uninvertible_matrix.MakeIdentity(); @@ -2385,7 +2395,7 @@ TEST_F(DrawPropertiesTest, UpdateActiveTreeDrawProperties(); EXPECT_TRUE(child->visible_layer_rect().IsEmpty()); - EXPECT_TRUE(child->drawable_content_rect().IsEmpty()); + EXPECT_TRUE(child->visible_drawable_content_rect().IsEmpty()); } TEST_F(DrawPropertiesTest, @@ -2592,10 +2602,11 @@ TEST_F(DrawPropertiesTest, EXPECT_EQ(gfx::Rect(0, 0, 25, 25), child2->visible_layer_rect()); EXPECT_TRUE(child3->visible_layer_rect().IsEmpty()); - // But the DrawableContentRects are unclipped. - EXPECT_EQ(gfx::Rect(5, 5, 50, 50), child1->drawable_content_rect()); - EXPECT_EQ(gfx::Rect(75, 75, 50, 50), child2->drawable_content_rect()); - EXPECT_EQ(gfx::Rect(125, 125, 50, 50), child3->drawable_content_rect()); + // The visible_drawable_content_rect would be the visible rect in target + // space. + EXPECT_EQ(gfx::Rect(5, 5, 50, 50), child1->visible_drawable_content_rect()); + EXPECT_EQ(gfx::Rect(75, 75, 25, 25), child2->visible_drawable_content_rect()); + EXPECT_EQ(gfx::Rect(125, 125, 0, 0), child3->visible_drawable_content_rect()); } // Check that clipping does not propagate down surfaces. @@ -2651,8 +2662,9 @@ TEST_F(DrawPropertiesTest, DrawableAndVisibleContentRectsForSurfaceHierarchy) { // render_surface1 lives in the "unclipped universe" of render_surface1, and // is only implicitly clipped by render_surface1's content rect. So, - // render_surface2 grows to enclose all drawable content of its subtree. - EXPECT_EQ(gfx::RectF(5.f, 5.f, 170.f, 170.f), + // render_surface2 grows to enclose all visible drawable content of its + // subtree. + EXPECT_EQ(gfx::RectF(5.f, 5.f, 95.f, 95.f), GetRenderSurface(render_surface2)->DrawableContentRect()); // All layers that draw content into render_surface2 think they are unclipped @@ -2662,9 +2674,9 @@ TEST_F(DrawPropertiesTest, DrawableAndVisibleContentRectsForSurfaceHierarchy) { EXPECT_EQ(gfx::Rect(0, 0, 0, 0), child3->visible_layer_rect()); // DrawableContentRects are also unclipped. - EXPECT_EQ(gfx::Rect(5, 5, 50, 50), child1->drawable_content_rect()); - EXPECT_EQ(gfx::Rect(75, 75, 50, 50), child2->drawable_content_rect()); - EXPECT_EQ(gfx::Rect(125, 125, 50, 50), child3->drawable_content_rect()); + EXPECT_EQ(gfx::Rect(5, 5, 50, 50), child1->visible_drawable_content_rect()); + EXPECT_EQ(gfx::Rect(75, 75, 25, 25), child2->visible_drawable_content_rect()); + EXPECT_EQ(gfx::Rect(125, 125, 0, 0), child3->visible_drawable_content_rect()); } TEST_F(DrawPropertiesTest, @@ -2943,7 +2955,8 @@ TEST_F(DrawPropertiesTest, // All layers that draw content into the unclipped surface are also unclipped. EXPECT_EQ(gfx::Rect(0, 0, 50, 50), child1->visible_layer_rect()); - EXPECT_EQ(expected_surface_drawable_content, child1->drawable_content_rect()); + EXPECT_EQ(expected_surface_drawable_content, + child1->visible_drawable_content_rect()); } // Layers that have non-axis aligned bounds (due to transforms) have an @@ -2967,6 +2980,14 @@ TEST_F(DrawPropertiesTest, CreateEffectNode(render_surface).render_surface_reason = RenderSurfaceReason::kTest; CopyProperties(render_surface, child1); + // Make sure render surface takes content outside visible rect into + // consideration. + root->layer_tree_impl() + ->property_trees() + ->effect_tree.Node(child1->effect_tree_index()) + ->backdrop_filters.Append( + FilterOperation::CreateZoomFilter(1.f /* zoom */, 0 /* inset */)); + auto& child1_transform_node = CreateTransformNode(child1); child1_transform_node.origin = gfx::Point3F(25.f, 25.f, 0.f); child1_transform_node.post_translation = gfx::Vector2dF(25.f, 25.f); @@ -2993,7 +3014,7 @@ TEST_F(DrawPropertiesTest, EXPECT_EQ(gfx::Rect(0, 0, 25, 50), child1->visible_layer_rect()); // The child's DrawableContentRect is unclipped. - EXPECT_EQ(unclipped_surface_content, child1->drawable_content_rect()); + EXPECT_EQ(unclipped_surface_content, child1->visible_drawable_content_rect()); } TEST_F(DrawPropertiesTest, DrawableAndVisibleContentRectsInHighDPI) { @@ -3048,13 +3069,16 @@ TEST_F(DrawPropertiesTest, DrawableAndVisibleContentRectsInHighDPI) { GetRenderSurface(render_surface1)->DrawableContentRect()); // render_surface2 lives in the "unclipped universe" of render_surface1, and - // is only implicitly clipped by render_surface1. - EXPECT_EQ(gfx::RectF(10.f, 10.f, 350.f, 350.f), + // is only implicitly clipped by render_surface1, though it would only contain + // the visible content. + EXPECT_EQ(gfx::RectF(10.f, 10.f, 180.f, 180.f), GetRenderSurface(render_surface2)->DrawableContentRect()); - EXPECT_EQ(gfx::Rect(10, 10, 100, 100), child1->drawable_content_rect()); - EXPECT_EQ(gfx::Rect(150, 150, 100, 100), child2->drawable_content_rect()); - EXPECT_EQ(gfx::Rect(250, 250, 100, 100), child3->drawable_content_rect()); + EXPECT_EQ(gfx::Rect(10, 10, 100, 100), + child1->visible_drawable_content_rect()); + EXPECT_EQ(gfx::Rect(150, 150, 30, 30), + child2->visible_drawable_content_rect()); + EXPECT_EQ(gfx::Rect(250, 250, 0, 0), child3->visible_drawable_content_rect()); // The root layer does not actually draw content of its own. EXPECT_EQ(gfx::Rect(0, 0, 0, 0), root->visible_layer_rect()); @@ -3400,8 +3424,8 @@ TEST_F(DrawPropertiesTest, RenderSurfaceTransformsInHighDPI) { EXPECT_TRANSFORMATION_MATRIX_EQ( child->ScreenSpaceTransform(), duplicate_child_non_owner->ScreenSpaceTransform()); - EXPECT_EQ(child->drawable_content_rect(), - duplicate_child_non_owner->drawable_content_rect()); + EXPECT_EQ(child->visible_drawable_content_rect(), + duplicate_child_non_owner->visible_drawable_content_rect()); EXPECT_EQ(child->bounds(), duplicate_child_non_owner->bounds()); gfx::Transform expected_render_surface_draw_transform; @@ -3552,6 +3576,74 @@ TEST_F(DrawPropertiesTest, OpacityAnimatingOnPendingTree) { active_child->effect_tree_index())); } +class TransformInteropTest : public DrawPropertiesTestBase, + public testing::Test { + public: + TransformInteropTest() : DrawPropertiesTestBase(TransformInteropSettings()) {} + + protected: + LayerTreeSettings TransformInteropSettings() { + LayerListSettings settings; + + settings.enable_transform_interop = true; + return settings; + } +}; + +TEST_F(TransformInteropTest, BackfaceInvisibleTransform) { + LayerImpl* root = root_layer(); + root->SetDrawsContent(true); + LayerImpl* back_facing = AddLayer<LayerImpl>(); + LayerImpl* back_facing_double_sided = AddLayer<LayerImpl>(); + LayerImpl* front_facing = AddLayer<LayerImpl>(); + back_facing->SetDrawsContent(true); + back_facing_double_sided->SetDrawsContent(true); + front_facing->SetDrawsContent(true); + + root->SetBounds(gfx::Size(50, 50)); + back_facing->SetBounds(gfx::Size(50, 50)); + back_facing_double_sided->SetBounds(gfx::Size(50, 50)); + front_facing->SetBounds(gfx::Size(50, 50)); + CopyProperties(root, back_facing); + CopyProperties(root, front_facing); + + back_facing->SetShouldCheckBackfaceVisibility(true); + back_facing_double_sided->SetShouldCheckBackfaceVisibility(false); + front_facing->SetShouldCheckBackfaceVisibility(true); + + auto& back_facing_transform_node = CreateTransformNode(back_facing); + back_facing_transform_node.flattens_inherited_transform = false; + back_facing_transform_node.sorting_context_id = 1; + gfx::Transform rotate_about_y; + rotate_about_y.RotateAboutYAxis(180.0); + back_facing_transform_node.local = rotate_about_y; + + CopyProperties(back_facing, back_facing_double_sided); + + UpdateActiveTreeDrawProperties(); + + EXPECT_TRUE(draw_property_utils::IsLayerBackFaceVisible( + back_facing, back_facing->transform_tree_index(), + host_impl()->active_tree()->property_trees())); + EXPECT_TRUE(draw_property_utils::IsLayerBackFaceVisible( + back_facing, back_facing_double_sided->transform_tree_index(), + host_impl()->active_tree()->property_trees())); + EXPECT_FALSE(draw_property_utils::IsLayerBackFaceVisible( + front_facing, front_facing->transform_tree_index(), + host_impl()->active_tree()->property_trees())); + + EXPECT_TRUE( + draw_property_utils::LayerShouldBeSkippedForDrawPropertiesComputation( + back_facing, host_impl()->active_tree()->property_trees())); + EXPECT_FALSE( + draw_property_utils::LayerShouldBeSkippedForDrawPropertiesComputation( + back_facing_double_sided, + host_impl()->active_tree()->property_trees())); + EXPECT_FALSE( + draw_property_utils::LayerShouldBeSkippedForDrawPropertiesComputation( + front_facing, host_impl()->active_tree()->property_trees())); +} + using LCDTextTestParam = std::tuple<bool, bool>; class LCDTextTest : public DrawPropertiesTestBase, public testing::TestWithParam<LCDTextTestParam> { @@ -3699,6 +3791,16 @@ TEST_P(LCDTextTest, CanUseLCDText) { CheckCanUseLCDText(LCDTextDisallowedReason::kNone, grand_child_); } +TEST_P(LCDTextTest, CanUseLCDTextWithContentsOpaqueForText) { + child_->SetContentsOpaque(false); + child_->SetBackgroundColor(SK_ColorGREEN); + child_->SetContentsOpaqueForText(true); + CheckCanUseLCDText(LCDTextDisallowedReason::kNone, child_); + + child_->SetContentsOpaqueForText(false); + CheckCanUseLCDText(LCDTextDisallowedReason::kContentsNotOpaque, child_); +} + TEST_P(LCDTextTest, CanUseLCDTextWithAnimation) { // Sanity check: Make sure can_use_lcd_text_ is set on each node. UpdateActiveTreeDrawProperties(); @@ -4232,19 +4334,19 @@ TEST_F(DrawPropertiesTest, ClipParentWithInterveningRenderSurface) { EXPECT_EQ(gfx::Rect(0, 0, 5, 5), render_surface2->clip_rect()); EXPECT_TRUE(render_surface2->is_clipped()); - // The content rects of render_surface2 should have expanded to contain the - // clip child. - EXPECT_EQ(gfx::Rect(0, 0, 40, 40), - GetRenderSurface(render_surface1)->content_rect()); - EXPECT_EQ(gfx::Rect(-10, -10, 60, 60), - GetRenderSurface(render_surface2)->content_rect()); - // The clip child should have inherited the clip parent's clip (projected to // the right space, of course), but as render_surface1 already applies that // clip, clip_child need not apply it again. EXPECT_EQ(gfx::Rect(), clip_child->clip_rect()); EXPECT_EQ(gfx::Rect(9, 9, 40, 40), clip_child->visible_layer_rect()); EXPECT_FALSE(clip_child->is_clipped()); + + // The content rects of render_surface2 should have expanded to contain the + // clip child, but only the visible part of the clip child. + EXPECT_EQ(gfx::Rect(0, 0, 40, 40), + GetRenderSurface(render_surface1)->content_rect()); + EXPECT_EQ(clip_child->visible_drawable_content_rect(), + GetRenderSurface(render_surface2)->content_rect()); } TEST_F(DrawPropertiesTest, ClipParentScrolledInterveningLayer) { @@ -4329,19 +4431,19 @@ TEST_F(DrawPropertiesTest, ClipParentScrolledInterveningLayer) { EXPECT_EQ(gfx::Rect(0, 0, 5, 5), render_surface2->clip_rect()); EXPECT_TRUE(render_surface2->is_clipped()); - // The content rects of render_surface2 should have expanded to contain the - // clip child. - EXPECT_EQ(gfx::Rect(0, 0, 40, 40), - GetRenderSurface(render_surface1)->content_rect()); - EXPECT_EQ(gfx::Rect(-10, -10, 60, 60), - GetRenderSurface(render_surface2)->content_rect()); - // The clip child should have inherited the clip parent's clip (projected to // the right space, of course), but as render_surface1 already applies that // clip, clip_child need not apply it again. EXPECT_EQ(gfx::Rect(), clip_child->clip_rect()); EXPECT_EQ(gfx::Rect(12, 12, 40, 40), clip_child->visible_layer_rect()); EXPECT_FALSE(clip_child->is_clipped()); + + // The content rects of render_surface2 should have expanded to contain the + // clip child, but only the visible part of the clip child. + EXPECT_EQ(gfx::Rect(0, 0, 40, 40), + GetRenderSurface(render_surface1)->content_rect()); + EXPECT_EQ(clip_child->visible_drawable_content_rect(), + GetRenderSurface(render_surface2)->content_rect()); } TEST_F(DrawPropertiesTest, DescendantsOfClipChildren) { @@ -6904,7 +7006,8 @@ TEST_F(DrawPropertiesTest, RenderSurfaceWithUnclippedDescendantsClipsSubtree) { EXPECT_TRUE(test_layer->is_clipped()); EXPECT_FALSE(test_layer->render_target()->is_clipped()); EXPECT_EQ(gfx::Rect(-2, -2, 30, 30), test_layer->clip_rect()); - EXPECT_EQ(gfx::Rect(28, 28), test_layer->drawable_content_rect()); + EXPECT_EQ(gfx::Rect(26, 26), test_layer->visible_layer_rect()); + EXPECT_EQ(gfx::Rect(26, 26), test_layer->visible_drawable_content_rect()); } TEST_F(DrawPropertiesTest, @@ -7059,7 +7162,7 @@ TEST_F(DrawPropertiesTest, RenderSurfaceContentRectWithMultipleSurfaces) { GetRenderSurface(unclipped_surface)->content_rect()); EXPECT_EQ(gfx::Rect(50, 50), GetRenderSurface(unclipped_desc_surface)->content_rect()); - EXPECT_EQ(gfx::Rect(60, 60), + EXPECT_EQ(gfx::Rect(50, 50), GetRenderSurface(unclipped_desc_surface2)->content_rect()); EXPECT_EQ(gfx::Rect(50, 50), GetRenderSurface(clipped_surface)->content_rect()); @@ -7724,7 +7827,8 @@ TEST_F(DrawPropertiesTestWithLayerTree, CopyRequestScalingTest) { EXPECT_EQ(gfx::Rect(10, 10), ImplOf(test_layer)->visible_layer_rect()); EXPECT_EQ(transform, ImplOf(test_layer)->DrawTransform()); EXPECT_EQ(gfx::Rect(50, 50), ImplOf(test_layer)->clip_rect()); - EXPECT_EQ(gfx::Rect(50, 50), ImplOf(test_layer)->drawable_content_rect()); + EXPECT_EQ(gfx::Rect(50, 50), + ImplOf(test_layer)->visible_drawable_content_rect()); // Clear the copy request and call UpdateSurfaceContentsScale. GetPropertyTrees(root.get())->effect_tree.ClearCopyRequests(); diff --git a/chromium/cc/trees/draw_property_utils.cc b/chromium/cc/trees/draw_property_utils.cc index f9fdebf8218..7c65e513700 100644 --- a/chromium/cc/trees/draw_property_utils.cc +++ b/chromium/cc/trees/draw_property_utils.cc @@ -11,6 +11,7 @@ #include "base/containers/adapters.h" #include "base/containers/stack.h" +#include "base/logging.h" #include "cc/base/math_util.h" #include "cc/layers/draw_properties.h" #include "cc/layers/layer.h" @@ -381,6 +382,12 @@ bool IsTargetSpaceTransformBackFaceVisible( LayerImpl* layer, int transform_tree_index, const PropertyTrees* property_trees) { + const TransformTree& transform_tree = property_trees->transform_tree; + const TransformNode& transform_node = + *transform_tree.Node(transform_tree_index); + if (transform_node.delegates_to_parent_for_backface) + transform_tree_index = transform_node.parent_id; + gfx::Transform to_target; property_trees->GetToTarget(transform_tree_index, layer->render_target_effect_tree_index(), @@ -388,12 +395,42 @@ bool IsTargetSpaceTransformBackFaceVisible( return to_target.IsBackFaceVisible(); } -template <typename LayerType> -bool IsLayerBackFaceVisible(LayerType* layer, - int transform_tree_index, - const PropertyTrees* property_trees) { - return IsTargetSpaceTransformBackFaceVisible(layer, transform_tree_index, - property_trees); +bool IsTransformToRootOf3DRenderingContextBackFaceVisible( + Layer* layer, + int transform_tree_index, + const PropertyTrees* property_trees) { + // We do not skip back face invisible layers on main thread as target space + // transform will not be available here. + return false; +} + +bool IsTransformToRootOf3DRenderingContextBackFaceVisible( + LayerImpl* layer, + int transform_tree_index, + const PropertyTrees* property_trees) { + const TransformTree& transform_tree = property_trees->transform_tree; + + const TransformNode& transform_node = + *transform_tree.Node(transform_tree_index); + if (transform_node.delegates_to_parent_for_backface) + transform_tree_index = transform_node.parent_id; + + int root_id = transform_tree_index; + int sorting_context_id = transform_node.sorting_context_id; + + while (root_id > 0 && transform_tree.Node(root_id - 1)->sorting_context_id == + sorting_context_id) + root_id--; + + // TODO(chrishtr): cache this on the transform trees if needed, similar to + // |to_target| and |to_screen|. + gfx::Transform to_3d_root; + if (transform_tree_index != root_id) + property_trees->transform_tree.CombineTransformsBetween( + transform_tree_index, root_id, &to_3d_root); + to_3d_root.PreconcatTransform( + property_trees->transform_tree.Node(root_id)->to_parent); + return to_3d_root.IsBackFaceVisible(); } inline bool TransformToScreenIsKnown(Layer* layer, @@ -445,7 +482,8 @@ bool LayerNeedsUpdate(LayerType* layer, // backface is not visible. if (TransformToScreenIsKnown(layer, backface_transform_id, tree) && !HasSingularTransform(backface_transform_id, tree) && - IsLayerBackFaceVisible(layer, backface_transform_id, property_trees)) { + draw_property_utils::IsLayerBackFaceVisible( + layer, backface_transform_id, property_trees)) { UMA_HISTOGRAM_BOOLEAN( "Compositing.Renderer.LayerUpdateSkippedDueToBackface", true); return false; @@ -474,35 +512,6 @@ inline bool LayerShouldBeSkippedForDrawPropertiesComputation( !effect_node->is_drawn; } -inline bool LayerShouldBeSkippedForDrawPropertiesComputation( - LayerImpl* layer, - const TransformTree& transform_tree, - const EffectTree& effect_tree) { - const EffectNode* effect_node = effect_tree.Node(layer->effect_tree_index()); - - if (effect_node->HasRenderSurface() && effect_node->subtree_has_copy_request) - return false; - - // Skip if the node's subtree is hidden and no need to cache. - if (effect_node->subtree_hidden && !effect_node->cache_render_surface) - return true; - - // If the layer transform is not invertible, it should be skipped. In case the - // transform is animating and singular, we should not skip it. - const TransformNode* transform_node = - transform_tree.Node(layer->transform_tree_index()); - - if (!transform_node->node_and_ancestors_are_animated_or_invertible || - !effect_node->is_drawn) - return true; - - UMA_HISTOGRAM_BOOLEAN( - "Compositing.Renderer.LayerSkippedForDrawPropertiesDueToBackface", - effect_node->hidden_by_backface_visibility); - - return effect_node->hidden_by_backface_visibility; -} - gfx::Rect LayerDrawableContentRect( const LayerImpl* layer, const gfx::Rect& layer_bounds_in_target_space, @@ -922,8 +931,8 @@ void ComputeInitialRenderSurfaceList(LayerTreeImpl* layer_tree_impl, bool is_root = layer_tree_impl->IsRootLayer(layer); bool skip_draw_properties_computation = - LayerShouldBeSkippedForDrawPropertiesComputation( - layer, property_trees->transform_tree, property_trees->effect_tree); + draw_property_utils::LayerShouldBeSkippedForDrawPropertiesComputation( + layer, property_trees); bool skip_for_invertibility = SkipForInvertibility(layer, property_trees); @@ -1138,11 +1147,19 @@ void ComputeDrawPropertiesOfVisibleLayers(const LayerImplList* layer_list, // Compute drawable content rects for (LayerImpl* layer : *layer_list) { - gfx::Rect bounds_in_target_space = MathUtil::MapEnclosingClippedRect( - layer->draw_properties().target_space_transform, - gfx::Rect(layer->bounds())); - layer->draw_properties().drawable_content_rect = LayerDrawableContentRect( - layer, bounds_in_target_space, layer->draw_properties().clip_rect); + bool only_draws_visible_content = + property_trees->effect_tree.Node(layer->effect_tree_index()) + ->only_draws_visible_content; + gfx::Rect drawable_bounds = gfx::Rect(layer->visible_layer_rect()); + if (!only_draws_visible_content) { + drawable_bounds = gfx::Rect(layer->bounds()); + } + gfx::Rect visible_bounds_in_target_space = + MathUtil::MapEnclosingClippedRect( + layer->draw_properties().target_space_transform, drawable_bounds); + layer->draw_properties().visible_drawable_content_rect = + LayerDrawableContentRect(layer, visible_bounds_in_target_space, + layer->draw_properties().clip_rect); } } @@ -1164,6 +1181,61 @@ bool NodeMayContainBackdropBlurFilter(const EffectNode& node) { } // namespace +bool CC_EXPORT LayerShouldBeSkippedForDrawPropertiesComputation( + LayerImpl* layer, + const PropertyTrees* property_trees) { + const TransformTree& transform_tree = property_trees->transform_tree; + const EffectTree& effect_tree = property_trees->effect_tree; + const EffectNode* effect_node = effect_tree.Node(layer->effect_tree_index()); + + if (effect_node->HasRenderSurface() && effect_node->subtree_has_copy_request) + return false; + + // Skip if the node's subtree is hidden and no need to cache. + if (effect_node->subtree_hidden && !effect_node->cache_render_surface) + return true; + + // If the layer transform is not invertible, it should be skipped. In case the + // transform is animating and singular, we should not skip it. + const TransformNode* transform_node = + transform_tree.Node(layer->transform_tree_index()); + + if (!transform_node->node_and_ancestors_are_animated_or_invertible || + !effect_node->is_drawn) + return true; + if (layer->layer_tree_impl()->settings().enable_transform_interop) { + return layer->should_check_backface_visibility() && + IsLayerBackFaceVisible(layer, layer->transform_tree_index(), + property_trees); + } else { + return effect_node->hidden_by_backface_visibility; + } +} + +bool CC_EXPORT IsLayerBackFaceVisible(LayerImpl* layer, + int transform_tree_index, + const PropertyTrees* property_trees) { + if (layer->layer_tree_impl()->settings().enable_transform_interop) { + return IsTransformToRootOf3DRenderingContextBackFaceVisible( + layer, transform_tree_index, property_trees); + } else { + return IsTargetSpaceTransformBackFaceVisible(layer, transform_tree_index, + property_trees); + } +} + +bool CC_EXPORT IsLayerBackFaceVisible(Layer* layer, + int transform_tree_index, + const PropertyTrees* property_trees) { + if (layer->layer_tree_host()->GetSettings().enable_transform_interop) { + return IsTransformToRootOf3DRenderingContextBackFaceVisible( + layer, transform_tree_index, property_trees); + } else { + return IsTargetSpaceTransformBackFaceVisible(layer, transform_tree_index, + property_trees); + } +} + void ConcatInverseSurfaceContentsScale(const EffectNode* effect_node, gfx::Transform* transform) { DCHECK(effect_node->HasRenderSurface()); @@ -1195,7 +1267,6 @@ void FindLayersThatNeedUpdates(LayerTreeHost* layer_tree_host, void FindLayersThatNeedUpdates(LayerTreeImpl* layer_tree_impl, std::vector<LayerImpl*>* visible_layer_list) { const PropertyTrees* property_trees = layer_tree_impl->property_trees(); - const TransformTree& transform_tree = property_trees->transform_tree; const EffectTree& effect_tree = property_trees->effect_tree; for (auto* layer_impl : *layer_tree_impl) { @@ -1204,8 +1275,8 @@ void FindLayersThatNeedUpdates(LayerTreeImpl* layer_tree_impl, layer_impl->EnsureValidPropertyTreeIndices(); if (!IsRootLayer(layer_impl) && - LayerShouldBeSkippedForDrawPropertiesComputation( - layer_impl, transform_tree, effect_tree)) + LayerShouldBeSkippedForDrawPropertiesComputation(layer_impl, + property_trees)) continue; bool layer_is_drawn = diff --git a/chromium/cc/trees/draw_property_utils.h b/chromium/cc/trees/draw_property_utils.h index c42684eefce..b80b38e3ab9 100644 --- a/chromium/cc/trees/draw_property_utils.h +++ b/chromium/cc/trees/draw_property_utils.h @@ -68,6 +68,18 @@ void CC_EXPORT CalculateDrawProperties( RenderSurfaceList* output_render_surface_list, LayerImplList* output_update_layer_list_for_testing = nullptr); +bool CC_EXPORT LayerShouldBeSkippedForDrawPropertiesComputation( + LayerImpl* layer, + const PropertyTrees* property_trees); + +bool CC_EXPORT IsLayerBackFaceVisible(LayerImpl* layer, + int transform_tree_index, + const PropertyTrees* property_trees); + +bool CC_EXPORT IsLayerBackFaceVisible(Layer* layer, + int transform_tree_index, + const PropertyTrees* property_trees); + #if DCHECK_IS_ON() // Checks and logs if double background blur exists in any layers. Returns // true if no double background blur is detected, false otherwise. diff --git a/chromium/cc/trees/effect_node.cc b/chromium/cc/trees/effect_node.cc index a412c237f1c..9bae1e90b74 100644 --- a/chromium/cc/trees/effect_node.cc +++ b/chromium/cc/trees/effect_node.cc @@ -23,6 +23,7 @@ EffectNode::EffectNode() double_sided(true), trilinear_filtering(false), is_drawn(true), + only_draws_visible_content(true), subtree_hidden(false), has_potential_filter_animation(false), has_potential_backdrop_filter_animation(false), @@ -56,7 +57,6 @@ bool EffectNode::operator==(const EffectNode& other) const { backdrop_filters == other.backdrop_filters && backdrop_filter_bounds == other.backdrop_filter_bounds && backdrop_mask_element_id == other.backdrop_mask_element_id && - filters_origin == other.filters_origin && rounded_corner_bounds == other.rounded_corner_bounds && is_fast_rounded_corner == other.is_fast_rounded_corner && // The specific reason is just for tracing/testing/debugging, so just @@ -67,7 +67,9 @@ bool EffectNode::operator==(const EffectNode& other) const { hidden_by_backface_visibility == other.hidden_by_backface_visibility && double_sided == other.double_sided && trilinear_filtering == other.trilinear_filtering && - is_drawn == other.is_drawn && subtree_hidden == other.subtree_hidden && + is_drawn == other.is_drawn && + only_draws_visible_content == other.only_draws_visible_content && + subtree_hidden == other.subtree_hidden && has_potential_filter_animation == other.has_potential_filter_animation && has_potential_backdrop_filter_animation == @@ -163,6 +165,7 @@ void EffectNode::AsValueInto(base::trace_event::TracedValue* value) const { hidden_by_backface_visibility); value->SetBoolean("trilinear_filtering", trilinear_filtering); value->SetBoolean("is_drawn", is_drawn); + value->SetBoolean("only_draws_visible_content", only_draws_visible_content); value->SetBoolean("has_potential_filter_animation", has_potential_filter_animation); value->SetBoolean("has_potential_backdrop_filter_animation", diff --git a/chromium/cc/trees/effect_node.h b/chromium/cc/trees/effect_node.h index 01f662563e1..6663457250c 100644 --- a/chromium/cc/trees/effect_node.h +++ b/chromium/cc/trees/effect_node.h @@ -96,6 +96,13 @@ struct CC_EXPORT EffectNode { bool double_sided : 1; bool trilinear_filtering : 1; bool is_drawn : 1; + // In most cases we only need to draw the visible part of any content + // contributing to the effect. For copy request case, we would need to copy + // the entire content, and could not only draw the visible part. In the rare + // case of a backdrop zoom filter we need to take into consideration the + // content offscreen to make sure the backdrop zoom filter is applied with the + // correct center. + bool only_draws_visible_content : 1; // TODO(jaydasika) : Delete this after implementation of // SetHideLayerAndSubtree is cleaned up. (crbug.com/595843) bool subtree_hidden : 1; diff --git a/chromium/cc/trees/frame_rate_counter.cc b/chromium/cc/trees/frame_rate_counter.cc deleted file mode 100644 index 816877c4ab3..00000000000 --- a/chromium/cc/trees/frame_rate_counter.cc +++ /dev/null @@ -1,133 +0,0 @@ -// Copyright 2012 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "cc/trees/frame_rate_counter.h" - -#include <stddef.h> - -#include <algorithm> -#include <limits> - -#include "base/memory/ptr_util.h" -#include "base/metrics/histogram_macros.h" - -namespace cc { - -// The following constants are measured in seconds. - -// Two thresholds (measured in seconds) that describe what is considered to be a -// "no-op frame" that should not be counted. -// - if the frame is too fast, then given our compositor implementation, the -// frame probably was a no-op and did not draw. -// - if the frame is too slow, then there is probably not animating content, so -// we should not pollute the average. -static const double kFrameTooFast = 1.0 / 70.0; -static const double kFrameTooSlow = 1.5; - -// If a frame takes longer than this threshold (measured in seconds) then we -// (naively) assume that it missed a screen refresh; that is, we dropped a -// frame. -// TODO(brianderson): Determine this threshold based on monitor refresh rate, -// crbug.com/138642. -static const double kDroppedFrameTime = 1.0 / 50.0; - -// static -std::unique_ptr<FrameRateCounter> FrameRateCounter::Create( - bool has_impl_thread) { - return base::WrapUnique(new FrameRateCounter(has_impl_thread)); -} - -base::TimeDelta FrameRateCounter::RecentFrameInterval(size_t n) const { - DCHECK_GT(n, 0u); - DCHECK_LT(n, ring_buffer_.BufferSize()); - return ring_buffer_.ReadBuffer(n) - ring_buffer_.ReadBuffer(n - 1); -} - -FrameRateCounter::FrameRateCounter(bool has_impl_thread) - : has_impl_thread_(has_impl_thread), dropped_frame_count_(0) {} - -void FrameRateCounter::SaveTimeStamp(base::TimeTicks timestamp) { - ring_buffer_.SaveToBuffer(timestamp); - - // Check if frame interval can be computed. - if (ring_buffer_.CurrentIndex() < 2) - return; - - base::TimeDelta frame_interval_seconds = - RecentFrameInterval(ring_buffer_.BufferSize() - 1); - - if (!IsBadFrameInterval(frame_interval_seconds) && - frame_interval_seconds.InSecondsF() > kDroppedFrameTime) - dropped_frame_count_ += - frame_interval_seconds.InSecondsF() / kDroppedFrameTime; -} - -bool FrameRateCounter::IsBadFrameInterval( - base::TimeDelta interval_between_consecutive_frames) const { - double delta = interval_between_consecutive_frames.InSecondsF(); - bool scheduler_allows_double_frames = !has_impl_thread_; - bool interval_too_fast = - scheduler_allows_double_frames ? delta < kFrameTooFast : delta <= 0.0; - bool interval_too_slow = delta > kFrameTooSlow; - return interval_too_fast || interval_too_slow; -} - -void FrameRateCounter::GetMinAndMaxFPS(double* min_fps, double* max_fps) const { - *min_fps = std::numeric_limits<double>::max(); - *max_fps = 0.0; - - for (RingBufferType::Iterator it = --ring_buffer_.End(); it; --it) { - base::TimeDelta delta = RecentFrameInterval(it.index() + 1); - - if (IsBadFrameInterval(delta)) - continue; - - DCHECK_GT(delta.InSecondsF(), 0.f); - double fps = 1.0 / delta.InSecondsF(); - - *min_fps = std::min(fps, *min_fps); - *max_fps = std::max(fps, *max_fps); - } - - if (*min_fps > *max_fps) - *min_fps = *max_fps; -} - -double FrameRateCounter::GetAverageFPS() const { - int frame_count = 0; - double frame_times_total = 0.0; - double average_fps = 0.0; - - // Walk backwards through the samples looking for a run of good frame - // timings from which to compute the mean. - // - // Slow frames occur just because the user is inactive, and should be - // ignored. Fast frames are ignored if the scheduler is in single-thread - // mode in order to represent the true frame rate in spite of the fact that - // the first few swapbuffers happen instantly which skews the statistics - // too much for short lived animations. - // - // IsBadFrameInterval encapsulates the frame too slow/frame too fast logic. - - for (RingBufferType::Iterator it = --ring_buffer_.End(); - it && frame_times_total < 1.0; --it) { - base::TimeDelta delta = RecentFrameInterval(it.index() + 1); - - if (!IsBadFrameInterval(delta)) { - frame_count++; - frame_times_total += delta.InSecondsF(); - } else if (frame_count) { - break; - } - } - - if (frame_count) { - DCHECK_GT(frame_times_total, 0.0); - average_fps = frame_count / frame_times_total; - } - - return average_fps; -} - -} // namespace cc diff --git a/chromium/cc/trees/frame_rate_counter.h b/chromium/cc/trees/frame_rate_counter.h deleted file mode 100644 index f1445fb16da..00000000000 --- a/chromium/cc/trees/frame_rate_counter.h +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright 2012 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef CC_TREES_FRAME_RATE_COUNTER_H_ -#define CC_TREES_FRAME_RATE_COUNTER_H_ - -#include <stddef.h> - -#include <memory> - -#include "base/containers/ring_buffer.h" -#include "base/time/time.h" - -namespace cc { - -// This class maintains a history of timestamps, and provides functionality to -// intelligently compute average frames per second. -class FrameRateCounter { - public: - static std::unique_ptr<FrameRateCounter> Create(bool has_impl_thread); - - FrameRateCounter(const FrameRateCounter&) = delete; - FrameRateCounter& operator=(const FrameRateCounter&) = delete; - - size_t current_frame_number() const { return ring_buffer_.CurrentIndex(); } - int dropped_frame_count() const { return dropped_frame_count_; } - size_t time_stamp_history_size() const { return ring_buffer_.BufferSize(); } - - void SaveTimeStamp(base::TimeTicks timestamp); - - // n = 0 returns the oldest frame interval retained in the history, while n = - // time_stamp_history_size() - 1 returns the most recent frame interval. - base::TimeDelta RecentFrameInterval(size_t n) const; - - // This is a heuristic that can be used to ignore frames in a reasonable way. - // Returns true if the given frame interval is too fast or too slow, based on - // constant thresholds. - bool IsBadFrameInterval( - base::TimeDelta interval_between_consecutive_frames) const; - - void GetMinAndMaxFPS(double* min_fps, double* max_fps) const; - double GetAverageFPS() const; - - typedef base::RingBuffer<base::TimeTicks, 136> RingBufferType; - RingBufferType::Iterator begin() const { return ring_buffer_.Begin(); } - RingBufferType::Iterator end() const { return ring_buffer_.End(); } - - private: - explicit FrameRateCounter(bool has_impl_thread); - - RingBufferType ring_buffer_; - - bool has_impl_thread_; - int dropped_frame_count_; -}; - -} // namespace cc - -#endif // CC_TREES_FRAME_RATE_COUNTER_H_ diff --git a/chromium/cc/trees/image_animation_controller.cc b/chromium/cc/trees/image_animation_controller.cc index a7e1ebf6de6..2cb4f9d1bd1 100644 --- a/chromium/cc/trees/image_animation_controller.cc +++ b/chromium/cc/trees/image_animation_controller.cc @@ -4,6 +4,8 @@ #include "cc/trees/image_animation_controller.h" +#include <sstream> + #include "base/bind.h" #include "base/metrics/histogram_macros.h" #include "base/trace_event/trace_event.h" @@ -360,6 +362,25 @@ bool ImageAnimationController::AnimationState::AdvanceFrame( // skipped trying to catch up. DCHECK_GT(num_of_frames_advanced, 0u); last_num_frames_skipped_ = num_of_frames_advanced - 1u; + switch (repetitions_completed_) { + case 0: + UMA_HISTOGRAM_COUNTS_100000( + "AnimatedImage.NumOfFramesSkipped.FirstAnimationLoop", + last_num_frames_skipped_); + break; + case 1: + UMA_HISTOGRAM_COUNTS_100000( + "AnimatedImage.NumOfFramesSkipped.SecondAnimationLoop", + last_num_frames_skipped_); + break; + case 2: + case 3: + case 4: + UMA_HISTOGRAM_COUNTS_100000( + "AnimatedImage.NumOfFramesSkipped.ThirdToFifthAnimationLoop", + last_num_frames_skipped_); + break; + } UMA_HISTOGRAM_COUNTS_100000("AnimatedImage.NumOfFramesSkipped.Compositor", last_num_frames_skipped_); return needs_invalidation(); diff --git a/chromium/cc/trees/layer_tree_host.cc b/chromium/cc/trees/layer_tree_host.cc index b1a4798682b..d5e57eaa4a5 100644 --- a/chromium/cc/trees/layer_tree_host.cc +++ b/chromium/cc/trees/layer_tree_host.cc @@ -18,6 +18,7 @@ #include "base/command_line.h" #include "base/containers/adapters.h" #include "base/location.h" +#include "base/logging.h" #include "base/memory/ptr_util.h" #include "base/metrics/histogram_functions.h" #include "base/metrics/histogram_macros.h" @@ -94,6 +95,9 @@ LayerTreeHost::InitParams::InitParams(InitParams&&) = default; LayerTreeHost::InitParams& LayerTreeHost::InitParams::operator=(InitParams&&) = default; +LayerTreeHost::ScrollAnimationState::ScrollAnimationState() = default; +LayerTreeHost::ScrollAnimationState::~ScrollAnimationState() = default; + std::unique_ptr<LayerTreeHost> LayerTreeHost::CreateThreaded( scoped_refptr<base::SingleThreadTaskRunner> impl_task_runner, InitParams params) { @@ -263,14 +267,6 @@ const LayerTreeSettings& LayerTreeHost::GetSettings() const { void LayerTreeHost::QueueSwapPromise( std::unique_ptr<SwapPromise> swap_promise) { swap_promise_manager_.QueueSwapPromise(std::move(swap_promise)); - - // Request a main frame if one is not already in progress. This might either - // A) request a commit ahead of time or B) request a commit which is not - // needed because there are not pending updates. If B) then the frame will - // be aborted early and the swap promises will be broken (see - // EarlyOut_NoUpdates). - if (!inside_main_frame_) - SetNeedsAnimate(); } void LayerTreeHost::WillBeginMainFrame() { @@ -609,6 +605,11 @@ void LayerTreeHost::SetNeedsAnimate() { events_metrics_manager_.SaveActiveEventMetrics(); } +void LayerTreeHost::SetNeedsAnimateIfNotInsideMainFrame() { + if (!inside_main_frame_) + SetNeedsAnimate(); +} + DISABLE_CFI_PERF void LayerTreeHost::SetNeedsUpdateLayers() { proxy_->SetNeedsUpdateLayers(); @@ -785,6 +786,13 @@ bool LayerTreeHost::CaptureContent(std::vector<NodeId>* content) { return true; } +void LayerTreeHost::DidObserveFirstScrollDelay( + base::TimeDelta first_scroll_delay, + base::TimeTicks first_scroll_timestamp) { + client_->DidObserveFirstScrollDelay(first_scroll_delay, + first_scroll_timestamp); +} + bool LayerTreeHost::DoUpdateLayers() { TRACE_EVENT1("cc,benchmark", "LayerTreeHost::DoUpdateLayers", "source_frame_number", SourceFrameNumber()); @@ -849,6 +857,24 @@ void LayerTreeHost::ApplyViewportChanges(const ScrollAndScaleSet& info) { if (info.inner_viewport_scroll.element_id) inner_viewport_scroll_delta = info.inner_viewport_scroll.scroll_delta; + // When a new scroll-animation starts, it is necessary to check + // |info.manipulation_info| to make sure the scroll-animation was started by + // an input event. + // If there is already an ongoing scroll-animation, then it is necessary to + // only look at |info.ongoing_scroll_animation| (since it is possible for the + // scroll-animation to continue even if no event was handled). + bool new_ongoing_scroll = + scroll_animation_.in_progress + ? info.ongoing_scroll_animation + : (info.ongoing_scroll_animation && info.manipulation_info); + if (scroll_animation_.in_progress && !new_ongoing_scroll) { + scroll_animation_.in_progress = false; + if (!scroll_animation_.end_notification.is_null()) + std::move(scroll_animation_.end_notification).Run(); + } else { + scroll_animation_.in_progress = new_ongoing_scroll; + } + if (inner_viewport_scroll_delta.IsZero() && info.page_scale_delta == 1.f && info.elastic_overscroll_delta.IsZero() && !info.top_controls_delta && !info.bottom_controls_delta && @@ -985,6 +1011,14 @@ void LayerTreeHost::NotifyThroughputTrackerResults( client_->NotifyThroughputTrackerResults(std::move(results)); } +void LayerTreeHost::SubmitThroughputData(ukm::SourceId source_id, + int aggregated_percent, + int impl_percent, + base::Optional<int> main_percent) { + client_->SubmitThroughputData(source_id, aggregated_percent, impl_percent, + main_percent); +} + const base::WeakPtr<InputHandler>& LayerTreeHost::GetInputHandler() const { return input_handler_weak_ptr_; } @@ -1068,6 +1102,15 @@ void LayerTreeHost::RequestPresentationTimeForNextFrame( pending_presentation_time_callbacks_.push_back(std::move(callback)); } +void LayerTreeHost::RequestScrollAnimationEndNotification( + base::OnceClosure callback) { + DCHECK(scroll_animation_.end_notification.is_null()); + if (scroll_animation_.in_progress) + scroll_animation_.end_notification = std::move(callback); + else + std::move(callback).Run(); +} + void LayerTreeHost::SetRootLayer(scoped_refptr<Layer> root_layer) { if (root_layer_.get() == root_layer.get()) return; @@ -1118,9 +1161,13 @@ Layer* LayerTreeHost::InnerViewportScrollLayerForTesting() const { } Layer* LayerTreeHost::OuterViewportScrollLayerForTesting() const { + return LayerByElementId(OuterViewportScrollElementId()); +} + +ElementId LayerTreeHost::OuterViewportScrollElementId() const { auto* scroll_node = property_trees()->scroll_tree.Node(viewport_property_ids_.outer_scroll); - return scroll_node ? LayerByElementId(scroll_node->element_id) : nullptr; + return scroll_node ? scroll_node->element_id : ElementId(); } void LayerTreeHost::RegisterSelection(const LayerSelection& selection) { @@ -1439,10 +1486,6 @@ void LayerTreeHost::AddLayerShouldPushProperties(Layer* layer) { layers_that_should_push_properties_.insert(layer); } -void LayerTreeHost::RemoveLayerShouldPushProperties(Layer* layer) { - layers_that_should_push_properties_.erase(layer); -} - void LayerTreeHost::ClearLayersThatShouldPushProperties() { layers_that_should_push_properties_.clear(); } @@ -1566,6 +1609,9 @@ void LayerTreeHost::PushLayerTreePropertiesTo(LayerTreeImpl* tree_impl) { } tree_impl->set_display_transform_hint(display_transform_hint_); + + if (delegated_ink_metadata_) + tree_impl->set_delegated_ink_metadata(std::move(delegated_ink_metadata_)); } void LayerTreeHost::PushSurfaceRangesTo(LayerTreeImpl* tree_impl) { diff --git a/chromium/cc/trees/layer_tree_host.h b/chromium/cc/trees/layer_tree_host.h index 8aae435b230..f365bd9a909 100644 --- a/chromium/cc/trees/layer_tree_host.h +++ b/chromium/cc/trees/layer_tree_host.h @@ -13,6 +13,7 @@ #include <set> #include <string> #include <unordered_map> +#include <utility> #include <vector> #include "base/callback_forward.h" @@ -49,6 +50,7 @@ #include "cc/trees/swap_promise_manager.h" #include "cc/trees/target_property.h" #include "cc/trees/viewport_layers.h" +#include "components/viz/common/delegated_ink_metadata.h" #include "components/viz/common/resources/resource_format.h" #include "components/viz/common/surfaces/local_surface_id_allocation.h" #include "services/metrics/public/cpp/ukm_source_id.h" @@ -215,6 +217,9 @@ class CC_EXPORT LayerTreeHost : public MutatorHostClient { // full commit synchronization or layer updates. void SetNeedsAnimate(); + // Calls SetNeedsAnimate() if there is no main frame already in progress. + void SetNeedsAnimateIfNotInsideMainFrame(); + // Requests a main frame update and also ensure that the host pulls layer // updates from the client, even if no content might have changed, without // forcing a full commit synchronization. @@ -316,6 +321,10 @@ class CC_EXPORT LayerTreeHost : public MutatorHostClient { base::OnceCallback<void(const gfx::PresentationFeedback&)>; void RequestPresentationTimeForNextFrame(PresentationTimeCallback callback); + // Registers a callback that is run when any ongoing scroll-animation ends. If + // there are no ongoing animations, then the callback is run immediately. + void RequestScrollAnimationEndNotification(base::OnceClosure callback); + // Layer tree accessors and modifiers ------------------------ // Sets or gets the root of the Layer tree. Children of the root Layer are @@ -352,6 +361,8 @@ class CC_EXPORT LayerTreeHost : public MutatorHostClient { Layer* InnerViewportScrollLayerForTesting() const; Layer* OuterViewportScrollLayerForTesting() const; + ElementId OuterViewportScrollElementId() const; + // Sets or gets the position of touch handles for a text selection. These are // submitted to the display compositor along with the Layer tree's contents // allowing it to present the selection handles. This is done because the @@ -388,8 +399,6 @@ class CC_EXPORT LayerTreeHost : public MutatorHostClient { void SetViewportVisibleRect(const gfx::Rect& visible_rect); - gfx::Rect viewport_visible_rect() const { return viewport_visible_rect_; } - gfx::Rect device_viewport_rect() const { return device_viewport_rect_; } void SetBrowserControlsParams(const BrowserControlsParams& params); @@ -457,6 +466,10 @@ class CC_EXPORT LayerTreeHost : public MutatorHostClient { return raster_color_space_; } + bool HasCompositorDrivenScrollAnimationForTesting() const { + return scroll_animation_.in_progress; + } + // This layer tree may be embedded in a hierarchy that has page scale // factor controlled at the top level. We represent that scale here as // 'external_page_scale_factor', a value that affects raster scale in the @@ -503,7 +516,6 @@ class CC_EXPORT LayerTreeHost : public MutatorHostClient { // These are internal methods, called from the Layer itself when changing a // property or completing a PushPropertiesTo. void AddLayerShouldPushProperties(Layer* layer); - void RemoveLayerShouldPushProperties(Layer* layer); void ClearLayersThatShouldPushProperties(); // The current set of all Layers attached to the LayerTreeHost's tree that // have been marked as needing PushPropertiesTo in the next commit. @@ -593,6 +605,10 @@ class CC_EXPORT LayerTreeHost : public MutatorHostClient { void RecordEndOfFrameMetrics(base::TimeTicks frame_begin_time, ActiveFrameSequenceTrackers trackers); void NotifyThroughputTrackerResults(CustomTrackerResults results); + void SubmitThroughputData(ukm::SourceId source_id, + int aggregated_percent, + int impl_percent, + base::Optional<int> main_percent); LayerTreeHostClient* client() { return client_; } LayerTreeHostSchedulingClient* scheduling_client() { @@ -693,6 +709,17 @@ class CC_EXPORT LayerTreeHost : public MutatorHostClient { impl_commit_start_time_ = commit_start_time; } + void SetDelegatedInkMetadata( + std::unique_ptr<viz::DelegatedInkMetadata> metadata) { + delegated_ink_metadata_ = std::move(metadata); + } + viz::DelegatedInkMetadata* DelegatedInkMetadataForTesting() { + return delegated_ink_metadata_.get(); + } + + void DidObserveFirstScrollDelay(base::TimeDelta first_scroll_delay, + base::TimeTicks first_scroll_timestamp); + protected: LayerTreeHost(InitParams params, CompositorMode mode); @@ -886,6 +913,17 @@ class CC_EXPORT LayerTreeHost : public MutatorHostClient { // added here. std::vector<PresentationTimeCallback> pending_presentation_time_callbacks_; + struct ScrollAnimationState { + ScrollAnimationState(); + ~ScrollAnimationState(); + + // Tracks whether there is an ongoing compositor-driven scroll animation. + bool in_progress = false; + + // Callback to run when the scroll-animation ends. + base::OnceClosure end_notification; + } scroll_animation_; + // Latency information for work done in ProxyMain::BeginMainFrame. The // unique_ptr is allocated in RequestMainFrameUpdate, and passed to Blink's // LocalFrameView that fills in the fields. This object adds the timing for @@ -899,6 +937,12 @@ class CC_EXPORT LayerTreeHost : public MutatorHostClient { EventsMetricsManager events_metrics_manager_; + // Metadata required for drawing a delegated ink trail onto the end of a + // stroke. std::unique_ptr was specifically chosen so that it would be cleared + // as it is forwarded along the pipeline to avoid old information incorrectly + // sticking around and potentially being reused. + std::unique_ptr<viz::DelegatedInkMetadata> delegated_ink_metadata_; + // Used to vend weak pointers to LayerTreeHost to ScopedDeferMainFrameUpdate // objects. base::WeakPtrFactory<LayerTreeHost> defer_main_frame_update_weak_ptr_factory_{ diff --git a/chromium/cc/trees/layer_tree_host_client.h b/chromium/cc/trees/layer_tree_host_client.h index ae48bd94a18..b4b8c607969 100644 --- a/chromium/cc/trees/layer_tree_host_client.h +++ b/chromium/cc/trees/layer_tree_host_client.h @@ -11,6 +11,7 @@ #include "base/time/time.h" #include "cc/input/browser_controls_state.h" #include "cc/metrics/frame_sequence_tracker_collection.h" +#include "services/metrics/public/cpp/ukm_source_id.h" #include "ui/gfx/geometry/scroll_offset.h" #include "ui/gfx/geometry/vector2d_f.h" @@ -101,6 +102,9 @@ class LayerTreeHostClient { virtual void WillUpdateLayers() = 0; virtual void DidUpdateLayers() = 0; + virtual void DidObserveFirstScrollDelay( + base::TimeDelta first_scroll_delay, + base::TimeTicks first_scroll_timestamp) = 0; // Notification that the proxy started or stopped deferring main frame updates virtual void OnDeferMainFrameUpdatesChanged(bool) = 0; @@ -169,6 +173,12 @@ class LayerTreeHostClient { // RecordEndOfFrameMetrics. virtual std::unique_ptr<BeginMainFrameMetrics> GetBeginMainFrameMetrics() = 0; virtual void NotifyThroughputTrackerResults(CustomTrackerResults results) = 0; + // LayerTreeHost calls this when there is new throughput data. The client send + // the data to the browser process who will aggregate them and report to UKM. + virtual void SubmitThroughputData(ukm::SourceId source_id, + int aggregated_percent, + int impl_percent, + base::Optional<int> main_percent) = 0; protected: virtual ~LayerTreeHostClient() = default; diff --git a/chromium/cc/trees/layer_tree_host_impl.cc b/chromium/cc/trees/layer_tree_host_impl.cc index 4fcc3210311..0fbcdf35620 100644 --- a/chromium/cc/trees/layer_tree_host_impl.cc +++ b/chromium/cc/trees/layer_tree_host_impl.cc @@ -20,6 +20,7 @@ #include "base/debug/crash_logging.h" #include "base/debug/dump_without_crashing.h" #include "base/location.h" +#include "base/logging.h" #include "base/memory/ptr_util.h" #include "base/memory/read_only_shared_memory_region.h" #include "base/metrics/histogram.h" @@ -30,6 +31,7 @@ #include "base/trace_event/traced_value.h" #include "build/build_config.h" #include "cc/base/devtools_instrumentation.h" +#include "cc/base/features.h" #include "cc/base/histograms.h" #include "cc/base/math_util.h" #include "cc/base/switches.h" @@ -40,6 +42,7 @@ #include "cc/input/page_scale_animation.h" #include "cc/input/scroll_elasticity_helper.h" #include "cc/input/scroll_state.h" +#include "cc/input/scroll_utils.h" #include "cc/input/scrollbar.h" #include "cc/input/scrollbar_animation_controller.h" #include "cc/input/scroller_size_metrics.h" @@ -79,7 +82,6 @@ #include "cc/trees/debug_rect_history.h" #include "cc/trees/draw_property_utils.h" #include "cc/trees/effect_node.h" -#include "cc/trees/frame_rate_counter.h" #include "cc/trees/image_animation_controller.h" #include "cc/trees/latency_info_swap_promise_monitor.h" #include "cc/trees/layer_tree_frame_sink.h" @@ -125,6 +127,7 @@ #include "ui/gfx/geometry/scroll_offset.h" #include "ui/gfx/geometry/size_conversions.h" #include "ui/gfx/geometry/vector2d_conversions.h" +#include "ui/gfx/geometry/vector2d_f.h" #include "ui/gfx/skia_util.h" namespace cc { @@ -248,6 +251,41 @@ void PopulateMetadataContentColorUsage( } } +// Registers callbacks, as needed, to track First Scroll Latency. +void ApplyFirstScrollTracking(const ui::LatencyInfo* latency, + uint32_t frame_token, + LayerTreeHostImpl* impl) { + base::TimeTicks creation_timestamp; + // If |latency| isn't tracking a scroll, we don't need to do extra + // first-scroll tracking. + auto scroll_update_started = + ui::INPUT_EVENT_LATENCY_FIRST_SCROLL_UPDATE_ORIGINAL_COMPONENT; + auto scroll_update_continued = + ui::INPUT_EVENT_LATENCY_SCROLL_UPDATE_ORIGINAL_COMPONENT; + if (!latency->FindLatency(scroll_update_started, &creation_timestamp) && + !latency->FindLatency(scroll_update_continued, &creation_timestamp)) { + return; + } + + // Construct a callback that, given presentation feedback, will report the + // time span between the scroll input-event creation and the + // presentation timestamp. + LayerTreeHost::PresentationTimeCallback presentation_callback = + base::BindOnce( + [](base::TimeTicks event_creation, + LayerTreeHostImpl* layer_tree_host_impl, + const gfx::PresentationFeedback& feedback) { + layer_tree_host_impl->DidObserveScrollDelay( + feedback.timestamp - event_creation, event_creation); + }, + creation_timestamp, impl); + + // Tell the |LayerTreeHostImpl| to run our callback with the presentation + // feedback corresponding to the given |frame_token|. + impl->RegisterCompositorPresentationTimeCallback( + frame_token, std::move(presentation_callback)); +} + // These values are persisted to logs. Entries should not be renumbered and // numeric values should never be reused. enum class SourceIdConsistency : int { @@ -333,8 +371,6 @@ LayerTreeHostImpl::LayerTreeHostImpl( ? std::numeric_limits<size_t>::max() : settings.scheduled_raster_task_limit, settings.ToTileManagerSettings()), - fps_counter_( - FrameRateCounter::Create(task_runner_provider_->HasImplThread())), memory_history_(MemoryHistory::Create()), debug_rect_history_(DebugRectHistory::Create()), mutator_host_(std::move(mutator_host)), @@ -379,12 +415,14 @@ LayerTreeHostImpl::LayerTreeHostImpl( if (!base::CommandLine::ForCurrentProcess()->HasSwitch( switches::kDisableLayerTreeHostMemoryPressure)) { - memory_pressure_listener_.reset( - new base::MemoryPressureListener(base::BindRepeating( - &LayerTreeHostImpl::OnMemoryPressure, base::Unretained(this)))); + memory_pressure_listener_ = std::make_unique<base::MemoryPressureListener>( + FROM_HERE, base::BindRepeating(&LayerTreeHostImpl::OnMemoryPressure, + base::Unretained(this))); } SetDebugState(settings.initial_debug_state); + compositor_frame_reporting_controller_->SetDroppedFrameCounter( + &dropped_frame_counter_); } LayerTreeHostImpl::~LayerTreeHostImpl() { @@ -428,7 +466,6 @@ LayerTreeHostImpl::~LayerTreeHostImpl() { // Clear the UKM Manager so that we do not try to report when the // UKM System has shut down. compositor_frame_reporting_controller_->SetUkmManager(nullptr); - frame_trackers_.SetUkmManager(nullptr); } void LayerTreeHostImpl::WillSendBeginMainFrame() { @@ -1034,6 +1071,7 @@ bool LayerTreeHostImpl::ScrollLayerTo(ElementId element_id, } bool LayerTreeHostImpl::ScrollingShouldSwitchtoMainThread() { + DCHECK(!base::FeatureList::IsEnabled(features::kScrollUnification)); ScrollTree& scroll_tree = active_tree_->property_trees()->scroll_tree; ScrollNode* scroll_node = scroll_tree.CurrentlyScrollingNode(); @@ -1550,8 +1588,9 @@ DrawResult LayerTreeHostImpl::PrepareToDraw(FrameData* frame) { base::saturated_cast<int>(active_tree_->picture_layers().size()), 1, 400, 20); - // TODO(yigu): Maybe we should use the same check above. Need to figure out - // why exactly we skip 0. + // TODO(pdr): Instead of skipping empty picture layers, maybe we should + // accumulate layer->GetRasterSource()->GetMemoryUsage() above and skip + // recording when the accumulated memory usage is 0. if (!active_tree()->picture_layers().empty()) { UMA_HISTOGRAM_CUSTOM_COUNTS( base::StringPrintf("Compositing.%s.GPUMemoryForTilingsInKb", @@ -1719,7 +1758,7 @@ void LayerTreeHostImpl::ResetTreesForTesting() { } size_t LayerTreeHostImpl::SourceAnimationFrameNumberForTesting() const { - return fps_counter_->current_frame_number(); + return *next_frame_token_; } void LayerTreeHostImpl::UpdateTileManagerMemoryPolicy( @@ -2192,6 +2231,15 @@ viz::CompositorFrameMetadata LayerTreeHostImpl::MakeCompositorFrameMetadata() { metadata.display_transform_hint = active_tree_->display_transform_hint(); + if (std::unique_ptr<viz::DelegatedInkMetadata> delegated_ink_metadata = + active_tree_->take_delegated_ink_metadata()) { + TRACE_EVENT_INSTANT1( + "cc", "Delegated Ink Metadata set on compositor frame metadata", + TRACE_EVENT_SCOPE_THREAD, "point", + delegated_ink_metadata->point().ToString()); + metadata.delegated_ink_metadata = std::move(delegated_ink_metadata); + } + return metadata; } @@ -2324,6 +2372,19 @@ bool LayerTreeHostImpl::DrawLayers(FrameData* frame) { std::move(compositor_frame), /*hit_test_data_changed=*/false, debug_state_.show_hit_test_borders); + // This is expected to be true roughly every 5 seconds. + if (frame_trackers_.HasThroughputData()) { + ukm::SourceId source_id = ukm_manager_->source_id(); + // source_id can be invalid in tests. + if (source_id != ukm::kInvalidSourceId) { + int aggregated_percent = frame_trackers_.TakeLastAggregatedPercent(); + int impl_percent = frame_trackers_.TakeLastImplPercent(); + base::Optional<int> main_percent = frame_trackers_.TakeLastMainPercent(); + client_->SubmitThroughputData(source_id, aggregated_percent, impl_percent, + main_percent); + } + } + #if DCHECK_IS_ON() if (!doing_sync_draw_) { // The throughput computation (in |FrameSequenceTracker|) depends on the @@ -2387,8 +2448,6 @@ viz::CompositorFrame LayerTreeHostImpl::GenerateCompositorFrame( TRACE_ID_GLOBAL(CurrentBeginFrameArgs().trace_id), TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT, "step", "GenerateCompositorFrame"); - base::TimeTicks frame_time = CurrentBeginFrameArgs().frame_time; - fps_counter_->SaveTimeStamp(frame_time); rendering_stats_instrumentation_->IncrementFrameCount(1); memory_history_->SaveEntry(tile_manager_.memory_stats_from_last_assign()); @@ -2457,6 +2516,8 @@ viz::CompositorFrame LayerTreeHostImpl::GenerateCompositorFrame( if (render_frame_metadata_observer_) { last_draw_render_frame_metadata_ = MakeRenderFrameMetadata(frame); + last_draw_render_frame_metadata_->has_delegated_ink_metadata = + metadata.delegated_ink_metadata.get(); // We cache the value of any new vertical scroll direction so that we can // accurately determine when the next change in vertical scroll direction @@ -2477,6 +2538,8 @@ viz::CompositorFrame LayerTreeHostImpl::GenerateCompositorFrame( base::TimeTicks draw_time = base::TimeTicks::Now(); ukm::SourceId exemplar = metadata.latency_info.front().ukm_source_id(); + ApplyFirstScrollTracking(&metadata.latency_info.front(), + metadata.frame_token, this); bool all_valid = true; bool all_unique = true; for (auto& latency : metadata.latency_info) { @@ -3653,6 +3716,16 @@ void LayerTreeHostImpl::DidChangeBrowserControlsPosition() { SetFullViewportDamage(); } +void LayerTreeHostImpl::DidObserveScrollDelay( + base::TimeDelta scroll_delay, + base::TimeTicks scroll_timestamp) { + // Record First Scroll Delay. + if (!has_observed_first_scroll_delay_) { + client_->DidObserveFirstScrollDelay(scroll_delay, scroll_timestamp); + has_observed_first_scroll_delay_ = true; + } +} + float LayerTreeHostImpl::TopControlsHeight() const { return active_tree_->top_controls_height(); } @@ -3690,20 +3763,43 @@ void LayerTreeHostImpl::BindToClient(InputHandlerClient* client) { input_handler_client_ = client; } -gfx::Vector2dF LayerTreeHostImpl::ResolveScrollPercentageToPixels( +gfx::Vector2dF LayerTreeHostImpl::ResolveScrollGranularityToPixels( const ScrollNode& scroll_node, - const gfx::Vector2dF& scroll_delta) { - gfx::Vector2dF scroll_delta_in_pixels; - scroll_delta_in_pixels.set_x(scroll_delta.x() * - scroll_node.container_bounds.width()); - scroll_delta_in_pixels.set_y(scroll_delta.y() * - scroll_node.container_bounds.height()); - return scroll_delta_in_pixels; + const gfx::Vector2dF& scroll_delta, + ui::ScrollGranularity granularity) { + gfx::Vector2dF pixel_delta = scroll_delta; + + if (granularity == ui::ScrollGranularity::kScrollByPage) { + // Page should use a percentage of the scroller so change the parameters + // and let the percentage case below resolve it. + granularity = ui::ScrollGranularity::kScrollByPercentage; + pixel_delta.Scale(kMinFractionToStepWhenPaging); + } + + if (granularity == ui::ScrollGranularity::kScrollByPercentage) { + gfx::SizeF scroller_size = gfx::SizeF(scroll_node.container_bounds); + + gfx::SizeF viewport_size = + InnerViewportScrollNode() + ? gfx::SizeF(InnerViewportScrollNode()->container_bounds) + : gfx::SizeF(active_tree()->GetDeviceViewport().size()); + + // Convert from rootframe coordinates to screen coordinates (physical + // pixels). + scroller_size.Scale(active_tree()->page_scale_factor_for_scroll()); + + pixel_delta = ScrollUtils::ResolveScrollPercentageToPixels( + scroll_delta, scroller_size, viewport_size); + } + + return pixel_delta; } InputHandler::ScrollStatus LayerTreeHostImpl::TryScroll( const ScrollTree& scroll_tree, ScrollNode* scroll_node) const { + DCHECK(!base::FeatureList::IsEnabled(features::kScrollUnification)); + InputHandler::ScrollStatus scroll_status; scroll_status.main_thread_scrolling_reasons = MainThreadScrollingReason::kNotScrollingOnMain; @@ -3736,9 +3832,9 @@ InputHandler::ScrollStatus LayerTreeHostImpl::TryScroll( } // If an associated scrolling layer is not found, the scroll node must not - // support impl-scrolling. The root, secondary root, and inner viewports are - // all exceptions to this and may not have a layer because it is not required - // for hit testing. + // support impl-scrolling. The root, secondary root, and inner viewports + // are all exceptions to this and may not have a layer because it is not + // required for hit testing. if (scroll_node->id != ScrollTree::kRootNodeId && scroll_node->id != ScrollTree::kSecondaryRootNodeId && !scroll_node->scrolls_inner_viewport && @@ -3804,6 +3900,7 @@ ScrollNode* LayerTreeHostImpl::FindScrollNodeForCompositedScrolling( LayerImpl* layer_impl, bool* scroll_on_main_thread, uint32_t* main_thread_scrolling_reasons) const { + DCHECK(!base::FeatureList::IsEnabled(features::kScrollUnification)); DCHECK(scroll_on_main_thread); DCHECK(main_thread_scrolling_reasons); *main_thread_scrolling_reasons = @@ -3894,7 +3991,13 @@ InputHandler::ScrollStatus LayerTreeHostImpl::RootScrollBegin( scroll_state->data()->set_current_native_scrolling_element( OuterViewportScrollNode()->element_id); - return ScrollBegin(scroll_state, type); + InputHandler::ScrollStatus scroll_status = ScrollBegin(scroll_state, type); + + // Since we provided an ElementId, there should never be a need to perform a + // hit test. + DCHECK(!scroll_status.needs_main_thread_hit_test); + + return scroll_status; } InputHandler::ScrollStatus LayerTreeHostImpl::ScrollBegin( @@ -3937,59 +4040,145 @@ InputHandler::ScrollStatus LayerTreeHostImpl::ScrollBegin( // this should only happen in ScrollEnd. We should DCHECK here that the state // is cleared instead. https://crbug.com/1016229 ClearCurrentlyScrollingNode(); + auto& scroll_tree = active_tree_->property_trees()->scroll_tree; - if (auto specified_element_id = - scroll_state->data()->current_native_scrolling_element()) { + ElementId target_element_id = scroll_state->target_element_id(); + + if (target_element_id && !scroll_state->is_main_thread_hit_tested()) { + TRACE_EVENT_INSTANT0("cc", "Latched scroll node provided", + TRACE_EVENT_SCOPE_THREAD); // If the caller passed in an element_id we can skip all the hit-testing // bits and provide a node straight-away. - auto& scroll_tree = active_tree_->property_trees()->scroll_tree; - scrolling_node = scroll_tree.FindNodeFromElementId(specified_element_id); - - // We still need to confirm the targeted node exists and can scroll on the - // compositor. - if (scrolling_node) { - scroll_status = TryScroll(active_tree_->property_trees()->scroll_tree, - scrolling_node); - if (IsMainThreadScrolling(scroll_status, scrolling_node)) - scroll_on_main_thread = true; + scrolling_node = scroll_tree.FindNodeFromElementId(target_element_id); + + // In unified scrolling, if we found a node we get to scroll it. + if (!base::FeatureList::IsEnabled(features::kScrollUnification)) { + // We still need to confirm the targeted node exists and can scroll on + // the compositor. + if (scrolling_node) { + scroll_status = TryScroll(active_tree_->property_trees()->scroll_tree, + scrolling_node); + if (IsMainThreadScrolling(scroll_status, scrolling_node)) + scroll_on_main_thread = true; + } } } else { - gfx::Point viewport_point(scroll_state->position_x(), - scroll_state->position_y()); + ScrollNode* starting_node = nullptr; + if (target_element_id) { + TRACE_EVENT_INSTANT0("cc", "Unlatched scroll node provided", + TRACE_EVENT_SCOPE_THREAD); + // We had an element id but we should still perform the walk up the + // scroll tree from the targeted node to latch to a scroller that can + // scroll in the given direction. This mode is only used when scroll + // unification is enabled and the targeted scroller comes back from a + // main thread hit test. + DCHECK(scroll_state->data()->is_main_thread_hit_tested); + DCHECK(base::FeatureList::IsEnabled(features::kScrollUnification)); + starting_node = scroll_tree.FindNodeFromElementId(target_element_id); + + if (!starting_node) { + // The main thread sent us an element_id that the compositor doesn't + // have a scroll node for. This can happen in some racy conditions, a + // freshly created scroller hasn't yet been committed or a + // scroller-destroying commit beats the hit test back to the compositor + // thread. However, these cases shouldn't be user perceptible. + scroll_status.main_thread_scrolling_reasons = + MainThreadScrollingReason::kNoScrollingLayer; + scroll_status.thread = SCROLL_IGNORED; + return scroll_status; + } + } else { + TRACE_EVENT_INSTANT0("cc", "Hit Testing for ScrollNode", + TRACE_EVENT_SCOPE_THREAD); + gfx::Point viewport_point(scroll_state->position_x(), + scroll_state->position_y()); + gfx::PointF device_viewport_point = gfx::ScalePoint( + gfx::PointF(viewport_point), active_tree_->device_scale_factor()); + + if (base::FeatureList::IsEnabled(features::kScrollUnification)) { + if (scroll_state->data()->is_main_thread_hit_tested) { + // The client should have discarded the scroll when the hit test came + // back with an invalid element id. If we somehow get here, we should + // drop the scroll as continuing could cause us to infinitely bounce + // back and forth between here and hit testing on the main thread. + NOTREACHED(); + scroll_status.main_thread_scrolling_reasons = + MainThreadScrollingReason::kNoScrollingLayer; + scroll_status.thread = SCROLL_IGNORED; + return scroll_status; + } - gfx::PointF device_viewport_point = gfx::ScalePoint( - gfx::PointF(viewport_point), active_tree_->device_scale_factor()); - LayerImpl* layer_impl = - active_tree_->FindLayerThatIsHitByPoint(device_viewport_point); + // Touch dragging the scrollbar requires falling back to main-thread + // scrolling. + // TODO(bokan): This could be trivially handled in the compositor by + // the new ScrollbarController and should be removed. + { + LayerImpl* first_scrolling_layer_or_scrollbar = + active_tree_->FindFirstScrollingLayerOrScrollbarThatIsHitByPoint( + device_viewport_point); + if (IsTouchDraggingScrollbar(first_scrolling_layer_or_scrollbar, + type)) { + TRACE_EVENT_INSTANT0("cc", "Scrollbar Scrolling", + TRACE_EVENT_SCOPE_THREAD); + scroll_status.thread = SCROLL_ON_MAIN_THREAD; + scroll_status.main_thread_scrolling_reasons = + MainThreadScrollingReason::kScrollbarScrolling; + return scroll_status; + } + } - if (layer_impl) { - LayerImpl* first_scrolling_layer_or_scrollbar = - active_tree_->FindFirstScrollingLayerOrScrollbarThatIsHitByPoint( - device_viewport_point); + ScrollHitTestResult scroll_hit_test = + HitTestScrollNode(device_viewport_point); + + if (!scroll_hit_test.hit_test_successful) { + // This result tells the client that the compositor doesn't have + // enough information to target this scroll. The client should + // perform a hit test in Blink and call this method again, with the + // ElementId of the hit-tested scroll node. + TRACE_EVENT_INSTANT0("cc", "Request Main Thread Hit Test", + TRACE_EVENT_SCOPE_THREAD); + scroll_status.thread = SCROLL_ON_IMPL_THREAD; + scroll_status.needs_main_thread_hit_test = true; + return scroll_status; + } - // Touch dragging the scrollbar requires falling back to main-thread - // scrolling. - if (IsTouchDraggingScrollbar(first_scrolling_layer_or_scrollbar, type)) { - TRACE_EVENT_INSTANT0("cc", "Scrollbar Scrolling", - TRACE_EVENT_SCOPE_THREAD); - scroll_status.thread = SCROLL_ON_MAIN_THREAD; - scroll_status.main_thread_scrolling_reasons = - MainThreadScrollingReason::kScrollbarScrolling; - return scroll_status; - } else if (!IsInitialScrollHitTestReliable( - layer_impl, first_scrolling_layer_or_scrollbar)) { - TRACE_EVENT_INSTANT0("cc", "Failed Hit Test", TRACE_EVENT_SCOPE_THREAD); - scroll_status.thread = SCROLL_UNKNOWN; - scroll_status.main_thread_scrolling_reasons = - MainThreadScrollingReason::kFailedHitTest; - return scroll_status; + starting_node = scroll_hit_test.scroll_node; + } else { + LayerImpl* layer_impl = + active_tree_->FindLayerThatIsHitByPoint(device_viewport_point); + + if (layer_impl) { + LayerImpl* first_scrolling_layer_or_scrollbar = + active_tree_->FindFirstScrollingLayerOrScrollbarThatIsHitByPoint( + device_viewport_point); + + // Touch dragging the scrollbar requires falling back to main-thread + // scrolling. + if (IsTouchDraggingScrollbar(first_scrolling_layer_or_scrollbar, + type)) { + TRACE_EVENT_INSTANT0("cc", "Scrollbar Scrolling", + TRACE_EVENT_SCOPE_THREAD); + scroll_status.thread = SCROLL_ON_MAIN_THREAD; + scroll_status.main_thread_scrolling_reasons = + MainThreadScrollingReason::kScrollbarScrolling; + return scroll_status; + } else if (!IsInitialScrollHitTestReliable( + layer_impl, first_scrolling_layer_or_scrollbar)) { + TRACE_EVENT_INSTANT0("cc", "Failed Hit Test", + TRACE_EVENT_SCOPE_THREAD); + scroll_status.thread = SCROLL_UNKNOWN; + scroll_status.main_thread_scrolling_reasons = + MainThreadScrollingReason::kFailedHitTest; + return scroll_status; + } + } + + starting_node = FindScrollNodeForCompositedScrolling( + device_viewport_point, layer_impl, &scroll_on_main_thread, + &scroll_status.main_thread_scrolling_reasons); } } - ScrollNode* starting_node = FindScrollNodeForCompositedScrolling( - device_viewport_point, layer_impl, &scroll_on_main_thread, - &scroll_status.main_thread_scrolling_reasons); - // The above finds the ScrollNode that's hit by the given point but we // still need to walk up the scroll tree looking for the first node that // can consume delta from the scroll state. @@ -3997,6 +4186,10 @@ InputHandler::ScrollStatus LayerTreeHostImpl::ScrollBegin( } if (scroll_on_main_thread) { + // Under scroll unification we can request a main thread hit test, but we + // should never send scrolls to the main thread. + DCHECK(!base::FeatureList::IsEnabled(features::kScrollUnification)); + RecordCompositorSlowScrollMetric(type, MAIN_THREAD); scroll_status.thread = SCROLL_ON_MAIN_THREAD; return scroll_status; @@ -4004,11 +4197,20 @@ InputHandler::ScrollStatus LayerTreeHostImpl::ScrollBegin( scroll_status.main_thread_scrolling_reasons = MainThreadScrollingReason::kNoScrollingLayer; if (settings_.is_layer_tree_for_subframe) { + // OOPIFs never have a viewport scroll node so if we can't scroll + // we need to be bubble up to the parent frame. This happens by + // returning SCROLL_UNKNOWN. TRACE_EVENT_INSTANT0("cc", "Ignored - No ScrollNode (OOPIF)", TRACE_EVENT_SCOPE_THREAD); scroll_status.thread = SCROLL_UNKNOWN; } else { - TRACE_EVENT_INSTANT0("cc", "Ignroed - No ScrollNode", + // If we didn't hit a layer above we'd usually fallback to the + // viewport scroll node. However, there may not be one if a scroll + // is received before the root layer has been attached. Chrome now + // drops input until the first commit is received so this probably + // can't happen in a typical browser session but there may still be + // configurations where input is allowed prior to a commit. + TRACE_EVENT_INSTANT0("cc", "Ignored - No ScrollNode", TRACE_EVENT_SCOPE_THREAD); scroll_status.thread = SCROLL_IGNORED; } @@ -4034,13 +4236,22 @@ InputHandler::ScrollStatus LayerTreeHostImpl::ScrollBegin( return scroll_status; } -ScrollNode* LayerTreeHostImpl::HitTestScrollNode( +LayerTreeHostImpl::ScrollHitTestResult LayerTreeHostImpl::HitTestScrollNode( const gfx::PointF& device_viewport_point) const { + ScrollHitTestResult result; + result.scroll_node = nullptr; + result.hit_test_successful = false; + LayerImpl* layer_impl = active_tree_->FindLayerThatIsHitByPoint(device_viewport_point); - if (!layer_impl) - return nullptr; + if (!layer_impl) { + result.hit_test_successful = true; + if (InnerViewportScrollNode()) + result.scroll_node = GetNodeToScroll(InnerViewportScrollNode()); + + return result; + } // There are some cases where the hit layer may not be correct (e.g. layer // squashing). If we detect this case, we can't target a scroll node here. @@ -4052,10 +4263,19 @@ ScrollNode* LayerTreeHostImpl::HitTestScrollNode( if (!IsInitialScrollHitTestReliable(layer_impl, first_scrolling_layer_or_scrollbar)) { TRACE_EVENT_INSTANT0("cc", "Failed Hit Test", TRACE_EVENT_SCOPE_THREAD); - return nullptr; + return result; } } + // If we hit a non-fast scrollable region, that means there's some reason we + // can't scroll in this region. Primarily, because there's another scroller + // there that isn't composited and we don't know about so we'll return + // nullptr in that case. + if (active_tree_->PointHitsNonFastScrollableRegion(device_viewport_point, + *layer_impl)) { + return result; + } + // If we hit a scrollbar layer, get the ScrollNode from its associated // scrolling layer, rather than directly from the scrollbar layer. The latter // would return the parent scroller's ScrollNode. @@ -4068,7 +4288,9 @@ ScrollNode* LayerTreeHostImpl::HitTestScrollNode( ScrollTree& scroll_tree = active_tree_->property_trees()->scroll_tree; ScrollNode* scroll_node = scroll_tree.Node(layer_impl->scroll_tree_index()); - return GetNodeToScroll(scroll_node); + result.scroll_node = GetNodeToScroll(scroll_node); + result.hit_test_successful = true; + return result; } // Requires falling back to main thread scrolling when it hit tests in scrollbar @@ -4602,10 +4824,9 @@ bool LayerTreeHostImpl::CanConsumeDelta(const ScrollState& scroll_state, return false; } delta_to_scroll = local_scroll_delta; - } else if (scroll_state.delta_granularity() == - ui::ScrollGranularity::kScrollByPercentage) { - delta_to_scroll = - ResolveScrollPercentageToPixels(scroll_node, delta_to_scroll); + } else { + delta_to_scroll = ResolveScrollGranularityToPixels( + scroll_node, delta_to_scroll, scroll_state.delta_granularity()); } if (ComputeScrollDelta(scroll_node, delta_to_scroll) != gfx::Vector2dF()) @@ -4656,21 +4877,27 @@ InputHandlerScrollResult LayerTreeHostImpl::ScrollUpdate( // The current_native_scrolling_element should only be set for ScrollBegin. DCHECK(!scroll_state->data()->current_native_scrolling_element()); + TRACE_EVENT2("cc", "LayerTreeHostImpl::ScrollUpdate", "dx", + scroll_state->delta_x(), "dy", scroll_state->delta_y()); if (!CurrentlyScrollingNode()) return InputHandlerScrollResult(); last_scroll_update_state_ = *scroll_state; - bool is_delta_percent_units = scroll_state->delta_granularity() == - ui::ScrollGranularity::kScrollByPercentage; - if (is_delta_percent_units) { - gfx::Vector2dF resolvedScrollDelta = ResolveScrollPercentageToPixels( - *CurrentlyScrollingNode(), - gfx::Vector2dF(scroll_state->delta_x(), scroll_state->delta_y())); - - scroll_state->data()->delta_x = resolvedScrollDelta.x(); - scroll_state->data()->delta_y = resolvedScrollDelta.y(); + gfx::Vector2dF resolvedScrollDelta = ResolveScrollGranularityToPixels( + *CurrentlyScrollingNode(), + gfx::Vector2dF(scroll_state->delta_x(), scroll_state->delta_y()), + scroll_state->delta_granularity()); + + scroll_state->data()->delta_x = resolvedScrollDelta.x(); + scroll_state->data()->delta_y = resolvedScrollDelta.y(); + // The decision of whether or not we'll animate a scroll comes down to + // whether the granularity is specified in precise pixels or not. Thus we + // need to preserve a precise granularity if that's what was specified; all + // others are animated and so can be resolved to regular pixels. + if (scroll_state->delta_granularity() != + ui::ScrollGranularity::kScrollByPrecisePixel) { scroll_state->data()->delta_granularity = ui::ScrollGranularity::kScrollByPixel; } @@ -4994,9 +5221,6 @@ void LayerTreeHostImpl::RecordScrollBegin( ScrollBeginThreadState scroll_start_state) { auto tracker_type = GetTrackerTypeForScroll(input_type); DCHECK_NE(tracker_type, FrameSequenceTrackerType::kMaxType); - auto* metrics = frame_trackers_.StartSequence(tracker_type); - if (!metrics) - return; // The main-thread is the 'scrolling thread' if: // (1) the scroll is driven by the main thread, or @@ -5015,7 +5239,7 @@ void LayerTreeHostImpl::RecordScrollBegin( scrolling_thread = FrameSequenceMetrics::ThreadType::kMain; break; } - metrics->SetScrollingThread(scrolling_thread); + frame_trackers_.StartScrollSequence(tracker_type, scrolling_thread); } void LayerTreeHostImpl::RecordScrollEnd(ui::ScrollInputType input_type) { @@ -5078,11 +5302,14 @@ InputHandlerPointerResult LayerTreeHostImpl::MouseMoveAt( gfx::PointF device_viewport_point = gfx::ScalePoint( gfx::PointF(viewport_point), active_tree_->device_scale_factor()); - ScrollNode* scroll_node = HitTestScrollNode(device_viewport_point); + + ScrollHitTestResult hit_test = HitTestScrollNode(device_viewport_point); + + ScrollNode* scroll_node = hit_test.scroll_node; // The hit test can fail in some cases, e.g. we don't know if a region of a // squashed layer has content or is empty. - if (!scroll_node) + if (!hit_test.hit_test_successful || !scroll_node) return result; // Scrollbars for the viewport are registered with the outer viewport layer. @@ -5242,6 +5469,9 @@ std::unique_ptr<ScrollAndScaleSet> LayerTreeHostImpl::ProcessScrollDeltas() { scroll_info->overscroll_delta = overscroll_delta_for_main_thread_; overscroll_delta_for_main_thread_ = gfx::Vector2dF(); + scroll_info->ongoing_scroll_animation = + !!mutator_host_->ImplOnlyScrollAnimatingElement(); + // Use the |last_latched_scroller_| rather than the |CurrentlyScrollingNode| // since the latter may be cleared by a GSE before we've committed these // values to the main thread. @@ -5398,9 +5628,10 @@ void LayerTreeHostImpl::RegisterScrollbarAnimationController( } void LayerTreeHostImpl::DidUnregisterScrollbarLayer( - ElementId scroll_element_id) { + ElementId scroll_element_id, + ScrollbarOrientation orientation) { scrollbar_animation_controllers_.erase(scroll_element_id); - scrollbar_controller_->DidUnregisterScrollbar(scroll_element_id); + scrollbar_controller_->DidUnregisterScrollbar(scroll_element_id, orientation); } ScrollbarAnimationController* @@ -6230,7 +6461,7 @@ void LayerTreeHostImpl::InitializeUkm( void LayerTreeHostImpl::SetActiveURL(const GURL& url, ukm::SourceId source_id) { tile_manager_.set_active_url(url); - + has_observed_first_scroll_delay_ = false; // The active tree might still be from content for the previous page when the // recorder is updated here, since new content will be pushed with the next // main frame. But we should only get a few impl frames wrong here in that diff --git a/chromium/cc/trees/layer_tree_host_impl.h b/chromium/cc/trees/layer_tree_host_impl.h index f7ce4e673e6..47dcba6decb 100644 --- a/chromium/cc/trees/layer_tree_host_impl.h +++ b/chromium/cc/trees/layer_tree_host_impl.h @@ -29,6 +29,7 @@ #include "cc/input/scrollbar_animation_controller.h" #include "cc/input/scrollbar_controller.h" #include "cc/layers/layer_collections.h" +#include "cc/metrics/dropped_frame_counter.h" #include "cc/metrics/event_metrics.h" #include "cc/metrics/events_metrics_manager.h" #include "cc/metrics/frame_sequence_tracker_collection.h" @@ -83,7 +84,6 @@ class BrowserControlsOffsetManager; class CompositorFrameReportingController; class DebugRectHistory; class EvictionTilePriorityQueue; -class FrameRateCounter; class ImageAnimationController; class LCDTextMetricsReporter; class LayerImpl; @@ -176,6 +176,17 @@ class LayerTreeHostImplClient { virtual void NotifyThroughputTrackerResults(CustomTrackerResults results) = 0; + // Send the throughput data to the main thread's LayerTreeHostClient, which + // then send the data to the browser process and eventually report to UKM. + virtual void SubmitThroughputData(ukm::SourceId source_id, + int aggregated_percent, + int impl_percent, + base::Optional<int> main_percent) = 0; + + virtual void DidObserveFirstScrollDelay( + base::TimeDelta first_scroll_delay, + base::TimeTicks first_scroll_timestamp) = 0; + protected: virtual ~LayerTreeHostImplClient() = default; }; @@ -333,6 +344,8 @@ class CC_EXPORT LayerTreeHostImpl : public InputHandler, float CurrentTopControlsShownRatio() const override; float CurrentBottomControlsShownRatio() const override; void DidChangeBrowserControlsPosition() override; + void DidObserveScrollDelay(base::TimeDelta scroll_delay, + base::TimeTicks scroll_timestamp); bool HaveRootScrollNode() const override; void SetNeedsCommit() override; @@ -469,7 +482,8 @@ class CC_EXPORT LayerTreeHostImpl : public InputHandler, void RegisterScrollbarAnimationController(ElementId scroll_element_id, float initial_opacity); - void DidUnregisterScrollbarLayer(ElementId scroll_element_id); + void DidUnregisterScrollbarLayer(ElementId scroll_element_id, + ScrollbarOrientation orientation); ScrollbarAnimationController* ScrollbarAnimationControllerForElementId( ElementId scroll_element_id) const; void FlashAllScrollbars(bool did_scroll); @@ -610,7 +624,8 @@ class CC_EXPORT LayerTreeHostImpl : public InputHandler, const ScrollNode* CurrentlyScrollingNode() const; bool scroll_affects_scroll_handler() const { - return scroll_affects_scroll_handler_; + return settings_.enable_synchronized_scrolling && + scroll_affects_scroll_handler_; } void QueueSwapPromiseForMainThreadScrollUpdate( std::unique_ptr<SwapPromise> swap_promise); @@ -641,9 +656,8 @@ class CC_EXPORT LayerTreeHostImpl : public InputHandler, const gfx::Transform& DrawTransform() const; std::unique_ptr<ScrollAndScaleSet> ProcessScrollDeltas(); - FrameRateCounter* fps_counter() { return fps_counter_.get(); } - base::Optional<int> current_universal_throughput() { - return frame_trackers_.current_universal_throughput(); + DroppedFrameCounter* dropped_frame_counter() { + return &dropped_frame_counter_; } MemoryHistory* memory_history() { return memory_history_.get(); } DebugRectHistory* debug_rect_history() { return debug_rect_history_.get(); } @@ -735,6 +749,13 @@ class CC_EXPORT LayerTreeHostImpl : public InputHandler, gfx::Vector2dF ComputeScrollDelta(const ScrollNode& scroll_node, const gfx::Vector2dF& delta); + // Resolves a delta in the given granularity for the |scroll_node| into + // physical pixels to scroll. + gfx::Vector2dF ResolveScrollGranularityToPixels( + const ScrollNode& scroll_node, + const gfx::Vector2dF& scroll_delta, + ui::ScrollGranularity granularity); + void ScheduleMicroBenchmark(std::unique_ptr<MicroBenchmarkImpl> benchmark); viz::CompositorFrameMetadata MakeCompositorFrameMetadata(); @@ -785,15 +806,6 @@ class CC_EXPORT LayerTreeHostImpl : public InputHandler, : task_runner_provider_->MainThreadTaskRunner(); } - // Determines whether the given scroll node can scroll on the compositor - // thread or if there are any reasons it must be scrolled on the main thread - // or not at all. Note: in general, this is not sufficient to determine if a - // scroll can occur on the compositor thread. If hit testing to a scroll - // node, the caller must also check whether the hit point intersects a - // non-fast-scrolling-region of any ancestor scrolling layers. - InputHandler::ScrollStatus TryScroll(const ScrollTree& scroll_tree, - ScrollNode* scroll_node) const; - // Return all ScrollNode indices that have an associated layer with a non-fast // region that intersects the point. base::flat_set<int> NonFastScrollableNodes( @@ -894,14 +906,20 @@ class CC_EXPORT LayerTreeHostImpl : public InputHandler, void CollectScrollDeltas(ScrollAndScaleSet* scroll_info); void CollectScrollbarUpdates(ScrollAndScaleSet* scroll_info) const; - gfx::Vector2dF ResolveScrollPercentageToPixels( - const ScrollNode& scroll_node, - const gfx::Vector2dF& resolved_pixels); - // Returns the ScrollNode we should use to scroll, accounting for viewport // scroll chaining rules. ScrollNode* GetNodeToScroll(ScrollNode* node) const; + // Determines whether the given scroll node can scroll on the compositor + // thread or if there are any reasons it must be scrolled on the main thread + // or not at all. Note: in general, this is not sufficient to determine if a + // scroll can occur on the compositor thread. If hit testing to a scroll + // node, the caller must also check whether the hit point intersects a + // non-fast-scrolling-region of any ancestor scrolling layers. Can be removed + // after scroll unification https://crbug.com/476553. + InputHandler::ScrollStatus TryScroll(const ScrollTree& scroll_tree, + ScrollNode* scroll_node) const; + // Transforms viewport start point and scroll delta to local start point and // local delta, respectively. If the transformation of either the start or end // point of a scroll is clipped, the function returns false. @@ -957,13 +975,14 @@ class CC_EXPORT LayerTreeHostImpl : public InputHandler, LayerImpl* first_scrolling_layer_or_drawn_scrollbar, ui::ScrollInputType type); - // Initial scroll hit testing can be unreliable in the presence of squashed - // layers. In this case, we fall back to main thread scrolling. This function - // compares |layer_impl| returned from a regular hit test to the layer - // returned from a hit test performed only on scrollers and scrollbars. If the - // closest scrolling ancestor of |layer_impl| is not the other layer, then the - // layer_impl must be a squasing layer overtop of some other scroller and we - // must rely on the main thread. + // |layer| is returned from a regular hit test, and + // |first_scrolling_layer_or_drawn_scrollbar| is returned from a hit test + // performed only on scrollers and scrollbars. Initial scroll hit testing can + // be unreliable if the latter is not the direct scroll ancestor of the + // former. In this case, we will fall back to main thread scrolling because + // the compositor thread doesn't know which layer to scroll. This happens when + // a layer covers a scroller that doesn't scroll the former, or a scroller is + // masked by a mask layer for mask image, clip-path, rounded border, etc. // // Note, position: fixed layers use the inner viewport as their ScrollNode // (since they don't scroll with the outer viewport), however, scrolls from @@ -1018,21 +1037,35 @@ class CC_EXPORT LayerTreeHostImpl : public InputHandler, void ClearCurrentlyScrollingNode(); // Performs a hit test to determine the ScrollNode to use when scrolling at - // |viewport_point|. Can return nullptr if the hit test fails; see the - // comment in IsInitialScrollHitTestReliable - ScrollNode* HitTestScrollNode(const gfx::PointF& device_viewport_point) const; + // |viewport_point|. If no layer is hit, this falls back to the inner + // viewport scroll node. Returns: + // - If |hit_test_sucessful| is false, hit testing has failed and the + // compositor cannot determine the correct scroll node (e.g. see comments in + // IsInitialScrollHitTestReliable). |scroll_node| is always nullptr in this + // case. + // - If |hit_test_successful| is true, returns the ScrollNode to use in + // |scroll_node|. This can be nullptr if no layer was hit and there are no + // viewport nodes (e.g. OOPIF, UI compositor). + struct ScrollHitTestResult { + ScrollNode* scroll_node; + bool hit_test_successful; + }; + ScrollHitTestResult HitTestScrollNode( + const gfx::PointF& device_viewport_point) const; // Similar to above but includes complicated logic to determine whether the // ScrollNode is able to be scrolled on the compositor or requires main // thread scrolling. If main thread scrolling is required // |scroll_on_main_thread| is set to true and the reason is given in // |main_thread_scrolling_reason| to on of the enum values in - // main_thread_scrolling_reason.h. + // main_thread_scrolling_reason.h. Can be removed after scroll unification + // https://crbug.com/476553. ScrollNode* FindScrollNodeForCompositedScrolling( const gfx::PointF& device_viewport_point, LayerImpl* layer_hit_by_point, bool* scroll_on_main_thread, uint32_t* main_thread_scrolling_reason) const; + void StartScrollbarFadeRecursive(LayerImpl* layer); void SetManagedMemoryPolicy(const ManagedMemoryPolicy& policy); @@ -1226,7 +1259,7 @@ class CC_EXPORT LayerTreeHostImpl : public InputHandler, std::unique_ptr<PageScaleAnimation> page_scale_animation_; - std::unique_ptr<FrameRateCounter> fps_counter_; + DroppedFrameCounter dropped_frame_counter_; std::unique_ptr<MemoryHistory> memory_history_; std::unique_ptr<DebugRectHistory> debug_rect_history_; @@ -1400,6 +1433,7 @@ class CC_EXPORT LayerTreeHostImpl : public InputHandler, std::unique_ptr<LCDTextMetricsReporter> lcd_text_metrics_reporter_; FrameRateEstimator frame_rate_estimator_; + bool has_observed_first_scroll_delay_ = false; // Must be the last member to ensure this is destroyed first in the // destruction order and invalidates all weak pointers. diff --git a/chromium/cc/trees/layer_tree_host_impl_unittest.cc b/chromium/cc/trees/layer_tree_host_impl_unittest.cc index 3d1cfc76a5d..fd82323bf7e 100644 --- a/chromium/cc/trees/layer_tree_host_impl_unittest.cc +++ b/chromium/cc/trees/layer_tree_host_impl_unittest.cc @@ -27,10 +27,12 @@ #include "cc/animation/animation_host.h" #include "cc/animation/animation_id_provider.h" #include "cc/animation/transform_operations.h" +#include "cc/base/features.h" #include "cc/base/histograms.h" #include "cc/input/browser_controls_offset_manager.h" #include "cc/input/main_thread_scrolling_reason.h" #include "cc/input/page_scale_animation.h" +#include "cc/input/scroll_utils.h" #include "cc/layers/append_quads_data.h" #include "cc/layers/heads_up_display_layer_impl.h" #include "cc/layers/layer_impl.h" @@ -262,7 +264,16 @@ class LayerTreeHostImplTest : public testing::Test, void NotifyPaintWorkletStateChange( Scheduler::PaintWorkletState state) override {} void NotifyThroughputTrackerResults(CustomTrackerResults results) override {} + void SubmitThroughputData(ukm::SourceId source_id, + int aggregated_percent, + int impl_percent, + base::Optional<int> main_percent) override {} + void DidObserveFirstScrollDelay( + base::TimeDelta first_scroll_delay, + base::TimeTicks first_scroll_timestamp) override { + first_scroll_observed++; + } void set_reduce_memory_result(bool reduce_memory_result) { reduce_memory_result_ = reduce_memory_result; } @@ -496,15 +507,21 @@ class LayerTreeHostImplTest : public testing::Test, layer_tree_impl->DidBecomeActive(); // The point hits squash1 layer and also scroll layer, because scroll layer - // is not an ancestor of squash1 layer, we cannot scroll on impl thread. + // is not an ancestor of squash1 layer, we cannot scroll on impl thread + // (without at least a hit test on the main thread). InputHandler::ScrollStatus status = host_impl_->ScrollBegin( BeginState(gfx::Point(230, 150), gfx::Vector2dF(0, 10), ui::ScrollInputType::kWheel) .get(), ui::ScrollInputType::kWheel); - ASSERT_EQ(InputHandler::SCROLL_UNKNOWN, status.thread); - ASSERT_EQ(MainThreadScrollingReason::kFailedHitTest, - status.main_thread_scrolling_reasons); + if (base::FeatureList::IsEnabled(features::kScrollUnification)) { + ASSERT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD, status.thread); + ASSERT_TRUE(status.needs_main_thread_hit_test); + } else { + ASSERT_EQ(InputHandler::SCROLL_UNKNOWN, status.thread); + ASSERT_EQ(MainThreadScrollingReason::kFailedHitTest, + status.main_thread_scrolling_reasons); + } // The point hits squash1 layer and also scrollbar layer. status = host_impl_->ScrollBegin( @@ -512,9 +529,14 @@ class LayerTreeHostImplTest : public testing::Test, ui::ScrollInputType::kWheel) .get(), ui::ScrollInputType::kWheel); - ASSERT_EQ(InputHandler::SCROLL_UNKNOWN, status.thread); - ASSERT_EQ(MainThreadScrollingReason::kFailedHitTest, - status.main_thread_scrolling_reasons); + if (base::FeatureList::IsEnabled(features::kScrollUnification)) { + ASSERT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD, status.thread); + ASSERT_TRUE(status.needs_main_thread_hit_test); + } else { + ASSERT_EQ(InputHandler::SCROLL_UNKNOWN, status.thread); + ASSERT_EQ(MainThreadScrollingReason::kFailedHitTest, + status.main_thread_scrolling_reasons); + } // The point hits squash2 layer and also scroll layer, because scroll layer // is an ancestor of squash2 layer, we should scroll on impl. @@ -670,8 +692,8 @@ class LayerTreeHostImplTest : public testing::Test, host_impl_ = nullptr; } - void WhiteListedTouchActionTestHelper(float device_scale_factor, - float page_scale_factor) { + void AllowedTouchActionTestHelper(float device_scale_factor, + float page_scale_factor) { SetupViewportLayersInnerScrolls(gfx::Size(100, 100), gfx::Size(200, 200)); DrawFrame(); @@ -825,6 +847,26 @@ class LayerTreeHostImplTest : public testing::Test, scoped_refptr<AnimationTimeline> timeline_; std::unique_ptr<base::Thread> image_worker_; int next_layer_id_ = 2; + int first_scroll_observed = 0; +}; + +// Allows parameterizing the above class on the scroll unification flag. We +// can't parameterize the base class itself because it serves as a base to +// other parameterized descendants. Can be removed when unification ships. +class ScrollUnifiedLayerTreeHostImplTest + : public LayerTreeHostImplTest, + public testing::WithParamInterface<bool> { + public: + ScrollUnifiedLayerTreeHostImplTest() { + if (GetParam()) { + scoped_feature_list.InitAndEnableFeature(features::kScrollUnification); + } else { + scoped_feature_list.InitAndDisableFeature(features::kScrollUnification); + } + } + + private: + base::test::ScopedFeatureList scoped_feature_list; }; class CommitToPendingTreeLayerTreeHostImplTest : public LayerTreeHostImplTest { @@ -901,7 +943,11 @@ class TestInputHandlerClient : public InputHandlerClient { float max_page_scale_factor_; }; -TEST_F(LayerTreeHostImplTest, LocalAndExternalPinchState) { +INSTANTIATE_TEST_SUITE_P(All, + ScrollUnifiedLayerTreeHostImplTest, + testing::Bool()); + +TEST_P(ScrollUnifiedLayerTreeHostImplTest, LocalAndExternalPinchState) { // PinchGestureBegin/End update pinch_gesture_active() properly. EXPECT_FALSE(host_impl_->pinch_gesture_active()); host_impl_->PinchGestureBegin(); @@ -925,7 +971,7 @@ TEST_F(LayerTreeHostImplTest, LocalAndExternalPinchState) { EXPECT_FALSE(host_impl_->pinch_gesture_active()); } -TEST_F(LayerTreeHostImplTest, NotifyIfCanDrawChanged) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, NotifyIfCanDrawChanged) { // Note: It is not possible to disable the renderer once it has been set, // so we do not need to test that disabling the renderer notifies us // that can_draw changed. @@ -961,7 +1007,7 @@ TEST_F(LayerTreeHostImplTest, NotifyIfCanDrawChanged) { on_can_draw_state_changed_called_ = false; } -TEST_F(LayerTreeHostImplTest, ResourcelessDrawWithEmptyViewport) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ResourcelessDrawWithEmptyViewport) { CreateHostImpl(DefaultSettings(), FakeLayerTreeFrameSink::CreateSoftware()); SetupViewportLayersInnerScrolls(gfx::Size(50, 50), gfx::Size(100, 100)); @@ -979,7 +1025,7 @@ TEST_F(LayerTreeHostImplTest, ResourcelessDrawWithEmptyViewport) { ASSERT_EQ(fake_layer_tree_frame_sink->num_sent_frames(), 1u); } -TEST_F(LayerTreeHostImplTest, ScrollDeltaNoLayers) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollDeltaNoLayers) { host_impl_->active_tree()->SetRootLayerForTesting(nullptr); std::unique_ptr<ScrollAndScaleSet> scroll_info = @@ -987,7 +1033,7 @@ TEST_F(LayerTreeHostImplTest, ScrollDeltaNoLayers) { ASSERT_EQ(scroll_info->scrolls.size(), 0u); } -TEST_F(LayerTreeHostImplTest, ScrollDeltaTreeButNoChanges) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollDeltaTreeButNoChanges) { LayerImpl* root = SetupDefaultRootLayer(gfx::Size(10, 10)); { LayerImpl* child1 = AddLayer(); @@ -1015,7 +1061,7 @@ TEST_F(LayerTreeHostImplTest, ScrollDeltaTreeButNoChanges) { ExpectClearedScrollDeltasRecursive(root); } -TEST_F(LayerTreeHostImplTest, ScrollDeltaRepeatedScrolls) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollDeltaRepeatedScrolls) { gfx::ScrollOffset scroll_offset(20, 30); gfx::ScrollOffset scroll_delta(11, -15); @@ -1075,7 +1121,9 @@ TEST_F(CommitToPendingTreeLayerTreeHostImplTest, "Compositing.Renderer.GPUMemoryForTilingsInKb", 1); } -TEST_F(LayerTreeHostImplTest, ScrollBeforeRootLayerAttached) { +// This test verifies that we drop a scroll (and don't crash) if a scroll is +// received before the root layer has been attached. https://crbug.com/895817. +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollBeforeRootLayerAttached) { InputHandler::ScrollStatus status = host_impl_->ScrollBegin(BeginState(gfx::Point(), gfx::Vector2dF(0, 1), ui::ScrollInputType::kWheel) @@ -1100,7 +1148,7 @@ TEST_F(LayerTreeHostImplTest, ScrollBeforeRootLayerAttached) { // to pre-commit input deferral which causes some input events to be dropped // before the first commit in a renderer has occurred. See the flag // kAllowPreCommitInput and how it's used. -TEST_F(LayerTreeHostImplTest, ScrollUpdateAndEndNoOpWithoutBegin) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollUpdateAndEndNoOpWithoutBegin) { SetupViewportLayersOuterScrolls(gfx::Size(100, 100), gfx::Size(1000, 1000)); // Simulate receiving a gesture mid-stream so that the Begin wasn't ever @@ -1141,7 +1189,7 @@ TEST_F(LayerTreeHostImplTest, ScrollUpdateAndEndNoOpWithoutBegin) { // Test that specifying a scroller to ScrollBegin (i.e. avoid hit testing) // returns the correct status if the scroller cannot be scrolled on the // compositor thread. -TEST_F(LayerTreeHostImplTest, TargetMainThreadScroller) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, TargetMainThreadScroller) { SetupViewportLayersOuterScrolls(gfx::Size(100, 100), gfx::Size(1000, 1000)); ScrollStateData scroll_state_data; @@ -1160,22 +1208,32 @@ TEST_F(LayerTreeHostImplTest, TargetMainThreadScroller) { } // Now add a MainThreadScrollingReason. Trying to target the node this time - // should result in a failed ScrollBegin with the appropriate return value. + // should result in a failed ScrollBegin with the appropriate return value, + // unless scroll unification is enabled - since all nodes can be scrolled + // from the compositor in that mode. host_impl_->OuterViewportScrollNode()->main_thread_scrolling_reasons = MainThreadScrollingReason::kThreadedScrollingDisabled; { InputHandler::ScrollStatus status = host_impl_->ScrollBegin( scroll_state.get(), ui::ScrollInputType::kWheel); - EXPECT_EQ(InputHandler::SCROLL_ON_MAIN_THREAD, status.thread); - EXPECT_EQ( - host_impl_->OuterViewportScrollNode()->main_thread_scrolling_reasons, - status.main_thread_scrolling_reasons); - EXPECT_FALSE(host_impl_->CurrentlyScrollingNode()); + if (base::FeatureList::IsEnabled(features::kScrollUnification)) { + EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD, status.thread); + EXPECT_FALSE(status.needs_main_thread_hit_test); + EXPECT_EQ( + MainThreadScrollingReason::kThreadedScrollingDisabled, + host_impl_->CurrentlyScrollingNode()->main_thread_scrolling_reasons); + } else { + EXPECT_EQ(InputHandler::SCROLL_ON_MAIN_THREAD, status.thread); + EXPECT_EQ( + host_impl_->OuterViewportScrollNode()->main_thread_scrolling_reasons, + status.main_thread_scrolling_reasons); + EXPECT_FALSE(host_impl_->CurrentlyScrollingNode()); + } } } -TEST_F(LayerTreeHostImplTest, ScrollRootCallsCommitAndRedraw) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollRootCallsCommitAndRedraw) { SetupViewportLayersInnerScrolls(gfx::Size(50, 50), gfx::Size(100, 100)); DrawFrame(); @@ -1206,7 +1264,8 @@ TEST_F(LayerTreeHostImplTest, ScrollRootCallsCommitAndRedraw) { // makes some sense since touchscreen/high-precision touchpad scrolling has a // physical metaphor (movement sticks to finger) so smoothness should be // prioritized. -TEST_F(LayerTreeHostImplTest, ActivelyTouchScrollingOnlyAfterScrollMovement) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, + ActivelyTouchScrollingOnlyAfterScrollMovement) { SetupViewportLayersOuterScrolls(gfx::Size(50, 50), gfx::Size(100, 100)); DrawFrame(); @@ -1287,7 +1346,7 @@ TEST_F(LayerTreeHostImplTest, ActivelyTouchScrollingOnlyAfterScrollMovement) { } } -TEST_F(LayerTreeHostImplTest, ScrollWithoutRootLayer) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollWithoutRootLayer) { // We should not crash when trying to scroll an empty layer tree. InputHandler::ScrollStatus status = host_impl_->ScrollBegin(BeginState(gfx::Point(), gfx::Vector2dF(0, 10), @@ -1299,7 +1358,7 @@ TEST_F(LayerTreeHostImplTest, ScrollWithoutRootLayer) { status.main_thread_scrolling_reasons); } -TEST_F(LayerTreeHostImplTest, ScrollWithoutRenderer) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollWithoutRenderer) { auto gl_owned = std::make_unique<viz::TestGLES2Interface>(); gl_owned->LoseContextCHROMIUM(GL_GUILTY_CONTEXT_RESET_ARB, GL_INNOCENT_CONTEXT_RESET_ARB); @@ -1323,7 +1382,7 @@ TEST_F(LayerTreeHostImplTest, ScrollWithoutRenderer) { status.main_thread_scrolling_reasons); } -TEST_F(LayerTreeHostImplTest, ReplaceTreeWhileScrolling) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ReplaceTreeWhileScrolling) { SetupViewportLayersInnerScrolls(gfx::Size(50, 50), gfx::Size(100, 100)); DrawFrame(); @@ -1356,7 +1415,8 @@ TEST_F(LayerTreeHostImplTest, ReplaceTreeWhileScrolling) { scroll_delta)); } -TEST_F(LayerTreeHostImplTest, ActivateTreeScrollingNodeDisappeared) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, + ActivateTreeScrollingNodeDisappeared) { SetupViewportLayersOuterScrolls(gfx::Size(100, 100), gfx::Size(1000, 1000)); auto status = host_impl_->ScrollBegin( @@ -1385,7 +1445,7 @@ TEST_F(LayerTreeHostImplTest, ActivateTreeScrollingNodeDisappeared) { EXPECT_FALSE(host_impl_->active_tree()->CurrentlyScrollingNode()); } -TEST_F(LayerTreeHostImplTest, ScrollBlocksOnWheelEventHandlers) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollBlocksOnWheelEventHandlers) { SetupViewportLayersInnerScrolls(gfx::Size(50, 50), gfx::Size(100, 100)); auto* scroll = InnerViewportScrollLayer(); scroll->SetWheelEventHandlerRegion(Region(gfx::Rect(20, 20))); @@ -1415,7 +1475,7 @@ TEST_F(LayerTreeHostImplTest, ScrollBlocksOnWheelEventHandlers) { host_impl_->ScrollEnd(); } -TEST_F(LayerTreeHostImplTest, ScrollBlocksOnTouchEventHandlers) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollBlocksOnTouchEventHandlers) { SetupViewportLayersInnerScrolls(gfx::Size(50, 50), gfx::Size(100, 100)); DrawFrame(); @@ -1467,9 +1527,9 @@ TEST_F(LayerTreeHostImplTest, ScrollBlocksOnTouchEventHandlers) { EXPECT_EQ(TouchAction::kPanX, touch_action); } -TEST_F(LayerTreeHostImplTest, ShouldScrollOnMainThread) { - SetupViewportLayersInnerScrolls(gfx::Size(50, 50), gfx::Size(100, 100)); - host_impl_->InnerViewportScrollNode()->main_thread_scrolling_reasons = +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ShouldScrollOnMainThread) { + SetupViewportLayersOuterScrolls(gfx::Size(50, 50), gfx::Size(100, 100)); + host_impl_->OuterViewportScrollNode()->main_thread_scrolling_reasons = MainThreadScrollingReason::kHasBackgroundAttachmentFixedObjects; DrawFrame(); @@ -1478,30 +1538,48 @@ TEST_F(LayerTreeHostImplTest, ShouldScrollOnMainThread) { ui::ScrollInputType::kWheel) .get(), ui::ScrollInputType::kWheel); - EXPECT_EQ(InputHandler::SCROLL_ON_MAIN_THREAD, status.thread); - EXPECT_EQ(MainThreadScrollingReason::kHasBackgroundAttachmentFixedObjects, - status.main_thread_scrolling_reasons); + if (base::FeatureList::IsEnabled(features::kScrollUnification)) { + EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD, status.thread); + EXPECT_FALSE(status.needs_main_thread_hit_test); + EXPECT_EQ( + MainThreadScrollingReason::kHasBackgroundAttachmentFixedObjects, + host_impl_->CurrentlyScrollingNode()->main_thread_scrolling_reasons); + } else { + EXPECT_EQ(InputHandler::SCROLL_ON_MAIN_THREAD, status.thread); + EXPECT_EQ(MainThreadScrollingReason::kHasBackgroundAttachmentFixedObjects, + status.main_thread_scrolling_reasons); + } status = host_impl_->ScrollBegin(BeginState(gfx::Point(), gfx::Vector2d(0, 10), ui::ScrollInputType::kTouchscreen) .get(), ui::ScrollInputType::kTouchscreen); - EXPECT_EQ(InputHandler::SCROLL_ON_MAIN_THREAD, status.thread); - EXPECT_EQ(MainThreadScrollingReason::kHasBackgroundAttachmentFixedObjects, - status.main_thread_scrolling_reasons); + if (base::FeatureList::IsEnabled(features::kScrollUnification)) { + EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD, status.thread); + EXPECT_FALSE(status.needs_main_thread_hit_test); + EXPECT_EQ( + MainThreadScrollingReason::kHasBackgroundAttachmentFixedObjects, + host_impl_->CurrentlyScrollingNode()->main_thread_scrolling_reasons); + } else { + EXPECT_EQ(InputHandler::SCROLL_ON_MAIN_THREAD, status.thread); + EXPECT_EQ(MainThreadScrollingReason::kHasBackgroundAttachmentFixedObjects, + status.main_thread_scrolling_reasons); + } } -TEST_F(LayerTreeHostImplTest, ScrollWithOverlappingNonScrollableLayer) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, + ScrollWithOverlappingNonScrollableLayer) { CreateAndTestNonScrollableLayers(false); } -TEST_F(LayerTreeHostImplTest, +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollWithOverlappingTransparentNonScrollableLayer) { CreateAndTestNonScrollableLayers(true); } -TEST_F(LayerTreeHostImplTest, ScrolledOverlappingDrawnScrollbarLayer) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, + ScrolledOverlappingDrawnScrollbarLayer) { LayerTreeImpl* layer_tree_impl = host_impl_->active_tree(); gfx::Size content_size = gfx::Size(360, 600); gfx::Size scroll_content_size = gfx::Size(345, 3800); @@ -1534,9 +1612,16 @@ TEST_F(LayerTreeHostImplTest, ScrolledOverlappingDrawnScrollbarLayer) { ui::ScrollInputType::kWheel) .get(), ui::ScrollInputType::kWheel); - EXPECT_EQ(InputHandler::SCROLL_UNKNOWN, status.thread); - EXPECT_EQ(MainThreadScrollingReason::kFailedHitTest, - status.main_thread_scrolling_reasons); + if (base::FeatureList::IsEnabled(features::kScrollUnification)) { + EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD, status.thread); + EXPECT_EQ(MainThreadScrollingReason::kNotScrollingOnMain, + status.main_thread_scrolling_reasons); + EXPECT_TRUE(status.needs_main_thread_hit_test); + } else { + EXPECT_EQ(InputHandler::SCROLL_UNKNOWN, status.thread); + EXPECT_EQ(MainThreadScrollingReason::kFailedHitTest, + status.main_thread_scrolling_reasons); + } // The point hits the drawn scrollbar layer completely and should scroll on // the impl thread. @@ -1623,7 +1708,7 @@ TEST_F(LayerTreeHostImplTestInvokeMainThreadCallbacks, mock_details.presentation_feedback); } -TEST_F(LayerTreeHostImplTest, NonFastScrollableRegionBasic) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, NonFastScrollableRegionBasic) { SetupViewportLayersInnerScrolls(gfx::Size(100, 100), gfx::Size(200, 200)); LayerImpl* outer_scroll = OuterViewportScrollLayer(); @@ -1631,24 +1716,40 @@ TEST_F(LayerTreeHostImplTest, NonFastScrollableRegionBasic) { DrawFrame(); - // All scroll types inside the non-fast scrollable region should fail. + // All scroll types inside the non-fast scrollable region should fail. When + // scroll unification is enabled, these scrolls succeed but request a main + // thread hit test. InputHandler::ScrollStatus status = host_impl_->ScrollBegin( BeginState(gfx::Point(25, 25), gfx::Vector2d(0, 10), ui::ScrollInputType::kWheel) .get(), ui::ScrollInputType::kWheel); - EXPECT_EQ(InputHandler::SCROLL_ON_MAIN_THREAD, status.thread); - EXPECT_EQ(MainThreadScrollingReason::kNonFastScrollableRegion, - status.main_thread_scrolling_reasons); + if (base::FeatureList::IsEnabled(features::kScrollUnification)) { + EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD, status.thread); + EXPECT_EQ(MainThreadScrollingReason::kNotScrollingOnMain, + status.main_thread_scrolling_reasons); + EXPECT_TRUE(status.needs_main_thread_hit_test); + } else { + EXPECT_EQ(InputHandler::SCROLL_ON_MAIN_THREAD, status.thread); + EXPECT_EQ(MainThreadScrollingReason::kNonFastScrollableRegion, + status.main_thread_scrolling_reasons); + } status = host_impl_->ScrollBegin( BeginState(gfx::Point(25, 25), gfx::Vector2d(0, 10), ui::ScrollInputType::kTouchscreen) .get(), ui::ScrollInputType::kTouchscreen); - EXPECT_EQ(InputHandler::SCROLL_ON_MAIN_THREAD, status.thread); - EXPECT_EQ(MainThreadScrollingReason::kNonFastScrollableRegion, - status.main_thread_scrolling_reasons); + if (base::FeatureList::IsEnabled(features::kScrollUnification)) { + EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD, status.thread); + EXPECT_EQ(MainThreadScrollingReason::kNotScrollingOnMain, + status.main_thread_scrolling_reasons); + EXPECT_TRUE(status.needs_main_thread_hit_test); + } else { + EXPECT_EQ(InputHandler::SCROLL_ON_MAIN_THREAD, status.thread); + EXPECT_EQ(MainThreadScrollingReason::kNonFastScrollableRegion, + status.main_thread_scrolling_reasons); + } // All scroll types outside this region should succeed. status = host_impl_->ScrollBegin( @@ -1679,7 +1780,7 @@ TEST_F(LayerTreeHostImplTest, NonFastScrollableRegionBasic) { host_impl_->ScrollEnd(); } -TEST_F(LayerTreeHostImplTest, NonFastScrollableRegionWithOffset) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, NonFastScrollableRegionWithOffset) { SetupViewportLayersInnerScrolls(gfx::Size(100, 100), gfx::Size(200, 200)); LayerImpl* outer_scroll = OuterViewportScrollLayer(); @@ -1699,6 +1800,7 @@ TEST_F(LayerTreeHostImplTest, NonFastScrollableRegionWithOffset) { EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD, status.thread); EXPECT_EQ(MainThreadScrollingReason::kNotScrollingOnMain, status.main_thread_scrolling_reasons); + EXPECT_FALSE(status.needs_main_thread_hit_test); host_impl_->ScrollUpdate(UpdateState(gfx::Point(), gfx::Vector2d(0, 1), ui::ScrollInputType::kWheel) @@ -1711,12 +1813,19 @@ TEST_F(LayerTreeHostImplTest, NonFastScrollableRegionWithOffset) { ui::ScrollInputType::kWheel) .get(), ui::ScrollInputType::kWheel); - EXPECT_EQ(InputHandler::SCROLL_ON_MAIN_THREAD, status.thread); - EXPECT_EQ(MainThreadScrollingReason::kNonFastScrollableRegion, - status.main_thread_scrolling_reasons); + if (base::FeatureList::IsEnabled(features::kScrollUnification)) { + EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD, status.thread); + EXPECT_EQ(MainThreadScrollingReason::kNotScrollingOnMain, + status.main_thread_scrolling_reasons); + EXPECT_TRUE(status.needs_main_thread_hit_test); + } else { + EXPECT_EQ(InputHandler::SCROLL_ON_MAIN_THREAD, status.thread); + EXPECT_EQ(MainThreadScrollingReason::kNonFastScrollableRegion, + status.main_thread_scrolling_reasons); + } } -TEST_F(LayerTreeHostImplTest, ScrollHandlerNotPresent) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollHandlerNotPresent) { SetupViewportLayersInnerScrolls(gfx::Size(100, 100), gfx::Size(200, 200)); EXPECT_FALSE(host_impl_->active_tree()->have_scroll_event_handlers()); DrawFrame(); @@ -1731,7 +1840,7 @@ TEST_F(LayerTreeHostImplTest, ScrollHandlerNotPresent) { EXPECT_FALSE(host_impl_->scroll_affects_scroll_handler()); } -TEST_F(LayerTreeHostImplTest, ScrollHandlerPresent) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollHandlerPresent) { SetupViewportLayersInnerScrolls(gfx::Size(100, 100), gfx::Size(200, 200)); host_impl_->active_tree()->set_have_scroll_event_handlers(true); DrawFrame(); @@ -1746,7 +1855,7 @@ TEST_F(LayerTreeHostImplTest, ScrollHandlerPresent) { EXPECT_FALSE(host_impl_->scroll_affects_scroll_handler()); } -TEST_F(LayerTreeHostImplTest, ScrollUpdateReturnsCorrectValue) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollUpdateReturnsCorrectValue) { SetupViewportLayersInnerScrolls(gfx::Size(100, 100), gfx::Size(200, 200)); DrawFrame(); @@ -1848,7 +1957,7 @@ TEST_F(LayerTreeHostImplTest, ScrollUpdateReturnsCorrectValue) { // TODO(sunyunjia): Move scroll snap tests to a separate file. // https://crbug.com/851690 -TEST_F(LayerTreeHostImplTest, ScrollSnapOnX) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollSnapOnX) { LayerImpl* overflow = CreateLayerForSnapping(); gfx::Point pointer_position(10, 10); @@ -1893,7 +2002,7 @@ TEST_F(LayerTreeHostImplTest, ScrollSnapOnX) { GetSnapContainerData(overflow)->GetTargetSnapAreaElementIds()); } -TEST_F(LayerTreeHostImplTest, ScrollSnapOnY) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollSnapOnY) { LayerImpl* overflow = CreateLayerForSnapping(); gfx::Point pointer_position(10, 10); @@ -1938,7 +2047,7 @@ TEST_F(LayerTreeHostImplTest, ScrollSnapOnY) { GetSnapContainerData(overflow)->GetTargetSnapAreaElementIds()); } -TEST_F(LayerTreeHostImplTest, ScrollSnapOnBoth) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollSnapOnBoth) { LayerImpl* overflow = CreateLayerForSnapping(); gfx::Point pointer_position(10, 10); @@ -1984,7 +2093,7 @@ TEST_F(LayerTreeHostImplTest, ScrollSnapOnBoth) { // Simulate a ScrollBegin and ScrollEnd without any intervening ScrollUpdate. // This test passes if it doesn't crash. -TEST_F(LayerTreeHostImplTest, SnapAfterEmptyScroll) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, SnapAfterEmptyScroll) { CreateLayerForSnapping(); gfx::Point pointer_position(10, 10); @@ -1999,7 +2108,7 @@ TEST_F(LayerTreeHostImplTest, SnapAfterEmptyScroll) { host_impl_->ScrollEnd(true); } -TEST_F(LayerTreeHostImplTest, ScrollSnapAfterAnimatedScroll) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollSnapAfterAnimatedScroll) { LayerImpl* overflow = CreateLayerForSnapping(); gfx::Point pointer_position(10, 10); @@ -2057,7 +2166,7 @@ TEST_F(LayerTreeHostImplTest, ScrollSnapAfterAnimatedScroll) { GetSnapContainerData(overflow)->GetTargetSnapAreaElementIds()); } -TEST_F(LayerTreeHostImplTest, SnapAnimationTargetUpdated) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, SnapAnimationTargetUpdated) { LayerImpl* overflow = CreateLayerForSnapping(); gfx::Point pointer_position(10, 10); @@ -2120,7 +2229,7 @@ TEST_F(LayerTreeHostImplTest, SnapAnimationTargetUpdated) { EXPECT_VECTOR_EQ(gfx::Vector2dF(0, 50), CurrentScrollOffset(overflow)); } -TEST_F(LayerTreeHostImplTest, SnapAnimationCancelledByScroll) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, SnapAnimationCancelledByScroll) { LayerImpl* overflow = CreateLayerForSnapping(); gfx::Point pointer_position(10, 10); @@ -2183,7 +2292,7 @@ TEST_F(LayerTreeHostImplTest, SnapAnimationCancelledByScroll) { GetSnapContainerData(overflow)->GetTargetSnapAreaElementIds()); } -TEST_F(LayerTreeHostImplTest, +TEST_P(ScrollUnifiedLayerTreeHostImplTest, SnapAnimationShouldNotStartWhenScrollEndsAtSnapTarget) { LayerImpl* overflow = CreateLayerForSnapping(); @@ -2230,7 +2339,7 @@ TEST_F(LayerTreeHostImplTest, GetSnapContainerData(overflow)->GetTargetSnapAreaElementIds()); } -TEST_F(LayerTreeHostImplTest, +TEST_P(ScrollUnifiedLayerTreeHostImplTest, GetSnapFlingInfoAndSetAnimatingSnapTargetWhenZoomed) { LayerImpl* overflow = CreateLayerForSnapping(); // Scales the page to its 1/5. @@ -2270,7 +2379,8 @@ TEST_F(LayerTreeHostImplTest, GetSnapContainerData(overflow)->GetTargetSnapAreaElementIds()); } -TEST_F(LayerTreeHostImplTest, SnapFlingAnimationEndWithoutFinishing) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, + SnapFlingAnimationEndWithoutFinishing) { LayerImpl* overflow = CreateLayerForSnapping(); // Scales the page to its 1/5. host_impl_->active_tree()->PushPageScaleFromMainThread(0.2f, 0.1f, 5.f); @@ -2310,7 +2420,8 @@ TEST_F(LayerTreeHostImplTest, SnapFlingAnimationEndWithoutFinishing) { GetSnapContainerData(overflow)->GetTargetSnapAreaElementIds()); } -TEST_F(LayerTreeHostImplTest, OverscrollBehaviorPreventsPropagation) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, + OverscrollBehaviorPreventsPropagation) { const gfx::Size kViewportSize(100, 100); const gfx::Size kContentSize(200, 200); SetupViewportLayersOuterScrolls(kViewportSize, kContentSize); @@ -2508,7 +2619,7 @@ TEST_F(LayerTreeHostImplTest, OverscrollBehaviorPreventsPropagation) { EXPECT_VECTOR_EQ(gfx::Vector2dF(10, 10), CurrentScrollOffset(overflow)); } -TEST_F(LayerTreeHostImplTest, ScrollWithUserUnscrollableLayers) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollWithUserUnscrollableLayers) { const gfx::Size kViewportSize(100, 100); const gfx::Size kContentSize(200, 200); SetupViewportLayersOuterScrolls(kViewportSize, kContentSize); @@ -2582,7 +2693,10 @@ TEST_F(LayerTreeHostImplTest, ScrollWithUserUnscrollableLayers) { EXPECT_VECTOR_EQ(gfx::Vector2dF(10, 20), CurrentScrollOffset(overflow)); } -TEST_F(LayerTreeHostImplTest, ForceMainThreadScrollWithoutScrollLayer) { +// Test that if a scroll node doesn't have an associated Layer, scrolling +// is forced to the main thread. With scroll unification, this kind of scroll +// is now handled on the impl thread but will force a repaint on each scroll. +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollNodeWithoutScrollLayer) { SetupViewportLayersInnerScrolls(gfx::Size(100, 100), gfx::Size(200, 200)); ScrollNode* scroll_node = host_impl_->OuterViewportScrollNode(); // Change the scroll node so that it no longer has an associated layer. @@ -2595,9 +2709,20 @@ TEST_F(LayerTreeHostImplTest, ForceMainThreadScrollWithoutScrollLayer) { ui::ScrollInputType::kWheel) .get(), ui::ScrollInputType::kWheel); - EXPECT_EQ(InputHandler::SCROLL_ON_MAIN_THREAD, status.thread); - EXPECT_EQ(MainThreadScrollingReason::kNonFastScrollableRegion, - status.main_thread_scrolling_reasons); + + if (base::FeatureList::IsEnabled(features::kScrollUnification)) { + EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD, status.thread); + EXPECT_EQ(MainThreadScrollingReason::kNotScrollingOnMain, + status.main_thread_scrolling_reasons); + // We don't have a layer for the scroller but we didn't hit a non-fast + // scrolling region or fail hit testing the layer - we don't need a main + // thread hit test in this case. + EXPECT_FALSE(status.needs_main_thread_hit_test); + } else { + EXPECT_EQ(InputHandler::SCROLL_ON_MAIN_THREAD, status.thread); + EXPECT_EQ(MainThreadScrollingReason::kNonFastScrollableRegion, + status.main_thread_scrolling_reasons); + } } TEST_F(CommitToPendingTreeLayerTreeHostImplTest, @@ -2712,7 +2837,8 @@ TEST_F(CommitToPendingTreeLayerTreeHostImplTest, EXPECT_FALSE(did_request_commit_); } -TEST_F(LayerTreeHostImplTest, AnimationSchedulingCommitToActiveTree) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, + AnimationSchedulingCommitToActiveTree) { EXPECT_TRUE(host_impl_->CommitToActiveTree()); auto* root = SetupDefaultRootLayer(gfx::Size(50, 50)); @@ -2749,7 +2875,8 @@ TEST_F(LayerTreeHostImplTest, AnimationSchedulingCommitToActiveTree) { host_impl_ = nullptr; } -TEST_F(LayerTreeHostImplTest, AnimationSchedulingOnLayerDestruction) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, + AnimationSchedulingOnLayerDestruction) { LayerImpl* root = SetupDefaultRootLayer(gfx::Size(50, 50)); LayerImpl* child = AddLayer(); @@ -2828,7 +2955,7 @@ class MissingTilesLayer : public LayerImpl { bool has_missing_tiles_; }; -TEST_F(LayerTreeHostImplTest, ImplPinchZoom) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ImplPinchZoom) { SetupViewportLayersInnerScrolls(gfx::Size(50, 50), gfx::Size(100, 100)); DrawFrame(); @@ -2909,7 +3036,7 @@ TEST_F(LayerTreeHostImplTest, ImplPinchZoom) { } } -TEST_F(LayerTreeHostImplTest, ViewportScrollbarGeometry) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ViewportScrollbarGeometry) { // Tests for correct behavior of solid color scrollbars on unscrollable pages // under tricky fractional scale/size issues. @@ -2966,7 +3093,7 @@ TEST_F(LayerTreeHostImplTest, ViewportScrollbarGeometry) { EXPECT_TRUE(h_scrollbar->CanScrollOrientation()); } -TEST_F(LayerTreeHostImplTest, ViewportScrollOrder) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ViewportScrollOrder) { LayerTreeSettings settings = DefaultSettings(); CreateHostImpl(settings, CreateLayerTreeFrameSink()); host_impl_->active_tree()->PushPageScaleFromMainThread(1, 0.25f, 4); @@ -3036,7 +3163,8 @@ TEST_F(LayerTreeHostImplTest, ViewportScrollOrder) { // Make sure scrolls smaller than a unit applied to the viewport don't get // dropped. crbug.com/539334. -TEST_F(LayerTreeHostImplTest, ScrollViewportWithFractionalAmounts) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, + ScrollViewportWithFractionalAmounts) { host_impl_->active_tree()->PushPageScaleFromMainThread(1, 1, 2); const gfx::Size content_size(1000, 1000); @@ -3093,7 +3221,7 @@ TEST_F(LayerTreeHostImplTest, ScrollViewportWithFractionalAmounts) { // Tests that scrolls during a pinch gesture (i.e. "two-finger" scrolls) work // as expected. That is, scrolling during a pinch should bubble from the inner // to the outer viewport. -TEST_F(LayerTreeHostImplTest, ScrollDuringPinchGesture) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollDuringPinchGesture) { LayerTreeSettings settings = DefaultSettings(); CreateHostImpl(settings, CreateLayerTreeFrameSink()); host_impl_->active_tree()->PushPageScaleFromMainThread(1, 1, 2); @@ -3153,7 +3281,7 @@ TEST_F(LayerTreeHostImplTest, ScrollDuringPinchGesture) { // Tests the "snapping" of pinch-zoom gestures to the screen edge. That is, when // a pinch zoom is anchored within a certain margin of the screen edge, we // should assume the user means to scroll into the edge of the screen. -TEST_F(LayerTreeHostImplTest, PinchZoomSnapsToScreenEdge) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, PinchZoomSnapsToScreenEdge) { LayerTreeSettings settings = DefaultSettings(); CreateHostImpl(settings, CreateLayerTreeFrameSink()); host_impl_->active_tree()->PushPageScaleFromMainThread(1, 1, 2); @@ -3242,7 +3370,8 @@ TEST_F(LayerTreeHostImplTest, PinchZoomSnapsToScreenEdge) { CurrentScrollOffset(InnerViewportScrollLayer())); } -TEST_F(LayerTreeHostImplTest, ImplPinchZoomWheelBubbleBetweenViewports) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, + ImplPinchZoomWheelBubbleBetweenViewports) { const gfx::Size content_size(200, 200); const gfx::Size viewport_size(100, 100); SetupViewportLayersOuterScrolls(viewport_size, content_size); @@ -3307,7 +3436,7 @@ TEST_F(LayerTreeHostImplTest, ImplPinchZoomWheelBubbleBetweenViewports) { CurrentScrollOffset(inner_scroll_layer)); } -TEST_F(LayerTreeHostImplTest, ScrollWithSwapPromises) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollWithSwapPromises) { ui::LatencyInfo latency_info; latency_info.set_trace_id(5); latency_info.AddLatencyNumber(ui::INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT); @@ -3337,7 +3466,7 @@ TEST_F(LayerTreeHostImplTest, ScrollWithSwapPromises) { // Test that scrolls targeting a layer with a non-null scroll_parent() don't // bubble up. -TEST_F(LayerTreeHostImplTest, ScrollDoesntBubble) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollDoesntBubble) { SetupViewportLayersInnerScrolls(gfx::Size(50, 50), gfx::Size(100, 100)); LayerImpl* viewport_scroll = InnerViewportScrollLayer(); @@ -3420,7 +3549,7 @@ TEST_F(LayerTreeHostImplTest, ScrollDoesntBubble) { } } -TEST_F(LayerTreeHostImplTest, PinchGesture) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, PinchGesture) { SetupViewportLayersInnerScrolls(gfx::Size(50, 50), gfx::Size(100, 100)); DrawFrame(); @@ -3610,7 +3739,7 @@ TEST_F(LayerTreeHostImplTest, PinchGesture) { } } -TEST_F(LayerTreeHostImplTest, SyncSubpixelScrollDelta) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, SyncSubpixelScrollDelta) { SetupViewportLayersInnerScrolls(gfx::Size(50, 50), gfx::Size(100, 100)); DrawFrame(); @@ -3660,7 +3789,8 @@ TEST_F(LayerTreeHostImplTest, SyncSubpixelScrollDelta) { scroll_layer->ScreenSpaceTransform().To2dTranslation()); } -TEST_F(LayerTreeHostImplTest, SyncSubpixelScrollFromFractionalActiveBase) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, + SyncSubpixelScrollFromFractionalActiveBase) { SetupViewportLayersInnerScrolls(gfx::Size(50, 50), gfx::Size(100, 100)); DrawFrame(); @@ -3699,7 +3829,8 @@ TEST_F(LayerTreeHostImplTest, SyncSubpixelScrollFromFractionalActiveBase) { gfx::Vector2dF(0, -1)); } -TEST_F(LayerTreeHostImplTest, PinchZoomTriggersPageScaleAnimation) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, + PinchZoomTriggersPageScaleAnimation) { SetupViewportLayersInnerScrolls(gfx::Size(50, 50), gfx::Size(100, 100)); DrawFrame(); @@ -3821,7 +3952,7 @@ TEST_F(LayerTreeHostImplTest, PinchZoomTriggersPageScaleAnimation) { } } -TEST_F(LayerTreeHostImplTest, PageScaleAnimation) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, PageScaleAnimation) { SetupViewportLayersInnerScrolls(gfx::Size(50, 50), gfx::Size(100, 100)); DrawFrame(); @@ -3950,7 +4081,7 @@ TEST_F(LayerTreeHostImplTest, PageScaleAnimation) { } } -TEST_F(LayerTreeHostImplTest, PageScaleAnimationNoOp) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, PageScaleAnimationNoOp) { SetupViewportLayersInnerScrolls(gfx::Size(50, 50), gfx::Size(100, 100)); DrawFrame(); @@ -4008,7 +4139,8 @@ TEST_F(LayerTreeHostImplTest, PageScaleAnimationNoOp) { } } -TEST_F(LayerTreeHostImplTest, PageScaleAnimationTransferedOnSyncTreeActivate) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, + PageScaleAnimationTransferedOnSyncTreeActivate) { CreatePendingTree(); host_impl_->pending_tree()->PushPageScaleFromMainThread(1, 1, 1); SetupViewportLayers(host_impl_->pending_tree(), gfx::Size(50, 50), @@ -4132,7 +4264,8 @@ TEST_F(LayerTreeHostImplTest, PageScaleAnimationTransferedOnSyncTreeActivate) { gfx::ScrollOffset(-50, -50))); } -TEST_F(LayerTreeHostImplTest, PageScaleAnimationCompletedNotification) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, + PageScaleAnimationCompletedNotification) { SetupViewportLayersInnerScrolls(gfx::Size(50, 50), gfx::Size(100, 100)); DrawFrame(); @@ -4181,7 +4314,8 @@ TEST_F(LayerTreeHostImplTest, PageScaleAnimationCompletedNotification) { host_impl_->DidFinishImplFrame(begin_frame_args); } -TEST_F(LayerTreeHostImplTest, MaxScrollOffsetAffectedByViewportBoundsDelta) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, + MaxScrollOffsetAffectedByViewportBoundsDelta) { SetupViewportLayersInnerScrolls(gfx::Size(50, 50), gfx::Size(100, 100)); host_impl_->active_tree()->PushPageScaleFromMainThread(1, 0.5f, 4); DrawFrame(); @@ -4207,7 +4341,8 @@ TEST_F(LayerTreeHostImplTest, MaxScrollOffsetAffectedByViewportBoundsDelta) { // Ensures scroll gestures coming from scrollbars cause animations in the // appropriate scenarios. -TEST_F(LayerTreeHostImplTest, AnimatedGranularityCausesSmoothScroll) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, + AnimatedGranularityCausesSmoothScroll) { gfx::Size viewport_size(300, 200); gfx::Size content_size(1000, 1000); SetupViewportLayersOuterScrolls(viewport_size, content_size); @@ -4275,7 +4410,8 @@ TEST_F(LayerTreeHostImplTest, AnimatedGranularityCausesSmoothScroll) { // Ensures scroll gestures coming from scrollbars don't cause animations if // smooth scrolling is disabled. -TEST_F(LayerTreeHostImplTest, NonAnimatedGranularityCausesInstantScroll) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, + NonAnimatedGranularityCausesInstantScroll) { // Disable animated scrolling LayerTreeSettings settings = DefaultSettings(); settings.enable_smooth_scroll = false; @@ -4574,7 +4710,8 @@ TEST_F(LayerTreeHostImplTestScrollbarAnimation, NoAnimator) { RunTest(LayerTreeSettings::NO_ANIMATOR); } -class LayerTreeHostImplTestScrollbarOpacity : public LayerTreeHostImplTest { +class LayerTreeHostImplTestScrollbarOpacity + : public ScrollUnifiedLayerTreeHostImplTest { protected: void RunTest(LayerTreeSettings::ScrollbarAnimator animator) { LayerTreeSettings settings = DefaultSettings(); @@ -4663,19 +4800,24 @@ class LayerTreeHostImplTestScrollbarOpacity : public LayerTreeHostImplTest { } }; -TEST_F(LayerTreeHostImplTestScrollbarOpacity, Android) { +INSTANTIATE_TEST_SUITE_P(All, + LayerTreeHostImplTestScrollbarOpacity, + testing::Bool()); + +TEST_P(LayerTreeHostImplTestScrollbarOpacity, Android) { RunTest(LayerTreeSettings::ANDROID_OVERLAY); } -TEST_F(LayerTreeHostImplTestScrollbarOpacity, AuraOverlay) { +TEST_P(LayerTreeHostImplTestScrollbarOpacity, AuraOverlay) { RunTest(LayerTreeSettings::AURA_OVERLAY); } -TEST_F(LayerTreeHostImplTestScrollbarOpacity, NoAnimator) { +TEST_P(LayerTreeHostImplTestScrollbarOpacity, NoAnimator) { RunTest(LayerTreeSettings::NO_ANIMATOR); } -class LayerTreeHostImplTestMultiScrollable : public LayerTreeHostImplTest { +class LayerTreeHostImplTestMultiScrollable + : public ScrollUnifiedLayerTreeHostImplTest { public: void SetUpLayers(LayerTreeSettings settings) { is_aura_scrollbar_ = @@ -4732,7 +4874,11 @@ class LayerTreeHostImplTestMultiScrollable : public LayerTreeHostImplTest { SolidColorScrollbarLayerImpl* scrollbar_2_; }; -TEST_F(LayerTreeHostImplTestMultiScrollable, +INSTANTIATE_TEST_SUITE_P(All, + LayerTreeHostImplTestMultiScrollable, + testing::Bool()); + +TEST_P(LayerTreeHostImplTestMultiScrollable, ScrollbarFlashAfterAnyScrollUpdate) { LayerTreeSettings settings = DefaultSettings(); settings.scrollbar_fade_delay = base::TimeDelta::FromMilliseconds(500); @@ -4777,7 +4923,7 @@ TEST_F(LayerTreeHostImplTestMultiScrollable, EXPECT_FALSE(animation_task_.is_null()); } -TEST_F(LayerTreeHostImplTestMultiScrollable, ScrollbarFlashWhenMouseEnter) { +TEST_P(LayerTreeHostImplTestMultiScrollable, ScrollbarFlashWhenMouseEnter) { LayerTreeSettings settings = DefaultSettings(); settings.scrollbar_fade_delay = base::TimeDelta::FromMilliseconds(500); settings.scrollbar_fade_duration = base::TimeDelta::FromMilliseconds(300); @@ -4803,7 +4949,7 @@ TEST_F(LayerTreeHostImplTestMultiScrollable, ScrollbarFlashWhenMouseEnter) { EXPECT_FALSE(animation_task_.is_null()); } -TEST_F(LayerTreeHostImplTest, ScrollHitTestOnScrollbar) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollHitTestOnScrollbar) { LayerTreeSettings settings = DefaultSettings(); settings.scrollbar_fade_delay = base::TimeDelta::FromMilliseconds(500); settings.scrollbar_fade_duration = base::TimeDelta::FromMilliseconds(300); @@ -4892,7 +5038,8 @@ TEST_F(LayerTreeHostImplTest, ScrollHitTestOnScrollbar) { } } -TEST_F(LayerTreeHostImplTest, ScrollbarVisibilityChangeCausesRedrawAndCommit) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, + ScrollbarVisibilityChangeCausesRedrawAndCommit) { LayerTreeSettings settings = DefaultSettings(); settings.scrollbar_animator = LayerTreeSettings::AURA_OVERLAY; settings.scrollbar_fade_delay = base::TimeDelta::FromMilliseconds(20); @@ -4961,7 +5108,7 @@ TEST_F(LayerTreeHostImplTest, ScrollbarVisibilityChangeCausesRedrawAndCommit) { } } -TEST_F(LayerTreeHostImplTest, ScrollbarInnerLargerThanOuter) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollbarInnerLargerThanOuter) { LayerTreeSettings settings = DefaultSettings(); CreateHostImpl(settings, CreateLayerTreeFrameSink()); @@ -4983,7 +5130,7 @@ TEST_F(LayerTreeHostImplTest, ScrollbarInnerLargerThanOuter) { EXPECT_EQ(300, horiz_scrollbar->clip_layer_length()); } -TEST_F(LayerTreeHostImplTest, ScrollbarRegistration) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollbarRegistration) { LayerTreeSettings settings = DefaultSettings(); settings.scrollbar_animator = LayerTreeSettings::ANDROID_OVERLAY; settings.scrollbar_fade_delay = base::TimeDelta::FromMilliseconds(20); @@ -5077,7 +5224,7 @@ TEST_F(LayerTreeHostImplTest, ScrollbarRegistration) { root_scroll_element_id)); } -TEST_F(LayerTreeHostImplTest, ScrollBeforeMouseMove) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollBeforeMouseMove) { LayerTreeSettings settings = DefaultSettings(); settings.scrollbar_animator = LayerTreeSettings::AURA_OVERLAY; settings.scrollbar_fade_delay = base::TimeDelta::FromMilliseconds(20); @@ -5218,18 +5365,18 @@ void LayerTreeHostImplTest::SetupMouseMoveAtWithDeviceScale( scrollbar_animation_controller->MouseIsOverScrollbarThumb(VERTICAL)); } -TEST_F(LayerTreeHostImplTest, MouseMoveAtWithDeviceScaleOf1) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, MouseMoveAtWithDeviceScaleOf1) { SetupMouseMoveAtWithDeviceScale(1); } -TEST_F(LayerTreeHostImplTest, MouseMoveAtWithDeviceScaleOf2) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, MouseMoveAtWithDeviceScaleOf2) { SetupMouseMoveAtWithDeviceScale(2); } // This test verifies that only SurfaceLayers in the viewport and have fallbacks // that are different are included in viz::CompositorFrameMetadata's // |activation_dependencies|. -TEST_F(LayerTreeHostImplTest, ActivationDependenciesInMetadata) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ActivationDependenciesInMetadata) { SetupViewportLayersInnerScrolls(gfx::Size(50, 50), gfx::Size(100, 100)); LayerImpl* root = root_layer(); @@ -5308,7 +5455,8 @@ TEST_F(LayerTreeHostImplTest, ActivationDependenciesInMetadata) { // Verify that updating the set of referenced surfaces for the active tree // causes a new CompositorFrame to be submitted, even if there is no other // damage. -TEST_F(LayerTreeHostImplTest, SurfaceReferencesChangeCausesDamage) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, + SurfaceReferencesChangeCausesDamage) { SetupViewportLayersInnerScrolls(gfx::Size(50, 50), gfx::Size(100, 100)); auto* fake_layer_tree_frame_sink = static_cast<FakeLayerTreeFrameSink*>(host_impl_->layer_tree_frame_sink()); @@ -5340,7 +5488,7 @@ TEST_F(LayerTreeHostImplTest, SurfaceReferencesChangeCausesDamage) { } } -TEST_F(LayerTreeHostImplTest, CompositorFrameMetadata) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, CompositorFrameMetadata) { SetupViewportLayersInnerScrolls(gfx::Size(50, 50), gfx::Size(100, 100)); host_impl_->active_tree()->PushPageScaleFromMainThread(1, 0.5f, 4); DrawFrame(); @@ -5467,7 +5615,8 @@ class DidDrawCheckLayer : public LayerImpl { bool did_draw_called_; }; -TEST_F(LayerTreeHostImplTest, DamageShouldNotCareAboutContributingLayers) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, + DamageShouldNotCareAboutContributingLayers) { auto* root = SetupRootLayer<DidDrawCheckLayer>(host_impl_->active_tree(), gfx::Size(10, 10)); @@ -5551,7 +5700,7 @@ TEST_F(LayerTreeHostImplTest, DamageShouldNotCareAboutContributingLayers) { } } -TEST_F(LayerTreeHostImplTest, WillDrawReturningFalseDoesNotCall) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, WillDrawReturningFalseDoesNotCall) { // The root layer is always drawn, so run this test on a child layer that // will be masked out by the root layer's bounds. auto* root = SetupRootLayer<DidDrawCheckLayer>(host_impl_->active_tree(), @@ -5574,7 +5723,7 @@ TEST_F(LayerTreeHostImplTest, WillDrawReturningFalseDoesNotCall) { EXPECT_FALSE(layer->did_draw_called()); } -TEST_F(LayerTreeHostImplTest, DidDrawNotCalledOnHiddenLayer) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, DidDrawNotCalledOnHiddenLayer) { // The root layer is always drawn, so run this test on a child layer that // will be masked out by the root layer's bounds. auto* root = SetupRootLayer<DidDrawCheckLayer>(host_impl_->active_tree(), @@ -5613,7 +5762,7 @@ TEST_F(LayerTreeHostImplTest, DidDrawNotCalledOnHiddenLayer) { EXPECT_FALSE(layer->visible_layer_rect().IsEmpty()); } -TEST_F(LayerTreeHostImplTest, WillDrawNotCalledOnOccludedLayer) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, WillDrawNotCalledOnOccludedLayer) { gfx::Size big_size(1000, 1000); auto* root = SetupRootLayer<DidDrawCheckLayer>(host_impl_->active_tree(), big_size); @@ -5641,7 +5790,7 @@ TEST_F(LayerTreeHostImplTest, WillDrawNotCalledOnOccludedLayer) { EXPECT_TRUE(top_layer->did_draw_called()); } -TEST_F(LayerTreeHostImplTest, DidDrawCalledOnAllLayers) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, DidDrawCalledOnAllLayers) { auto* root = SetupRootLayer<DidDrawCheckLayer>(host_impl_->active_tree(), gfx::Size(10, 10)); auto* layer1 = AddLayer<DidDrawCheckLayer>(host_impl_->active_tree()); @@ -5906,11 +6055,12 @@ TEST_F(LayerTreeHostImplPrepareToDrawTest, } } -TEST_F(LayerTreeHostImplTest, ScrollRootIgnored) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollRootIgnored) { SetupDefaultRootLayer(gfx::Size(10, 10)); DrawFrame(); - // Scroll event is ignored because layer is not scrollable. + // Scroll event is ignored because layer is not scrollable and there is no + // viewport. InputHandler::ScrollStatus status = host_impl_->ScrollBegin(BeginState(gfx::Point(), gfx::Vector2dF(0, 10), ui::ScrollInputType::kWheel) @@ -5923,7 +6073,7 @@ TEST_F(LayerTreeHostImplTest, ScrollRootIgnored) { EXPECT_FALSE(did_request_commit_); } -TEST_F(LayerTreeHostImplTest, ClampingAfterActivation) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ClampingAfterActivation) { CreatePendingTree(); host_impl_->pending_tree()->PushPageScaleFromMainThread(1, 1, 1); SetupViewportLayers(host_impl_->pending_tree(), gfx::Size(50, 50), @@ -5945,7 +6095,8 @@ TEST_F(LayerTreeHostImplTest, ClampingAfterActivation) { EXPECT_EQ(CurrentScrollOffset(active_outer_layer), gfx::ScrollOffset(0, 0)); } -class LayerTreeHostImplBrowserControlsTest : public LayerTreeHostImplTest { +class LayerTreeHostImplBrowserControlsTest + : public ScrollUnifiedLayerTreeHostImplTest { public: LayerTreeHostImplBrowserControlsTest() // Make the clip size the same as the layer (content) size so the layer is @@ -6002,6 +6153,10 @@ class LayerTreeHostImplBrowserControlsTest : public LayerTreeHostImplTest { LayerTreeSettings settings_; }; // class LayerTreeHostImplBrowserControlsTest +INSTANTIATE_TEST_SUITE_P(All, + LayerTreeHostImplBrowserControlsTest, + testing::Bool()); + #define EXPECT_VIEWPORT_GEOMETRIES(expected_browser_controls_shown_ratio) \ do { \ auto* tree = host_impl_->active_tree(); \ @@ -6031,7 +6186,7 @@ class LayerTreeHostImplBrowserControlsTest : public LayerTreeHostImplTest { // size). Since the viewport got larger, the effective scrollable "content" also // did. This ensures, for one thing, that the overscroll glow is shown in the // right place. -TEST_F(LayerTreeHostImplBrowserControlsTest, +TEST_P(LayerTreeHostImplBrowserControlsTest, HidingBrowserControlsExpandsScrollableSize) { SetupBrowserControlsAndScrollLayerWithVirtualViewport( gfx::Size(50, 50), gfx::Size(50, 50), gfx::Size(50, 50)); @@ -6074,11 +6229,108 @@ TEST_F(LayerTreeHostImplBrowserControlsTest, host_impl_->ScrollEnd(); } +TEST_P(LayerTreeHostImplBrowserControlsTest, + HidingBrowserControlsExpandsClipAncestorsOfReplacedOuterScroller) { + SetupBrowserControlsAndScrollLayerWithVirtualViewport( + gfx::Size(180, 180), gfx::Size(180, 180), gfx::Size(180, 180)); + + LayerTreeImpl* active_tree = host_impl_->active_tree(); + PropertyTrees* property_trees = active_tree->property_trees(); + LayerImpl* original_outer_scroll = OuterViewportScrollLayer(); + + LayerImpl* parent_clip_layer = AddLayer(); + CopyProperties(original_outer_scroll, parent_clip_layer); + parent_clip_layer->SetBounds(gfx::Size(160, 160)); + CreateClipNode(parent_clip_layer); + LayerImpl* clip_layer = AddLayer(); + clip_layer->SetBounds(gfx::Size(150, 150)); + CopyProperties(parent_clip_layer, clip_layer); + CreateClipNode(clip_layer); + LayerImpl* scroll_layer = + AddScrollableLayer(clip_layer, gfx::Size(150, 150), gfx::Size(300, 300)); + GetScrollNode(scroll_layer)->scrolls_outer_viewport = true; + ClipNode* original_outer_clip = GetClipNode(original_outer_scroll); + ClipNode* parent_clip = GetClipNode(parent_clip_layer); + ClipNode* scroll_clip = GetClipNode(clip_layer); + + auto viewport_property_ids = active_tree->ViewportPropertyIdsForTesting(); + viewport_property_ids.outer_clip = clip_layer->clip_tree_index(); + viewport_property_ids.outer_scroll = scroll_layer->scroll_tree_index(); + active_tree->SetViewportPropertyIds(viewport_property_ids); + UpdateDrawProperties(active_tree); + + EXPECT_EQ(scroll_layer, OuterViewportScrollLayer()); + EXPECT_EQ(GetScrollNode(scroll_layer), + active_tree->OuterViewportScrollNode()); + + EXPECT_EQ(1, active_tree->CurrentTopControlsShownRatio()); + EXPECT_EQ(50, host_impl_->browser_controls_manager()->ContentTopOffset()); + EXPECT_EQ(gfx::Vector2dF(), + property_trees->inner_viewport_container_bounds_delta()); + EXPECT_EQ(gfx::Vector2dF(), + property_trees->outer_viewport_container_bounds_delta()); + EXPECT_EQ(gfx::SizeF(300, 300), active_tree->ScrollableSize()); + EXPECT_EQ(gfx::RectF(0, 0, 180, 180), original_outer_clip->clip); + EXPECT_EQ(gfx::RectF(0, 0, 160, 160), parent_clip->clip); + EXPECT_EQ(gfx::RectF(0, 0, 150, 150), scroll_clip->clip); + + EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD, + host_impl_ + ->ScrollBegin(BeginState(gfx::Point(), gfx::Vector2dF(0, 25), + ui::ScrollInputType::kTouchscreen) + .get(), + ui::ScrollInputType::kTouchscreen) + .thread); + + // Hide the browser controls by a bit, the scrollable size should increase but + // the actual content bounds shouldn't. + host_impl_->browser_controls_manager()->ScrollBy(gfx::Vector2dF(0, 25)); + EXPECT_EQ(0.5f, active_tree->CurrentTopControlsShownRatio()); + EXPECT_EQ(25, host_impl_->browser_controls_manager()->ContentTopOffset()); + EXPECT_EQ(gfx::Vector2dF(0, 25), + property_trees->inner_viewport_container_bounds_delta()); + EXPECT_EQ(gfx::Vector2dF(0, 25), + property_trees->outer_viewport_container_bounds_delta()); + EXPECT_EQ(gfx::SizeF(300, 300), active_tree->ScrollableSize()); + EXPECT_EQ(gfx::RectF(0, 0, 150, 175), scroll_clip->clip); + EXPECT_EQ(gfx::RectF(0, 0, 160, 175), parent_clip->clip); + EXPECT_EQ(gfx::RectF(0, 0, 180, 180), original_outer_clip->clip); + + // Fully hide the browser controls. + host_impl_->browser_controls_manager()->ScrollBy(gfx::Vector2dF(0, 25)); + EXPECT_EQ(0, active_tree->CurrentTopControlsShownRatio()); + EXPECT_EQ(0, host_impl_->browser_controls_manager()->ContentTopOffset()); + EXPECT_EQ(gfx::Vector2dF(0, 50), + property_trees->inner_viewport_container_bounds_delta()); + EXPECT_EQ(gfx::Vector2dF(0, 50), + property_trees->outer_viewport_container_bounds_delta()); + EXPECT_EQ(gfx::SizeF(300, 300), active_tree->ScrollableSize()); + EXPECT_EQ(gfx::RectF(0, 0, 150, 200), scroll_clip->clip); + EXPECT_EQ(gfx::RectF(0, 0, 160, 200), parent_clip->clip); + EXPECT_EQ(gfx::RectF(0, 0, 180, 200), original_outer_clip->clip); + + // Scrolling additionally shouldn't have any effect. + host_impl_->browser_controls_manager()->ScrollBy(gfx::Vector2dF(0, 25)); + EXPECT_EQ(0, active_tree->CurrentTopControlsShownRatio()); + EXPECT_EQ(0, host_impl_->browser_controls_manager()->ContentTopOffset()); + EXPECT_EQ(gfx::Vector2dF(0, 50), + property_trees->inner_viewport_container_bounds_delta()); + EXPECT_EQ(gfx::Vector2dF(0, 50), + property_trees->outer_viewport_container_bounds_delta()); + EXPECT_EQ(gfx::SizeF(300, 300), active_tree->ScrollableSize()); + EXPECT_EQ(gfx::RectF(0, 0, 150, 200), scroll_clip->clip); + EXPECT_EQ(gfx::RectF(0, 0, 160, 200), parent_clip->clip); + EXPECT_EQ(gfx::RectF(0, 0, 180, 200), original_outer_clip->clip); + + host_impl_->browser_controls_manager()->ScrollEnd(); + host_impl_->ScrollEnd(); +} + // Ensure that moving the browser controls (i.e. omnibox/url-bar on mobile) on // pages with a non-1 minimum page scale factor (e.g. legacy desktop page) // correctly scales the clipping adjustment performed to show the newly exposed // region of the page. -TEST_F(LayerTreeHostImplBrowserControlsTest, +TEST_P(LayerTreeHostImplBrowserControlsTest, MovingBrowserControlsOuterClipDeltaScaled) { gfx::Size inner_size = gfx::Size(100, 100); gfx::Size outer_size = gfx::Size(100, 100); @@ -6122,7 +6374,7 @@ TEST_F(LayerTreeHostImplBrowserControlsTest, } // Tests that browser controls affect the position of horizontal scrollbars. -TEST_F(LayerTreeHostImplBrowserControlsTest, +TEST_P(LayerTreeHostImplBrowserControlsTest, HidingBrowserControlsAdjustsScrollbarPosition) { SetupBrowserControlsAndScrollLayerWithVirtualViewport( gfx::Size(50, 50), gfx::Size(50, 50), gfx::Size(50, 50)); @@ -6198,7 +6450,7 @@ TEST_F(LayerTreeHostImplBrowserControlsTest, host_impl_->ScrollEnd(); } -TEST_F(LayerTreeHostImplBrowserControlsTest, +TEST_P(LayerTreeHostImplBrowserControlsTest, ScrollBrowserControlsByFractionalAmount) { SetupBrowserControlsAndScrollLayerWithVirtualViewport( gfx::Size(10, 10), gfx::Size(10, 10), gfx::Size(10, 10)); @@ -6231,7 +6483,7 @@ TEST_F(LayerTreeHostImplBrowserControlsTest, // scroll initiated on the inner viewport, causing the browser controls to show // and thus making the outer viewport scrollable, still scrolls the outer // viewport. -TEST_F(LayerTreeHostImplBrowserControlsTest, +TEST_P(LayerTreeHostImplBrowserControlsTest, BrowserControlsOuterViewportBecomesScrollable) { SetupBrowserControlsAndScrollLayerWithVirtualViewport( gfx::Size(10, 50), gfx::Size(10, 50), gfx::Size(10, 100)); @@ -6322,7 +6574,7 @@ TEST_F(LayerTreeHostImplBrowserControlsTest, // Test that the fixed position container delta is appropriately adjusted // by the browser controls showing/hiding and page scale doesn't affect it. -TEST_F(LayerTreeHostImplBrowserControlsTest, FixedContainerDelta) { +TEST_P(LayerTreeHostImplBrowserControlsTest, FixedContainerDelta) { SetupBrowserControlsAndScrollLayerWithVirtualViewport( gfx::Size(100, 100), gfx::Size(100, 100), gfx::Size(100, 100)); DrawFrame(); @@ -6396,7 +6648,7 @@ TEST_F(LayerTreeHostImplBrowserControlsTest, FixedContainerDelta) { // Push a browser controls ratio from the main thread that we didn't send as a // delta and make sure that the ratio is clamped to the [0, 1] range. -TEST_F(LayerTreeHostImplBrowserControlsTest, BrowserControlsPushUnsentRatio) { +TEST_P(LayerTreeHostImplBrowserControlsTest, BrowserControlsPushUnsentRatio) { SetupBrowserControlsAndScrollLayerWithVirtualViewport( gfx::Size(10, 50), gfx::Size(10, 50), gfx::Size(10, 100)); DrawFrame(); @@ -6420,7 +6672,7 @@ TEST_F(LayerTreeHostImplBrowserControlsTest, BrowserControlsPushUnsentRatio) { // Test that if a scrollable sublayer doesn't consume the scroll, // browser controls should hide when scrolling down. -TEST_F(LayerTreeHostImplBrowserControlsTest, +TEST_P(LayerTreeHostImplBrowserControlsTest, BrowserControlsScrollableSublayer) { gfx::Size sub_content_size(100, 400); gfx::Size sub_content_layer_size(100, 300); @@ -6470,7 +6722,7 @@ TEST_F(LayerTreeHostImplBrowserControlsTest, // Ensure setting the browser controls position explicitly using the setters on // the TreeImpl correctly affects the browser controls manager and viewport // bounds for the active tree. -TEST_F(LayerTreeHostImplBrowserControlsTest, +TEST_P(LayerTreeHostImplBrowserControlsTest, PositionBrowserControlsToActiveTreeExplicitly) { SetupBrowserControlsAndScrollLayerWithVirtualViewport( layer_size_, layer_size_, layer_size_); @@ -6501,7 +6753,7 @@ TEST_F(LayerTreeHostImplBrowserControlsTest, // Ensure setting the browser controls position explicitly using the setters on // the TreeImpl correctly affects the browser controls manager and viewport // bounds for the pending tree. -TEST_F(LayerTreeHostImplBrowserControlsTest, +TEST_P(LayerTreeHostImplBrowserControlsTest, PositionBrowserControlsToPendingTreeExplicitly) { CreatePendingTree(); SetupBrowserControlsAndScrollLayerWithVirtualViewport( @@ -6546,7 +6798,7 @@ TEST_F(LayerTreeHostImplBrowserControlsTest, // Test that the top_controls delta and sent delta are appropriately // applied on sync tree activation. The total browser controls offset shouldn't // change after the activation. -TEST_F(LayerTreeHostImplBrowserControlsTest, ApplyDeltaOnTreeActivation) { +TEST_P(LayerTreeHostImplBrowserControlsTest, ApplyDeltaOnTreeActivation) { SetupBrowserControlsAndScrollLayerWithVirtualViewport( layer_size_, layer_size_, layer_size_); DrawFrame(); @@ -6586,7 +6838,7 @@ TEST_F(LayerTreeHostImplBrowserControlsTest, ApplyDeltaOnTreeActivation) { // the inner viewport container bounds. That is, the browser controls layout // height is the amount that the inner viewport container was shrunk outside // the compositor to accommodate the browser controls. -TEST_F(LayerTreeHostImplBrowserControlsTest, +TEST_P(LayerTreeHostImplBrowserControlsTest, BrowserControlsLayoutHeightChanged) { SetupBrowserControlsAndScrollLayerWithVirtualViewport( layer_size_, layer_size_, layer_size_); @@ -6629,7 +6881,7 @@ TEST_F(LayerTreeHostImplBrowserControlsTest, // Test that showing/hiding the browser controls when the viewport is fully // scrolled doesn't incorrectly change the viewport offset due to clamping from // changing viewport bounds. -TEST_F(LayerTreeHostImplBrowserControlsTest, +TEST_P(LayerTreeHostImplBrowserControlsTest, BrowserControlsViewportOffsetClamping) { SetupBrowserControlsAndScrollLayerWithVirtualViewport( gfx::Size(100, 100), gfx::Size(200, 200), gfx::Size(200, 400)); @@ -6705,7 +6957,7 @@ TEST_F(LayerTreeHostImplBrowserControlsTest, // Test that the browser controls coming in and out maintains the same aspect // ratio between the inner and outer viewports. -TEST_F(LayerTreeHostImplBrowserControlsTest, BrowserControlsAspectRatio) { +TEST_P(LayerTreeHostImplBrowserControlsTest, BrowserControlsAspectRatio) { SetupBrowserControlsAndScrollLayerWithVirtualViewport( gfx::Size(100, 100), gfx::Size(200, 200), gfx::Size(200, 400)); host_impl_->active_tree()->PushPageScaleFromMainThread(1, 0.5f, 2); @@ -6745,7 +6997,7 @@ TEST_F(LayerTreeHostImplBrowserControlsTest, BrowserControlsAspectRatio) { } // Test that scrolling the outer viewport affects the browser controls. -TEST_F(LayerTreeHostImplBrowserControlsTest, +TEST_P(LayerTreeHostImplBrowserControlsTest, BrowserControlsScrollOuterViewport) { SetupBrowserControlsAndScrollLayerWithVirtualViewport( gfx::Size(100, 100), gfx::Size(200, 200), gfx::Size(200, 400)); @@ -6821,7 +7073,7 @@ TEST_F(LayerTreeHostImplBrowserControlsTest, host_impl_->ScrollEnd(); } -TEST_F(LayerTreeHostImplBrowserControlsTest, +TEST_P(LayerTreeHostImplBrowserControlsTest, ScrollNonScrollableRootWithBrowserControls) { SetupBrowserControlsAndScrollLayerWithVirtualViewport( layer_size_, layer_size_, layer_size_); @@ -6892,7 +7144,7 @@ TEST_F(LayerTreeHostImplBrowserControlsTest, // was occurring because the UpdateViewportContainerSizes was being called // before the property trees were updated with the bounds_delta. // crbug.com/597266. -TEST_F(LayerTreeHostImplBrowserControlsTest, +TEST_P(LayerTreeHostImplBrowserControlsTest, ViewportBoundsDeltaOnTreeActivation) { const gfx::Size inner_viewport_size(1000, 1000); const gfx::Size outer_viewport_size(1000, 1000); @@ -7016,7 +7268,7 @@ TEST_F(LayerTreeHostImplBrowserControlsTest, } } -TEST_F(LayerTreeHostImplTest, ScrollNonCompositedRoot) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollNonCompositedRoot) { // Test the configuration where a non-composited root layer is embedded in a // scrollable outer layer. gfx::Size surface_size(10, 10); @@ -7054,7 +7306,7 @@ TEST_F(LayerTreeHostImplTest, ScrollNonCompositedRoot) { EXPECT_TRUE(did_request_commit_); } -TEST_F(LayerTreeHostImplTest, ScrollChildCallsCommitAndRedraw) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollChildCallsCommitAndRedraw) { gfx::Size surface_size(10, 10); gfx::Size contents_size(20, 20); @@ -7083,33 +7335,35 @@ TEST_F(LayerTreeHostImplTest, ScrollChildCallsCommitAndRedraw) { EXPECT_TRUE(did_request_commit_); } -TEST_F(LayerTreeHostImplTest, ScrollMissesChild) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollMissesChild) { gfx::Size viewport_size(5, 5); gfx::Size surface_size(10, 10); - LayerImpl* root = SetupDefaultRootLayer(surface_size); - AddScrollableLayer(root, viewport_size, surface_size); + SetupViewportLayersOuterScrolls(viewport_size, surface_size); + AddScrollableLayer(OuterViewportScrollLayer(), viewport_size, surface_size); DrawFrame(); - // Scroll event is ignored because the input coordinate is outside the layer - // boundaries. + // A scroll that doesn't hit any layers should fall back to viewport + // scrolling. InputHandler::ScrollStatus status = host_impl_->ScrollBegin( BeginState(gfx::Point(15, 5), gfx::Vector2dF(0, 10), ui::ScrollInputType::kWheel) .get(), ui::ScrollInputType::kWheel); - EXPECT_EQ(InputHandler::SCROLL_IGNORED, status.thread); - EXPECT_EQ(MainThreadScrollingReason::kNoScrollingLayer, - status.main_thread_scrolling_reasons); + EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD, status.thread); + EXPECT_EQ(host_impl_->CurrentlyScrollingNode(), + host_impl_->OuterViewportScrollNode()); EXPECT_FALSE(did_request_redraw_); EXPECT_FALSE(did_request_commit_); } -TEST_F(LayerTreeHostImplTest, ScrollMissesBackfacingChild) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollMissesBackfacingChild) { gfx::Size viewport_size(5, 5); gfx::Size surface_size(10, 10); - LayerImpl* root = SetupDefaultRootLayer(viewport_size); - LayerImpl* child = AddScrollableLayer(root, viewport_size, surface_size); + + SetupViewportLayersOuterScrolls(viewport_size, surface_size); + LayerImpl* child = AddScrollableLayer(OuterViewportScrollLayer(), + viewport_size, surface_size); gfx::Transform matrix; matrix.RotateAboutXAxis(180.0); @@ -7118,22 +7372,22 @@ TEST_F(LayerTreeHostImplTest, ScrollMissesBackfacingChild) { CreateEffectNode(child).double_sided = false; DrawFrame(); - // Scroll event is ignored because the scrollable layer is not facing the - // viewer and there is nothing scrollable behind it. + // The scroll shouldn't hit the child layer because the it isn't facing the + // viewer. The hit test should go through it and hit the outer viewport. InputHandler::ScrollStatus status = host_impl_->ScrollBegin( BeginState(gfx::Point(5, 5), gfx::Vector2dF(0, 10), ui::ScrollInputType::kWheel) .get(), ui::ScrollInputType::kWheel); - EXPECT_EQ(InputHandler::SCROLL_IGNORED, status.thread); - EXPECT_EQ(MainThreadScrollingReason::kNoScrollingLayer, - status.main_thread_scrolling_reasons); + EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD, status.thread); + EXPECT_EQ(host_impl_->CurrentlyScrollingNode(), + host_impl_->OuterViewportScrollNode()); EXPECT_FALSE(did_request_redraw_); EXPECT_FALSE(did_request_commit_); } -TEST_F(LayerTreeHostImplTest, ScrollBlockedByContentLayer) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollLayerWithMainThreadReason) { gfx::Size scroll_container_size(5, 5); gfx::Size surface_size(10, 10); LayerImpl* root = SetupDefaultRootLayer(surface_size); @@ -7148,19 +7402,28 @@ TEST_F(LayerTreeHostImplTest, ScrollBlockedByContentLayer) { MainThreadScrollingReason::kHasBackgroundAttachmentFixedObjects; DrawFrame(); - // Scrolling fails because the content layer is asking to be scrolled on the - // main thread. InputHandler::ScrollStatus status = host_impl_->ScrollBegin( BeginState(gfx::Point(5, 5), gfx::Vector2dF(0, 10), ui::ScrollInputType::kWheel) .get(), ui::ScrollInputType::kWheel); - EXPECT_EQ(InputHandler::SCROLL_ON_MAIN_THREAD, status.thread); - EXPECT_EQ(MainThreadScrollingReason::kHasBackgroundAttachmentFixedObjects, - status.main_thread_scrolling_reasons); + if (base::FeatureList::IsEnabled(features::kScrollUnification)) { + EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD, status.thread); + EXPECT_FALSE(status.needs_main_thread_hit_test); + EXPECT_EQ( + MainThreadScrollingReason::kHasBackgroundAttachmentFixedObjects, + host_impl_->CurrentlyScrollingNode()->main_thread_scrolling_reasons); + } else { + // Scrolling fails because the content layer is asking to be scrolled on the + // main thread. + EXPECT_EQ(InputHandler::SCROLL_ON_MAIN_THREAD, status.thread); + EXPECT_EQ(MainThreadScrollingReason::kHasBackgroundAttachmentFixedObjects, + status.main_thread_scrolling_reasons); + } } -TEST_F(LayerTreeHostImplTest, ScrollRootAndChangePageScaleOnMainThread) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, + ScrollRootAndChangePageScaleOnMainThread) { gfx::Size inner_viewport_size(20, 20); gfx::Size outer_viewport_size(40, 40); gfx::Size content_size(80, 80); @@ -7203,7 +7466,8 @@ TEST_F(LayerTreeHostImplTest, ScrollRootAndChangePageScaleOnMainThread) { EXPECT_EQ(1, host_impl_->active_tree()->page_scale_delta()); } -TEST_F(LayerTreeHostImplTest, ScrollRootAndChangePageScaleOnImplThread) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, + ScrollRootAndChangePageScaleOnImplThread) { gfx::Size inner_viewport_size(20, 20); gfx::Size outer_viewport_size(40, 40); gfx::Size content_size(80, 80); @@ -7255,7 +7519,8 @@ TEST_F(LayerTreeHostImplTest, ScrollRootAndChangePageScaleOnImplThread) { EXPECT_EQ(page_scale, host_impl_->active_tree()->current_page_scale_factor()); } -TEST_F(LayerTreeHostImplTest, PageScaleDeltaAppliedToRootScrollLayerOnly) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, + PageScaleDeltaAppliedToRootScrollLayerOnly) { host_impl_->active_tree()->PushPageScaleFromMainThread(1, 1, 2); gfx::Size viewport_size(5, 5); gfx::Size surface_size(10, 10); @@ -7305,7 +7570,8 @@ TEST_F(LayerTreeHostImplTest, PageScaleDeltaAppliedToRootScrollLayerOnly) { outer_scroll->DrawTransform().matrix().getDouble(1, 1)); } -TEST_F(LayerTreeHostImplTest, ScrollChildAndChangePageScaleOnMainThread) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, + ScrollChildAndChangePageScaleOnMainThread) { SetupViewportLayers(host_impl_->active_tree(), gfx::Size(15, 15), gfx::Size(30, 30), gfx::Size(50, 50)); LayerImpl* outer_scroll = OuterViewportScrollLayer(); @@ -7345,7 +7611,7 @@ TEST_F(LayerTreeHostImplTest, ScrollChildAndChangePageScaleOnMainThread) { EXPECT_EQ(1, host_impl_->active_tree()->page_scale_delta()); } -TEST_F(LayerTreeHostImplTest, ScrollChildBeyondLimit) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollChildBeyondLimit) { // Scroll a child layer beyond its maximum scroll range and make sure the // parent layer isn't scrolled. gfx::Size surface_size(10, 10); @@ -7490,7 +7756,7 @@ TEST_F(LayerTreeHostImplTimelinesTest, ScrollAnimatedLatchToChild) { host_impl_ = nullptr; } -TEST_F(LayerTreeHostImplTest, ScrollWithoutBubbling) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollWithoutBubbling) { // Scroll a child layer beyond its maximum scroll range and make sure the // the scroll doesn't bubble up to the parent layer. gfx::Size surface_size(20, 20); @@ -7629,7 +7895,8 @@ TEST_F(LayerTreeHostImplTest, ScrollWithoutBubbling) { // Ensure that layers who's scroll parent is the InnerViewportScrollNode are // still able to scroll on the compositor. -TEST_F(LayerTreeHostImplTest, ChildrenOfInnerScrollNodeCanScrollOnThread) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, + ChildrenOfInnerScrollNodeCanScrollOnThread) { gfx::Size viewport_size(10, 10); gfx::Size content_size(20, 20); SetupViewportLayersOuterScrolls(viewport_size, content_size); @@ -7673,7 +7940,7 @@ TEST_F(LayerTreeHostImplTest, ChildrenOfInnerScrollNodeCanScrollOnThread) { } } -TEST_F(LayerTreeHostImplTest, ScrollEventBubbling) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollEventBubbling) { // When we try to scroll a non-scrollable child layer, the scroll delta // should be applied to one of its ancestors if possible. gfx::Size viewport_size(10, 10); @@ -7716,7 +7983,7 @@ TEST_F(LayerTreeHostImplTest, ScrollEventBubbling) { } } -TEST_F(LayerTreeHostImplTest, ScrollBeforeRedraw) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollBeforeRedraw) { gfx::Size surface_size(10, 10); SetupViewportLayersNoScrolls(surface_size); UpdateDrawProperties(host_impl_->active_tree()); @@ -7740,7 +8007,7 @@ TEST_F(LayerTreeHostImplTest, ScrollBeforeRedraw) { .thread); } -TEST_F(LayerTreeHostImplTest, ScrollAxisAlignedRotatedLayer) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollAxisAlignedRotatedLayer) { SetupViewportLayersInnerScrolls(gfx::Size(50, 50), gfx::Size(100, 100)); auto* scroll_layer = InnerViewportScrollLayer(); scroll_layer->SetDrawsContent(true); @@ -7798,7 +8065,7 @@ TEST_F(LayerTreeHostImplTest, ScrollAxisAlignedRotatedLayer) { wheel_scroll_delta)); } -TEST_F(LayerTreeHostImplTest, ScrollNonAxisAlignedRotatedLayer) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollNonAxisAlignedRotatedLayer) { SetupViewportLayersInnerScrolls(gfx::Size(50, 50), gfx::Size(100, 100)); auto* scroll_layer = InnerViewportScrollLayer(); float child_layer_angle = -20; @@ -7891,7 +8158,7 @@ TEST_F(LayerTreeHostImplTest, ScrollNonAxisAlignedRotatedLayer) { } } -TEST_F(LayerTreeHostImplTest, ScrollPerspectiveTransformedLayer) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollPerspectiveTransformedLayer) { // When scrolling an element with perspective, the distance scrolled // depends on the point at which the scroll begins. SetupViewportLayersInnerScrolls(gfx::Size(50, 50), gfx::Size(100, 100)); @@ -7973,7 +8240,7 @@ TEST_F(LayerTreeHostImplTest, ScrollPerspectiveTransformedLayer) { } } -TEST_F(LayerTreeHostImplTest, ScrollScaledLayer) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollScaledLayer) { SetupViewportLayersInnerScrolls(gfx::Size(50, 50), gfx::Size(100, 100)); auto* scroll_layer = InnerViewportScrollLayer(); @@ -8032,7 +8299,7 @@ TEST_F(LayerTreeHostImplTest, ScrollScaledLayer) { wheel_scroll_delta)); } -TEST_F(LayerTreeHostImplTest, ScrollViewportRounding) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollViewportRounding) { int width = 332; int height = 20; int scale = 3; @@ -8048,7 +8315,7 @@ TEST_F(LayerTreeHostImplTest, ScrollViewportRounding) { MaxScrollOffset(inner_viewport_scroll_layer)); } -TEST_F(LayerTreeHostImplTest, RootLayerScrollOffsetDelegation) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, RootLayerScrollOffsetDelegation) { TestInputHandlerClient scroll_watcher; SetupViewportLayersInnerScrolls(gfx::Size(10, 20), gfx::Size(100, 100)); auto* scroll_layer = InnerViewportScrollLayer(); @@ -8166,7 +8433,7 @@ void CheckLayerScrollDelta(LayerImpl* layer, gfx::Vector2dF scroll_delta) { EXPECT_EQ(expected_point.ToString(), translated_point.ToString()); } -TEST_F(LayerTreeHostImplTest, +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ExternalRootLayerScrollOffsetDelegationReflectedInNextDraw) { SetupViewportLayersInnerScrolls(gfx::Size(10, 20), gfx::Size(100, 100)); auto* scroll_layer = InnerViewportScrollLayer(); @@ -8201,7 +8468,7 @@ TEST_F(LayerTreeHostImplTest, // Ensure the viewport correctly handles the user_scrollable bits. That is, if // the outer viewport disables user scrolling, we should still be able to // scroll the inner viewport. -TEST_F(LayerTreeHostImplTest, ViewportUserScrollable) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ViewportUserScrollable) { gfx::Size viewport_size(100, 100); gfx::Size content_size(200, 200); SetupViewportLayersOuterScrolls(viewport_size, content_size); @@ -8367,7 +8634,7 @@ TEST_F(LayerTreeHostImplTest, ViewportUserScrollable) { // Ensure that the SetSynchronousInputHandlerRootScrollOffset method used by // the WebView API correctly respects the user_scrollable bits on both of the // inner and outer viewport scroll nodes. -TEST_F(LayerTreeHostImplTest, SetRootScrollOffsetUserScrollable) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, SetRootScrollOffsetUserScrollable) { gfx::Size viewport_size(100, 100); gfx::Size content_size(200, 200); SetupViewportLayersOuterScrolls(viewport_size, content_size); @@ -8480,14 +8747,14 @@ TEST_F(LayerTreeHostImplTest, SetRootScrollOffsetUserScrollable) { // The SetSynchronousInputHandlerRootScrollOffset API can be called while there // is no inner viewport set. This test passes if we don't crash. -TEST_F(LayerTreeHostImplTest, SetRootScrollOffsetNoViewportCrash) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, SetRootScrollOffsetNoViewportCrash) { auto* inner_scroll = InnerViewportScrollLayer(); ASSERT_FALSE(inner_scroll); gfx::ScrollOffset scroll_offset(25, 30); host_impl_->SetSynchronousInputHandlerRootScrollOffset(scroll_offset); } -TEST_F(LayerTreeHostImplTest, OverscrollRoot) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, OverscrollRoot) { InputHandlerScrollResult scroll_result; SetupViewportLayersInnerScrolls(gfx::Size(50, 50), gfx::Size(100, 100)); @@ -8641,7 +8908,7 @@ TEST_F(LayerTreeHostImplTest, OverscrollRoot) { host_impl_->ScrollEnd(); } -TEST_F(LayerTreeHostImplTest, OverscrollChildWithoutBubbling) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, OverscrollChildWithoutBubbling) { // Scroll child layers beyond their maximum scroll range and make sure root // overscroll does not accumulate. InputHandlerScrollResult scroll_result; @@ -8746,7 +9013,7 @@ TEST_F(LayerTreeHostImplTest, OverscrollChildWithoutBubbling) { } } -TEST_F(LayerTreeHostImplTest, OverscrollChildEventBubbling) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, OverscrollChildEventBubbling) { // When we try to scroll a non-scrollable child layer, the scroll delta // should be applied to one of its ancestors if possible. Overscroll should // be reflected only when it has bubbled up to the root scrolling layer. @@ -8784,7 +9051,7 @@ TEST_F(LayerTreeHostImplTest, OverscrollChildEventBubbling) { } } -TEST_F(LayerTreeHostImplTest, OverscrollAlways) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, OverscrollAlways) { InputHandlerScrollResult scroll_result; LayerTreeSettings settings = DefaultSettings(); CreateHostImpl(settings, CreateLayerTreeFrameSink()); @@ -8813,7 +9080,7 @@ TEST_F(LayerTreeHostImplTest, OverscrollAlways) { EXPECT_EQ(gfx::Vector2dF(0, 10), host_impl_->accumulated_root_overscroll()); } -TEST_F(LayerTreeHostImplTest, NoOverscrollWhenNotAtEdge) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, NoOverscrollWhenNotAtEdge) { InputHandlerScrollResult scroll_result; gfx::Size viewport_size(100, 100); gfx::Size content_size(200, 200); @@ -8898,7 +9165,7 @@ TEST_F(LayerTreeHostImplTest, NoOverscrollWhenNotAtEdge) { } } -TEST_F(LayerTreeHostImplTest, NoOverscrollOnNonViewportLayers) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, NoOverscrollOnNonViewportLayers) { const gfx::Size content_size(200, 200); const gfx::Size viewport_size(100, 100); @@ -8963,7 +9230,15 @@ TEST_F(LayerTreeHostImplTest, NoOverscrollOnNonViewportLayers) { host_impl_->ScrollEnd(); } -TEST_F(LayerTreeHostImplTest, OverscrollOnMainThread) { +// This ensures that the --disable-threaded-scrolling flag is respected even +// when a scroll occurs outside of a layer which normally falls back to the +// inner viewport layer. https://crbug.com/625100. +TEST_P(ScrollUnifiedLayerTreeHostImplTest, OverscrollOnMainThread) { + // TODO(bokan): The meaning of this flag is lost with scroll unification. We + // need to decide what we should do with it. https://crbug.com/1086625. + if (base::FeatureList::IsEnabled(features::kScrollUnification)) + return; + InputHandlerScrollResult scroll_result; LayerTreeSettings settings = DefaultSettings(); CreateHostImpl(settings, CreateLayerTreeFrameSink()); @@ -9005,7 +9280,7 @@ TEST_F(LayerTreeHostImplTest, OverscrollOnMainThread) { // Test that scrolling the inner viewport directly works, as can happen when the // scroll chains up to it from an sibling of the outer viewport. -TEST_F(LayerTreeHostImplTest, ScrollFromOuterViewportSibling) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollFromOuterViewportSibling) { const gfx::Size viewport_size(100, 100); SetupViewportLayersNoScrolls(viewport_size); @@ -9081,7 +9356,8 @@ TEST_F(LayerTreeHostImplTest, ScrollFromOuterViewportSibling) { // Test that scrolls chain correctly when a child scroller on the page (e.g. a // scrolling div) is set as the outer viewport. This happens in the // rootScroller proposal. -TEST_F(LayerTreeHostImplTest, ScrollChainingWithReplacedOuterViewport) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, + ScrollChainingWithReplacedOuterViewport) { const gfx::Size content_size(200, 200); const gfx::Size viewport_size(100, 100); @@ -9211,7 +9487,7 @@ TEST_F(LayerTreeHostImplTest, ScrollChainingWithReplacedOuterViewport) { // scrolling div) is set as the outer viewport but scrolls start from a layer // that's not a descendant of the outer viewport. This happens in the // rootScroller proposal. -TEST_F(LayerTreeHostImplTest, RootScrollerScrollNonDescendant) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, RootScrollerScrollNonDescendant) { const gfx::Size content_size(300, 300); const gfx::Size viewport_size(300, 300); @@ -9381,7 +9657,7 @@ TEST_F(LayerTreeHostImplTest, RootScrollerScrollNonDescendant) { } } -TEST_F(LayerTreeHostImplTest, OverscrollOnImplThread) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, OverscrollOnImplThread) { InputHandlerScrollResult scroll_result; LayerTreeSettings settings = DefaultSettings(); CreateHostImpl(settings, CreateLayerTreeFrameSink()); @@ -9514,7 +9790,7 @@ class BlendStateCheckLayer : public LayerImpl { viz::ResourceId resource_id_; }; -TEST_F(LayerTreeHostImplTest, BlendingOffWhenDrawingOpaqueLayers) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, BlendingOffWhenDrawingOpaqueLayers) { LayerImpl* root = SetupDefaultRootLayer(gfx::Size(10, 10)); root->SetDrawsContent(false); @@ -9710,7 +9986,7 @@ static bool MayContainVideoBitSetOnFrameData(LayerTreeHostImpl* host_impl) { return frame.may_contain_video; } -TEST_F(LayerTreeHostImplTest, MayContainVideo) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, MayContainVideo) { gfx::Size big_size(1000, 1000); auto* root = SetupRootLayer<DidDrawCheckLayer>(host_impl_->active_tree(), big_size); @@ -10020,7 +10296,7 @@ class FakeDrawableLayerImpl : public LayerImpl { // Make sure damage tracking propagates all the way to the viz::CompositorFrame // submitted to the LayerTreeFrameSink, where it should request to swap only // the sub-buffer that is damaged. -TEST_F(LayerTreeHostImplTest, PartialSwapReceivesDamageRect) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, PartialSwapReceivesDamageRect) { auto gl_owned = std::make_unique<viz::TestGLES2Interface>(); gl_owned->set_have_post_sub_buffer(true); scoped_refptr<viz::TestContextProvider> context_provider( @@ -10108,7 +10384,7 @@ TEST_F(LayerTreeHostImplTest, PartialSwapReceivesDamageRect) { layer_tree_host_impl->ReleaseLayerTreeFrameSink(); } -TEST_F(LayerTreeHostImplTest, RootLayerDoesntCreateExtraSurface) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, RootLayerDoesntCreateExtraSurface) { LayerImpl* root = SetupDefaultRootLayer(gfx::Size(10, 10)); LayerImpl* child = AddLayer(); child->SetBounds(gfx::Size(10, 10)); @@ -10153,7 +10429,7 @@ class FakeLayerWithQuads : public LayerImpl { : LayerImpl(tree_impl, id) {} }; -TEST_F(LayerTreeHostImplTest, LayersFreeTextures) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, LayersFreeTextures) { scoped_refptr<viz::TestContextProvider> context_provider = viz::TestContextProvider::Create(); viz::TestSharedImageInterface* sii = context_provider->SharedImageInterface(); @@ -10187,7 +10463,7 @@ TEST_F(LayerTreeHostImplTest, LayersFreeTextures) { EXPECT_EQ(0u, sii->shared_image_count()); } -TEST_F(LayerTreeHostImplTest, HasTransparentBackground) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, HasTransparentBackground) { SetupDefaultRootLayer(gfx::Size(10, 10)); host_impl_->active_tree()->set_background_color(SK_ColorWHITE); UpdateDrawProperties(host_impl_->active_tree()); @@ -10332,7 +10608,7 @@ class GLRendererWithSetupQuadForAntialiasing : public viz::GLRenderer { using viz::GLRenderer::ShouldAntialiasQuad; }; -TEST_F(LayerTreeHostImplTest, FarAwayQuadsDontNeedAA) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, FarAwayQuadsDontNeedAA) { // Due to precision issues (especially on Android), sometimes far // away quads can end up thinking they need AA. float device_scale_factor = 4 / 3; @@ -10433,7 +10709,7 @@ class CountingSoftwareDevice : public viz::SoftwareOutputDevice { int frames_began_, frames_ended_; }; -TEST_F(LayerTreeHostImplTest, +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ForcedDrawToSoftwareDeviceSkipsUnsupportedLayers) { set_reduce_memory_result(false); EXPECT_TRUE(CreateHostImpl(DefaultSettings(), @@ -10468,7 +10744,7 @@ TEST_F(LayerTreeHostImplTest, } // Checks that we use the memory limits provided. -TEST_F(LayerTreeHostImplTest, MemoryLimits) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, MemoryLimits) { host_impl_->ReleaseLayerTreeFrameSink(); host_impl_ = nullptr; @@ -10628,7 +10904,7 @@ TEST_F(LayerTreeHostImplTestPrepareTiles, PrepareTilesWhenInvisible) { EXPECT_TRUE(fake_host_impl_->prepare_tiles_needed()); } -TEST_F(LayerTreeHostImplTest, UIResourceManagement) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, UIResourceManagement) { auto test_context_provider = viz::TestContextProvider::Create(); viz::TestSharedImageInterface* sii = test_context_provider->SharedImageInterface(); @@ -10671,7 +10947,7 @@ TEST_F(LayerTreeHostImplTest, UIResourceManagement) { EXPECT_EQ(0u, sii->shared_image_count()); } -TEST_F(LayerTreeHostImplTest, CreateETC1UIResource) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, CreateETC1UIResource) { auto test_context_provider = viz::TestContextProvider::Create(); viz::TestSharedImageInterface* sii = test_context_provider->SharedImageInterface(); @@ -10696,7 +10972,7 @@ TEST_F(LayerTreeHostImplTest, CreateETC1UIResource) { EXPECT_NE(0u, id1); } -TEST_F(LayerTreeHostImplTest, ObeyMSAACaps) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ObeyMSAACaps) { LayerTreeSettings msaaSettings = DefaultSettings(); msaaSettings.gpu_rasterization_msaa_sample_count = 4; @@ -10806,22 +11082,27 @@ TEST_P(LayerTreeHostImplTestWithRenderer, ShutdownReleasesContext) { LayerImpl* root = SetupDefaultRootLayer(gfx::Size(10, 10)); struct Helper { std::unique_ptr<viz::CopyOutputResult> unprocessed_result; - void OnResult(std::unique_ptr<viz::CopyOutputResult> result) { + void OnResult(base::OnceClosure finished, + std::unique_ptr<viz::CopyOutputResult> result) { unprocessed_result = std::move(result); + std::move(finished).Run(); } } helper; GetEffectNode(root)->has_copy_request = true; + base::RunLoop copy_request_run_loop; GetPropertyTrees(root)->effect_tree.AddCopyRequest( root->effect_tree_index(), std::make_unique<viz::CopyOutputRequest>( viz::CopyOutputRequest::ResultFormat::RGBA_TEXTURE, - base::BindOnce(&Helper::OnResult, base::Unretained(&helper)))); + base::BindOnce(&Helper::OnResult, base::Unretained(&helper), + copy_request_run_loop.QuitClosure()))); DrawFrame(); auto* sii = context_provider->SharedImageInterface(); // The CopyOutputResult has a ref on the viz::ContextProvider and a shared // image allocated. + copy_request_run_loop.Run(); EXPECT_TRUE(helper.unprocessed_result); EXPECT_FALSE(context_provider->HasOneRef()); EXPECT_EQ(1u, sii->shared_image_count()); @@ -10839,12 +11120,15 @@ TEST_P(LayerTreeHostImplTestWithRenderer, ShutdownReleasesContext) { helper.unprocessed_result.reset(); } -TEST_F(LayerTreeHostImplTest, ScrollUnknownNotOnAncestorChain) { +// This tests the case where hit testing only on scrollable layers returns a +// layer that's outside the scroll chain of the first hit test *any* layer. See +// LayerTreeHostImpl::IsInitialScrollHitTestReliable for details. +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollHitTestIsNotReliable) { // If we ray cast a scroller that is not on the first layer's ancestor chain, // we should return SCROLL_UNKNOWN. gfx::Size viewport_size(50, 50); gfx::Size content_size(100, 100); - SetupViewportLayersInnerScrolls(viewport_size, content_size); + SetupViewportLayersOuterScrolls(viewport_size, content_size); LayerImpl* occluder_layer = AddLayer(); occluder_layer->SetDrawsContent(true); @@ -10863,20 +11147,27 @@ TEST_F(LayerTreeHostImplTest, ScrollUnknownNotOnAncestorChain) { ui::ScrollInputType::kWheel) .get(), ui::ScrollInputType::kWheel); - EXPECT_EQ(InputHandler::SCROLL_UNKNOWN, status.thread); - EXPECT_EQ(MainThreadScrollingReason::kFailedHitTest, - status.main_thread_scrolling_reasons); + if (base::FeatureList::IsEnabled(features::kScrollUnification)) { + EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD, status.thread); + EXPECT_TRUE(status.needs_main_thread_hit_test); + } else { + EXPECT_EQ(InputHandler::SCROLL_UNKNOWN, status.thread); + EXPECT_EQ(MainThreadScrollingReason::kFailedHitTest, + status.main_thread_scrolling_reasons); + } } -TEST_F(LayerTreeHostImplTest, ScrollUnknownScrollAncestorMismatch) { +// Similar but different case to above. See +// LayerTreeHostImpl::IsInitialScrollHitTestReliable for details. +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollHitTestAncestorMismatch) { // If we ray cast a scroller this is on the first layer's ancestor chain, but // is not the first scroller we encounter when walking up from the layer, we // should also return SCROLL_UNKNOWN. gfx::Size viewport_size(50, 50); gfx::Size content_size(100, 100); - SetupViewportLayersInnerScrolls(viewport_size, content_size); + SetupViewportLayersOuterScrolls(viewport_size, content_size); - LayerImpl* scroll_layer = InnerViewportScrollLayer(); + LayerImpl* scroll_layer = OuterViewportScrollLayer(); LayerImpl* child_scroll_clip = AddLayer(); CopyProperties(scroll_layer, child_scroll_clip); @@ -10899,12 +11190,17 @@ TEST_F(LayerTreeHostImplTest, ScrollUnknownScrollAncestorMismatch) { ui::ScrollInputType::kWheel) .get(), ui::ScrollInputType::kWheel); - EXPECT_EQ(InputHandler::SCROLL_UNKNOWN, status.thread); - EXPECT_EQ(MainThreadScrollingReason::kFailedHitTest, - status.main_thread_scrolling_reasons); + if (base::FeatureList::IsEnabled(features::kScrollUnification)) { + EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD, status.thread); + EXPECT_TRUE(status.needs_main_thread_hit_test); + } else { + EXPECT_EQ(InputHandler::SCROLL_UNKNOWN, status.thread); + EXPECT_EQ(MainThreadScrollingReason::kFailedHitTest, + status.main_thread_scrolling_reasons); + } } -TEST_F(LayerTreeHostImplTest, ScrollInvisibleScroller) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollInvisibleScroller) { gfx::Size viewport_size(50, 50); gfx::Size content_size(100, 100); SetupViewportLayersInnerScrolls(viewport_size, content_size); @@ -10932,7 +11228,8 @@ TEST_F(LayerTreeHostImplTest, ScrollInvisibleScroller) { // Make sure LatencyInfo carried by LatencyInfoSwapPromise are passed // in viz::CompositorFrameMetadata. -TEST_F(LayerTreeHostImplTest, LatencyInfoPassedToCompositorFrameMetadata) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, + LatencyInfoPassedToCompositorFrameMetadata) { LayerTreeSettings settings = DefaultSettings(); settings.commit_to_active_tree = false; CreateHostImpl(settings, CreateLayerTreeFrameSink()); @@ -10962,7 +11259,8 @@ TEST_F(LayerTreeHostImplTest, LatencyInfoPassedToCompositorFrameMetadata) { } #if defined(OS_ANDROID) -TEST_F(LayerTreeHostImplTest, SelectionBoundsPassedToCompositorFrameMetadata) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, + SelectionBoundsPassedToCompositorFrameMetadata) { LayerImpl* root = SetupRootLayer<SolidColorLayerImpl>( host_impl_->active_tree(), gfx::Size(10, 10)); UpdateDrawProperties(host_impl_->active_tree()); @@ -10992,7 +11290,7 @@ TEST_F(LayerTreeHostImplTest, SelectionBoundsPassedToCompositorFrameMetadata) { EXPECT_TRUE(selection_after.end.visible()); } -TEST_F(LayerTreeHostImplTest, HiddenSelectionBoundsStayHidden) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, HiddenSelectionBoundsStayHidden) { LayerImpl* root = SetupDefaultRootLayer(gfx::Size(10, 10)); UpdateDrawProperties(host_impl_->active_tree()); @@ -11051,7 +11349,7 @@ class SimpleSwapPromiseMonitor : public SwapPromiseMonitor { int* set_needs_redraw_count_; }; -TEST_F(LayerTreeHostImplTest, SimpleSwapPromiseMonitor) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, SimpleSwapPromiseMonitor) { int set_needs_commit_count = 0; int set_needs_redraw_count = 0; @@ -11622,7 +11920,7 @@ TEST_F(LayerTreeHostImplWithBrowserControlsTest, // Tests that when we set a child scroller (e.g. a scrolling div) as the outer // viewport, scrolling it controls the browser controls. -TEST_F(LayerTreeHostImplBrowserControlsTest, +TEST_P(LayerTreeHostImplBrowserControlsTest, ReplacedOuterViewportScrollsBrowserControls) { const gfx::Size scroll_content_size(400, 400); const gfx::Size root_layer_size(200, 200); @@ -11859,7 +12157,8 @@ TEST_F(LayerTreeHostImplWithImplicitLimitsTest, ImplicitMemoryLimits) { 150u * 1024u * 1024u); } -TEST_F(LayerTreeHostImplTest, ExternalTransformReflectedInNextDraw) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, + ExternalTransformReflectedInNextDraw) { const gfx::Size viewport_size(50, 50); const gfx::Size layer_size(100, 100); gfx::Transform external_transform; @@ -11885,7 +12184,7 @@ TEST_F(LayerTreeHostImplTest, ExternalTransformReflectedInNextDraw) { external_transform, layer->draw_properties().target_space_transform); } -TEST_F(LayerTreeHostImplTest, ExternalTransformSetNeedsRedraw) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ExternalTransformSetNeedsRedraw) { const gfx::Size viewport_size(100, 100); SetupDefaultRootLayer(viewport_size); UpdateDrawProperties(host_impl_->active_tree()); @@ -11914,7 +12213,7 @@ TEST_F(LayerTreeHostImplTest, ExternalTransformSetNeedsRedraw) { EXPECT_FALSE(last_on_draw_frame_->has_no_damage); } -TEST_F(LayerTreeHostImplTest, OnMemoryPressure) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, OnMemoryPressure) { gfx::Size size(200, 200); viz::ResourceFormat format = viz::RGBA_8888; gfx::ColorSpace color_space = gfx::ColorSpace::CreateSRGB(); @@ -11936,7 +12235,7 @@ TEST_F(LayerTreeHostImplTest, OnMemoryPressure) { EXPECT_LT(memory_usage_after_memory_pressure, current_memory_usage); } -TEST_F(LayerTreeHostImplTest, OnDrawConstraintSetNeedsRedraw) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, OnDrawConstraintSetNeedsRedraw) { const gfx::Size viewport_size(100, 100); SetupDefaultRootLayer(viewport_size); UpdateDrawProperties(host_impl_->active_tree()); @@ -11969,7 +12268,7 @@ TEST_F(LayerTreeHostImplTest, OnDrawConstraintSetNeedsRedraw) { // This test verifies that the viewport damage rect is the full viewport and not // just part of the viewport in the presence of an external viewport. -TEST_F(LayerTreeHostImplTest, FullViewportDamageAfterOnDraw) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, FullViewportDamageAfterOnDraw) { const gfx::Size viewport_size(100, 100); SetupDefaultRootLayer(viewport_size); UpdateDrawProperties(host_impl_->active_tree()); @@ -12089,7 +12388,8 @@ TEST_F(CommitToPendingTreeLayerTreeHostImplTest, EXPECT_TRUE(host_impl_->active_tree()->needs_update_draw_properties()); } -TEST_F(LayerTreeHostImplTest, ExternalViewportAffectsVisibleRects) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, + ExternalViewportAffectsVisibleRects) { const gfx::Size viewport_size(50, 50); const gfx::Size layer_size(100, 100); SetupViewportLayersInnerScrolls(viewport_size, layer_size); @@ -12118,7 +12418,8 @@ TEST_F(LayerTreeHostImplTest, ExternalViewportAffectsVisibleRects) { EXPECT_EQ(gfx::Rect(90, 90), content_layer->visible_layer_rect()); } -TEST_F(LayerTreeHostImplTest, ExternalTransformAffectsVisibleRects) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, + ExternalTransformAffectsVisibleRects) { const gfx::Size viewport_size(50, 50); const gfx::Size layer_size(100, 100); SetupViewportLayersInnerScrolls(viewport_size, layer_size); @@ -12152,7 +12453,8 @@ TEST_F(LayerTreeHostImplTest, ExternalTransformAffectsVisibleRects) { EXPECT_EQ(gfx::Rect(50, 50), content_layer->visible_layer_rect()); } -TEST_F(LayerTreeHostImplTest, ExternalTransformAffectsSublayerScaleFactor) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, + ExternalTransformAffectsSublayerScaleFactor) { const gfx::Size viewport_size(50, 50); const gfx::Size layer_size(100, 100); SetupViewportLayersInnerScrolls(viewport_size, layer_size); @@ -12196,7 +12498,7 @@ TEST_F(LayerTreeHostImplTest, ExternalTransformAffectsSublayerScaleFactor) { #if defined(OS_MACOSX) // Ensure Mac wheel scrolling causes instant scrolling. This test can be removed // once https://crbug.com/574283 is fixed. -TEST_F(LayerTreeHostImplTest, MacWheelIsNonAnimated) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, MacWheelIsNonAnimated) { const gfx::Size content_size(1000, 1000); const gfx::Size viewport_size(50, 100); SetupViewportLayersOuterScrolls(viewport_size, content_size); @@ -12219,7 +12521,116 @@ TEST_F(LayerTreeHostImplTest, MacWheelIsNonAnimated) { } #endif -TEST_F(LayerTreeHostImplTest, ScrollAnimated) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, OneScrollForFirstScrollDelay) { + LayerTreeSettings settings = DefaultSettings(); + settings.commit_to_active_tree = false; + CreateHostImpl(settings, CreateLayerTreeFrameSink()); + SetupRootLayer<SolidColorLayerImpl>(host_impl_->active_tree(), + gfx::Size(10, 10)); + UpdateDrawProperties(host_impl_->active_tree()); + EXPECT_EQ(first_scroll_observed, 0); + + // LatencyInfo for the first scroll. + ui::LatencyInfo latency_info; + latency_info.set_trace_id(5); + latency_info.AddLatencyNumber( + ui::INPUT_EVENT_LATENCY_SCROLL_UPDATE_ORIGINAL_COMPONENT); + std::unique_ptr<SwapPromise> swap_promise( + new LatencyInfoSwapPromise(latency_info)); + host_impl_->active_tree()->QueuePinnedSwapPromise(std::move(swap_promise)); + + host_impl_->SetFullViewportDamage(); + host_impl_->SetNeedsRedraw(); + DrawFrame(); + + constexpr uint32_t frame_token_1 = 1; + viz::FrameTimingDetails mock_details; + mock_details.presentation_feedback = ExampleFeedback(); + // When the LayerTreeHostImpl receives presentation feedback, the callback + // will be fired. + host_impl_->DidPresentCompositorFrame(frame_token_1, mock_details); + + EXPECT_EQ(first_scroll_observed, 1); +} + +TEST_P(ScrollUnifiedLayerTreeHostImplTest, OtherInputsForFirstScrollDelay) { + LayerTreeSettings settings = DefaultSettings(); + settings.commit_to_active_tree = false; + CreateHostImpl(settings, CreateLayerTreeFrameSink()); + SetupRootLayer<SolidColorLayerImpl>(host_impl_->active_tree(), + gfx::Size(10, 10)); + UpdateDrawProperties(host_impl_->active_tree()); + EXPECT_EQ(first_scroll_observed, 0); + + // LatencyInfo for the first input, which is not scroll. + ui::LatencyInfo latency_info; + latency_info.set_trace_id(5); + latency_info.AddLatencyNumber(ui::INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT); + std::unique_ptr<SwapPromise> swap_promise( + new LatencyInfoSwapPromise(latency_info)); + host_impl_->active_tree()->QueuePinnedSwapPromise(std::move(swap_promise)); + + host_impl_->SetFullViewportDamage(); + host_impl_->SetNeedsRedraw(); + DrawFrame(); + + constexpr uint32_t frame_token_1 = 1; + viz::FrameTimingDetails mock_details; + mock_details.presentation_feedback = ExampleFeedback(); + // When the LayerTreeHostImpl receives presentation feedback, the callback + // will be fired. + host_impl_->DidPresentCompositorFrame(frame_token_1, mock_details); + + EXPECT_EQ(first_scroll_observed, 0); +} + +TEST_P(ScrollUnifiedLayerTreeHostImplTest, MultipleScrollsForFirstScrollDelay) { + LayerTreeSettings settings = DefaultSettings(); + settings.commit_to_active_tree = false; + CreateHostImpl(settings, CreateLayerTreeFrameSink()); + SetupRootLayer<SolidColorLayerImpl>(host_impl_->active_tree(), + gfx::Size(10, 10)); + UpdateDrawProperties(host_impl_->active_tree()); + EXPECT_EQ(first_scroll_observed, 0); + + // LatencyInfo for the first scroll. + ui::LatencyInfo latency_info; + latency_info.set_trace_id(5); + latency_info.AddLatencyNumber( + ui::INPUT_EVENT_LATENCY_SCROLL_UPDATE_ORIGINAL_COMPONENT); + std::unique_ptr<SwapPromise> swap_promise( + new LatencyInfoSwapPromise(latency_info)); + host_impl_->active_tree()->QueuePinnedSwapPromise(std::move(swap_promise)); + DrawFrame(); + constexpr uint32_t frame_token_1 = 1; + viz::FrameTimingDetails mock_details; + mock_details.presentation_feedback = ExampleFeedback(); + // When the LayerTreeHostImpl receives presentation feedback, the callback + // will be fired. + host_impl_->DidPresentCompositorFrame(frame_token_1, mock_details); + EXPECT_EQ(first_scroll_observed, 1); + + // LatencyInfo for the second scroll. + ui::LatencyInfo latency_info2; + latency_info2.set_trace_id(6); + latency_info2.AddLatencyNumber( + ui::INPUT_EVENT_LATENCY_SCROLL_UPDATE_ORIGINAL_COMPONENT); + std::unique_ptr<SwapPromise> swap_promise2( + new LatencyInfoSwapPromise(latency_info2)); + host_impl_->active_tree()->QueuePinnedSwapPromise(std::move(swap_promise2)); + host_impl_->SetFullViewportDamage(); + host_impl_->SetNeedsRedraw(); + DrawFrame(); + constexpr uint32_t frame_token_2 = 2; + viz::FrameTimingDetails mock_details2; + mock_details2.presentation_feedback = ExampleFeedback(); + // When the LayerTreeHostImpl receives presentation feedback, the callback + // will be fired. + host_impl_->DidPresentCompositorFrame(frame_token_2, mock_details2); + EXPECT_EQ(first_scroll_observed, 1); +} + +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollAnimated) { const gfx::Size content_size(1000, 1000); const gfx::Size viewport_size(50, 100); SetupViewportLayersOuterScrolls(viewport_size, content_size); @@ -12327,7 +12738,7 @@ TEST_F(LayerTreeHostImplTest, ScrollAnimated) { // Tests latching behavior, in particular when a ScrollEnd is received but a // new ScrollBegin is received before the animation from the previous gesture // stream is finished. -TEST_F(LayerTreeHostImplTest, ScrollAnimatedLatching) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollAnimatedLatching) { const gfx::Size content_size(1000, 1000); const gfx::Size viewport_size(50, 100); SetupViewportLayersOuterScrolls(viewport_size, content_size); @@ -12426,7 +12837,7 @@ TEST_F(LayerTreeHostImplTest, ScrollAnimatedLatching) { // factor. That is, if you zoom into the page, a wheel scroll should scroll the // content *less* than before so that it appears to move the same distance when // zoomed in. -TEST_F(LayerTreeHostImplTest, ScrollAnimatedWhileZoomed) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollAnimatedWhileZoomed) { const gfx::Size content_size(1000, 1000); const gfx::Size viewport_size(50, 100); SetupViewportLayersOuterScrolls(viewport_size, content_size); @@ -12503,7 +12914,7 @@ TEST_F(LayerTreeHostImplTest, ScrollAnimatedWhileZoomed) { // inner viewport is animating. Specifically this test makes sure the animation // update doesn't get confused between the currently scrolling node and the // currently animating node which are different. See https://crbug.com/1070561. -TEST_F(LayerTreeHostImplTest, ScrollAnimatedUpdateInnerViewport) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollAnimatedUpdateInnerViewport) { const gfx::Size content_size(210, 1000); const gfx::Size viewport_size(200, 200); SetupViewportLayersOuterScrolls(viewport_size, content_size); @@ -12602,7 +13013,8 @@ TEST_F(LayerTreeHostImplTest, ScrollAnimatedUpdateInnerViewport) { } // This tests that faded-out Aura scrollbars can't be interacted with. -TEST_F(LayerTreeHostImplTest, FadedOutPaintedOverlayScrollbarHitTest) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, + FadedOutPaintedOverlayScrollbarHitTest) { LayerTreeSettings settings = DefaultSettings(); settings.compositor_threaded_scrollbar_scrolling = true; CreateHostImpl(settings, CreateLayerTreeFrameSink()); @@ -12645,7 +13057,7 @@ TEST_F(LayerTreeHostImplTest, FadedOutPaintedOverlayScrollbarHitTest) { auto& scrollbar_effect_node = CreateEffectNode(scrollbar); scrollbar_effect_node.opacity = 0.8; - host_impl_->MouseDown(gfx::PointF(350, 18), /*shift_modifier*/ false); + host_impl_->MouseDown(gfx::PointF(350, 18), /*jump_key_modifier*/ false); InputHandlerPointerResult result = host_impl_->MouseMoveAt(gfx::Point(350, 28)); EXPECT_GT(result.scroll_offset.y(), 0u); @@ -12654,7 +13066,7 @@ TEST_F(LayerTreeHostImplTest, FadedOutPaintedOverlayScrollbarHitTest) { // Scrolling shouldn't occur at opacity = 0. scrollbar_effect_node.opacity = 0; - host_impl_->MouseDown(gfx::PointF(350, 18), /*shift_modifier*/ false); + host_impl_->MouseDown(gfx::PointF(350, 18), /*jump_key_modifier*/ false); result = host_impl_->MouseMoveAt(gfx::Point(350, 28)); EXPECT_EQ(result.scroll_offset.y(), 0u); host_impl_->MouseUp(gfx::PointF(350, 28)); @@ -12664,11 +13076,73 @@ TEST_F(LayerTreeHostImplTest, FadedOutPaintedOverlayScrollbarHitTest) { host_impl_ = nullptr; } +// Tests that deleting a horizontal scrollbar doesn't affect the autoscroll task +// for the vertical scrollbar. +TEST_P(ScrollUnifiedLayerTreeHostImplTest, AutoscrollOnDeletedScrollbar) { + LayerTreeSettings settings = DefaultSettings(); + settings.compositor_threaded_scrollbar_scrolling = true; + CreateHostImpl(settings, CreateLayerTreeFrameSink()); + + // Setup the viewport. + const gfx::Size viewport_size = gfx::Size(360, 600); + const gfx::Size content_size = gfx::Size(345, 3800); + SetupViewportLayersOuterScrolls(viewport_size, content_size); + LayerImpl* scroll_layer = OuterViewportScrollLayer(); + + // Set up the scrollbar and its dimensions. + LayerTreeImpl* layer_tree_impl = host_impl_->active_tree(); + auto* scrollbar = AddLayer<PaintedScrollbarLayerImpl>( + layer_tree_impl, ScrollbarOrientation::VERTICAL, false, true); + SetupScrollbarLayerCommon(scroll_layer, scrollbar); + scrollbar->SetHitTestable(true); + + const gfx::Size scrollbar_size = gfx::Size(15, 600); + scrollbar->SetBounds(scrollbar_size); + + // Set up the thumb dimensions. + scrollbar->SetThumbThickness(15); + scrollbar->SetThumbLength(50); + scrollbar->SetTrackRect(gfx::Rect(0, 15, 15, 575)); + scrollbar->SetOffsetToTransformParent(gfx::Vector2dF(345, 0)); + + TestInputHandlerClient input_handler_client; + host_impl_->BindToClient(&input_handler_client); + + // PointerDown on the scrollbar schedules an autoscroll task. + host_impl_->MouseDown(gfx::PointF(350, 580), /*jump_key_modifier*/ false); + EXPECT_TRUE(!host_impl_->CurrentlyScrollingNode()); + + // Now, unregister the horizontal scrollbar and test that the autoscroll task + // that was scheduled for the vertical scrollbar still exists. (Note that + // adding a horizontal scrollbar layer is not needed. This test merely checks + // the logic inside ScrollbarController::DidUnregisterScrollbar) + host_impl_->DidUnregisterScrollbarLayer(scroll_layer->element_id(), + ScrollbarOrientation::HORIZONTAL); + EXPECT_TRUE(host_impl_->scrollbar_controller_for_testing() + ->AutoscrollTaskIsScheduled()); + + // If a call comes in to delete the scrollbar layer for which the autoscroll + // was scheduled, the autoscroll task should be cancelled. + host_impl_->DidUnregisterScrollbarLayer(scroll_layer->element_id(), + ScrollbarOrientation::VERTICAL); + EXPECT_FALSE(host_impl_->scrollbar_controller_for_testing() + ->AutoscrollTaskIsScheduled()); + + // End the scroll. + host_impl_->MouseUp(gfx::PointF(350, 580)); + host_impl_->ScrollEnd(); + EXPECT_TRUE(!host_impl_->CurrentlyScrollingNode()); + + // Tear down the LayerTreeHostImpl before the InputHandlerClient. + host_impl_->ReleaseLayerTreeFrameSink(); + host_impl_ = nullptr; +} + // Tests that a pointerdown followed by pointermove(s) produces // InputHandlerPointerResult with scroll_offset > 0 even though the GSB might // have been dispatched *after* the first pointermove was handled by the // ScrollbarController. -TEST_F(LayerTreeHostImplTest, PointerMoveOutOfSequence) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, PointerMoveOutOfSequence) { LayerTreeSettings settings = DefaultSettings(); settings.compositor_threaded_scrollbar_scrolling = true; CreateHostImpl(settings, CreateLayerTreeFrameSink()); @@ -12701,7 +13175,7 @@ TEST_F(LayerTreeHostImplTest, PointerMoveOutOfSequence) { // PointerDown sets up the state needed to initiate a drag. Although, the // resulting GSB won't be dispatched until the next VSync. Hence, the // CurrentlyScrollingNode is expected to be null. - host_impl_->MouseDown(gfx::PointF(350, 18), /*shift_modifier*/ false); + host_impl_->MouseDown(gfx::PointF(350, 18), /*jump_key_modifier*/ false); EXPECT_TRUE(!host_impl_->CurrentlyScrollingNode()); // PointerMove arrives before the next VSync. This still needs to be handled @@ -12740,7 +13214,7 @@ TEST_F(LayerTreeHostImplTest, PointerMoveOutOfSequence) { } // This tests that faded-out Mac scrollbars can't be interacted with. -TEST_F(LayerTreeHostImplTest, FadedOutPaintedScrollbarHitTest) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, FadedOutPaintedScrollbarHitTest) { LayerTreeSettings settings = DefaultSettings(); settings.compositor_threaded_scrollbar_scrolling = true; CreateHostImpl(settings, CreateLayerTreeFrameSink()); @@ -12774,14 +13248,14 @@ TEST_F(LayerTreeHostImplTest, FadedOutPaintedScrollbarHitTest) { // scroll. scrollbar->set_scrollbar_painted_opacity(0); InputHandlerPointerResult result = - host_impl_->MouseDown(gfx::PointF(350, 100), /*shift_modifier*/ false); + host_impl_->MouseDown(gfx::PointF(350, 100), /*jump_key_modifier*/ false); EXPECT_EQ(result.scroll_offset.y(), 0u); // MouseDown on the track of a scrollbar with opacity > 0 should produce a // scroll. scrollbar->set_scrollbar_painted_opacity(1); result = - host_impl_->MouseDown(gfx::PointF(350, 100), /*shift_modifier*/ false); + host_impl_->MouseDown(gfx::PointF(350, 100), /*jump_key_modifier*/ false); EXPECT_GT(result.scroll_offset.y(), 0u); // Tear down the LayerTreeHostImpl before the InputHandlerClient. @@ -12789,7 +13263,8 @@ TEST_F(LayerTreeHostImplTest, FadedOutPaintedScrollbarHitTest) { host_impl_ = nullptr; } -TEST_F(LayerTreeHostImplTest, SingleGSUForScrollbarThumbDragPerFrame) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, + SingleGSUForScrollbarThumbDragPerFrame) { LayerTreeSettings settings = DefaultSettings(); settings.compositor_threaded_scrollbar_scrolling = true; CreateHostImpl(settings, CreateLayerTreeFrameSink()); @@ -12839,7 +13314,7 @@ TEST_F(LayerTreeHostImplTest, SingleGSUForScrollbarThumbDragPerFrame) { // MouseDown on the thumb should not produce a scroll. InputHandlerPointerResult result = - host_impl_->MouseDown(gfx::PointF(350, 18), /*shift_modifier*/ false); + host_impl_->MouseDown(gfx::PointF(350, 18), /*jump_key_modifier*/ false); EXPECT_EQ(result.scroll_offset.y(), 0u); // The first request for a GSU should be processed as expected. @@ -12875,9 +13350,176 @@ TEST_F(LayerTreeHostImplTest, SingleGSUForScrollbarThumbDragPerFrame) { host_impl_ = nullptr; } +// Tests that the scheduled autoscroll task aborts if a 2nd mousedown occurs in +// the same frame. +TEST_F(LayerTreeHostImplTest, AutoscrollTaskAbort) { + LayerTreeSettings settings = DefaultSettings(); + settings.compositor_threaded_scrollbar_scrolling = true; + CreateHostImpl(settings, CreateLayerTreeFrameSink()); + + // Setup the viewport. + const gfx::Size viewport_size = gfx::Size(360, 600); + const gfx::Size content_size = gfx::Size(345, 3800); + SetupViewportLayersOuterScrolls(viewport_size, content_size); + LayerImpl* scroll_layer = OuterViewportScrollLayer(); + + // Set up the scrollbar and its dimensions. + LayerTreeImpl* layer_tree_impl = host_impl_->active_tree(); + auto* scrollbar = AddLayer<PaintedScrollbarLayerImpl>( + layer_tree_impl, VERTICAL, /*is_left_side_vertical_scrollbar*/ false, + /*is_overlay*/ false); + + SetupScrollbarLayer(scroll_layer, scrollbar); + const gfx::Size scrollbar_size = gfx::Size(15, 600); + scrollbar->SetBounds(scrollbar_size); + host_impl_->set_force_smooth_wheel_scrolling_for_testing(true); + + // Set up the thumb dimensions. + scrollbar->SetThumbThickness(15); + scrollbar->SetThumbLength(50); + scrollbar->SetTrackRect(gfx::Rect(0, 15, 15, 575)); + + // Set up scrollbar arrows. + scrollbar->SetBackButtonRect( + gfx::Rect(gfx::Point(345, 0), gfx::Size(15, 15))); + scrollbar->SetForwardButtonRect( + gfx::Rect(gfx::Point(345, 570), gfx::Size(15, 15))); + scrollbar->SetOffsetToTransformParent(gfx::Vector2dF(345, 0)); + + TestInputHandlerClient input_handler_client; + host_impl_->BindToClient(&input_handler_client); + + { + // An autoscroll task gets scheduled on mousedown. + InputHandlerPointerResult result = host_impl_->MouseDown( + gfx::PointF(350, 575), /*jump_key_modifier*/ false); + EXPECT_EQ(result.type, PointerResultType::kScrollbarScroll); + auto begin_state = BeginState(gfx::Point(350, 575), gfx::Vector2d(0, 40), + ui::ScrollInputType::kScrollbar); + EXPECT_EQ( + InputHandler::SCROLL_ON_IMPL_THREAD, + host_impl_ + ->ScrollBegin(begin_state.get(), ui::ScrollInputType::kScrollbar) + .thread); + EXPECT_TRUE(host_impl_->scrollbar_controller_for_testing() + ->AutoscrollTaskIsScheduled()); + } + + { + // Another mousedown occurs in the same frame. InputHandlerProxy calls + // LayerTreeHostImpl::ScrollEnd and the autoscroll task should be cancelled. + InputHandlerPointerResult result = host_impl_->MouseDown( + gfx::PointF(350, 575), /*jump_key_modifier*/ false); + EXPECT_EQ(result.type, PointerResultType::kScrollbarScroll); + host_impl_->ScrollEnd(); + EXPECT_FALSE(host_impl_->scrollbar_controller_for_testing() + ->AutoscrollTaskIsScheduled()); + } + + // Tear down the LayerTreeHostImpl before the InputHandlerClient. + host_impl_->ReleaseLayerTreeFrameSink(); + host_impl_ = nullptr; +} + +// Tests that the ScrollbarController handles jump clicks. +TEST_F(LayerTreeHostImplTest, JumpOnScrollbarClick) { + LayerTreeSettings settings = DefaultSettings(); + settings.compositor_threaded_scrollbar_scrolling = true; + CreateHostImpl(settings, CreateLayerTreeFrameSink()); + + // Setup the viewport. + const gfx::Size viewport_size = gfx::Size(360, 600); + const gfx::Size content_size = gfx::Size(345, 3800); + SetupViewportLayersOuterScrolls(viewport_size, content_size); + LayerImpl* scroll_layer = OuterViewportScrollLayer(); + + // Set up the scrollbar and its dimensions. + LayerTreeImpl* layer_tree_impl = host_impl_->active_tree(); + auto* scrollbar = AddLayer<PaintedScrollbarLayerImpl>( + layer_tree_impl, VERTICAL, /*is_left_side_vertical_scrollbar*/ false, + /*is_overlay*/ false); + + SetupScrollbarLayer(scroll_layer, scrollbar); + const gfx::Size scrollbar_size = gfx::Size(15, 600); + scrollbar->SetBounds(scrollbar_size); + host_impl_->set_force_smooth_wheel_scrolling_for_testing(true); + + // Set up the thumb dimensions. + scrollbar->SetThumbThickness(15); + scrollbar->SetThumbLength(50); + scrollbar->SetTrackRect(gfx::Rect(0, 15, 15, 575)); + + // Set up scrollbar arrows. + scrollbar->SetBackButtonRect( + gfx::Rect(gfx::Point(345, 0), gfx::Size(15, 15))); + scrollbar->SetForwardButtonRect( + gfx::Rect(gfx::Point(345, 570), gfx::Size(15, 15))); + scrollbar->SetOffsetToTransformParent(gfx::Vector2dF(345, 0)); + + TestInputHandlerClient input_handler_client; + host_impl_->BindToClient(&input_handler_client); + + // Verify all 4 combinations of JumpOnTrackClick and jump_key_modifier. + { + // Click on track when JumpOnTrackClick is false and jump_key_modifier is + // false. Expected to perform a regular track scroll. + scrollbar->SetJumpOnTrackClick(false); + InputHandlerPointerResult result = host_impl_->MouseDown( + gfx::PointF(350, 400), /*jump_key_modifier*/ false); + EXPECT_EQ(result.type, PointerResultType::kScrollbarScroll); + EXPECT_EQ(result.scroll_offset.y(), 525); + result = host_impl_->MouseUp(gfx::PointF(350, 400)); + EXPECT_EQ(result.type, PointerResultType::kScrollbarScroll); + EXPECT_EQ(result.scroll_offset.y(), 0); + } + + { + // Click on track when JumpOnTrackClick is false and jump_key_modifier is + // true. Expected to perform scroller jump to the clicked location. + scrollbar->SetJumpOnTrackClick(false); + InputHandlerPointerResult result = host_impl_->MouseDown( + gfx::PointF(350, 400), /*jump_key_modifier*/ true); + EXPECT_EQ(result.type, PointerResultType::kScrollbarScroll); + EXPECT_EQ(result.scroll_offset.y(), 2194); + result = host_impl_->MouseUp(gfx::PointF(350, 400)); + EXPECT_EQ(result.type, PointerResultType::kScrollbarScroll); + EXPECT_EQ(result.scroll_offset.y(), 0); + } + + { + // Click on track when JumpOnTrackClick is true and jump_key_modifier is + // false. Expected to perform scroller jump to the clicked location. + scrollbar->SetJumpOnTrackClick(true); + InputHandlerPointerResult result = host_impl_->MouseDown( + gfx::PointF(350, 400), /*jump_key_modifier*/ false); + EXPECT_EQ(result.type, PointerResultType::kScrollbarScroll); + EXPECT_EQ(result.scroll_offset.y(), 2194); + result = host_impl_->MouseUp(gfx::PointF(350, 400)); + EXPECT_EQ(result.type, PointerResultType::kScrollbarScroll); + EXPECT_EQ(result.scroll_offset.y(), 0); + } + + { + // Click on track when JumpOnTrackClick is true and jump_key_modifier is + // true. Expected to perform a regular track scroll. + scrollbar->SetJumpOnTrackClick(true); + InputHandlerPointerResult result = host_impl_->MouseDown( + gfx::PointF(350, 400), /*jump_key_modifier*/ true); + EXPECT_EQ(result.type, PointerResultType::kScrollbarScroll); + EXPECT_EQ(result.scroll_offset.y(), 525); + result = host_impl_->MouseUp(gfx::PointF(350, 400)); + EXPECT_EQ(result.type, PointerResultType::kScrollbarScroll); + EXPECT_EQ(result.scroll_offset.y(), 0); + } + + // Tear down the LayerTreeHostImpl before the InputHandlerClient. + host_impl_->ReleaseLayerTreeFrameSink(); + host_impl_ = nullptr; +} + // Tests that an animated scrollbar scroll aborts when a different device (like // a mousewheel) wants to animate the scroll offset. -TEST_F(LayerTreeHostImplTest, AnimatedScrollYielding) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, AnimatedScrollYielding) { LayerTreeSettings settings = DefaultSettings(); settings.compositor_threaded_scrollbar_scrolling = true; CreateHostImpl(settings, CreateLayerTreeFrameSink()); @@ -12919,7 +13561,7 @@ TEST_F(LayerTreeHostImplTest, AnimatedScrollYielding) { { // Set up an animated scrollbar autoscroll. host_impl_->scrollbar_controller_for_testing()->HandlePointerDown( - gfx::PointF(350, 560), /*shift_modifier*/ false); + gfx::PointF(350, 560), /*jump_key_modifier*/ false); auto begin_state = BeginState(gfx::Point(350, 560), gfx::Vector2d(0, 40), ui::ScrollInputType::kScrollbar); EXPECT_EQ( @@ -12981,7 +13623,7 @@ TEST_F(LayerTreeHostImplTest, AnimatedScrollYielding) { // Tests that changing the scroller length in the middle of a thumb drag doesn't // cause the scroller to jump. -TEST_F(LayerTreeHostImplTest, ThumbDragScrollerLengthIncrease) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ThumbDragScrollerLengthIncrease) { LayerTreeSettings settings = DefaultSettings(); settings.compositor_threaded_scrollbar_scrolling = true; CreateHostImpl(settings, CreateLayerTreeFrameSink()); @@ -13028,7 +13670,7 @@ TEST_F(LayerTreeHostImplTest, ThumbDragScrollerLengthIncrease) { begin_frame_args.frame_id.sequence_number++; host_impl_->WillBeginImplFrame(begin_frame_args); InputHandlerPointerResult result = - host_impl_->MouseDown(gfx::PointF(350, 18), /*shift_modifier*/ false); + host_impl_->MouseDown(gfx::PointF(350, 18), /*jump_key_modifier*/ false); EXPECT_EQ(result.scroll_offset.y(), 0); EXPECT_EQ(result.type, PointerResultType::kScrollbarScroll); host_impl_->DidFinishImplFrame(begin_frame_args); @@ -13082,7 +13724,7 @@ TEST_F(LayerTreeHostImplTest, ThumbDragScrollerLengthIncrease) { host_impl_ = nullptr; } -TEST_F(LayerTreeHostImplTest, MainThreadFallback) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, MainThreadFallback) { LayerTreeSettings settings = DefaultSettings(); settings.compositor_threaded_scrollbar_scrolling = true; CreateHostImpl(settings, CreateLayerTreeFrameSink()); @@ -13118,7 +13760,7 @@ TEST_F(LayerTreeHostImplTest, MainThreadFallback) { // Clicking on the track should produce a scroll on the compositor thread. InputHandlerPointerResult compositor_threaded_scrolling_result = - host_impl_->MouseDown(gfx::PointF(350, 500), /*shift_modifier*/ false); + host_impl_->MouseDown(gfx::PointF(350, 500), /*jump_key_modifier*/ false); host_impl_->MouseUp(gfx::PointF(350, 500)); EXPECT_EQ(compositor_threaded_scrolling_result.scroll_offset.y(), 525u); EXPECT_FALSE(GetScrollNode(scroll_layer)->main_thread_scrolling_reasons); @@ -13129,7 +13771,7 @@ TEST_F(LayerTreeHostImplTest, MainThreadFallback) { GetScrollNode(scroll_layer)->main_thread_scrolling_reasons = MainThreadScrollingReason::kHasNonLayerViewportConstrainedObjects; compositor_threaded_scrolling_result = - host_impl_->MouseDown(gfx::PointF(350, 500), /*shift_modifier*/ false); + host_impl_->MouseDown(gfx::PointF(350, 500), /*jump_key_modifier*/ false); host_impl_->MouseUp(gfx::PointF(350, 500)); EXPECT_EQ(compositor_threaded_scrolling_result.scroll_offset.y(), 0u); @@ -13138,7 +13780,8 @@ TEST_F(LayerTreeHostImplTest, MainThreadFallback) { host_impl_ = nullptr; } -TEST_F(LayerTreeHostImplTest, SecondScrollAnimatedBeginNotIgnored) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, + SecondScrollAnimatedBeginNotIgnored) { const gfx::Size content_size(1000, 1000); const gfx::Size viewport_size(50, 100); SetupViewportLayersOuterScrolls(viewport_size, content_size); @@ -13165,7 +13808,8 @@ TEST_F(LayerTreeHostImplTest, SecondScrollAnimatedBeginNotIgnored) { // Verfify that a smooth scroll animation doesn't jump when UpdateTarget gets // called before the animation is started. -TEST_F(LayerTreeHostImplTest, AnimatedScrollUpdateTargetBeforeStarting) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, + AnimatedScrollUpdateTargetBeforeStarting) { const gfx::Size content_size(1000, 1000); const gfx::Size viewport_size(50, 100); SetupViewportLayersOuterScrolls(viewport_size, content_size); @@ -13223,7 +13867,7 @@ TEST_F(LayerTreeHostImplTest, AnimatedScrollUpdateTargetBeforeStarting) { EXPECT_TRUE(y > 1 && y < 49); } -TEST_F(LayerTreeHostImplTest, ScrollAnimatedWithDelay) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollAnimatedWithDelay) { const gfx::Size content_size(1000, 1000); const gfx::Size viewport_size(50, 100); SetupViewportLayersOuterScrolls(viewport_size, content_size); @@ -13752,7 +14396,7 @@ TEST_F(LayerTreeHostImplTimelinesTest, ScrollAnimatedChangingBounds) { EXPECT_EQ(gfx::ScrollOffset(250, 250), CurrentScrollOffset(scrolling_layer)); } -TEST_F(LayerTreeHostImplTest, InvalidLayerNotAddedToRasterQueue) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, InvalidLayerNotAddedToRasterQueue) { CreatePendingTree(); Region empty_invalidation; @@ -13786,7 +14430,7 @@ TEST_F(LayerTreeHostImplTest, InvalidLayerNotAddedToRasterQueue) { EXPECT_TRUE(empty_raster_priority_queue_all->IsEmpty()); } -TEST_F(LayerTreeHostImplTest, DidBecomeActive) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, DidBecomeActive) { CreatePendingTree(); host_impl_->ActivateSyncTree(); CreatePendingTree(); @@ -13819,7 +14463,8 @@ TEST_F(LayerTreeHostImplTest, DidBecomeActive) { EXPECT_EQ(2u, raw_mask_layer->did_become_active_call_count()); } -TEST_F(LayerTreeHostImplTest, WheelScrollWithPageScaleFactorOnInnerLayer) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, + WheelScrollWithPageScaleFactorOnInnerLayer) { SetupViewportLayersInnerScrolls(gfx::Size(50, 50), gfx::Size(100, 100)); auto* scroll_layer = InnerViewportScrollLayer(); host_impl_->active_tree()->SetDeviceViewportRect(gfx::Rect(50, 50)); @@ -13892,7 +14537,7 @@ size_t CountRenderPassesWithId(const viz::RenderPassList& list, [id](const std::unique_ptr<viz::RenderPass>& p) { return p->id == id; }); } -TEST_F(LayerTreeHostImplTest, RemoveUnreferencedRenderPass) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, RemoveUnreferencedRenderPass) { TestFrameData frame; frame.render_passes.push_back(viz::RenderPass::Create()); viz::RenderPass* pass3 = frame.render_passes.back().get(); @@ -13927,7 +14572,7 @@ TEST_F(LayerTreeHostImplTest, RemoveUnreferencedRenderPass) { EXPECT_EQ(1u, frame.render_passes[0]->id); } -TEST_F(LayerTreeHostImplTest, RemoveEmptyRenderPass) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, RemoveEmptyRenderPass) { TestFrameData frame; frame.render_passes.push_back(viz::RenderPass::Create()); viz::RenderPass* pass3 = frame.render_passes.back().get(); @@ -13968,7 +14613,7 @@ TEST_F(LayerTreeHostImplTest, RemoveEmptyRenderPass) { pass1->quad_list.ElementAt(0)->material); } -TEST_F(LayerTreeHostImplTest, DoNotRemoveEmptyRootRenderPass) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, DoNotRemoveEmptyRootRenderPass) { TestFrameData frame; frame.render_passes.push_back(viz::RenderPass::Create()); viz::RenderPass* pass3 = frame.render_passes.back().get(); @@ -14024,7 +14669,7 @@ class FakeVideoFrameController : public VideoFrameController { bool did_draw_frame_ = false; }; -TEST_F(LayerTreeHostImplTest, AddVideoFrameControllerInsideFrame) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, AddVideoFrameControllerInsideFrame) { viz::BeginFrameArgs begin_frame_args = viz::CreateBeginFrameArgsForTesting(BEGINFRAME_FROM_HERE, 0, 2); FakeVideoFrameController controller; @@ -14047,7 +14692,8 @@ TEST_F(LayerTreeHostImplTest, AddVideoFrameControllerInsideFrame) { EXPECT_FALSE(controller.did_draw_frame()); } -TEST_F(LayerTreeHostImplTest, AddVideoFrameControllerOutsideFrame) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, + AddVideoFrameControllerOutsideFrame) { viz::BeginFrameArgs begin_frame_args = viz::CreateBeginFrameArgsForTesting(BEGINFRAME_FROM_HERE, 0, 2); FakeVideoFrameController controller; @@ -14078,7 +14724,8 @@ TEST_F(LayerTreeHostImplTest, AddVideoFrameControllerOutsideFrame) { } // Tests that SetDeviceScaleFactor correctly impacts GPU rasterization. -TEST_F(LayerTreeHostImplTest, GpuRasterizationStatusDeviceScaleFactor) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, + GpuRasterizationStatusDeviceScaleFactor) { // Create a host impl with MSAA support (4 samples). LayerTreeSettings msaaSettings = DefaultSettings(); msaaSettings.gpu_rasterization_msaa_sample_count = -1; @@ -14108,7 +14755,8 @@ TEST_F(LayerTreeHostImplTest, GpuRasterizationStatusDeviceScaleFactor) { } // Tests that explicit MSAA sample count correctly impacts GPU rasterization. -TEST_F(LayerTreeHostImplTest, GpuRasterizationStatusExplicitMSAACount) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, + GpuRasterizationStatusExplicitMSAACount) { // Create a host impl with MSAA support and a forced sample count of 4. LayerTreeSettings msaaSettings = DefaultSettings(); msaaSettings.gpu_rasterization_msaa_sample_count = 4; @@ -14245,7 +14893,7 @@ TEST_F(MsaaCompatibilityLayerTreeHostImplTest, 0); } -TEST_F(LayerTreeHostImplTest, UpdatePageScaleFactorOnActiveTree) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, UpdatePageScaleFactorOnActiveTree) { // Check page scale factor update in property trees when an update is made // on the active tree. CreatePendingTree(); @@ -14293,7 +14941,8 @@ TEST_F(LayerTreeHostImplTest, UpdatePageScaleFactorOnActiveTree) { EXPECT_EQ(gfx::Point3F(), active_tree_node->origin); } -TEST_F(LayerTreeHostImplTest, SubLayerScaleForNodeInSubtreeOfPageScaleLayer) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, + SubLayerScaleForNodeInSubtreeOfPageScaleLayer) { // Checks that the sublayer scale of a transform node in the subtree of the // page scale layer is updated without a property tree rebuild. host_impl_->active_tree()->PushPageScaleFromMainThread(1, 1, 3); @@ -14321,7 +14970,8 @@ TEST_F(LayerTreeHostImplTest, SubLayerScaleForNodeInSubtreeOfPageScaleLayer) { // Checks that if we lose a GPU raster enabled LayerTreeFrameSink and replace // it with a software LayerTreeFrameSink, LayerTreeHostImpl correctly // re-computes GPU rasterization status. -TEST_F(LayerTreeHostImplTest, RecomputeGpuRasterOnLayerTreeFrameSinkChange) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, + RecomputeGpuRasterOnLayerTreeFrameSinkChange) { host_impl_->ReleaseLayerTreeFrameSink(); host_impl_ = nullptr; @@ -14533,7 +15183,7 @@ void LayerTreeHostImplTest::SetupMouseMoveAtTestScrollbarStates( animation_task_.Reset(); // Only the MouseMove's location will affect the overlay scrollbar. - host_impl_->MouseDown(gfx::PointF(60, 50), /*shift_modifier*/ false); + host_impl_->MouseDown(gfx::PointF(60, 50), /*jump_key_modifier*/ false); host_impl_->MouseMoveAt(gfx::Point(60, 50)); host_impl_->MouseUp(gfx::PointF(60, 50)); @@ -14544,29 +15194,30 @@ void LayerTreeHostImplTest::SetupMouseMoveAtTestScrollbarStates( host_impl_->MouseMoveAt(gfx::Point(40, 150)); animation_task_.Reset(); - host_impl_->MouseDown(gfx::PointF(40, 150), /*shift_modifier*/ false); + host_impl_->MouseDown(gfx::PointF(40, 150), /*jump_key_modifier*/ false); host_impl_->MouseUp(gfx::PointF(40, 150)); EXPECT_TRUE(animation_task_.is_null()); // Near scrollbar_1, then mouse down and unregister // scrollbar_2_animation_controller, then mouse up should not cause crash. host_impl_->MouseMoveAt(gfx::Point(40, 150)); - host_impl_->MouseDown(gfx::PointF(40, 150), /*shift_modifier*/ false); - host_impl_->DidUnregisterScrollbarLayer(root_scroll->element_id()); + host_impl_->MouseDown(gfx::PointF(40, 150), /*jump_key_modifier*/ false); + host_impl_->DidUnregisterScrollbarLayer(root_scroll->element_id(), + ScrollbarOrientation::VERTICAL); host_impl_->MouseUp(gfx::PointF(40, 150)); } -TEST_F(LayerTreeHostImplTest, +TEST_P(ScrollUnifiedLayerTreeHostImplTest, LayerTreeHostImplTestScrollbarStatesInMainThreadScrolling) { SetupMouseMoveAtTestScrollbarStates(true); } -TEST_F(LayerTreeHostImplTest, +TEST_P(ScrollUnifiedLayerTreeHostImplTest, LayerTreeHostImplTestScrollbarStatesInNotMainThreadScrolling) { SetupMouseMoveAtTestScrollbarStates(false); } -TEST_F(LayerTreeHostImplTest, CheckerImagingTileInvalidation) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, CheckerImagingTileInvalidation) { LayerTreeSettings settings = LegacySWSettings(); settings.commit_to_active_tree = false; settings.enable_checker_imaging = true; @@ -14651,7 +15302,7 @@ TEST_F(LayerTreeHostImplTest, CheckerImagingTileInvalidation) { EXPECT_EQ(expected_invalidation, *(root->GetPendingInvalidation())); } -TEST_F(LayerTreeHostImplTest, RasterColorSpace) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, RasterColorSpace) { LayerTreeSettings settings = DefaultSettings(); CreateHostImpl(settings, CreateLayerTreeFrameSink()); // The default raster color space should be sRGB. @@ -14666,7 +15317,7 @@ TEST_F(LayerTreeHostImplTest, RasterColorSpace) { gfx::ColorSpace::CreateDisplayP3D65()); } -TEST_F(LayerTreeHostImplTest, RasterColorSpaceSoftware) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, RasterColorSpaceSoftware) { LayerTreeSettings settings = DefaultSettings(); CreateHostImpl(settings, FakeLayerTreeFrameSink::CreateSoftware()); // Software composited resources should always use sRGB as their color space. @@ -14680,7 +15331,7 @@ TEST_F(LayerTreeHostImplTest, RasterColorSpaceSoftware) { gfx::ColorSpace::CreateSRGB()); } -TEST_F(LayerTreeHostImplTest, RasterColorPrefersSRGB) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, RasterColorPrefersSRGB) { auto p3 = gfx::ColorSpace::CreateDisplayP3D65(); LayerTreeSettings settings = DefaultSettings(); @@ -14697,7 +15348,7 @@ TEST_F(LayerTreeHostImplTest, RasterColorPrefersSRGB) { EXPECT_EQ(host_impl_->GetRasterColorSpace(gfx::ContentColorUsage::kSRGB), p3); } -TEST_F(LayerTreeHostImplTest, UpdatedTilingsForNonDrawingLayers) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, UpdatedTilingsForNonDrawingLayers) { gfx::Size layer_bounds(500, 500); CreatePendingTree(); @@ -14749,7 +15400,8 @@ TEST_F(LayerTreeHostImplTest, UpdatedTilingsForNonDrawingLayers) { ->can_require_tiles_for_activation()); } -TEST_F(LayerTreeHostImplTest, RasterTilePrioritizationForNonDrawingLayers) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, + RasterTilePrioritizationForNonDrawingLayers) { gfx::Size layer_bounds(500, 500); CreatePendingTree(); auto* root = @@ -14811,7 +15463,7 @@ TEST_F(LayerTreeHostImplTest, RasterTilePrioritizationForNonDrawingLayers) { EXPECT_EQ(queue->Top().tile()->layer_id(), 3); } -TEST_F(LayerTreeHostImplTest, DrawAfterDroppingTileResources) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, DrawAfterDroppingTileResources) { LayerTreeSettings settings = DefaultSettings(); settings.using_synchronous_renderer_compositor = true; CreateHostImpl(settings, CreateLayerTreeFrameSink()); @@ -14845,20 +15497,20 @@ TEST_F(LayerTreeHostImplTest, DrawAfterDroppingTileResources) { EXPECT_GT(layer->tilings()->num_tilings(), 0u); } -TEST_F(LayerTreeHostImplTest, WhiteListedTouchActionTest1) { - WhiteListedTouchActionTestHelper(1.0f, 1.0f); +TEST_P(ScrollUnifiedLayerTreeHostImplTest, AllowedTouchActionTest1) { + AllowedTouchActionTestHelper(1.0f, 1.0f); } -TEST_F(LayerTreeHostImplTest, WhiteListedTouchActionTest2) { - WhiteListedTouchActionTestHelper(1.0f, 0.789f); +TEST_P(ScrollUnifiedLayerTreeHostImplTest, AllowedTouchActionTest2) { + AllowedTouchActionTestHelper(1.0f, 0.789f); } -TEST_F(LayerTreeHostImplTest, WhiteListedTouchActionTest3) { - WhiteListedTouchActionTestHelper(2.345f, 1.0f); +TEST_P(ScrollUnifiedLayerTreeHostImplTest, AllowedTouchActionTest3) { + AllowedTouchActionTestHelper(2.345f, 1.0f); } -TEST_F(LayerTreeHostImplTest, WhiteListedTouchActionTest4) { - WhiteListedTouchActionTestHelper(2.654f, 0.678f); +TEST_P(ScrollUnifiedLayerTreeHostImplTest, AllowedTouchActionTest4) { + AllowedTouchActionTestHelper(2.654f, 0.678f); } // Test implementation of RenderFrameMetadataObserver which can optionally @@ -14893,7 +15545,7 @@ class TestRenderFrameMetadataObserver : public RenderFrameMetadataObserver { base::Optional<RenderFrameMetadata> last_metadata_; }; -TEST_F(LayerTreeHostImplTest, RenderFrameMetadata) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, RenderFrameMetadata) { SetupViewportLayersInnerScrolls(gfx::Size(50, 50), gfx::Size(100, 100)); host_impl_->active_tree()->SetDeviceViewportRect(gfx::Rect(50, 50)); host_impl_->active_tree()->PushPageScaleFromMainThread(1, 0.5f, 4); @@ -15026,7 +15678,8 @@ TEST_F(LayerTreeHostImplTest, RenderFrameMetadata) { } } -TEST_F(LayerTreeHostImplTest, SelectionBoundsPassedToRenderFrameMetadata) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, + SelectionBoundsPassedToRenderFrameMetadata) { LayerImpl* root = SetupDefaultRootLayer(gfx::Size(10, 10)); UpdateDrawProperties(host_impl_->active_tree()); @@ -15078,7 +15731,7 @@ TEST_F(LayerTreeHostImplTest, SelectionBoundsPassedToRenderFrameMetadata) { EXPECT_TRUE(selection_2.end.visible()); } -TEST_F(LayerTreeHostImplTest, +TEST_P(ScrollUnifiedLayerTreeHostImplTest, VerticalScrollDirectionChangesPassedToRenderFrameMetadata) { // Set up the viewport. SetupViewportLayersInnerScrolls(gfx::Size(50, 50), gfx::Size(100, 100)); @@ -15166,7 +15819,8 @@ TEST_F(LayerTreeHostImplTest, // Tests ScrollUpdate() to see if the method sets the scroll tree's currently // scrolling node. -TEST_F(LayerTreeHostImplTest, ScrollUpdateDoesNotSetScrollingNode) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, + ScrollUpdateDoesNotSetScrollingNode) { SetupViewportLayersInnerScrolls(gfx::Size(50, 50), gfx::Size(100, 100)); UpdateDrawProperties(host_impl_->active_tree()); @@ -15531,7 +16185,8 @@ TEST_F(HitTestRegionListGeneratingLayerTreeHostImplTest, InvalidFrameSinkId) { hit_test_region_list->regions[0].rect.ToString()); } -TEST_F(LayerTreeHostImplTest, ImplThreadPhaseUponImplSideInvalidation) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, + ImplThreadPhaseUponImplSideInvalidation) { LayerTreeSettings settings = DefaultSettings(); CreateHostImpl(settings, CreateLayerTreeFrameSink()); // In general invalidation should never be ran outside the impl frame. @@ -15552,7 +16207,7 @@ TEST_F(LayerTreeHostImplTest, ImplThreadPhaseUponImplSideInvalidation) { // Test passes when there is no crash. } -TEST_F(LayerTreeHostImplTest, SkipOnDrawDoesNotUpdateDrawParams) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, SkipOnDrawDoesNotUpdateDrawParams) { EXPECT_TRUE(CreateHostImpl(DefaultSettings(), FakeLayerTreeFrameSink::CreateSoftware())); SetupViewportLayersInnerScrolls(gfx::Size(50, 50), gfx::Size(100, 100)); @@ -15581,7 +16236,7 @@ TEST_F(LayerTreeHostImplTest, SkipOnDrawDoesNotUpdateDrawParams) { // Test that a touch scroll over a SolidColorScrollbarLayer, the scrollbar used // on Android, does not register as a scrollbar scroll and result in main // threaded scrolling. -TEST_F(LayerTreeHostImplTest, TouchScrollOnAndroidScrollbar) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, TouchScrollOnAndroidScrollbar) { LayerTreeImpl* layer_tree_impl = host_impl_->active_tree(); gfx::Size viewport_size = gfx::Size(360, 600); gfx::Size scroll_content_size = gfx::Size(360, 3800); @@ -15786,7 +16441,7 @@ TEST_F(ForceActivateAfterPaintWorkletPaintLayerTreeHostImplTest, // Verify that the device scale factor is not used to rescale scrollbar deltas // in percent-based scrolling. -TEST_F(LayerTreeHostImplTest, PercentBasedScrollbarDeltasDSF3) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, PercentBasedScrollbarDeltasDSF3) { LayerTreeSettings settings = DefaultSettings(); settings.percent_based_scrolling = true; settings.use_zoom_for_dsf = true; @@ -15798,21 +16453,21 @@ TEST_F(LayerTreeHostImplTest, PercentBasedScrollbarDeltasDSF3) { LayerImpl* content_layer = AddContentLayer(); LayerImpl* scroll_layer = AddScrollableLayer( - content_layer, gfx::Size(185, 200), gfx::Size(185, 3800)); + content_layer, gfx::Size(185, 500), gfx::Size(185, 3800)); auto* scrollbar = AddLayer<PaintedScrollbarLayerImpl>( host_impl_->active_tree(), VERTICAL, false, true); SetupScrollbarLayer(scroll_layer, scrollbar); - scrollbar->SetBounds(gfx::Size(15, 200)); + scrollbar->SetBounds(gfx::Size(15, 500)); scrollbar->SetThumbThickness(15); scrollbar->SetThumbLength(50); - scrollbar->SetTrackRect(gfx::Rect(0, 15, 15, 185)); + scrollbar->SetTrackRect(gfx::Rect(0, 15, 15, 485)); scrollbar->SetBackButtonRect(gfx::Rect(gfx::Point(0, 0), gfx::Size(15, 15))); scrollbar->SetForwardButtonRect( - gfx::Rect(gfx::Point(0, 185), gfx::Size(15, 15))); + gfx::Rect(gfx::Point(0, 485), gfx::Size(15, 15))); scrollbar->SetOffsetToTransformParent(gfx::Vector2dF(185, 0)); DrawFrame(); @@ -15821,13 +16476,13 @@ TEST_F(LayerTreeHostImplTest, PercentBasedScrollbarDeltasDSF3) { host_impl_->BindToClient(&input_handler_client); // Test scrolling with device scale factor = 3. - const float expected_delta = kPercentDeltaForDirectionalScroll * 200u; + const float expected_delta = floorf(kPercentDeltaForDirectionalScroll * 500); host_impl_->active_tree()->set_painted_device_scale_factor(3); InputHandlerPointerResult scroll_result = - host_impl_->MouseDown(gfx::PointF(190, 190), false); - host_impl_->MouseUp(gfx::PointF(190, 190)); + host_impl_->MouseDown(gfx::PointF(190, 490), false); + host_impl_->MouseUp(gfx::PointF(190, 490)); EXPECT_EQ(scroll_result.scroll_offset.y(), expected_delta); EXPECT_FALSE(GetScrollNode(scroll_layer)->main_thread_scrolling_reasons); @@ -15837,8 +16492,8 @@ TEST_F(LayerTreeHostImplTest, PercentBasedScrollbarDeltasDSF3) { host_impl_->active_tree()->set_painted_device_scale_factor(1); InputHandlerPointerResult scroll_with_dsf_1 = - host_impl_->MouseDown(gfx::PointF(190, 190), false); - host_impl_->MouseUp(gfx::PointF(190, 190)); + host_impl_->MouseDown(gfx::PointF(190, 490), false); + host_impl_->MouseUp(gfx::PointF(190, 490)); EXPECT_EQ(scroll_with_dsf_1.scroll_offset.y(), expected_delta); EXPECT_FALSE(GetScrollNode(scroll_layer)->main_thread_scrolling_reasons); @@ -15848,5 +16503,497 @@ TEST_F(LayerTreeHostImplTest, PercentBasedScrollbarDeltasDSF3) { host_impl_ = nullptr; } +class UnifiedScrollingTest : public LayerTreeHostImplTest { + public: + using Point = gfx::Point; + using ScrollInputType = ui::ScrollInputType; + using ScrollOffset = gfx::ScrollOffset; + using ScrollStatus = InputHandler::ScrollStatus; + using Size = gfx::Size; + using Rect = gfx::Rect; + using Vector2d = gfx::Vector2d; + + void SetUp() override { + scoped_feature_list.InitAndEnableFeature(features::kScrollUnification); + LayerTreeHostImplTest::SetUp(); + + cur_time_ = base::TimeTicks() + base::TimeDelta::FromMilliseconds(100); + begin_frame_args_ = + viz::CreateBeginFrameArgsForTesting(BEGINFRAME_FROM_HERE, 0, 1); + + SetupViewportLayersOuterScrolls(gfx::Size(100, 100), gfx::Size(200, 200)); + } + + void CreateUncompositedScrollerAndNonFastScrollableRegion() { + // Create an uncompositd scroll node that corresponds to a non fast + // scrollable region on the outer viewport scroll layer. + Size scrollable_content_bounds(100, 100); + Size container_bounds(50, 50); + CreateScrollNodeForUncompositedScroller( + GetPropertyTrees(), host_impl_->OuterViewportScrollNode()->id, + ScrollerElementId(), scrollable_content_bounds, container_bounds); + OuterViewportScrollLayer()->SetNonFastScrollableRegion(Rect(0, 0, 50, 50)); + + host_impl_->active_tree()->set_needs_update_draw_properties(); + UpdateDrawProperties(host_impl_->active_tree()); + host_impl_->active_tree()->DidBecomeActive(); + DrawFrame(); + } + + void CreateScroller(uint32_t main_thread_scrolling_reasons) { + // Creates a regular compositeds scroller that comes with a ScrollNode and + // Layer. + Size scrollable_content_bounds(100, 100); + Size container_bounds(50, 50); + + LayerImpl* layer = + AddScrollableLayer(OuterViewportScrollLayer(), container_bounds, + scrollable_content_bounds); + scroller_layer_ = layer; + GetScrollNode(layer)->main_thread_scrolling_reasons = + main_thread_scrolling_reasons; + GetScrollNode(layer)->is_composited = true; + + UpdateDrawProperties(host_impl_->active_tree()); + host_impl_->active_tree()->DidBecomeActive(); + DrawFrame(); + } + + void CreateSuashingLayerCoveringWholeViewport() { + // Add a (simulated) "squashing layer". It's scroll parent is the outer + // viewport but it covers the entire viewport and any scrollers underneath + // it. + LayerImpl* squashing_layer = AddLayer(); + squashing_layer->SetBounds(gfx::Size(100, 100)); + squashing_layer->SetDrawsContent(true); + squashing_layer->SetHitTestable(true); + CopyProperties(OuterViewportScrollLayer(), squashing_layer); + UpdateDrawProperties(host_impl_->active_tree()); + } + + ScrollStatus ScrollBegin(const Vector2d& delta) { + auto scroll_state = + BeginState(Point(25, 25), delta, ScrollInputType::kWheel); + ScrollStatus status = + host_impl_->ScrollBegin(scroll_state.get(), ScrollInputType::kWheel); + + if (status.needs_main_thread_hit_test) + to_be_continued_scroll_begin_ = std::move(scroll_state); + + return status; + } + + ScrollStatus ContinuedScrollBegin(ElementId element_id) { + DCHECK(to_be_continued_scroll_begin_) + << "ContinuedScrollBegin needs to come after a ScrollBegin that " + "requested a main frame"; + std::unique_ptr<ScrollState> scroll_state = + std::move(to_be_continued_scroll_begin_); + + scroll_state->data()->set_current_native_scrolling_element(element_id); + scroll_state->data()->is_main_thread_hit_tested = true; + + return host_impl_->ScrollBegin(scroll_state.get(), ScrollInputType::kWheel); + } + + InputHandlerScrollResult ScrollUpdate(const Vector2d& delta) { + auto scroll_state = + UpdateState(Point(25, 25), delta, ScrollInputType::kWheel); + return host_impl_->ScrollUpdate(scroll_state.get()); + } + + InputHandlerScrollResult AnimatedScrollUpdate(const Vector2d& delta) { + auto scroll_state = AnimatedUpdateState(Point(25, 25), delta); + return host_impl_->ScrollUpdate(scroll_state.get()); + } + + void ScrollEnd() { return host_impl_->ScrollEnd(); } + + // An animation is setup in the first BeginFrame so it won't actually update + // in the first one. Use a named method for that so it's clear rather than + // mysteriously calling BeginFrame twice. + void StartAnimation() { BeginFrame(base::TimeDelta()); } + + void BeginFrame(base::TimeDelta forward) { + cur_time_ += forward; + begin_frame_args_.frame_time = cur_time_; + begin_frame_args_.frame_id.sequence_number++; + host_impl_->WillBeginImplFrame(begin_frame_args_); + host_impl_->Animate(); + host_impl_->UpdateAnimationState(true); + host_impl_->DidFinishImplFrame(begin_frame_args_); + } + + ScrollOffset GetScrollOffset(ScrollNode* node) { + return GetPropertyTrees()->scroll_tree.current_scroll_offset( + node->element_id); + } + + ScrollOffset ScrollerOffset() { + return GetPropertyTrees()->scroll_tree.current_scroll_offset( + ScrollerElementId()); + } + + PropertyTrees* GetPropertyTrees() { + return host_impl_->active_tree()->property_trees(); + } + + ScrollNode* CurrentlyScrollingNode() { + return host_impl_->CurrentlyScrollingNode(); + } + + ScrollNode* ScrollerNode() { + ScrollNode* node = GetPropertyTrees()->scroll_tree.FindNodeFromElementId( + ScrollerElementId()); + DCHECK(node); + return node; + } + ElementId ScrollerElementId() const { + if (scroller_layer_) + return scroller_layer_->element_id(); + + return ElementId(1234); + } + + base::TimeDelta kFrameInterval = base::TimeDelta::FromMilliseconds(16); + + // Parameterized test body. Defined inline with tests. + void TestUncompositedScrollingState(bool mutates_transform_tree); + + private: + LayerImpl* scroller_layer_ = nullptr; + + base::TimeTicks cur_time_; + viz::BeginFrameArgs begin_frame_args_; + + std::unique_ptr<ScrollState> to_be_continued_scroll_begin_; + base::test::ScopedFeatureList scoped_feature_list; +}; + +// A ScrollBegin that hits a non fast scrollable region must return a request +// for a main thread hit test. +TEST_F(UnifiedScrollingTest, UnifiedScrollNonFastScrollableRegion) { + CreateUncompositedScrollerAndNonFastScrollableRegion(); + + // When ScrollUnification is turned on, scrolling inside a non-fast + // scrollable region should request a main thread hit test. It's the client's + // responsibility to request a hit test from Blink. It can then call + // ScrollBegin again, providing the element_id to scroll. + { + ScrollStatus status = ScrollBegin(Vector2d(0, 10)); + + EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD, status.thread); + EXPECT_TRUE(status.needs_main_thread_hit_test); + + // The scroll hasn't started yet though. + EXPECT_FALSE(CurrentlyScrollingNode()); + } + + // Simulate the scroll hit test coming back from the main thread. This time + // ScrollBegin will be called with an element id provided so that a hit test + // is unnecessary. + { + ScrollStatus status = ContinuedScrollBegin(ScrollerElementId()); + + EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD, status.thread); + EXPECT_FALSE(status.needs_main_thread_hit_test); + + EXPECT_TRUE(CurrentlyScrollingNode()); + EXPECT_EQ(ScrollerNode(), CurrentlyScrollingNode()); + } + + // Ensure ScrollUpdates can successfully scroll this node. They shouldn't + // mutate the associated transform node. + { + EXPECT_TRUE(ScrollUpdate(Vector2d(0, 10)).did_scroll); + EXPECT_EQ(ScrollOffset(0, 10), ScrollerOffset()); + } + + // Try to scroll past the end. Ensure the max scrolling bounds are respected. + { + EXPECT_TRUE(ScrollUpdate(Vector2d(0, 1000)).did_scroll); + EXPECT_EQ(ScrollOffset(0, 50), ScrollerOffset()); + } + + // Overscrolling should cause the scroll update to be dropped. + { + EXPECT_FALSE(ScrollUpdate(Vector2d(0, 10)).did_scroll); + EXPECT_EQ(ScrollOffset(0, 50), ScrollerOffset()); + } + + host_impl_->ScrollEnd(); +} + +// A main thread hit test should still go through latch bubbling. That is, if +// the hit tested scroller is fully scrolled and cannot consume the scroll, we +// should chain up to its ancestor. +TEST_F(UnifiedScrollingTest, MainThreadHitTestLatchBubbling) { + CreateUncompositedScrollerAndNonFastScrollableRegion(); + + // Start with the scroller fully scrolled. + { + ScrollBegin(Vector2d(0, 1000)); + ContinuedScrollBegin(ScrollerElementId()); + ScrollUpdate(Vector2d(0, 1000)); + ScrollEnd(); + ASSERT_EQ(ScrollOffset(0, 50), ScrollerOffset()); + } + + { + ScrollStatus status = ScrollBegin(Vector2d(0, 10)); + ASSERT_TRUE(status.needs_main_thread_hit_test); + status = ContinuedScrollBegin(ScrollerElementId()); + + // Since the hit tested scroller in ContinuedScrollBegin was fully + // scrolled, we should latch to the viewport instead. + EXPECT_TRUE(CurrentlyScrollingNode()); + EXPECT_EQ(host_impl_->OuterViewportScrollNode(), CurrentlyScrollingNode()); + } +} + +using UnifiedScrollingDeathTest = UnifiedScrollingTest; + +// A main thread hit test that with an empty target id should be dropped. +TEST_F(UnifiedScrollingDeathTest, EmptyMainThreadHitTest) { + ::testing::FLAGS_gtest_death_test_style = "threadsafe"; + CreateUncompositedScrollerAndNonFastScrollableRegion(); + { + ElementId kInvalidId; + DCHECK(!kInvalidId); + + ScrollStatus status = ScrollBegin(Vector2d(0, 10)); + + // Note, we have a NOTREACHED here to make sure this cannot happen. If + // DCHECKs are enabled we can just make sure we get killed when we end up + // in this situation. +#if DCHECK_IS_ON() + EXPECT_DEATH_IF_SUPPORTED({ status = ContinuedScrollBegin(kInvalidId); }, + ""); +#else + status = ContinuedScrollBegin(kInvalidId); + EXPECT_EQ(InputHandler::SCROLL_IGNORED, status.thread); + EXPECT_EQ(MainThreadScrollingReason::kNoScrollingLayer, + status.main_thread_scrolling_reasons); +#endif + } +} + +// A main thread hit test that returns a scroll node we can't find should be +// dropped. +TEST_F(UnifiedScrollingTest, MainThreadHitTestScrollNodeNotFound) { + CreateUncompositedScrollerAndNonFastScrollableRegion(); + + { + ElementId kUnknown(42); + DCHECK(!GetPropertyTrees()->scroll_tree.FindNodeFromElementId(kUnknown)); + + ScrollStatus status = ScrollBegin(Vector2d(0, 10)); + status = ContinuedScrollBegin(kUnknown); + EXPECT_EQ(InputHandler::SCROLL_IGNORED, status.thread); + EXPECT_EQ(MainThreadScrollingReason::kNoScrollingLayer, + status.main_thread_scrolling_reasons); + } +} + +// The presence of a squashing layer is one reason we might "fail to hit test" +// on the compositor. A heuristic is used that if the first hit scrollable +// layer isn't a scrolling ancestor of the first hit layer, we probably hit a +// squashing layer which might have empty regions we don't know how to hit +// test. This requires falling back to the main thread. However, with scroll +// unification, we may still be able to scroll the final scroller entirely on +// the compositor. See LayerTreeHostImpl::IsInitialScrollHitTestReliable. +TEST_F(UnifiedScrollingTest, SquashingLayerCausesMainThreadHitTest) { + // Create a scroller that should scroll on the compositor thread and the add + // "squashing" layer over top of it. This simulates the case where a + // squashing layer obscuring a scroller makes the hit test unreliable. + CreateScroller(MainThreadScrollingReason::kNotScrollingOnMain); + CreateSuashingLayerCoveringWholeViewport(); + + // When ScrollUnification is turned on, scrolling over a squashing-like layer + // that cannot be reliably hit tested on the compositor should request a main + // thread hit test. + { + ScrollStatus status = ScrollBegin(Vector2d(0, 10)); + EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD, status.thread); + EXPECT_TRUE(status.needs_main_thread_hit_test); + } + + // Resolving the hit test should allow the scroller underneath to scroll as + // normal on the impl thread. + { + ScrollStatus status = ContinuedScrollBegin(ScrollerElementId()); + EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD, status.thread); + EXPECT_FALSE(status.needs_main_thread_hit_test); + + EXPECT_TRUE(CurrentlyScrollingNode()); + EXPECT_EQ(ScrollerNode(), CurrentlyScrollingNode()); + } +} + +// Under unified scroling, a composited scroller with a main thread scrolling +// reason should be scrolled on the compositor. Ensure ScrollBegin returns +// success without needing a main thread hit test. +TEST_F(UnifiedScrollingTest, MainThreadScrollingReasonsScrollOnCompositor) { + CreateScroller( + MainThreadScrollingReason::kHasBackgroundAttachmentFixedObjects); + + { + ScrollStatus status = ScrollBegin(Vector2d(0, 10)); + EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD, status.thread); + EXPECT_FALSE(status.needs_main_thread_hit_test); + } +} + +// This tests whether or not various kinds of scrolling mutates the transform +// tree or not. It is parameterized and used by tests below. +void UnifiedScrollingTest::TestUncompositedScrollingState( + bool mutates_transform_tree) { + TransformTree& tree = GetPropertyTrees()->transform_tree; + TransformNode* transform_node = tree.Node(ScrollerNode()->transform_id); + + // Ensure we're in a clean state to start. + { + ASSERT_EQ(transform_node->element_id, ScrollerElementId()); + ASSERT_TRUE(transform_node->scrolls); + + ASSERT_EQ(ScrollOffset(0, 0), transform_node->scroll_offset); + ASSERT_FALSE(transform_node->transform_changed); + ASSERT_FALSE(transform_node->needs_local_transform_update); + ASSERT_FALSE(tree.needs_update()); + } + + // Start a scroll, ensure the scroll tree was updated and a commit was + // requested. Check that the transform tree mutation was as expected for the + // test parameter. + { + ScrollStatus status = ScrollBegin(Vector2d(0, 10)); + if (status.needs_main_thread_hit_test) + ContinuedScrollBegin(ScrollerElementId()); + + ASSERT_EQ(ScrollerNode(), CurrentlyScrollingNode()); + + did_request_commit_ = false; + + ScrollUpdate(Vector2d(0, 10)); + ASSERT_EQ(ScrollOffset(0, 10), ScrollerOffset()); + EXPECT_TRUE(did_request_commit_); + + // Ensure the transform tree was updated only if expected. + if (mutates_transform_tree) + EXPECT_EQ(ScrollOffset(0, 10), transform_node->scroll_offset); + else + EXPECT_EQ(ScrollOffset(0, 0), transform_node->scroll_offset); + EXPECT_EQ(mutates_transform_tree, transform_node->transform_changed); + EXPECT_EQ(mutates_transform_tree, + transform_node->needs_local_transform_update); + EXPECT_EQ(mutates_transform_tree, tree.needs_update()); + } + + // Perform animated scroll update. Ensure the same things. + { + did_request_commit_ = false; + + AnimatedScrollUpdate(Vector2d(0, 10)); + ASSERT_TRUE(host_impl_->mutator_host()->ImplOnlyScrollAnimatingElement()); + ASSERT_EQ(ScrollOffset(0, 10), ScrollerOffset()); + + StartAnimation(); + BeginFrame(kFrameInterval); + BeginFrame(base::TimeDelta::FromMilliseconds(500)); + BeginFrame(kFrameInterval); + + ASSERT_EQ(ScrollOffset(0, 20), ScrollerOffset()); + EXPECT_TRUE(did_request_commit_); + + if (mutates_transform_tree) + EXPECT_EQ(ScrollOffset(0, 20), transform_node->scroll_offset); + else + EXPECT_EQ(ScrollOffset(0, 0), transform_node->scroll_offset); + EXPECT_EQ(mutates_transform_tree, transform_node->transform_changed); + EXPECT_EQ(mutates_transform_tree, + transform_node->needs_local_transform_update); + EXPECT_EQ(mutates_transform_tree, tree.needs_update()); + } +} + +// When scrolling a main-thread hit tested scroller with main thread reasons, +// we should update the scroll node but the transform tree shouldn't be +// mutated. Also ensure NeedsCommit is set. A nice additional benefit of scroll +// unification should be seamless upgrade to a full compositor scroll if a main +// thread reason is removed. +TEST_F(UnifiedScrollingTest, MainThreadReasonsScrollDoesntAffectTransform) { + CreateScroller( + MainThreadScrollingReason::kHasBackgroundAttachmentFixedObjects); + + TestUncompositedScrollingState(/*mutates_transform_tree=*/false); + + ASSERT_EQ(ScrollerNode()->main_thread_scrolling_reasons, + MainThreadScrollingReason::kHasBackgroundAttachmentFixedObjects); + TransformTree& tree = GetPropertyTrees()->transform_tree; + TransformNode* transform_node = tree.Node(ScrollerNode()->transform_id); + + // Removing the main thread reason bit should start mutating the transform + // tree. + { + ScrollerNode()->main_thread_scrolling_reasons = + MainThreadScrollingReason::kNotScrollingOnMain; + UpdateDrawProperties(host_impl_->active_tree()); + host_impl_->active_tree()->DidBecomeActive(); + + ScrollUpdate(Vector2d(0, 10)); + ASSERT_EQ(ScrollOffset(0, 30), ScrollerOffset()); + + // The transform node should now be updated by the scroll. + EXPECT_EQ(ScrollOffset(0, 30), transform_node->scroll_offset); + EXPECT_TRUE(transform_node->transform_changed); + EXPECT_TRUE(transform_node->needs_local_transform_update); + EXPECT_TRUE(tree.needs_update()); + } + + ScrollEnd(); +} + +// When scrolling an uncomposited scroller, we shouldn't modify the transform +// tree. If a scroller is promoted mid-scroll it should start mutating the +// transform tree. +TEST_F(UnifiedScrollingTest, UncompositedScrollerDoesntAffectTransform) { + CreateUncompositedScrollerAndNonFastScrollableRegion(); + + TestUncompositedScrollingState(/*mutates_transform_tree=*/false); + + ASSERT_FALSE(ScrollerNode()->is_composited); + TransformTree& tree = GetPropertyTrees()->transform_tree; + TransformNode* transform_node = tree.Node(ScrollerNode()->transform_id); + + // Marking the node as composited should start updating the transform tree. + { + ScrollerNode()->is_composited = true; + UpdateDrawProperties(host_impl_->active_tree()); + host_impl_->active_tree()->DidBecomeActive(); + + ScrollUpdate(Vector2d(0, 10)); + ASSERT_EQ(ScrollOffset(0, 30), ScrollerOffset()); + + // The transform node should now be updated by the scroll. + EXPECT_EQ(ScrollOffset(0, 30), transform_node->scroll_offset); + EXPECT_TRUE(transform_node->transform_changed); + EXPECT_TRUE(transform_node->needs_local_transform_update); + EXPECT_TRUE(tree.needs_update()); + } + + ScrollEnd(); +} + +// When scrolling a composited scroller that just happens to have needed a main +// thread hit test first, we should modify the transform tree as usual. +TEST_F(UnifiedScrollingTest, CompositedWithSquashedLayerMutatesTransform) { + CreateScroller(MainThreadScrollingReason::kNotScrollingOnMain); + CreateSuashingLayerCoveringWholeViewport(); + + TestUncompositedScrollingState(/*mutates_transform_tree=*/true); + + ScrollEnd(); +} + } // namespace } // namespace cc diff --git a/chromium/cc/trees/layer_tree_host_pixeltest_blending.cc b/chromium/cc/trees/layer_tree_host_pixeltest_blending.cc index b55a4b3b733..0a6809e778a 100644 --- a/chromium/cc/trees/layer_tree_host_pixeltest_blending.cc +++ b/chromium/cc/trees/layer_tree_host_pixeltest_blending.cc @@ -6,12 +6,13 @@ #include "base/stl_util.h" #include "build/build_config.h" -#include "cc/layers/picture_image_layer.h" #include "cc/layers/solid_color_layer.h" #include "cc/paint/paint_image.h" #include "cc/paint/paint_image_builder.h" #include "cc/paint/render_surface_filters.h" #include "cc/paint/skia_paint_canvas.h" +#include "cc/test/fake_content_layer_client.h" +#include "cc/test/fake_picture_layer.h" #include "cc/test/layer_tree_pixel_resource_test.h" #include "cc/test/pixel_comparator.h" #include "cc/test/test_layer_tree_frame_sink.h" @@ -118,23 +119,19 @@ class LayerTreeHostBlendingPixelTest scoped_refptr<Layer> CreateColorfulBackdropLayer(int width, int height) { sk_sp<SkSurface> backing_store = CreateColorfulSurface(width, height); - scoped_refptr<PictureImageLayer> layer = PictureImageLayer::Create(); + gfx::Size bounds(width, height); + backdrop_client_.set_bounds(bounds); + backdrop_client_.add_draw_image(backing_store->makeImageSnapshot(), + gfx::Point(), PaintFlags()); + scoped_refptr<FakePictureLayer> layer = + FakePictureLayer::Create(&backdrop_client_); layer->SetIsDrawable(true); - layer->SetBounds(gfx::Size(width, height)); - layer->SetImage(PaintImageBuilder::WithDefault() - .set_id(PaintImage::GetNextId()) - .set_image(backing_store->makeImageSnapshot(), - PaintImage::GetNextContentId()) - .TakePaintImage(), - SkMatrix::I(), false); + layer->SetBounds(bounds); return layer; } void SetupMaskLayer(scoped_refptr<Layer> layer) { gfx::Size bounds = layer->bounds(); - scoped_refptr<PictureImageLayer> mask = PictureImageLayer::Create(); - mask->SetIsDrawable(true); - mask->SetBounds(bounds); sk_sp<SkSurface> surface = SkSurface::MakeRasterN32Premul(bounds.width(), bounds.height()); @@ -146,12 +143,15 @@ class LayerTreeHostBlendingPixelTest // cover the right half of it canvas->drawRect( SkRect::MakeXYWH(1, 0, bounds.width() - 1, bounds.height()), paint); - mask->SetImage(PaintImageBuilder::WithDefault() - .set_id(PaintImage::GetNextId()) - .set_image(surface->makeImageSnapshot(), - PaintImage::GetNextContentId()) - .TakePaintImage(), - SkMatrix::I(), false); + + mask_client_.set_bounds(bounds); + mask_client_.add_draw_image(surface->makeImageSnapshot(), gfx::Point(), + PaintFlags()); + + scoped_refptr<FakePictureLayer> mask = + FakePictureLayer::Create(&mask_client_); + mask->SetIsDrawable(true); + mask->SetBounds(bounds); layer->SetMaskLayer(mask); } @@ -254,6 +254,8 @@ class LayerTreeHostBlendingPixelTest bool force_antialiasing_; bool force_blending_with_shaders_; + FakeContentLayerClient mask_client_; + FakeContentLayerClient backdrop_client_; SkColor misc_opaque_color_ = 0xffc86464; }; diff --git a/chromium/cc/trees/layer_tree_host_pixeltest_filters.cc b/chromium/cc/trees/layer_tree_host_pixeltest_filters.cc index c93b84e3271..4042e97367c 100644 --- a/chromium/cc/trees/layer_tree_host_pixeltest_filters.cc +++ b/chromium/cc/trees/layer_tree_host_pixeltest_filters.cc @@ -309,55 +309,6 @@ TEST_P(LayerTreeHostFiltersPixelTest, BackdropFilterBlurOutsets) { base::FilePath(FILE_PATH_LITERAL("backdrop_filter_blur_outsets.png"))); } -class LayerTreeHostImageFiltersPixelTestLayerList - : public LayerTreeHostFiltersPixelTest { - public: - LayerTreeHostImageFiltersPixelTestLayerList() { SetUseLayerLists(); } - - void SetupTree() override { - SetInitialRootBounds(gfx::Size(200, 200)); - LayerTreePixelTest::SetupTree(); - - Layer* root = layer_tree_host()->root_layer(); - scoped_refptr<SolidColorLayer> background = - CreateSolidColorLayer(gfx::Rect(200, 200), SK_ColorYELLOW); - CopyProperties(root, background.get()); - root->AddChild(background); - - scoped_refptr<SolidColorLayer> foreground = - CreateSolidColorLayer(gfx::Rect(200, 200), SK_ColorRED); - CopyProperties(root, foreground.get()); - root->AddChild(foreground); - - EffectNode& effect_node = CreateEffectNode(foreground.get()); - float matrix[20] = {0}; - // This filter does a red-blue swap, so the foreground becomes blue. - matrix[2] = matrix[6] = matrix[10] = matrix[18] = 1.0f; - // Set up a crop rect to filter the bottom 200x100 pixels of the foreground. - SkImageFilter::CropRect crop_rect(SkRect::MakeXYWH(0, 100, 200, 100)); - FilterOperations filters; - filters.Append(FilterOperation::CreateReferenceFilter( - sk_make_sp<ColorFilterPaintFilter>(SkColorFilters::Matrix(matrix), - nullptr, &crop_rect))); - - effect_node.filters = filters; - effect_node.render_surface_reason = RenderSurfaceReason::kFilter; - - // Move the filters origin up by 100 pixels so the crop rect is applied - // only to the top 100 pixels, not the bottom. - effect_node.filters_origin = gfx::PointF(0.0f, -100.0f); - } -}; - -INSTANTIATE_TEST_SUITE_P(All, - LayerTreeHostImageFiltersPixelTestLayerList, - ::testing::ValuesIn(kRendererTypes)); - -TEST_P(LayerTreeHostImageFiltersPixelTestLayerList, NonZeroOrigin) { - RunPixelTestWithLayerList( - base::FilePath(FILE_PATH_LITERAL("blue_yellow.png"))); -} - class LayerTreeHostBlurFiltersPixelTestGPULayerList : public LayerTreeHostFiltersPixelTest { public: diff --git a/chromium/cc/trees/layer_tree_host_pixeltest_masks.cc b/chromium/cc/trees/layer_tree_host_pixeltest_masks.cc index 7e39b785622..702d4e5dbb7 100644 --- a/chromium/cc/trees/layer_tree_host_pixeltest_masks.cc +++ b/chromium/cc/trees/layer_tree_host_pixeltest_masks.cc @@ -7,13 +7,13 @@ #include "base/stl_util.h" #include "build/build_config.h" #include "cc/layers/content_layer_client.h" -#include "cc/layers/picture_image_layer.h" #include "cc/layers/picture_layer.h" #include "cc/layers/solid_color_layer.h" #include "cc/paint/paint_flags.h" #include "cc/paint/paint_image.h" #include "cc/paint/paint_image_builder.h" #include "cc/paint/paint_op_buffer.h" +#include "cc/test/fake_content_layer_client.h" #include "cc/test/fake_picture_layer.h" #include "cc/test/layer_tree_pixel_resource_test.h" #include "cc/test/pixel_comparator.h" @@ -318,22 +318,20 @@ TEST_P(LayerTreeHostMaskPixelTestWithLayerList, MaskWithEffectDifferentSize) { } TEST_P(LayerTreeHostMaskPixelTestWithLayerList, ImageMaskWithEffect) { - MaskContentLayerClient client(mask_bounds_); - scoped_refptr<PictureImageLayer> mask_layer = PictureImageLayer::Create(); + MaskContentLayerClient mask_client(mask_bounds_); sk_sp<SkSurface> surface = SkSurface::MakeRasterN32Premul(50, 50); SkCanvas* canvas = surface->getCanvas(); scoped_refptr<DisplayItemList> mask_display_list = - client.PaintContentsToDisplayList( + mask_client.PaintContentsToDisplayList( ContentLayerClient::PAINTING_BEHAVIOR_NORMAL); mask_display_list->Raster(canvas); - mask_layer->SetImage(PaintImageBuilder::WithDefault() - .set_id(PaintImage::GetNextId()) - .set_image(surface->makeImageSnapshot(), - PaintImage::GetNextContentId()) - .TakePaintImage(), - SkMatrix::I(), false); - mask_layer_ = mask_layer; + + FakeContentLayerClient layer_client; + layer_client.set_bounds(mask_bounds_); + layer_client.add_draw_image(surface->makeImageSnapshot(), gfx::Point(), + PaintFlags()); + mask_layer_ = FakePictureLayer::Create(&layer_client); pixel_comparator_ = std::make_unique<FuzzyPixelOffByOneComparator>(true /* discard_alpha */); @@ -350,10 +348,6 @@ TEST_P(LayerTreeHostMasksPixelTest, ImageMaskOfLayer) { gfx::Size mask_bounds(50, 50); - scoped_refptr<PictureImageLayer> mask = PictureImageLayer::Create(); - mask->SetIsDrawable(true); - mask->SetBounds(mask_bounds); - sk_sp<SkSurface> surface = SkSurface::MakeRasterN32Premul(50, 50); SkCanvas* canvas = surface->getCanvas(); MaskContentLayerClient client(mask_bounds); @@ -361,12 +355,14 @@ TEST_P(LayerTreeHostMasksPixelTest, ImageMaskOfLayer) { client.PaintContentsToDisplayList( ContentLayerClient::PAINTING_BEHAVIOR_NORMAL); mask_display_list->Raster(canvas); - mask->SetImage(PaintImageBuilder::WithDefault() - .set_id(PaintImage::GetNextId()) - .set_image(surface->makeImageSnapshot(), - PaintImage::GetNextContentId()) - .TakePaintImage(), - SkMatrix::I(), false); + + FakeContentLayerClient mask_client; + mask_client.set_bounds(mask_bounds); + mask_client.add_draw_image(surface->makeImageSnapshot(), gfx::Point(), + PaintFlags()); + scoped_refptr<FakePictureLayer> mask = FakePictureLayer::Create(&mask_client); + mask->SetIsDrawable(true); + mask->SetBounds(mask_bounds); scoped_refptr<SolidColorLayer> green = CreateSolidColorLayerWithBorder( gfx::Rect(25, 25, 50, 50), kCSSGreen, 1, SK_ColorBLACK); diff --git a/chromium/cc/trees/layer_tree_host_pixeltest_synchronous.cc b/chromium/cc/trees/layer_tree_host_pixeltest_synchronous.cc index 2739d9880bb..34d9ef117a4 100644 --- a/chromium/cc/trees/layer_tree_host_pixeltest_synchronous.cc +++ b/chromium/cc/trees/layer_tree_host_pixeltest_synchronous.cc @@ -4,7 +4,6 @@ #include "build/build_config.h" #include "cc/layers/content_layer_client.h" -#include "cc/layers/picture_image_layer.h" #include "cc/layers/picture_layer.h" #include "cc/layers/solid_color_layer.h" #include "cc/test/fake_content_layer_client.h" diff --git a/chromium/cc/trees/layer_tree_host_unittest.cc b/chromium/cc/trees/layer_tree_host_unittest.cc index edbee8ea336..2d94507a792 100644 --- a/chromium/cc/trees/layer_tree_host_unittest.cc +++ b/chromium/cc/trees/layer_tree_host_unittest.cc @@ -52,7 +52,6 @@ #include "cc/test/test_layer_tree_frame_sink.h" #include "cc/trees/clip_node.h" #include "cc/trees/effect_node.h" -#include "cc/trees/frame_rate_counter.h" #include "cc/trees/layer_tree_host_impl.h" #include "cc/trees/layer_tree_impl.h" #include "cc/trees/scroll_and_scale_set.h" @@ -6187,9 +6186,9 @@ class LayerTreeHostTestSwapPromiseDuringCommit : public LayerTreeHostTest { &set_needs_commit_count, &set_needs_redraw_count)); layer_tree_host()->QueueSwapPromise(std::move(swap_promise)); - // Queueing a swap promise from DidBeginMainFrame should cause a + // Queueing a swap promise from DidBeginMainFrame should not cause a // subsequent main frame to be scheduled. - EXPECT_EQ(1, set_needs_commit_count); + EXPECT_EQ(0, set_needs_commit_count); } EndTest(); @@ -8902,28 +8901,11 @@ class LayerTreeHostCustomThrougputTrackerTest : public LayerTreeHostTest { } void NotifyThroughputTrackerResults(CustomTrackerResults results) override { + // Check that data for kSequenceId is captured. Ideally, we should get + // 2 frame_expected and 2 frame_produced. But on slow bots, it is difficult + // to infer the correct numbers. Both frame_expected and frame_produced + // could drop to 1 (or even below). So no sanity check on data itself. ASSERT_TRUE(base::Contains(results, kSequenceId)); - const auto& throughput = results[kSequenceId]; - // Frame 3 and 4 are counted. See the sequence in DidCommit comment for - // normal case that expects 2 for both frames_expected and frames_produced. - // - // However, on slow bots, things could be different. - // - Begin frame could be skipped but still counted as expected frames, - // - // e(5,5)b(8)B(0,8)E(8)s(3)S(8)e(8,8)b(11) - // B(8,11)E(11)ts(4)S(11)e(11,11)P(3)e(14,14)P(4) - // - // B(0, 8) and B(8, 11) make frame_expected to be 4, more than 2 expected - // by test. - // - // - Finish before frame 4 is presented in multi-thread mode. - // - // e(3,3)b(4)B(0,4)E(4)s(2)e(4,4)b(6)B(4,6)E(6)s(3)S(4)e(6,6) - // P(2)e(7,7)P(3) - // - // Only P(3) is counted thus frames_produced is 1. - EXPECT_GE(throughput.frames_expected, 2u); - EXPECT_GE(throughput.frames_produced, 1u); EndTest(); } @@ -8931,5 +8913,112 @@ class LayerTreeHostCustomThrougputTrackerTest : public LayerTreeHostTest { SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeHostCustomThrougputTrackerTest); +// Confirm that DelegatedInkMetadata set on the LTH propagates to the +// CompositorFrameMetadata and RenderFrameMetadata, and then both are correctly +// reset when another frame is drawn without DelegatedInkMetadata. +class LayerTreeHostTestDelegatedInkMetadataOnAndOff + : public LayerTreeHostTest, + public RenderFrameMetadataObserver { + public: + // Provides a wrapper which can be passed to LayerTreeHost, but just forwards + // to the test class. + class ForwardingRenderFrameMetadataObserver + : public RenderFrameMetadataObserver { + public: + explicit ForwardingRenderFrameMetadataObserver( + RenderFrameMetadataObserver* target) + : target_(target) {} + + // RenderFrameMetadataObserver implementation. + void BindToCurrentThread() override { target_->BindToCurrentThread(); } + void OnRenderFrameSubmission( + const RenderFrameMetadata& render_frame_metadata, + viz::CompositorFrameMetadata* compositor_frame_metadata, + bool force_send) override { + target_->OnRenderFrameSubmission(render_frame_metadata, + compositor_frame_metadata, force_send); + } + + private: + RenderFrameMetadataObserver* target_ = nullptr; + }; + + void BeginTest() override { + // Set up a basic render frame observer for the LTH/LTHI to forward to. + layer_tree_host()->SetRenderFrameObserver( + std::make_unique<ForwardingRenderFrameMetadataObserver>(this)); + + // Setting up a basic frame that can be redrawn. + layer_tree_host()->SetViewportRectAndScale(gfx::Rect(10, 10), 1.f, + viz::LocalSurfaceIdAllocation()); + layer_tree_host()->root_layer()->SetBounds(gfx::Size(10, 10)); + layer_ = FakePictureLayer::Create(&client_); + layer_tree_host()->root_layer()->AddChild(layer_); + client_.set_bounds(layer_->bounds()); + + // Values chosen arbitrarily + SkColor color = SK_ColorDKGRAY; + double diameter = 1.000002; + gfx::PointF point = gfx::PointF(135, 45); + gfx::RectF area = gfx::RectF(173, 438); + base::TimeTicks timestamp = base::TimeTicks::Now(); + + expected_metadata_ = + viz::DelegatedInkMetadata(point, diameter, color, timestamp, area); + layer_tree_host()->SetDelegatedInkMetadata( + std::make_unique<viz::DelegatedInkMetadata>( + expected_metadata_.value())); + } + + void DidCommitAndDrawFrame() override { + // Cause a redraw to occur. + layer_->SetNeedsDisplay(); + } + + void DrawLayersOnThread(LayerTreeHostImpl* impl) override { + if (expected_metadata_.has_value()) { + // Now try again with no metadata to confirm everything is cleared out. + expected_metadata_.reset(); + } + } + + void ExpectMetadata(bool had_delegated_ink_metadata, + viz::DelegatedInkMetadata* actual_metadata) { + if (expected_metadata_.has_value()) { + EXPECT_TRUE(had_delegated_ink_metadata); + EXPECT_TRUE(actual_metadata); + EXPECT_EQ(expected_metadata_->point(), actual_metadata->point()); + EXPECT_EQ(expected_metadata_->color(), actual_metadata->color()); + EXPECT_EQ(expected_metadata_->diameter(), actual_metadata->diameter()); + EXPECT_EQ(expected_metadata_->presentation_area(), + actual_metadata->presentation_area()); + EXPECT_EQ(expected_metadata_->timestamp(), actual_metadata->timestamp()); + } else { + EXPECT_FALSE(had_delegated_ink_metadata); + EXPECT_FALSE(actual_metadata); + EndTest(); + } + } + + void AfterTest() override {} + + // RenderFrameMetadataObserver implementation. + void BindToCurrentThread() override {} + void OnRenderFrameSubmission( + const RenderFrameMetadata& render_frame_metadata, + viz::CompositorFrameMetadata* compositor_frame_metadata, + bool force_send) override { + ExpectMetadata(render_frame_metadata.has_delegated_ink_metadata, + compositor_frame_metadata->delegated_ink_metadata.get()); + } + + private: + base::Optional<viz::DelegatedInkMetadata> expected_metadata_; + FakeContentLayerClient client_; + scoped_refptr<Layer> layer_; +}; + +SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeHostTestDelegatedInkMetadataOnAndOff); + } // namespace } // namespace cc diff --git a/chromium/cc/trees/layer_tree_host_unittest_copyrequest.cc b/chromium/cc/trees/layer_tree_host_unittest_copyrequest.cc index bb64e9ef4a4..7a188881491 100644 --- a/chromium/cc/trees/layer_tree_host_unittest_copyrequest.cc +++ b/chromium/cc/trees/layer_tree_host_unittest_copyrequest.cc @@ -10,6 +10,7 @@ #include "base/threading/thread_task_runner_handle.h" #include "base/time/time.h" #include "cc/layers/effect_tree_layer_list_iterator.h" +#include "cc/test/cc_test_suite.h" #include "cc/test/fake_content_layer_client.h" #include "cc/test/fake_picture_layer.h" #include "cc/test/layer_tree_test.h" @@ -309,13 +310,19 @@ class LayerTreeHostCopyRequestTestLayerDestroyed base::BindOnce( &LayerTreeHostCopyRequestTestLayerDestroyed::CopyOutputCallback, base::Unretained(this)))); + // We expect that the RequestCopyOfOutput won't yet return results until + // the main is destroyed. So we RunUntilIdle to ensure no PostTask is + // currently queued to return the result. + CCTestSuite::RunUntilIdle(); EXPECT_EQ(0, callback_count_); // Destroy the main thread layer right away. main_destroyed_->RemoveFromParent(); main_destroyed_.reset(); - // Should callback with a NULL bitmap. + // Should callback with a NULL bitmap, result will be in a PostTask so + // RunUntilIdle(). + CCTestSuite::RunUntilIdle(); EXPECT_EQ(1, callback_count_); // Prevent drawing so we can't make a copy of the impl_destroyed layer. @@ -350,7 +357,6 @@ class LayerTreeHostCopyRequestTestLayerDestroyed } void CopyOutputCallback(std::unique_ptr<viz::CopyOutputResult> result) { - EXPECT_TRUE(layer_tree_host()->GetTaskRunnerProvider()->IsMainThread()); EXPECT_TRUE(result->IsEmpty()); ++callback_count_; } diff --git a/chromium/cc/trees/layer_tree_host_unittest_scroll.cc b/chromium/cc/trees/layer_tree_host_unittest_scroll.cc index a5f7b6e522a..3a95b182bf0 100644 --- a/chromium/cc/trees/layer_tree_host_unittest_scroll.cc +++ b/chromium/cc/trees/layer_tree_host_unittest_scroll.cc @@ -10,8 +10,10 @@ #include "base/single_thread_task_runner.h" #include "base/stl_util.h" #include "base/threading/thread_task_runner_handle.h" +#include "build/build_config.h" #include "cc/animation/animation_host.h" #include "cc/base/completion_event.h" +#include "cc/base/features.h" #include "cc/input/main_thread_scrolling_reason.h" #include "cc/input/scroll_elasticity_helper.h" #include "cc/layers/layer.h" @@ -42,11 +44,14 @@ using ::testing::Mock; namespace cc { namespace { -std::unique_ptr<ScrollState> BeginState(const gfx::Point& point) { +std::unique_ptr<ScrollState> BeginState(const gfx::Point& point, + const gfx::Vector2dF& delta_hint) { ScrollStateData scroll_state_data; scroll_state_data.is_beginning = true; scroll_state_data.position_x = point.x(); scroll_state_data.position_y = point.y(); + scroll_state_data.delta_x_hint = delta_hint.x(); + scroll_state_data.delta_y_hint = delta_hint.y(); std::unique_ptr<ScrollState> scroll_state(new ScrollState(scroll_state_data)); return scroll_state; } @@ -685,8 +690,9 @@ class LayerTreeHostScrollTestCaseWithChild : public LayerTreeHostScrollTest { gfx::Point scroll_point = gfx::ToCeiledPoint( gfx::PointF(-0.5f, -0.5f) + GetTransformNode(expected_scroll_layer_impl)->post_translation); - InputHandler::ScrollStatus status = impl->ScrollBegin( - BeginState(scroll_point).get(), ui::ScrollInputType::kTouchscreen); + InputHandler::ScrollStatus status = + impl->ScrollBegin(BeginState(scroll_point, scroll_amount_).get(), + ui::ScrollInputType::kTouchscreen); EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD, status.thread); impl->ScrollUpdate(UpdateState(gfx::Point(), scroll_amount_).get()); auto* scrolling_node = impl->CurrentlyScrollingNode(); @@ -708,8 +714,9 @@ class LayerTreeHostScrollTestCaseWithChild : public LayerTreeHostScrollTest { gfx::Point scroll_point = gfx::ToCeiledPoint( gfx::PointF(0.5f, 0.5f) + GetTransformNode(expected_scroll_layer_impl)->post_translation); - InputHandler::ScrollStatus status = impl->ScrollBegin( - BeginState(scroll_point).get(), ui::ScrollInputType::kWheel); + InputHandler::ScrollStatus status = + impl->ScrollBegin(BeginState(scroll_point, scroll_amount_).get(), + ui::ScrollInputType::kWheel); EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD, status.thread); impl->ScrollUpdate(UpdateState(gfx::Point(), scroll_amount_).get()); impl->ScrollEnd(); @@ -1058,6 +1065,116 @@ class LayerTreeHostScrollTestImplOnlyScroll : public LayerTreeHostScrollTest { // This tests scrolling on the impl side which is only possible with a thread. MULTI_THREAD_TEST_F(LayerTreeHostScrollTestImplOnlyScroll); +// TODO(crbug.com/574283): Mac currently doesn't support smooth scrolling wheel +// events. +#if !defined(OS_MACOSX) +// This test simulates scrolling on the impl thread such that it starts a scroll +// animation. It ensures that RequestScrollAnimationEndNotification() correctly +// notifies the callback after the animation ends. +class SmoothScrollAnimationEndNotification : public LayerTreeHostScrollTest { + public: + SmoothScrollAnimationEndNotification() = default; + + void InitializeSettings(LayerTreeSettings* settings) override { + LayerTreeHostScrollTest::InitializeSettings(settings); + settings->enable_smooth_scroll = true; + } + + void SetupTree() override { + LayerTreeHostScrollTest::SetupTree(); + + Layer* root_layer = layer_tree_host()->root_layer(); + Layer* root_scroll_layer = + layer_tree_host()->OuterViewportScrollLayerForTesting(); + + child_layer_ = Layer::Create(); + child_layer_->SetElementId( + LayerIdToElementIdForTesting(child_layer_->id())); + child_layer_->SetBounds(gfx::Size(110, 110)); + + child_layer_->SetIsDrawable(true); + child_layer_->SetHitTestable(true); + child_layer_->SetElementId( + LayerIdToElementIdForTesting(child_layer_->id())); + child_layer_->SetBounds(root_scroll_layer->bounds()); + root_layer->AddChild(child_layer_); + + CopyProperties(root_scroll_layer, child_layer_.get()); + CreateTransformNode(child_layer_.get()); + CreateScrollNode(child_layer_.get(), root_layer->bounds()); + } + + void BeginTest() override { PostSetNeedsCommitToMainThread(); } + + void WillCommit() override { + // Keep the test committing (otherwise the early out for no update + // will stall the test). + if (layer_tree_host()->SourceFrameNumber() < 2) { + layer_tree_host()->SetNeedsCommit(); + } + } + + void DidActivateTreeOnThread(LayerTreeHostImpl* host_impl) override { + if (host_impl->active_tree()->source_frame_number() < 0) + return; + + if (host_impl->active_tree()->source_frame_number() == 0) { + const gfx::Point scroll_point(10, 10); + const gfx::Vector2dF scroll_amount(350, -350); + auto scroll_state = BeginState(scroll_point, scroll_amount); + scroll_state->data()->delta_granularity = + ui::ScrollGranularity::kScrollByPixel; + InputHandler::ScrollStatus status = host_impl->ScrollBegin( + scroll_state.get(), ui::ScrollInputType::kWheel); + EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD, status.thread); + scroll_state = UpdateState(scroll_point, scroll_amount); + scroll_state->data()->delta_granularity = + ui::ScrollGranularity::kScrollByPixel; + host_impl->ScrollUpdate(scroll_state.get()); + + EXPECT_TRUE( + !!host_impl->mutator_host()->ImplOnlyScrollAnimatingElement()); + } else if (!scroll_end_requested_) { + host_impl->ScrollEnd(false); + scroll_end_requested_ = true; + } + PostSetNeedsCommitToMainThread(); + } + + void UpdateLayerTreeHost() override { + if (scroll_animation_started_) + return; + + if (layer_tree_host()->HasCompositorDrivenScrollAnimationForTesting()) { + scroll_animation_started_ = true; + layer_tree_host()->RequestScrollAnimationEndNotification( + base::BindOnce(&SmoothScrollAnimationEndNotification::OnScrollEnd, + base::Unretained(this))); + } + } + + void AfterTest() override { + EXPECT_TRUE(scroll_end_requested_); + EXPECT_TRUE(scroll_animation_started_); + EXPECT_TRUE(scroll_animation_ended_); + } + + private: + void OnScrollEnd() { + scroll_animation_ended_ = true; + EndTest(); + } + + scoped_refptr<Layer> child_layer_; + + bool scroll_end_requested_ = false; + bool scroll_animation_started_ = false; + bool scroll_animation_ended_ = false; +}; + +MULTI_THREAD_TEST_F(SmoothScrollAnimationEndNotification); +#endif // !defined(OS_MACOSX) + void DoGestureScroll(LayerTreeHostImpl* host_impl, const scoped_refptr<Layer>& scroller, gfx::Vector2dF offset) { @@ -1354,12 +1471,13 @@ class LayerTreeHostScrollTestScrollZeroMaxScrollOffset void SetupTree() override { LayerTreeHostScrollTest::SetupTree(); - // Add a sub-scroller to test TryScroll against. The outer viewport scroll + // Add a sub-scroller to test ScrollBegin against. The outer viewport scroll // will be latched to for scrolling even if it doesn't have any scroll // extent in the given direction to support overscroll actions. scroller_ = Layer::Create(); scroller_->SetIsDrawable(true); scroller_->SetHitTestable(true); + scroller_->SetBounds(gfx::Size(200, 200)); scroller_->SetElementId(LayerIdToElementIdForTesting(scroller_->id())); CopyProperties(layer_tree_host()->OuterViewportScrollLayerForTesting(), scroller_.get()); @@ -1369,61 +1487,91 @@ class LayerTreeHostScrollTestScrollZeroMaxScrollOffset layer_tree_host()->root_layer()->AddChild(scroller_.get()); } - void BeginTest() override { PostSetNeedsCommitToMainThread(); } + void BeginTest() override { NextStep(); } + + void NextStep() { + if (TestEnded()) + return; + + ++cur_step_; - void UpdateLayerTreeHost() override { ScrollTree& scroll_tree = layer_tree_host()->property_trees()->scroll_tree; ScrollNode* scroll_node = scroll_tree.Node(scroller_->scroll_tree_index()); - switch (layer_tree_host()->SourceFrameNumber()) { - case 0: + switch (cur_step_) { + case 1: // Set max_scroll_offset = (100, 100). scroll_node->bounds = scroll_node->container_bounds; scroll_node->bounds.Enlarge(100, 100); break; - case 1: + case 2: // Set max_scroll_offset = (0, 0). scroll_node->bounds = scroll_node->container_bounds; break; - case 2: + case 3: // Set max_scroll_offset = (-1, -1). scroll_node->bounds = gfx::Size(); break; } + + layer_tree_host()->SetNeedsCommit(); } void DrawLayersOnThread(LayerTreeHostImpl* impl) override { + if (TestEnded()) + return; + ScrollTree& scroll_tree = impl->active_tree()->property_trees()->scroll_tree; ScrollNode* scroll_node = scroll_tree.Node(scroller_->scroll_tree_index()); + + ScrollStateData scroll_state_data; + scroll_state_data.is_beginning = true; + // The position has to be at (0, 0) since the viewport in this test has + // bounds (1, 1). + scroll_state_data.position_x = 0; + scroll_state_data.position_y = 0; + scroll_state_data.delta_x_hint = 10; + scroll_state_data.delta_y_hint = 10; + scroll_state_data.is_direct_manipulation = true; + + ScrollState scroll_state(scroll_state_data); InputHandler::ScrollStatus status = - impl->TryScroll(scroll_tree, scroll_node); - switch (impl->active_tree()->source_frame_number()) { - case 0: - EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD, status.thread) - << "In Frame 0"; - EXPECT_EQ(MainThreadScrollingReason::kNotScrollingOnMain, - status.main_thread_scrolling_reasons) - << "In Frame 0"; - PostSetNeedsCommitToMainThread(); - break; + impl->ScrollBegin(&scroll_state, ui::ScrollInputType::kTouchscreen); + EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD, status.thread) + << "In Frame " << impl->active_tree()->source_frame_number(); + + switch (cur_step_) { case 1: - EXPECT_EQ(InputHandler::SCROLL_IGNORED, status.thread) << "In Frame 1"; - EXPECT_EQ(MainThreadScrollingReason::kNotScrollable, - status.main_thread_scrolling_reasons) - << "In Frame 1"; - PostSetNeedsCommitToMainThread(); + // Since the scroller has scroll extend and is scrollable, we should + // have targeted it. + EXPECT_EQ(scroll_node, impl->CurrentlyScrollingNode()) << "In Frame 0"; break; case 2: - EXPECT_EQ(InputHandler::SCROLL_IGNORED, status.thread) << "In Frame 2"; - EXPECT_EQ(MainThreadScrollingReason::kNotScrollable, - status.main_thread_scrolling_reasons) + // Since the max_scroll_offset is (0, 0) - we shouldn't target it and + // we should instead bubble up to the viewport. + EXPECT_EQ(impl->OuterViewportScrollNode(), + impl->CurrentlyScrollingNode()) + << "In Frame 1"; + break; + case 3: + // Since the max_scroll_offset is (-1, -1) - we shouldn't target it and + // we should instead bubble up to the viewport. + EXPECT_EQ(impl->OuterViewportScrollNode(), + impl->CurrentlyScrollingNode()) << "In Frame 2"; EndTest(); break; } + impl->ScrollEnd(); + MainThreadTaskRunner()->PostTask( + FROM_HERE, + base::BindOnce( + &LayerTreeHostScrollTestScrollZeroMaxScrollOffset::NextStep, + base::Unretained(this))); } private: + int cur_step_ = 0; scoped_refptr<Layer> scroller_; }; @@ -1453,14 +1601,23 @@ class LayerTreeHostScrollTestScrollNonDrawnLayer // checking whether the screen space point is inside the non-fast // scrollable region. InputHandler::ScrollStatus status = impl->ScrollBegin( - BeginState(gfx::Point(0, 0)).get(), ui::ScrollInputType::kTouchscreen); - EXPECT_EQ(InputHandler::SCROLL_ON_MAIN_THREAD, status.thread); - EXPECT_EQ(MainThreadScrollingReason::kNonFastScrollableRegion, - status.main_thread_scrolling_reasons); + BeginState(gfx::Point(0, 0), gfx::Vector2dF(0, 1)).get(), + ui::ScrollInputType::kTouchscreen); + if (base::FeatureList::IsEnabled(features::kScrollUnification)) { + EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD, status.thread); + EXPECT_TRUE(status.needs_main_thread_hit_test); + impl->ScrollEnd(); + } else { + EXPECT_EQ(InputHandler::SCROLL_ON_MAIN_THREAD, status.thread); + EXPECT_EQ(MainThreadScrollingReason::kNonFastScrollableRegion, + status.main_thread_scrolling_reasons); + } - status = impl->ScrollBegin(BeginState(gfx::Point(21, 21)).get(), - ui::ScrollInputType::kTouchscreen); + status = impl->ScrollBegin( + BeginState(gfx::Point(21, 21), gfx::Vector2dF(0, 1)).get(), + ui::ScrollInputType::kTouchscreen); EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD, status.thread); + EXPECT_FALSE(status.needs_main_thread_hit_test); EXPECT_EQ(MainThreadScrollingReason::kNotScrollingOnMain, status.main_thread_scrolling_reasons); @@ -1481,36 +1638,88 @@ class LayerTreeHostScrollTestImplScrollUnderMainThreadScrollingParent void SetupTree() override { LayerTreeHostScrollTest::SetupTree(); - GetScrollNode(layer_tree_host()->InnerViewportScrollLayerForTesting()) + GetScrollNode(layer_tree_host()->OuterViewportScrollLayerForTesting()) ->main_thread_scrolling_reasons = - MainThreadScrollingReason::kScrollbarScrolling; + MainThreadScrollingReason::kThreadedScrollingDisabled; + + scroller_ = Layer::Create(); + scroller_->SetIsDrawable(true); + scroller_->SetHitTestable(true); + scroller_->SetBounds(gfx::Size(200, 200)); + scroller_->SetElementId(LayerIdToElementIdForTesting(scroller_->id())); + CopyProperties(layer_tree_host()->OuterViewportScrollLayerForTesting(), + scroller_.get()); + CreateTransformNode(scroller_.get()); + CreateScrollNode(scroller_.get(), + layer_tree_host()->root_layer()->bounds()); + layer_tree_host()->root_layer()->AddChild(scroller_.get()); } void DrawLayersOnThread(LayerTreeHostImpl* impl) override { - LayerImpl* inner_scroll_layer = - impl->active_tree()->InnerViewportScrollLayerForTesting(); - LayerImpl* outer_scroll_layer = - impl->active_tree()->OuterViewportScrollLayerForTesting(); - ScrollTree& scroll_tree = impl->active_tree()->property_trees()->scroll_tree; - ScrollNode* inner_scroll_node = - scroll_tree.Node(inner_scroll_layer->scroll_tree_index()); - ScrollNode* outer_scroll_node = - scroll_tree.Node(outer_scroll_layer->scroll_tree_index()); + ScrollNode* scroller_scroll_node = + scroll_tree.Node(scroller_->scroll_tree_index()); + + ScrollStateData scroll_state_data; + scroll_state_data.is_beginning = true; + // To hit the scroller, the position has to be at (0, 0) since the viewport + // in this test has bounds (1, 1) and would otherwise clip the hit test. + scroll_state_data.position_x = 0; + scroll_state_data.position_y = 0; + scroll_state_data.delta_x_hint = 10; + scroll_state_data.delta_y_hint = 10; + scroll_state_data.is_direct_manipulation = true; + ScrollState scroll_state(scroll_state_data); + + // Scroll hitting the scroller layer. + { + InputHandler::ScrollStatus status = + impl->ScrollBegin(&scroll_state, ui::ScrollInputType::kTouchscreen); + if (base::FeatureList::IsEnabled(features::kScrollUnification)) { + EXPECT_EQ(impl->CurrentlyScrollingNode(), scroller_scroll_node); + EXPECT_FALSE(status.needs_main_thread_hit_test); + } else { + // Despite the fact that we hit the scroller, which has no main thread + // scrolling reason, we still must fallback to main thread scrolling due + // to the fact that it has a main thread scrolling ancestor. + EXPECT_EQ(impl->CurrentlyScrollingNode(), nullptr); + EXPECT_EQ(InputHandler::SCROLL_ON_MAIN_THREAD, status.thread); + EXPECT_EQ(MainThreadScrollingReason::kThreadedScrollingDisabled, + status.main_thread_scrolling_reasons); + } + impl->ScrollEnd(); + } - InputHandler::ScrollStatus status = - impl->TryScroll(scroll_tree, inner_scroll_node); - EXPECT_EQ(InputHandler::SCROLL_ON_MAIN_THREAD, status.thread); - EXPECT_EQ(MainThreadScrollingReason::kScrollbarScrolling, - status.main_thread_scrolling_reasons); + // Scroll hitting the viewport layer. + { + // A hit test outside the viewport should fallback to scrolling the + // viewport. + scroll_state.data()->position_y = 1000; + + InputHandler::ScrollStatus status = + impl->ScrollBegin(&scroll_state, ui::ScrollInputType::kTouchscreen); + if (base::FeatureList::IsEnabled(features::kScrollUnification)) { + EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD, status.thread); + EXPECT_FALSE(status.needs_main_thread_hit_test); + EXPECT_EQ(impl->CurrentlyScrollingNode(), + impl->OuterViewportScrollNode()); + } else { + // Since the viewport has a main thread scrolling reason, this + // too should fallback to the main thread. + EXPECT_EQ(impl->CurrentlyScrollingNode(), nullptr); + EXPECT_EQ(InputHandler::SCROLL_ON_MAIN_THREAD, status.thread); + EXPECT_EQ(MainThreadScrollingReason::kThreadedScrollingDisabled, + status.main_thread_scrolling_reasons); + } + impl->ScrollEnd(); + } - status = impl->TryScroll(scroll_tree, outer_scroll_node); - EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD, status.thread); - EXPECT_EQ(MainThreadScrollingReason::kNotScrollingOnMain, - status.main_thread_scrolling_reasons); EndTest(); } + + private: + scoped_refptr<Layer> scroller_; }; SINGLE_AND_MULTI_THREAD_TEST_F( @@ -2422,27 +2631,28 @@ class NonScrollingNonFastScrollableRegion : public LayerTreeHostScrollTest { void SetupTree() override { SetInitialRootBounds(gfx::Size(800, 600)); LayerTreeHostScrollTest::SetupTree(); - Layer* root = layer_tree_host()->root_layer(); - fake_content_layer_client_.set_bounds(root->bounds()); + Layer* outer_scroll = + layer_tree_host()->OuterViewportScrollLayerForTesting(); + fake_content_layer_client_.set_bounds(outer_scroll->bounds()); bottom_ = FakePictureLayer::Create(&fake_content_layer_client_); bottom_->SetElementId(LayerIdToElementIdForTesting(bottom_->id())); bottom_->SetBounds(gfx::Size(100, 100)); bottom_->SetNonFastScrollableRegion(Region(gfx::Rect(50, 50, 50, 50))); bottom_->SetHitTestable(true); - CopyProperties(root, bottom_.get()); - root->AddChild(bottom_); + CopyProperties(outer_scroll, bottom_.get()); + outer_scroll->AddChild(bottom_); middle_scrollable_ = FakePictureLayer::Create(&fake_content_layer_client_); middle_scrollable_->SetElementId( LayerIdToElementIdForTesting(middle_scrollable_->id())); - middle_scrollable_->SetBounds(gfx::Size(100, 100)); + middle_scrollable_->SetBounds(gfx::Size(100, 200)); middle_scrollable_->SetIsDrawable(true); middle_scrollable_->SetHitTestable(true); CopyProperties(bottom_.get(), middle_scrollable_.get()); CreateTransformNode(middle_scrollable_.get()); - CreateScrollNode(middle_scrollable_.get(), gfx::Size(100, 200)); - root->AddChild(middle_scrollable_); + CreateScrollNode(middle_scrollable_.get(), gfx::Size(100, 100)); + outer_scroll->AddChild(middle_scrollable_); top_ = FakePictureLayer::Create(&fake_content_layer_client_); top_->SetElementId(LayerIdToElementIdForTesting(top_->id())); @@ -2450,7 +2660,7 @@ class NonScrollingNonFastScrollableRegion : public LayerTreeHostScrollTest { top_->SetNonFastScrollableRegion(Region(gfx::Rect(0, 0, 50, 50))); top_->SetHitTestable(true); CopyProperties(middle_scrollable_.get(), top_.get()); - root->AddChild(top_); + outer_scroll->AddChild(top_); } void BeginTest() override { PostSetNeedsCommitToMainThread(); } @@ -2459,35 +2669,70 @@ class NonScrollingNonFastScrollableRegion : public LayerTreeHostScrollTest { if (TestEnded()) return; - // The top-left hit should immediately hit the top layer's non-fast region - // which forces main-thread scrolling. - auto top_left_status = - impl->ScrollBegin(BeginState(gfx::Point(20, 20)).get(), - ui::ScrollInputType::kTouchscreen); - EXPECT_EQ(InputHandler::SCROLL_ON_MAIN_THREAD, top_left_status.thread); - EXPECT_EQ(MainThreadScrollingReason::kNonFastScrollableRegion, - top_left_status.main_thread_scrolling_reasons); - - // The top-right hit should hit the top layer but not the non-fast region so - // the scroll should continue to scroll on the impl. - InputHandler::ScrollStatus top_right_status = - impl->ScrollBegin(BeginState(gfx::Point(80, 20)).get(), - ui::ScrollInputType::kTouchscreen); - EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD, top_right_status.thread); - EXPECT_EQ(MainThreadScrollingReason::kNotScrollingOnMain, - top_right_status.main_thread_scrolling_reasons); - impl->ScrollEnd(); + ScrollNode* scroll_node = + impl->active_tree()->property_trees()->scroll_tree.Node( + middle_scrollable_->scroll_tree_index()); - // The bottom-right should hit the bottom layer's non-fast region. Though - // the middle layer is a composited scroller and is hit first, we cannot do - // a fast scroll because an ancestor on the scroll chain has hit a non-fast - // region. - InputHandler::ScrollStatus bottom_right_status = - impl->ScrollBegin(BeginState(gfx::Point(80, 80)).get(), - ui::ScrollInputType::kTouchscreen); - EXPECT_EQ(InputHandler::SCROLL_ON_MAIN_THREAD, bottom_right_status.thread); - EXPECT_EQ(MainThreadScrollingReason::kNonFastScrollableRegion, - bottom_right_status.main_thread_scrolling_reasons); + // The top-left hit should immediately hit the top layer's non-fast region. + { + auto status = impl->ScrollBegin( + BeginState(gfx::Point(20, 20), gfx::Vector2dF(0, 1)).get(), + ui::ScrollInputType::kTouchscreen); + if (base::FeatureList::IsEnabled(features::kScrollUnification)) { + // Hitting a non fast region should request a hit test from the main + // thread. + EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD, status.thread); + EXPECT_TRUE(status.needs_main_thread_hit_test); + impl->ScrollEnd(); + } else { + // Prior to scroll unification, this forces scrolling to the main + // thread. + EXPECT_EQ(InputHandler::SCROLL_ON_MAIN_THREAD, status.thread); + EXPECT_EQ(MainThreadScrollingReason::kNonFastScrollableRegion, + status.main_thread_scrolling_reasons); + } + } + + // The top-right hit should hit the top layer but not the non-fast region + // so the scroll can be handled without involving the main thread. + { + InputHandler::ScrollStatus status = impl->ScrollBegin( + BeginState(gfx::Point(80, 20), gfx::Vector2dF(0, 1)).get(), + ui::ScrollInputType::kTouchscreen); + if (base::FeatureList::IsEnabled(features::kScrollUnification)) { + EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD, status.thread); + EXPECT_FALSE(status.needs_main_thread_hit_test); + } else { + EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD, status.thread); + EXPECT_EQ(MainThreadScrollingReason::kNotScrollingOnMain, + status.main_thread_scrolling_reasons); + } + EXPECT_EQ(scroll_node, impl->CurrentlyScrollingNode()); + impl->ScrollEnd(); + } + + // The bottom-right should hit the bottom layer's non-fast region. + { + InputHandler::ScrollStatus status = impl->ScrollBegin( + BeginState(gfx::Point(80, 80), gfx::Vector2dF(0, 1)).get(), + ui::ScrollInputType::kTouchscreen); + if (base::FeatureList::IsEnabled(features::kScrollUnification)) { + // Even though the point intersects a non-fast region, the first hit + // layer is scrollable from the compositor thread so no need to involve + // the main thread. + EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD, status.thread); + EXPECT_FALSE(status.needs_main_thread_hit_test); + EXPECT_EQ(scroll_node, impl->CurrentlyScrollingNode()); + impl->ScrollEnd(); + } else { + // Though the middle layer is a composited scroller and is hit first, we + // cannot do a fast scroll because an ancestor on the scroll chain has + // hit a non-fast region. + EXPECT_EQ(InputHandler::SCROLL_ON_MAIN_THREAD, status.thread); + EXPECT_EQ(MainThreadScrollingReason::kNonFastScrollableRegion, + status.main_thread_scrolling_reasons); + } + } EndTest(); } @@ -2501,5 +2746,70 @@ class NonScrollingNonFastScrollableRegion : public LayerTreeHostScrollTest { SINGLE_THREAD_TEST_F(NonScrollingNonFastScrollableRegion); +// This test verifies that scrolling in non layer list mode (used by UI +// compositor) is always "compositor scrolled", i.e. property trees are mutated +// and the updated layers redrawn. This test intentionally doesn't inherit +// from LayerTreeHostScrollTest since that enables LayerLists. +class UnifiedScrollingRepaintOnScroll : public LayerTreeTest { + public: + UnifiedScrollingRepaintOnScroll() { + scoped_feature_list.InitAndEnableFeature(features::kScrollUnification); + } + + void BeginTest() override { PostSetNeedsCommitToMainThread(); } + + void SetupTree() override { + LayerTreeTest::SetupTree(); + + layer_ = FakePictureLayer::Create(&client_); + layer_->SetScrollable(gfx::Size(10, 10)); + layer_->SetBounds(gfx::Size(100, 100)); + layer_->SetIsDrawable(true); + layer_->SetHitTestable(true); + layer_->SetElementId(LayerIdToElementIdForTesting(layer_->id())); + client_.set_bounds(layer_->bounds()); + layer_tree_host()->root_layer()->AddChild(layer_); + } + + void DrawLayersOnThread(LayerTreeHostImpl* impl) override { + if (is_done_) + return; + is_done_ = true; + EndTest(); + + TransformTree& transform_tree = + impl->active_tree()->property_trees()->transform_tree; + ASSERT_FALSE(transform_tree.needs_update()); + + // Perform a scroll over our FakePictureLayer. + { + InputHandler::ScrollStatus status = impl->ScrollBegin( + BeginState(gfx::Point(0, 0), gfx::Vector2dF(0, 10)).get(), + ui::ScrollInputType::kTouchscreen); + + ASSERT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD, status.thread); + ASSERT_EQ(layer_->scroll_tree_index(), + impl->CurrentlyScrollingNode()->id); + + impl->ScrollUpdate( + UpdateState(gfx::Point(), gfx::Vector2dF(0, 10)).get()); + impl->ScrollEnd(); + } + + // All scrolling in non-layer-list mode (i.e. UI compositor) should be + // "compositor" scrolling so it should mutate the property tree and redraw, + // rather than relying on an update from the main thread. + ASSERT_TRUE(transform_tree.needs_update()); + } + + private: + bool is_done_ = false; + scoped_refptr<Layer> layer_; + FakeContentLayerClient client_; + base::test::ScopedFeatureList scoped_feature_list; +}; + +MULTI_THREAD_TEST_F(UnifiedScrollingRepaintOnScroll); + } // namespace } // namespace cc diff --git a/chromium/cc/trees/layer_tree_impl.cc b/chromium/cc/trees/layer_tree_impl.cc index 600c987976c..7ab7bf797ef 100644 --- a/chromium/cc/trees/layer_tree_impl.cc +++ b/chromium/cc/trees/layer_tree_impl.cc @@ -10,7 +10,9 @@ #include <algorithm> #include <iterator> #include <limits> +#include <memory> #include <set> +#include <utility> #include "base/containers/adapters.h" #include "base/debug/crash_logging.h" @@ -24,6 +26,7 @@ #include "base/trace_event/trace_event.h" #include "base/trace_event/traced_value.h" #include "cc/base/devtools_instrumentation.h" +#include "cc/base/features.h" #include "cc/base/histograms.h" #include "cc/base/math_util.h" #include "cc/base/synced_property.h" @@ -209,17 +212,38 @@ void LayerTreeImpl::DidUpdateScrollOffset(ElementId id) { return; } + // This bit controls whether we'll update the transform node based on a + // changed scroll offset. If scroll unification is off, we always do this + // because the scroll handling code will only invoke a scroll update on nodes + // that can compositor scroll. However, with scroll unification, we can + // mutate scroll nodes which have main thread scrolling reasons, or aren't + // backed by a layer at all. In those cases, we don't want to produce any + // immediate changes in the compositor, we want the scroll to propagate + // through Blink in a commit and have Blink update properties, paint, + // compositing, etc. Thus, we avoid mutating the transform tree in this case. + // TODO(bokan): We SetNeedsCommit in LTHI when a scroll happens but in a + // normal compositor scroll there isn't much urgency for a commit to be + // scheduled. We should look into what we can do to make sure this is + // proritized accordingly. https://crbug.com/1082618. + bool can_realize_scroll_on_compositor = + !base::FeatureList::IsEnabled(features::kScrollUnification) || + (scroll_node->is_composited && + !scroll_node->main_thread_scrolling_reasons); + DCHECK(scroll_node->transform_id != TransformTree::kInvalidNodeId); TransformTree& transform_tree = property_trees()->transform_tree; auto* transform_node = transform_tree.Node(scroll_node->transform_id); - if (transform_node->scroll_offset != scroll_tree.current_scroll_offset(id)) { - transform_node->scroll_offset = scroll_tree.current_scroll_offset(id); - transform_node->needs_local_transform_update = true; - transform_tree.set_needs_update(true); + if (can_realize_scroll_on_compositor) { + if (transform_node->scroll_offset != + scroll_tree.current_scroll_offset(id)) { + transform_node->scroll_offset = scroll_tree.current_scroll_offset(id); + transform_node->needs_local_transform_update = true; + transform_tree.set_needs_update(true); + } + transform_node->transform_changed = true; + property_trees()->changed = true; + set_needs_update_draw_properties(); } - transform_node->transform_changed = true; - property_trees()->changed = true; - set_needs_update_draw_properties(); if (IsActiveTree()) { // Ensure the other trees are kept in sync. @@ -419,6 +443,13 @@ void LayerTreeImpl::UpdateViewportContainerSizes() { OuterViewportScrollNode()->container_bounds.height() + scaled_bounds_delta.y(); outer_clip_node->clip.set_height(adjusted_container_height); + + // Expand all clips between the outer viewport and the inner viewport. + auto* outer_ancestor = property_trees->clip_tree.parent(outer_clip_node); + while (outer_ancestor && outer_ancestor->id != ClipTree::kRootNodeId) { + outer_ancestor->clip.Union(outer_clip_node->clip); + outer_ancestor = property_trees->clip_tree.parent(outer_ancestor); + } } anchor.ResetViewportToAnchoredPosition(); @@ -477,6 +508,16 @@ OwnedLayerImplList LayerTreeImpl::DetachLayersKeepingRootLayerForTesting() { } void LayerTreeImpl::SetPropertyTrees(PropertyTrees* property_trees) { + // Updating the scroll tree shouldn't clobber the currently scrolling node so + // stash it and restore it at the end of this method. To maintain the + // current scrolling node we need to use element ids which are stable across + // the property tree update in SetPropertyTrees. + ElementId scrolling_element_id; + if (IsActiveTree()) { + if (ScrollNode* scrolling_node = CurrentlyScrollingNode()) + scrolling_element_id = scrolling_node->element_id; + } + std::vector<std::unique_ptr<RenderSurfaceImpl>> old_render_surfaces; property_trees_.effect_tree.TakeRenderSurfaces(&old_render_surfaces); property_trees_ = *property_trees; @@ -493,6 +534,13 @@ void LayerTreeImpl::SetPropertyTrees(PropertyTrees* property_trees) { // effect tree. if (IsActiveTree()) property_trees_.effect_tree.set_needs_update(true); + + const ScrollNode* scrolling_node = nullptr; + if (scrolling_element_id) { + auto& scroll_tree = property_trees_.scroll_tree; + scrolling_node = scroll_tree.FindNodeFromElementId(scrolling_element_id); + } + SetCurrentlyScrollingNode(scrolling_node); } void LayerTreeImpl::PushPropertyTreesTo(LayerTreeImpl* target_tree) { @@ -508,21 +556,8 @@ void LayerTreeImpl::PushPropertyTreesTo(LayerTreeImpl* target_tree) { target_tree->MoveChangeTrackingToLayers(); } - // To maintain the current scrolling node we need to use element ids which - // are stable across the property tree update in SetPropertyTrees. - ElementId scrolling_element_id; - if (ScrollNode* scrolling_node = target_tree->CurrentlyScrollingNode()) - scrolling_element_id = scrolling_node->element_id; - target_tree->SetPropertyTrees(&property_trees_); - const ScrollNode* scrolling_node = nullptr; - if (scrolling_element_id) { - auto& scroll_tree = target_tree->property_trees()->scroll_tree; - scrolling_node = scroll_tree.FindNodeFromElementId(scrolling_element_id); - } - target_tree->SetCurrentlyScrollingNode(scrolling_node); - std::vector<EventMetrics> events_metrics; events_metrics.swap(events_metrics_from_main_thread_); target_tree->AppendEventsMetricsFromMainThread(std::move(events_metrics)); @@ -618,6 +653,13 @@ void LayerTreeImpl::PushPropertiesTo(LayerTreeImpl* target_tree) { target_tree->HandleScrollbarShowRequestsFromMain(); target_tree->AddPresentationCallbacks(std::move(presentation_callbacks_)); presentation_callbacks_.clear(); + + if (delegated_ink_metadata_) { + TRACE_EVENT_INSTANT1("cc", "Delegated ink metadata pushed to tree", + TRACE_EVENT_SCOPE_THREAD, "point", + delegated_ink_metadata_->point().ToString()); + target_tree->set_delegated_ink_metadata(std::move(delegated_ink_metadata_)); + } } void LayerTreeImpl::HandleTickmarksVisibilityChange() { @@ -1581,12 +1623,8 @@ ImageAnimationController* LayerTreeImpl::image_animation_controller() const { return host_impl_->image_animation_controller(); } -FrameRateCounter* LayerTreeImpl::frame_rate_counter() const { - return host_impl_->fps_counter(); -} - -base::Optional<int> LayerTreeImpl::current_universal_throughput() { - return host_impl_->current_universal_throughput(); +DroppedFrameCounter* LayerTreeImpl::dropped_frame_counter() const { + return host_impl_->dropped_frame_counter(); } MemoryHistory* LayerTreeImpl::memory_history() const { @@ -1937,9 +1975,11 @@ void LayerTreeImpl::UnregisterScrollbar( if (scrollbar_ids.horizontal == Layer::INVALID_ID && scrollbar_ids.vertical == Layer::INVALID_ID) { element_id_to_scrollbar_layer_ids_.erase(scroll_element_id); - if (IsActiveTree()) { - host_impl_->DidUnregisterScrollbarLayer(scroll_element_id); - } + } + + if (IsActiveTree()) { + host_impl_->DidUnregisterScrollbarLayer(scroll_element_id, + scrollbar_layer->orientation()); } } @@ -2256,6 +2296,19 @@ LayerTreeImpl::FindLayersHitByPointInNonFastScrollableRegion( return layers; } +bool LayerTreeImpl::PointHitsNonFastScrollableRegion( + const gfx::PointF& screen_space_point, + const LayerImpl& layer) const { + // We assume the layer has already been hit tested. + DCHECK(PointHitsLayer(&layer, screen_space_point, nullptr)); + + if (layer.non_fast_scrollable_region().IsEmpty()) + return false; + + return PointHitsRegion(screen_space_point, layer.ScreenSpaceTransform(), + layer.non_fast_scrollable_region(), &layer); +} + struct HitTestFramedVisibleScrollableOrTouchableFunctor { bool operator()(LayerImpl* layer) const { return layer->HitTestable() && layer->frame_element_id(); diff --git a/chromium/cc/trees/layer_tree_impl.h b/chromium/cc/trees/layer_tree_impl.h index 5c2f586a0ea..d4583970c74 100644 --- a/chromium/cc/trees/layer_tree_impl.h +++ b/chromium/cc/trees/layer_tree_impl.h @@ -46,7 +46,7 @@ class ContextProvider; namespace cc { class DebugRectHistory; -class FrameRateCounter; +class DroppedFrameCounter; class HeadsUpDisplayLayerImpl; class ImageDecodeCache; class LayerTreeDebugState; @@ -127,8 +127,7 @@ class CC_EXPORT LayerTreeImpl { TileManager* tile_manager() const; ImageDecodeCache* image_decode_cache() const; ImageAnimationController* image_animation_controller() const; - FrameRateCounter* frame_rate_counter() const; - base::Optional<int> current_universal_throughput(); + DroppedFrameCounter* dropped_frame_counter() const; MemoryHistory* memory_history() const; DebugRectHistory* debug_rect_history() const; bool IsActiveTree() const; @@ -591,6 +590,8 @@ class CC_EXPORT LayerTreeImpl { // Return all layers with a hit non-fast scrollable region. std::vector<const LayerImpl*> FindLayersHitByPointInNonFastScrollableRegion( const gfx::PointF& screen_space_point); + bool PointHitsNonFastScrollableRegion(const gfx::PointF& scree_space_point, + const LayerImpl& layer) const; // Returns the ElementId representing a frame's document at the given point. // In cases where cc doesn't have enough information to perform accurate @@ -710,6 +711,18 @@ class CC_EXPORT LayerTreeImpl { return host_impl_->DrawTransform(); } + // These functions are used for plumbing DelegatedInkMetadata from blink + // through the compositor and into viz via a compositor frame. They should + // only be called after the JS API |updateInkTrailStartPoint| has been + // called, which populates the metadata with provided information. + void set_delegated_ink_metadata( + std::unique_ptr<viz::DelegatedInkMetadata> metadata) { + delegated_ink_metadata_ = std::move(metadata); + } + std::unique_ptr<viz::DelegatedInkMetadata> take_delegated_ink_metadata() { + return std::move(delegated_ink_metadata_); + } + protected: float ClampPageScaleFactorToLimits(float page_scale_factor) const; void PushPageScaleFactorAndLimits(const float* page_scale_factor, @@ -861,6 +874,8 @@ class CC_EXPORT LayerTreeImpl { // Event metrics that are reported back from the main thread. std::vector<EventMetrics> events_metrics_from_main_thread_; + + std::unique_ptr<viz::DelegatedInkMetadata> delegated_ink_metadata_; }; } // namespace cc diff --git a/chromium/cc/trees/layer_tree_settings.h b/chromium/cc/trees/layer_tree_settings.h index 95a901678c2..446d46764c5 100644 --- a/chromium/cc/trees/layer_tree_settings.h +++ b/chromium/cc/trees/layer_tree_settings.h @@ -187,10 +187,18 @@ class CC_EXPORT LayerTreeSettings { // Whether experimental de-jelly effect is allowed. bool allow_de_jelly_effect = false; + // Whether the compositor should attempt to sync with the scroll handlers + // before submitting a frame. + bool enable_synchronized_scrolling = true; + #if DCHECK_IS_ON() // Whether to check if any double blur exists. bool log_on_ui_double_background_blur = false; #endif + + // When enabled, enforces new interoperable semantics for 3D transforms. + // See crbug.com/1008483. + bool enable_transform_interop = false; }; class CC_EXPORT LayerListSettings : public LayerTreeSettings { diff --git a/chromium/cc/trees/mutator_host.h b/chromium/cc/trees/mutator_host.h index 1292e0ec826..e3cf671314b 100644 --- a/chromium/cc/trees/mutator_host.h +++ b/chromium/cc/trees/mutator_host.h @@ -160,7 +160,6 @@ class MutatorHost { // the scroller. Otherwise returns an invalid ElementId. virtual ElementId ImplOnlyScrollAnimatingElement() const = 0; - virtual size_t CompositedAnimationsCount() const = 0; virtual size_t MainThreadAnimationsCount() const = 0; virtual bool HasCustomPropertyAnimations() const = 0; virtual bool CurrentFrameHadRAF() const = 0; diff --git a/chromium/cc/trees/occlusion_tracker.h b/chromium/cc/trees/occlusion_tracker.h index d2498676dec..c734beff463 100644 --- a/chromium/cc/trees/occlusion_tracker.h +++ b/chromium/cc/trees/occlusion_tracker.h @@ -59,7 +59,7 @@ class CC_EXPORT OcclusionTracker { protected: struct StackObject { - StackObject() : target(0) {} + StackObject() : target(nullptr) {} explicit StackObject(const RenderSurfaceImpl* target) : target(target) {} const RenderSurfaceImpl* target; SimpleEnclosedRegion occlusion_from_outside_target; diff --git a/chromium/cc/trees/property_tree.cc b/chromium/cc/trees/property_tree.cc index cbdb9d43e73..00ce2f2e65d 100644 --- a/chromium/cc/trees/property_tree.cc +++ b/chromium/cc/trees/property_tree.cc @@ -493,24 +493,27 @@ void TransformTree::UpdateSnapping(TransformNode* node) { // rounded, then what we're after is the scroll delta X, where ST * X = ST'. // I.e., we want a transform that will realize our snap. It follows that // X = ST^-1 * ST'. We cache ST and ST^-1 to make this more efficient. - gfx::Transform rounded = ToScreen(node->id); - rounded.RoundTranslationComponents(); - gfx::Transform delta = FromScreen(node->id); - delta *= rounded; - - DCHECK(delta.IsApproximatelyIdentityOrTranslation(SkDoubleToScalar(1e-4))) + DCHECK_LT(node->id, static_cast<int>(cached_data_.size())); + gfx::Transform& to_screen = cached_data_[node->id].to_screen; + to_screen.RoundTranslationComponents(); + gfx::Transform& from_screen = cached_data_[node->id].from_screen; + gfx::Transform delta = from_screen; + delta *= to_screen; + + constexpr float kTolerance = 1e-4f; + DCHECK(delta.IsApproximatelyIdentityOrTranslation(kTolerance)) << delta.ToString(); gfx::Vector2dF translation = delta.To2dTranslation(); + node->snap_amount = translation; + if (translation.IsZero()) + return; - // Now that we have our delta, we must apply it to each of our combined, - // to/from matrices. - SetToScreen(node->id, rounded); - node->to_parent.Translate(translation.x(), translation.y()); - gfx::Transform from_screen = FromScreen(node->id); from_screen.matrix().postTranslate(-translation.x(), -translation.y(), 0); - SetFromScreen(node->id, from_screen); - node->snap_amount = translation; + node->to_parent.Translate(translation.x(), translation.y()); + // Avoid accumulation of errors in to_parent. + if (node->to_parent.IsApproximatelyIdentityOrIntegerTranslation(kTolerance)) + node->to_parent.RoundTranslationComponents(); } void TransformTree::UpdateTransformChanged(TransformNode* node, @@ -745,6 +748,17 @@ void EffectTree::UpdateHasMaskingChild(EffectNode* node, } } +void EffectTree::UpdateOnlyDrawsVisibleContent(EffectNode* node, + EffectNode* parent_node) { + node->only_draws_visible_content = !node->has_copy_request; + if (parent_node) + node->only_draws_visible_content &= parent_node->only_draws_visible_content; + if (!node->backdrop_filters.IsEmpty()) { + node->only_draws_visible_content &= + !node->backdrop_filters.HasFilterOfType(FilterOperation::ZOOM); + } +} + void EffectTree::UpdateSurfaceContentsScale(EffectNode* effect_node) { if (!effect_node->HasRenderSurface()) { effect_node->surface_contents_scale = gfx::Vector2dF(1.0f, 1.0f); @@ -829,6 +843,7 @@ void EffectTree::UpdateEffects(int id) { UpdateEffectChanged(node, parent_node); UpdateBackfaceVisibility(node, parent_node); UpdateHasMaskingChild(node, parent_node); + UpdateOnlyDrawsVisibleContent(node, parent_node); UpdateSurfaceContentsScale(node); } @@ -1273,6 +1288,8 @@ void ScrollTree::OnScrollOffsetAnimated(ElementId id, if (!property_trees()->is_active) return; + TRACE_EVENT2("cc", "ScrollTree::OnScrollOffsetAnimated", "x", + scroll_offset.x(), "y", scroll_offset.y()); ScrollNode* scroll_node = Node(scroll_tree_index); if (SetScrollOffset(id, ClampScrollOffsetToLimits(scroll_offset, *scroll_node))) @@ -1570,6 +1587,9 @@ void ScrollTree::SetBaseScrollOffset(ElementId id, bool ScrollTree::SetScrollOffset(ElementId id, const gfx::ScrollOffset& scroll_offset) { + // TODO(crbug.com/1087088): Remove TRACE_EVENT call when the bug is fixed + TRACE_EVENT2("cc", "ScrollTree::SetScrollOffset", "x", scroll_offset.x(), "y", + scroll_offset.y()); if (property_trees()->is_main_thread) { if (scroll_offset_map_[id] == scroll_offset) return false; diff --git a/chromium/cc/trees/property_tree.h b/chromium/cc/trees/property_tree.h index 4dcf7d7a0bd..6c41097cc7a 100644 --- a/chromium/cc/trees/property_tree.h +++ b/chromium/cc/trees/property_tree.h @@ -356,6 +356,7 @@ class CC_EXPORT EffectTree final : public PropertyTree<EffectNode> { void UpdateIsDrawn(EffectNode* node, EffectNode* parent_node); void UpdateBackfaceVisibility(EffectNode* node, EffectNode* parent_node); void UpdateHasMaskingChild(EffectNode* node, EffectNode* parent_node); + void UpdateOnlyDrawsVisibleContent(EffectNode* node, EffectNode* parent_node); // Stores copy requests, keyed by node id. std::unordered_multimap<int, std::unique_ptr<viz::CopyOutputRequest>> diff --git a/chromium/cc/trees/property_tree_builder.cc b/chromium/cc/trees/property_tree_builder.cc index b036be21cbd..fcf85d88d2d 100644 --- a/chromium/cc/trees/property_tree_builder.cc +++ b/chromium/cc/trees/property_tree_builder.cc @@ -612,6 +612,7 @@ void PropertyTreeBuilderContext::AddScrollNodeIfNeeded( node.user_scrollable_vertical = layer->GetUserScrollableVertical(); node.element_id = layer->element_id(); node.transform_id = data_for_children->transform_tree_parent; + node.is_composited = true; node_id = scroll_tree_.Insert(node, parent_id); data_for_children->scroll_tree_parent = node_id; diff --git a/chromium/cc/trees/property_tree_builder_unittest.cc b/chromium/cc/trees/property_tree_builder_unittest.cc index af760bc828a..183c3342de0 100644 --- a/chromium/cc/trees/property_tree_builder_unittest.cc +++ b/chromium/cc/trees/property_tree_builder_unittest.cc @@ -201,7 +201,7 @@ TEST_F(PropertyTreeBuilderTest, RenderSurfaceListForTransparentChild) { EXPECT_EQ(1U, GetRenderSurfaceList().size()); EXPECT_EQ(static_cast<viz::RenderPassId>(root->id()), GetRenderSurfaceList().at(0)->id()); - EXPECT_EQ(gfx::Rect(), ImplOf(root)->drawable_content_rect()); + EXPECT_EQ(gfx::Rect(), ImplOf(root)->visible_drawable_content_rect()); } TEST_F(PropertyTreeBuilderTest, diff --git a/chromium/cc/trees/proxy.h b/chromium/cc/trees/proxy.h index 0fe7a90fe3f..9afdb987bfc 100644 --- a/chromium/cc/trees/proxy.h +++ b/chromium/cc/trees/proxy.h @@ -8,7 +8,6 @@ #include <memory> #include <string> -#include "base/logging.h" #include "base/memory/ref_counted.h" #include "base/threading/platform_thread.h" #include "base/time/time.h" diff --git a/chromium/cc/trees/proxy_impl.cc b/chromium/cc/trees/proxy_impl.cc index 544ad6392e3..24e222ffbe0 100644 --- a/chromium/cc/trees/proxy_impl.cc +++ b/chromium/cc/trees/proxy_impl.cc @@ -370,26 +370,10 @@ void ProxyImpl::SetVideoNeedsBeginFrames(bool needs_begin_frames) { scheduler_->SetVideoNeedsBeginFrames(needs_begin_frames); } -size_t ProxyImpl::CompositedAnimationsCount() const { - return host_impl_->mutator_host()->CompositedAnimationsCount(); -} - -size_t ProxyImpl::MainThreadAnimationsCount() const { - return host_impl_->mutator_host()->MainThreadAnimationsCount(); -} - bool ProxyImpl::HasCustomPropertyAnimations() const { return host_impl_->mutator_host()->HasCustomPropertyAnimations(); } -bool ProxyImpl::CurrentFrameHadRAF() const { - return host_impl_->mutator_host()->CurrentFrameHadRAF(); -} - -bool ProxyImpl::NextFrameHasPendingRAF() const { - return host_impl_->mutator_host()->NextFrameHasPendingRAF(); -} - bool ProxyImpl::IsInsideDraw() { return inside_draw_; } @@ -542,14 +526,37 @@ void ProxyImpl::NotifyThroughputTrackerResults(CustomTrackerResults results) { proxy_main_weak_ptr_, std::move(results))); } +void ProxyImpl::SubmitThroughputData(ukm::SourceId source_id, + int aggregated_percent, + int impl_percent, + base::Optional<int> main_percent) { + DCHECK(IsImplThread()); + MainThreadTaskRunner()->PostTask( + FROM_HERE, + base::BindOnce(&ProxyMain::SubmitThroughputData, proxy_main_weak_ptr_, + source_id, aggregated_percent, impl_percent, + main_percent)); +} + +void ProxyImpl::DidObserveFirstScrollDelay( + base::TimeDelta first_scroll_delay, + base::TimeTicks first_scroll_timestamp) { + DCHECK(IsImplThread()); + MainThreadTaskRunner()->PostTask( + FROM_HERE, base::BindOnce(&ProxyMain::DidObserveFirstScrollDelay, + proxy_main_weak_ptr_, first_scroll_delay, + first_scroll_timestamp)); +} + bool ProxyImpl::WillBeginImplFrame(const viz::BeginFrameArgs& args) { DCHECK(IsImplThread()); return host_impl_->WillBeginImplFrame(args); } -void ProxyImpl::DidFinishImplFrame() { +void ProxyImpl::DidFinishImplFrame( + const viz::BeginFrameArgs& last_activated_args) { DCHECK(IsImplThread()); - host_impl_->DidFinishImplFrame(scheduler_->last_activate_origin_frame_args()); + host_impl_->DidFinishImplFrame(last_activated_args); } void ProxyImpl::DidNotProduceFrame(const viz::BeginFrameAck& ack, @@ -703,9 +710,6 @@ DrawResult ProxyImpl::DrawInternal(bool forced_draw) { base::AutoReset<bool> mark_inside(&inside_draw_, true); - if (host_impl_->pending_tree()) - host_impl_->pending_tree()->UpdateDrawProperties(); - // This method is called on a forced draw, regardless of whether we are able // to produce a frame, as the calling site on main thread is blocked until its // request completes, and we signal completion here. If CanDraw() is false, we @@ -756,6 +760,12 @@ DrawResult ProxyImpl::DrawInternal(bool forced_draw) { proxy_main_weak_ptr_)); } + // The tile visibility/priority of the pending tree needs to be updated so + // that it doesn't get activated before the raster is complete. But this needs + // to happen after the draw, off of the critical path to draw. + if (host_impl_->pending_tree()) + host_impl_->pending_tree()->UpdateDrawProperties(); + DCHECK_NE(INVALID_RESULT, result); return result; } diff --git a/chromium/cc/trees/proxy_impl.h b/chromium/cc/trees/proxy_impl.h index 3050199e43d..dbe9b20d3ba 100644 --- a/chromium/cc/trees/proxy_impl.h +++ b/chromium/cc/trees/proxy_impl.h @@ -119,10 +119,18 @@ class CC_EXPORT ProxyImpl : public LayerTreeHostImplClient, void NotifyPaintWorkletStateChange( Scheduler::PaintWorkletState state) override; void NotifyThroughputTrackerResults(CustomTrackerResults results) override; + void SubmitThroughputData(ukm::SourceId source_id, + int aggregated_percent, + int impl_percent, + base::Optional<int> main_percent) override; + void DidObserveFirstScrollDelay( + base::TimeDelta first_scroll_delay, + base::TimeTicks first_scroll_timestamp) override; // SchedulerClient implementation bool WillBeginImplFrame(const viz::BeginFrameArgs& args) override; - void DidFinishImplFrame() override; + void DidFinishImplFrame( + const viz::BeginFrameArgs& last_activated_args) override; void DidNotProduceFrame(const viz::BeginFrameAck& ack, FrameSkippedReason reason) override; void WillNotReceiveBeginFrame() override; @@ -140,11 +148,7 @@ class CC_EXPORT ProxyImpl : public LayerTreeHostImplClient, void ScheduledActionBeginMainFrameNotExpectedUntil( base::TimeTicks time) override; void FrameIntervalUpdated(base::TimeDelta interval) override {} - size_t CompositedAnimationsCount() const override; - size_t MainThreadAnimationsCount() const override; bool HasCustomPropertyAnimations() const override; - bool CurrentFrameHadRAF() const override; - bool NextFrameHasPendingRAF() const override; DrawResult DrawInternal(bool forced_draw); diff --git a/chromium/cc/trees/proxy_main.cc b/chromium/cc/trees/proxy_main.cc index 22976f62b02..1ad9795cd11 100644 --- a/chromium/cc/trees/proxy_main.cc +++ b/chromium/cc/trees/proxy_main.cc @@ -378,6 +378,22 @@ void ProxyMain::NotifyThroughputTrackerResults(CustomTrackerResults results) { layer_tree_host_->NotifyThroughputTrackerResults(std::move(results)); } +void ProxyMain::SubmitThroughputData(ukm::SourceId source_id, + int aggregated_percent, + int impl_percent, + base::Optional<int> main_percent) { + DCHECK(!task_runner_provider_->IsImplThread()); + layer_tree_host_->SubmitThroughputData(source_id, aggregated_percent, + impl_percent, main_percent); +} + +void ProxyMain::DidObserveFirstScrollDelay( + base::TimeDelta first_scroll_delay, + base::TimeTicks first_scroll_timestamp) { + layer_tree_host_->DidObserveFirstScrollDelay(first_scroll_delay, + first_scroll_timestamp); +} + bool ProxyMain::IsStarted() const { DCHECK(IsMainThread()); return started_; diff --git a/chromium/cc/trees/proxy_main.h b/chromium/cc/trees/proxy_main.h index 8738cb1f38f..050af82306b 100644 --- a/chromium/cc/trees/proxy_main.h +++ b/chromium/cc/trees/proxy_main.h @@ -58,6 +58,12 @@ class CC_EXPORT ProxyMain : public Proxy { std::vector<LayerTreeHost::PresentationTimeCallback> callbacks, const gfx::PresentationFeedback& feedback); void NotifyThroughputTrackerResults(CustomTrackerResults results); + void SubmitThroughputData(ukm::SourceId source_id, + int aggregated_percent, + int impl_percent, + base::Optional<int> main_percent); + void DidObserveFirstScrollDelay(base::TimeDelta first_scroll_delay, + base::TimeTicks first_scroll_timestamp); CommitPipelineStage max_requested_pipeline_stage() const { return max_requested_pipeline_stage_; diff --git a/chromium/cc/trees/render_frame_metadata.h b/chromium/cc/trees/render_frame_metadata.h index 43af9536c47..2b884785a9d 100644 --- a/chromium/cc/trees/render_frame_metadata.h +++ b/chromium/cc/trees/render_frame_metadata.h @@ -41,8 +41,7 @@ class CC_EXPORT RenderFrameMetadata { // specified. SkColor root_background_color = SK_ColorWHITE; - // Scroll offset of the root layer. This optional parameter is only valid - // during tests. + // Scroll offset of the root layer. base::Optional<gfx::Vector2dF> root_scroll_offset; // Selection region relative to the current viewport. If the selection is @@ -56,6 +55,11 @@ class CC_EXPORT RenderFrameMetadata { // are the same). bool is_mobile_optimized = false; + // Flag used to notify the browser process to start or stop forwarding points + // to viz for use in a delegated ink trail. True the entire time points should + // be forwarded, and forwarding stops as soon as it is false again. + bool has_delegated_ink_metadata = false; + // The device scale factor used to generate a CompositorFrame. float device_scale_factor = 1.f; diff --git a/chromium/cc/trees/scroll_and_scale_set.h b/chromium/cc/trees/scroll_and_scale_set.h index cef0850f33b..b7e9d9ed91a 100644 --- a/chromium/cc/trees/scroll_and_scale_set.h +++ b/chromium/cc/trees/scroll_and_scale_set.h @@ -97,6 +97,10 @@ struct CC_EXPORT ScrollAndScaleSet { // ended. bool scroll_gesture_did_end; + // Tracks whether there is an ongoing compositor-driven animation for a + // scroll. + bool ongoing_scroll_animation = false; + // Tracks different methods of scrolling (e.g. wheel, touch, precision // touchpad, etc.). ManipulationInfo manipulation_info; diff --git a/chromium/cc/trees/single_thread_proxy.cc b/chromium/cc/trees/single_thread_proxy.cc index 6f469a42c4e..c5449d5e85a 100644 --- a/chromium/cc/trees/single_thread_proxy.cc +++ b/chromium/cc/trees/single_thread_proxy.cc @@ -415,26 +415,10 @@ void SingleThreadProxy::SetVideoNeedsBeginFrames(bool needs_begin_frames) { scheduler_on_impl_thread_->SetVideoNeedsBeginFrames(needs_begin_frames); } -size_t SingleThreadProxy::CompositedAnimationsCount() const { - return 0; -} - -size_t SingleThreadProxy::MainThreadAnimationsCount() const { - return 0; -} - bool SingleThreadProxy::HasCustomPropertyAnimations() const { return false; } -bool SingleThreadProxy::CurrentFrameHadRAF() const { - return false; -} - -bool SingleThreadProxy::NextFrameHasPendingRAF() const { - return false; -} - bool SingleThreadProxy::IsInsideDraw() { return inside_draw_; } @@ -635,7 +619,7 @@ void SingleThreadProxy::CompositeImmediately(base::TimeTicks frame_begin_time, // another draw will never be scheduled, so break remaining promises. host_impl_->active_tree()->BreakSwapPromises(SwapPromise::SWAP_FAILS); - DidFinishImplFrame(); + DidFinishImplFrame(begin_frame_args); } } @@ -958,9 +942,9 @@ void SingleThreadProxy::ScheduledActionPerformImplSideInvalidation() { NotifyReadyToActivate(); } -void SingleThreadProxy::DidFinishImplFrame() { - host_impl_->DidFinishImplFrame( - scheduler_on_impl_thread_->last_activate_origin_frame_args()); +void SingleThreadProxy::DidFinishImplFrame( + const viz::BeginFrameArgs& last_activated_args) { + host_impl_->DidFinishImplFrame(last_activated_args); #if DCHECK_IS_ON() DCHECK(inside_impl_frame_) << "DidFinishImplFrame called while not inside an impl frame!"; diff --git a/chromium/cc/trees/single_thread_proxy.h b/chromium/cc/trees/single_thread_proxy.h index 96228af593c..ee1915c5222 100644 --- a/chromium/cc/trees/single_thread_proxy.h +++ b/chromium/cc/trees/single_thread_proxy.h @@ -77,7 +77,8 @@ class CC_EXPORT SingleThreadProxy : public Proxy, // SchedulerClient implementation bool WillBeginImplFrame(const viz::BeginFrameArgs& args) override; - void DidFinishImplFrame() override; + void DidFinishImplFrame( + const viz::BeginFrameArgs& last_activated_args) override; void DidNotProduceFrame(const viz::BeginFrameAck& ack, FrameSkippedReason reason) override; void WillNotReceiveBeginFrame() override; @@ -95,11 +96,7 @@ class CC_EXPORT SingleThreadProxy : public Proxy, void ScheduledActionBeginMainFrameNotExpectedUntil( base::TimeTicks time) override; void FrameIntervalUpdated(base::TimeDelta interval) override; - size_t CompositedAnimationsCount() const override; - size_t MainThreadAnimationsCount() const override; bool HasCustomPropertyAnimations() const override; - bool CurrentFrameHadRAF() const override; - bool NextFrameHasPendingRAF() const override; // LayerTreeHostImplClient implementation void DidLoseLayerTreeFrameSinkOnImplThread() override; @@ -137,9 +134,21 @@ class CC_EXPORT SingleThreadProxy : public Proxy, void NotifyPaintWorkletStateChange( Scheduler::PaintWorkletState state) override; void NotifyThroughputTrackerResults(CustomTrackerResults results) override; + void SubmitThroughputData(ukm::SourceId source_id, + int aggregated_percent, + int impl_percent, + base::Optional<int> main_percent) override {} void RequestNewLayerTreeFrameSink(); + void DidObserveFirstScrollDelay( + base::TimeDelta first_scroll_delay, + base::TimeTicks first_scroll_timestamp) override { + // Single-threaded mode is only for browser compositing and for renderers in + // layout tests. This will still get called in the latter case, but we don't + // need to record UKM in that case. + } + // Called by the legacy path where RenderWidget does the scheduling. // Rasterization of tiles is only performed when |raster| is true. void CompositeImmediately(base::TimeTicks frame_begin_time, bool raster); diff --git a/chromium/cc/trees/task_runner_provider.h b/chromium/cc/trees/task_runner_provider.h index 0bc19d31f38..232bc6da0f6 100644 --- a/chromium/cc/trees/task_runner_provider.h +++ b/chromium/cc/trees/task_runner_provider.h @@ -8,7 +8,7 @@ #include <memory> #include <string> -#include "base/logging.h" +#include "base/check.h" #include "base/memory/ptr_util.h" #include "base/memory/ref_counted.h" #include "base/single_thread_task_runner.h" diff --git a/chromium/cc/trees/transform_node.cc b/chromium/cc/trees/transform_node.cc index 780bba5ec67..b19c820baab 100644 --- a/chromium/cc/trees/transform_node.cc +++ b/chromium/cc/trees/transform_node.cc @@ -32,6 +32,7 @@ TransformNode::TransformNode() moved_by_outer_viewport_bounds_delta_y(false), in_subtree_of_page_scale_layer(false), transform_changed(false), + delegates_to_parent_for_backface(false), maximum_animation_scale(kNotScaled), starting_animation_scale(kNotScaled) {} @@ -62,6 +63,8 @@ bool TransformNode::operator==(const TransformNode& other) const { other.moved_by_outer_viewport_bounds_delta_y && in_subtree_of_page_scale_layer == other.in_subtree_of_page_scale_layer && + delegates_to_parent_for_backface == + other.delegates_to_parent_for_backface && transform_changed == other.transform_changed && scroll_offset == other.scroll_offset && snap_amount == other.snap_amount && diff --git a/chromium/cc/trees/transform_node.h b/chromium/cc/trees/transform_node.h index 401af01e617..47bb059ea1c 100644 --- a/chromium/cc/trees/transform_node.h +++ b/chromium/cc/trees/transform_node.h @@ -105,6 +105,10 @@ struct CC_EXPORT TransformNode { // We need to track changes to to_screen transform to compute the damage rect. bool transform_changed : 1; + // Whether the parent transform node should be used for checking backface + // visibility, not this transform one. + bool delegates_to_parent_for_backface : 1; + gfx::ScrollOffset scroll_offset; // This value stores the snapped amount whenever we snap. If the snap is due diff --git a/chromium/cc/trees/ukm_manager.cc b/chromium/cc/trees/ukm_manager.cc index 2141cf1a21c..9d4eab1315f 100644 --- a/chromium/cc/trees/ukm_manager.cc +++ b/chromium/cc/trees/ukm_manager.cc @@ -4,7 +4,12 @@ #include "cc/trees/ukm_manager.h" +#include <algorithm> +#include <utility> + +#include "cc/metrics/compositor_frame_reporter.h" #include "cc/metrics/throughput_ukm_reporter.h" +#include "components/viz/common/quads/compositor_frame.h" #include "services/metrics/public/cpp/ukm_builders.h" #include "services/metrics/public/cpp/ukm_recorder.h" @@ -187,11 +192,13 @@ void UkmManager::RecordAggregateThroughput(AggregationType aggregation_type, builder.Record(recorder_.get()); } -void UkmManager::RecordLatencyUKM( +void UkmManager::RecordCompositorLatencyUKM( CompositorFrameReporter::FrameReportType report_type, const std::vector<CompositorFrameReporter::StageData>& stage_history, const CompositorFrameReporter::ActiveTrackers& active_trackers, const viz::FrameTimingDetails& viz_breakdown) const { + using StageType = CompositorFrameReporter::StageType; + ukm::builders::Graphics_Smoothness_Latency builder(source_id_); if (report_type == CompositorFrameReporter::FrameReportType::kDroppedFrame) { @@ -202,7 +209,7 @@ void UkmManager::RecordLatencyUKM( for (const CompositorFrameReporter::StageData& stage : stage_history) { switch (stage.stage_type) { #define CASE_FOR_STAGE(name) \ - case CompositorFrameReporter::StageType::k##name: \ + case StageType::k##name: \ builder.Set##name((stage.end_time - stage.start_time).InMicroseconds()); \ break; CASE_FOR_STAGE(BeginImplFrameToSendBeginMainFrame); @@ -215,8 +222,7 @@ void UkmManager::RecordLatencyUKM( #undef CASE_FOR_STAGE // Break out kSubmitCompositorFrameToPresentationCompositorFrame to report // the viz breakdown. - case CompositorFrameReporter::StageType:: - kSubmitCompositorFrameToPresentationCompositorFrame: + case StageType::kSubmitCompositorFrameToPresentationCompositorFrame: builder.SetSubmitCompositorFrameToPresentationCompositorFrame( (stage.end_time - stage.start_time).InMicroseconds()); if (viz_breakdown.received_compositor_frame_timestamp.is_null()) @@ -287,4 +293,80 @@ void UkmManager::RecordLatencyUKM( builder.Record(recorder_.get()); } +void UkmManager::RecordEventLatencyUKM( + const std::vector<EventMetrics>& events_metrics, + const std::vector<CompositorFrameReporter::StageData>& stage_history, + const viz::FrameTimingDetails& viz_breakdown) const { + using StageType = CompositorFrameReporter::StageType; + + for (const EventMetrics& event_metrics : events_metrics) { + ukm::builders::Graphics_Smoothness_EventLatency builder(source_id_); + + builder.SetEventType(static_cast<int64_t>(event_metrics.type())); + + if (event_metrics.scroll_type()) { + builder.SetScrollInputType( + static_cast<int64_t>(*event_metrics.scroll_type())); + + if (!viz_breakdown.swap_timings.is_null()) { + builder.SetTotalLatencyToSwapEnd( + (viz_breakdown.swap_timings.swap_end - event_metrics.time_stamp()) + .InMicroseconds()); + } + } + + // It is possible for an event to arrive in the compositor in the middle of + // a frame (e.g. the browser received the event *after* renderer received a + // begin-impl, and the event reached the compositor before that frame + // ended). To handle such cases, find the first stage that happens after the + // event's arrival in the browser. + auto stage_it = std::find_if( + stage_history.begin(), stage_history.end(), + [&event_metrics](const CompositorFrameReporter::StageData& stage) { + return stage.start_time > event_metrics.time_stamp(); + }); + // TODO(crbug.com/1079116): Ideally, at least the start time of + // SubmitCompositorFrameToPresentationCompositorFrame stage should be + // greater than the event time stamp, but apparently, this is not always the + // case (see crbug.com/1093698). For now, skip to the next event in such + // cases. Hopefully, the work to reduce discrepancies between the new + // EventLatency and the old Event.Latency metrics would fix this issue. If + // not, we need to reconsider investigating this issue. + if (stage_it == stage_history.end()) + continue; + + builder.SetBrowserToRendererCompositor( + (stage_it->start_time - event_metrics.time_stamp()).InMicroseconds()); + + for (; stage_it != stage_history.end(); ++stage_it) { + // Total latency is calculated since the event timestamp. + const base::TimeTicks start_time = + stage_it->stage_type == StageType::kTotalLatency + ? event_metrics.time_stamp() + : stage_it->start_time; + + switch (stage_it->stage_type) { +#define CASE_FOR_STAGE(name) \ + case StageType::k##name: \ + builder.Set##name((stage_it->end_time - start_time).InMicroseconds()); \ + break; + CASE_FOR_STAGE(BeginImplFrameToSendBeginMainFrame); + CASE_FOR_STAGE(SendBeginMainFrameToCommit); + CASE_FOR_STAGE(Commit); + CASE_FOR_STAGE(EndCommitToActivation); + CASE_FOR_STAGE(Activation); + CASE_FOR_STAGE(EndActivateToSubmitCompositorFrame); + CASE_FOR_STAGE(SubmitCompositorFrameToPresentationCompositorFrame); + CASE_FOR_STAGE(TotalLatency); +#undef CASE_FOR_STAGE + default: + NOTREACHED(); + break; + } + } + + builder.Record(recorder_.get()); + } +} + } // namespace cc diff --git a/chromium/cc/trees/ukm_manager.h b/chromium/cc/trees/ukm_manager.h index 50653814f85..6a2cc1c03db 100644 --- a/chromium/cc/trees/ukm_manager.h +++ b/chromium/cc/trees/ukm_manager.h @@ -5,9 +5,14 @@ #ifndef CC_TREES_UKM_MANAGER_H_ #define CC_TREES_UKM_MANAGER_H_ +#include <memory> +#include <vector> + #include "cc/cc_export.h" #include "cc/metrics/compositor_frame_reporter.h" +#include "cc/metrics/event_metrics.h" #include "cc/metrics/frame_sequence_metrics.h" +#include "components/viz/common/frame_timing_details.h" #include "services/metrics/public/cpp/ukm_source_id.h" #include "url/gurl.h" @@ -33,6 +38,7 @@ class CC_EXPORT UkmManager { ~UkmManager(); void SetSourceId(ukm::SourceId source_id); + ukm::SourceId source_id() const { return source_id_; } // These metrics are recorded while a user interaction is in progress. void SetUserInteractionInProgress(bool in_progress); @@ -48,12 +54,17 @@ class CC_EXPORT UkmManager { int64_t throughput) const; void RecordAggregateThroughput(AggregationType aggregation_type, int64_t throughput_percent) const; - void RecordLatencyUKM( + void RecordCompositorLatencyUKM( CompositorFrameReporter::FrameReportType report_type, const std::vector<CompositorFrameReporter::StageData>& stage_history, const CompositorFrameReporter::ActiveTrackers& active_trackers, const viz::FrameTimingDetails& viz_breakdown) const; + void RecordEventLatencyUKM( + const std::vector<EventMetrics>& events_metrics, + const std::vector<CompositorFrameReporter::StageData>& stage_history, + const viz::FrameTimingDetails& viz_breakdown) const; + ukm::UkmRecorder* recorder_for_testing() { return recorder_.get(); } private: diff --git a/chromium/cc/trees/ukm_manager_unittest.cc b/chromium/cc/trees/ukm_manager_unittest.cc index 9f4dd926f5b..2b55162d45f 100644 --- a/chromium/cc/trees/ukm_manager_unittest.cc +++ b/chromium/cc/trees/ukm_manager_unittest.cc @@ -4,7 +4,14 @@ #include "cc/trees/ukm_manager.h" +#include <vector> + +#include "base/time/time.h" +#include "cc/metrics/compositor_frame_reporter.h" +#include "cc/metrics/event_metrics.h" #include "components/ukm/test_ukm_recorder.h" +#include "components/viz/common/frame_timing_details.h" +#include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" #include "url/gurl.h" @@ -23,6 +30,55 @@ const char kCheckerboardAreaRatio[] = "CheckerboardedContentAreaRatio"; const char kMissingTiles[] = "NumMissingTiles"; const char kCheckerboardedImagesCount[] = "CheckerboardedImagesCount"; +// Names of compositor/event latency UKM events. +const char kCompositorLatency[] = "Graphics.Smoothness.Latency"; +const char kEventLatency[] = "Graphics.Smoothness.EventLatency"; + +// Names of enum metrics used in compositor/event latency UKM metrics. +const char kMissedFrame[] = "MissedFrame"; +const char kEventType[] = "EventType"; +const char kScrollInputType[] = "ScrollInputType"; + +// Names of compositor stages and substages used in compositor/event latency UKM +// metrics. +const char kBrowserToRendererCompositor[] = "BrowserToRendererCompositor"; +const char kBeginImplFrameToSendBeginMainFrame[] = + "BeginImplFrameToSendBeginMainFrame"; +const char kSendBeginMainFrameToCommit[] = "SendBeginMainFrameToCommit"; +const char kCommit[] = "Commit"; +const char kEndCommitToActivation[] = "EndCommitToActivation"; +const char kActivation[] = "Activation"; +const char kEndActivateToSubmitCompositorFrame[] = + "EndActivateToSubmitCompositorFrame"; +const char kSubmitCompositorFrameToPresentationCompositorFrame[] = + "SubmitCompositorFrameToPresentationCompositorFrame"; +const char kVizBreakdownSubmitToReceiveCompositorFrame[] = + "SubmitCompositorFrameToPresentationCompositorFrame." + "SubmitToReceiveCompositorFrame"; +const char kVizBreakdownReceivedCompositorFrameToStartDraw[] = + "SubmitCompositorFrameToPresentationCompositorFrame." + "ReceivedCompositorFrameToStartDraw"; +const char kVizBreakdownStartDrawToSwapStart[] = + "SubmitCompositorFrameToPresentationCompositorFrame.StartDrawToSwapStart"; +const char kVizBreakdownSwapStartToSwapEnd[] = + "SubmitCompositorFrameToPresentationCompositorFrame.SwapStartToSwapEnd"; +const char kVizBreakdownSwapEndToPresentationCompositorFrame[] = + "SubmitCompositorFrameToPresentationCompositorFrame." + "SwapEndToPresentationCompositorFrame"; +const char kTotalLatencyToSwapEnd[] = "TotalLatencyToSwapEnd"; +const char kTotalLatency[] = "TotalLatency"; + +// Names of frame sequence types use in compositor latency UKM metrics (see +// FrameSequenceTrackerType enum). +const char kCompositorAnimation[] = "CompositorAnimation"; +const char kMainThreadAnimation[] = "MainThreadAnimation"; +const char kPinchZoom[] = "PinchZoom"; +const char kRAF[] = "RAF"; +const char kTouchScroll[] = "TouchScroll"; +const char kUniversal[] = "Universal"; +const char kVideo[] = "Video"; +const char kWheelScroll[] = "WheelScroll"; + class UkmManagerTest : public testing::Test { public: UkmManagerTest() { @@ -36,6 +92,8 @@ class UkmManagerTest : public testing::Test { manager_->SetSourceId(kTestSourceId1); } + ~UkmManagerTest() override = default; + protected: ukm::TestUkmRecorder* test_ukm_recorder_; std::unique_ptr<UkmManager> manager_; @@ -99,5 +157,300 @@ TEST_F(UkmManagerTest, Basic) { } } +class UkmManagerCompositorLatencyTest + : public UkmManagerTest, + public testing::WithParamInterface< + CompositorFrameReporter::FrameReportType> { + public: + UkmManagerCompositorLatencyTest() : report_type_(GetParam()) {} + ~UkmManagerCompositorLatencyTest() override = default; + + protected: + CompositorFrameReporter::FrameReportType report_type() const { + return report_type_; + } + + private: + CompositorFrameReporter::FrameReportType report_type_; +}; + +INSTANTIATE_TEST_SUITE_P( + All, + UkmManagerCompositorLatencyTest, + testing::Values( + CompositorFrameReporter::FrameReportType::kNonDroppedFrame, + CompositorFrameReporter::FrameReportType::kMissedDeadlineFrame, + CompositorFrameReporter::FrameReportType::kDroppedFrame, + CompositorFrameReporter::FrameReportType::kCompositorOnlyFrame)); + +TEST_P(UkmManagerCompositorLatencyTest, CompositorLatency) { + base::TimeTicks now = base::TimeTicks::Now(); + + const base::TimeTicks begin_impl_time = + (now += base::TimeDelta::FromMicroseconds(10)); + const base::TimeTicks begin_main_time = + (now += base::TimeDelta::FromMicroseconds(10)); + const base::TimeTicks begin_commit_time = + (now += base::TimeDelta::FromMicroseconds(10)); + const base::TimeTicks end_commit_time = + (now += base::TimeDelta::FromMicroseconds(10)); + const base::TimeTicks begin_activate_time = + (now += base::TimeDelta::FromMicroseconds(10)); + const base::TimeTicks end_activate_time = + (now += base::TimeDelta::FromMicroseconds(10)); + const base::TimeTicks submit_time = + (now += base::TimeDelta::FromMicroseconds(10)); + + viz::FrameTimingDetails viz_breakdown; + viz_breakdown.received_compositor_frame_timestamp = + (now += base::TimeDelta::FromMicroseconds(1)); + viz_breakdown.draw_start_timestamp = + (now += base::TimeDelta::FromMicroseconds(2)); + viz_breakdown.swap_timings.swap_start = + (now += base::TimeDelta::FromMicroseconds(3)); + viz_breakdown.swap_timings.swap_end = + (now += base::TimeDelta::FromMicroseconds(4)); + viz_breakdown.presentation_feedback.timestamp = + (now += base::TimeDelta::FromMicroseconds(5)); + + std::vector<CompositorFrameReporter::StageData> stage_history = { + { + CompositorFrameReporter::StageType:: + kBeginImplFrameToSendBeginMainFrame, + begin_impl_time, + begin_main_time, + }, + { + CompositorFrameReporter::StageType::kSendBeginMainFrameToCommit, + begin_main_time, + begin_commit_time, + }, + { + CompositorFrameReporter::StageType::kCommit, + begin_commit_time, + end_commit_time, + }, + { + CompositorFrameReporter::StageType::kEndCommitToActivation, + end_commit_time, + begin_activate_time, + }, + { + CompositorFrameReporter::StageType::kActivation, + begin_activate_time, + end_activate_time, + }, + { + CompositorFrameReporter::StageType:: + kEndActivateToSubmitCompositorFrame, + end_activate_time, + submit_time, + }, + { + CompositorFrameReporter::StageType:: + kSubmitCompositorFrameToPresentationCompositorFrame, + submit_time, + viz_breakdown.presentation_feedback.timestamp, + }, + { + CompositorFrameReporter::StageType::kTotalLatency, + begin_impl_time, + viz_breakdown.presentation_feedback.timestamp, + }, + }; + + CompositorFrameReporter::ActiveTrackers active_trackers; + active_trackers.set( + static_cast<size_t>(FrameSequenceTrackerType::kTouchScroll)); + active_trackers.set( + static_cast<size_t>(FrameSequenceTrackerType::kCompositorAnimation)); + active_trackers.set( + static_cast<size_t>(FrameSequenceTrackerType::kUniversal)); + + manager_->RecordCompositorLatencyUKM(report_type(), stage_history, + active_trackers, viz_breakdown); + + const auto& entries = + test_ukm_recorder_->GetEntriesByName(kCompositorLatency); + EXPECT_EQ(1u, entries.size()); + const auto* entry = entries[0]; + + EXPECT_NE(ukm::kInvalidSourceId, entry->source_id); + test_ukm_recorder_->ExpectEntrySourceHasUrl(entry, GURL(kTestUrl)); + + if (report_type() == + CompositorFrameReporter::FrameReportType::kDroppedFrame) { + test_ukm_recorder_->ExpectEntryMetric(entry, kMissedFrame, true); + } else { + EXPECT_FALSE(test_ukm_recorder_->EntryHasMetric(entry, kMissedFrame)); + } + + test_ukm_recorder_->ExpectEntryMetric( + entry, kBeginImplFrameToSendBeginMainFrame, + (begin_main_time - begin_impl_time).InMicroseconds()); + test_ukm_recorder_->ExpectEntryMetric( + entry, kSendBeginMainFrameToCommit, + (begin_commit_time - begin_main_time).InMicroseconds()); + test_ukm_recorder_->ExpectEntryMetric( + entry, kCommit, (end_commit_time - begin_commit_time).InMicroseconds()); + test_ukm_recorder_->ExpectEntryMetric( + entry, kEndCommitToActivation, + (begin_activate_time - end_commit_time).InMicroseconds()); + test_ukm_recorder_->ExpectEntryMetric( + entry, kActivation, + (end_activate_time - begin_activate_time).InMicroseconds()); + test_ukm_recorder_->ExpectEntryMetric( + entry, kEndActivateToSubmitCompositorFrame, + (submit_time - end_activate_time).InMicroseconds()); + test_ukm_recorder_->ExpectEntryMetric( + entry, kSubmitCompositorFrameToPresentationCompositorFrame, + (viz_breakdown.presentation_feedback.timestamp - submit_time) + .InMicroseconds()); + test_ukm_recorder_->ExpectEntryMetric( + entry, kVizBreakdownSubmitToReceiveCompositorFrame, + (viz_breakdown.received_compositor_frame_timestamp - submit_time) + .InMicroseconds()); + test_ukm_recorder_->ExpectEntryMetric( + entry, kVizBreakdownReceivedCompositorFrameToStartDraw, + (viz_breakdown.draw_start_timestamp - + viz_breakdown.received_compositor_frame_timestamp) + .InMicroseconds()); + test_ukm_recorder_->ExpectEntryMetric(entry, + kVizBreakdownStartDrawToSwapStart, + (viz_breakdown.swap_timings.swap_start - + viz_breakdown.draw_start_timestamp) + .InMicroseconds()); + test_ukm_recorder_->ExpectEntryMetric(entry, kVizBreakdownSwapStartToSwapEnd, + (viz_breakdown.swap_timings.swap_end - + viz_breakdown.swap_timings.swap_start) + .InMicroseconds()); + test_ukm_recorder_->ExpectEntryMetric( + entry, kVizBreakdownSwapEndToPresentationCompositorFrame, + (viz_breakdown.presentation_feedback.timestamp - + viz_breakdown.swap_timings.swap_end) + .InMicroseconds()); + test_ukm_recorder_->ExpectEntryMetric( + entry, kTotalLatency, + (viz_breakdown.presentation_feedback.timestamp - begin_impl_time) + .InMicroseconds()); + + test_ukm_recorder_->ExpectEntryMetric(entry, kCompositorAnimation, true); + test_ukm_recorder_->ExpectEntryMetric(entry, kTouchScroll, true); + EXPECT_FALSE(test_ukm_recorder_->EntryHasMetric(entry, kMainThreadAnimation)); + EXPECT_FALSE(test_ukm_recorder_->EntryHasMetric(entry, kPinchZoom)); + EXPECT_FALSE(test_ukm_recorder_->EntryHasMetric(entry, kRAF)); + EXPECT_FALSE(test_ukm_recorder_->EntryHasMetric(entry, kUniversal)); + EXPECT_FALSE(test_ukm_recorder_->EntryHasMetric(entry, kVideo)); + EXPECT_FALSE(test_ukm_recorder_->EntryHasMetric(entry, kWheelScroll)); +} + +TEST_F(UkmManagerTest, EventLatency) { + base::TimeTicks now = base::TimeTicks::Now(); + + const base::TimeTicks event_time = now; + std::unique_ptr<EventMetrics> event_metrics_ptrs[] = { + EventMetrics::Create(ui::ET_GESTURE_SCROLL_BEGIN, event_time, + ui::ScrollInputType::kWheel), + EventMetrics::Create(ui::ET_GESTURE_SCROLL_UPDATE, event_time, + ui::ScrollInputType::kWheel), + EventMetrics::Create(ui::ET_GESTURE_SCROLL_UPDATE, event_time, + ui::ScrollInputType::kWheel), + }; + EXPECT_THAT(event_metrics_ptrs, ::testing::Each(::testing::NotNull())); + std::vector<EventMetrics> events_metrics = { + *event_metrics_ptrs[0], *event_metrics_ptrs[1], *event_metrics_ptrs[2]}; + + const base::TimeTicks begin_impl_time = + (now += base::TimeDelta::FromMicroseconds(10)); + const base::TimeTicks end_activate_time = + (now += base::TimeDelta::FromMicroseconds(10)); + const base::TimeTicks submit_time = + (now += base::TimeDelta::FromMicroseconds(10)); + + viz::FrameTimingDetails viz_breakdown; + viz_breakdown.received_compositor_frame_timestamp = + (now += base::TimeDelta::FromMicroseconds(1)); + viz_breakdown.draw_start_timestamp = + (now += base::TimeDelta::FromMicroseconds(2)); + viz_breakdown.swap_timings.swap_start = + (now += base::TimeDelta::FromMicroseconds(3)); + viz_breakdown.swap_timings.swap_end = + (now += base::TimeDelta::FromMicroseconds(4)); + viz_breakdown.presentation_feedback.timestamp = + (now += base::TimeDelta::FromMicroseconds(5)); + + const base::TimeTicks swap_end_time = viz_breakdown.swap_timings.swap_end; + const base::TimeTicks present_time = + viz_breakdown.presentation_feedback.timestamp; + + std::vector<CompositorFrameReporter::StageData> stage_history = { + { + CompositorFrameReporter::StageType:: + kBeginImplFrameToSendBeginMainFrame, + begin_impl_time, + end_activate_time, + }, + { + CompositorFrameReporter::StageType:: + kEndActivateToSubmitCompositorFrame, + end_activate_time, + submit_time, + }, + { + CompositorFrameReporter::StageType:: + kSubmitCompositorFrameToPresentationCompositorFrame, + submit_time, + present_time, + }, + { + CompositorFrameReporter::StageType::kTotalLatency, + event_time, + present_time, + }, + }; + + manager_->RecordEventLatencyUKM(events_metrics, stage_history, viz_breakdown); + + const auto& entries = test_ukm_recorder_->GetEntriesByName(kEventLatency); + EXPECT_EQ(3u, entries.size()); + for (size_t i = 0; i < entries.size(); i++) { + const auto* entry = entries[i]; + const auto* event_metrics = event_metrics_ptrs[i].get(); + + EXPECT_NE(ukm::kInvalidSourceId, entry->source_id); + test_ukm_recorder_->ExpectEntrySourceHasUrl(entry, GURL(kTestUrl)); + + test_ukm_recorder_->ExpectEntryMetric( + entry, kEventType, static_cast<int64_t>(event_metrics->type())); + test_ukm_recorder_->ExpectEntryMetric( + entry, kScrollInputType, + static_cast<int64_t>(*event_metrics->scroll_type())); + + test_ukm_recorder_->ExpectEntryMetric( + entry, kBrowserToRendererCompositor, + (begin_impl_time - event_time).InMicroseconds()); + test_ukm_recorder_->ExpectEntryMetric( + entry, kBeginImplFrameToSendBeginMainFrame, + (end_activate_time - begin_impl_time).InMicroseconds()); + EXPECT_FALSE( + test_ukm_recorder_->EntryHasMetric(entry, kSendBeginMainFrameToCommit)); + EXPECT_FALSE(test_ukm_recorder_->EntryHasMetric(entry, kCommit)); + EXPECT_FALSE( + test_ukm_recorder_->EntryHasMetric(entry, kEndCommitToActivation)); + EXPECT_FALSE(test_ukm_recorder_->EntryHasMetric(entry, kActivation)); + test_ukm_recorder_->ExpectEntryMetric( + entry, kEndActivateToSubmitCompositorFrame, + (submit_time - end_activate_time).InMicroseconds()); + test_ukm_recorder_->ExpectEntryMetric( + entry, kSubmitCompositorFrameToPresentationCompositorFrame, + (present_time - submit_time).InMicroseconds()); + test_ukm_recorder_->ExpectEntryMetric( + entry, kTotalLatencyToSwapEnd, + (swap_end_time - event_time).InMicroseconds()); + test_ukm_recorder_->ExpectEntryMetric( + entry, kTotalLatency, (present_time - event_time).InMicroseconds()); + } +} + } // namespace } // namespace cc |