// Copyright 2016 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 "third_party/blink/renderer/core/paint/pre_paint_tree_walk.h" #include "base/test/scoped_feature_list.h" #include "cc/base/features.h" #include "testing/gtest/include/gtest/gtest.h" #include "third_party/blink/renderer/core/dom/events/native_event_listener.h" #include "third_party/blink/renderer/core/frame/local_dom_window.h" #include "third_party/blink/renderer/core/layout/layout_tree_as_text.h" #include "third_party/blink/renderer/core/layout/layout_view.h" #include "third_party/blink/renderer/core/paint/object_paint_properties.h" #include "third_party/blink/renderer/core/paint/paint_controller_paint_test.h" #include "third_party/blink/renderer/core/paint/paint_layer.h" #include "third_party/blink/renderer/core/paint/paint_property_tree_printer.h" #include "third_party/blink/renderer/core/testing/core_unit_test_helper.h" #include "third_party/blink/renderer/platform/graphics/paint/geometry_mapper.h" #include "third_party/blink/renderer/platform/graphics/paint/scroll_paint_property_node.h" #include "third_party/blink/renderer/platform/graphics/paint/transform_paint_property_node.h" #include "third_party/blink/renderer/platform/testing/runtime_enabled_features_test_helpers.h" #include "third_party/blink/renderer/platform/testing/unit_test_helpers.h" #include "third_party/blink/renderer/platform/wtf/hash_map.h" namespace blink { class PrePaintTreeWalkTest : public PaintControllerPaintTest { public: const TransformPaintPropertyNode* FramePreTranslation() { return GetDocument() .View() ->GetLayoutView() ->FirstFragment() .PaintProperties() ->PaintOffsetTranslation(); } const TransformPaintPropertyNode* FrameScrollTranslation() { return GetDocument() .View() ->GetLayoutView() ->FirstFragment() .PaintProperties() ->ScrollTranslation(); } private: void SetUp() override { EnableCompositing(); RenderingTest::SetUp(); } }; INSTANTIATE_PAINT_TEST_SUITE_P(PrePaintTreeWalkTest); TEST_P(PrePaintTreeWalkTest, PropertyTreesRebuiltWithBorderInvalidation) { SetBodyInnerHTML(R"HTML(
)HTML"); auto* transformed_element = GetDocument().getElementById("transformed"); const auto* transformed_properties = transformed_element->GetLayoutObject()->FirstFragment().PaintProperties(); EXPECT_EQ(FloatSize(100, 100), transformed_properties->Transform()->Translation2D()); // Artifically change the transform node. const_cast(transformed_properties)->ClearTransform(); EXPECT_EQ(nullptr, transformed_properties->Transform()); // Cause a paint invalidation. transformed_element->setAttribute(html_names::kClassAttr, "border"); UpdateAllLifecyclePhasesForTest(); // Should have changed back. EXPECT_EQ(FloatSize(100, 100), transformed_properties->Transform()->Translation2D()); } TEST_P(PrePaintTreeWalkTest, PropertyTreesRebuiltWithFrameScroll) { SetBodyInnerHTML(""); EXPECT_TRUE(FrameScrollTranslation()->IsIdentity()); // Cause a scroll invalidation and ensure the translation is updated. GetDocument().domWindow()->scrollTo(0, 100); UpdateAllLifecyclePhasesForTest(); EXPECT_EQ(FloatSize(0, -100), FrameScrollTranslation()->Translation2D()); } TEST_P(PrePaintTreeWalkTest, PropertyTreesRebuiltWithCSSTransformInvalidation) { SetBodyInnerHTML(R"HTML(
)HTML"); auto* transformed_element = GetDocument().getElementById("transformed"); const auto* transformed_properties = transformed_element->GetLayoutObject()->FirstFragment().PaintProperties(); EXPECT_EQ(FloatSize(100, 100), transformed_properties->Transform()->Translation2D()); // Invalidate the CSS transform property. transformed_element->setAttribute(html_names::kClassAttr, "transformB"); UpdateAllLifecyclePhasesForTest(); // The transform should have changed. EXPECT_EQ(FloatSize(200, 200), transformed_properties->Transform()->Translation2D()); } TEST_P(PrePaintTreeWalkTest, PropertyTreesRebuiltWithOpacityInvalidation) { SetBodyInnerHTML(R"HTML(
)HTML"); auto* transparent_element = GetDocument().getElementById("transparent"); const auto* transparent_properties = transparent_element->GetLayoutObject()->FirstFragment().PaintProperties(); EXPECT_EQ(0.9f, transparent_properties->Effect()->Opacity()); // Invalidate the opacity property. transparent_element->setAttribute(html_names::kClassAttr, "opacityB"); UpdateAllLifecyclePhasesForTest(); // The opacity should have changed. EXPECT_EQ(0.4f, transparent_properties->Effect()->Opacity()); } TEST_P(PrePaintTreeWalkTest, ClearSubsequenceCachingClipChange) { SetBodyInnerHTML(R"HTML(
content
)HTML"); auto* parent = GetDocument().getElementById("parent"); auto* child_paint_layer = GetPaintLayerByElementId("child"); EXPECT_FALSE(child_paint_layer->SelfNeedsRepaint()); EXPECT_FALSE(child_paint_layer->NeedsPaintPhaseFloat()); parent->setAttribute(html_names::kClassAttr, "clip"); UpdateAllLifecyclePhasesExceptPaint(); EXPECT_TRUE(child_paint_layer->SelfNeedsRepaint()); } TEST_P(PrePaintTreeWalkTest, ClearSubsequenceCachingClipChange2DTransform) { SetBodyInnerHTML(R"HTML(
content
)HTML"); auto* parent = GetDocument().getElementById("parent"); auto* child_paint_layer = GetPaintLayerByElementId("child"); EXPECT_FALSE(child_paint_layer->SelfNeedsRepaint()); EXPECT_FALSE(child_paint_layer->NeedsPaintPhaseFloat()); parent->setAttribute(html_names::kClassAttr, "clip"); UpdateAllLifecyclePhasesExceptPaint(); EXPECT_TRUE(child_paint_layer->SelfNeedsRepaint()); } TEST_P(PrePaintTreeWalkTest, ClearSubsequenceCachingClipChangePosAbs) { SetBodyInnerHTML(R"HTML(
content
)HTML"); auto* parent = GetDocument().getElementById("parent"); auto* child_paint_layer = GetPaintLayerByElementId("child"); EXPECT_FALSE(child_paint_layer->SelfNeedsRepaint()); EXPECT_FALSE(child_paint_layer->NeedsPaintPhaseFloat()); // This changes clips for absolute-positioned descendants of "child" but not // normal-position ones, which are already clipped to 50x50. parent->setAttribute(html_names::kClassAttr, "clip"); UpdateAllLifecyclePhasesExceptPaint(); EXPECT_TRUE(child_paint_layer->SelfNeedsRepaint()); } TEST_P(PrePaintTreeWalkTest, ClearSubsequenceCachingClipChangePosFixed) { SetBodyInnerHTML(R"HTML(
content
)HTML"); auto* parent = GetDocument().getElementById("parent"); auto* child_paint_layer = GetPaintLayerByElementId("child"); EXPECT_FALSE(child_paint_layer->SelfNeedsRepaint()); EXPECT_FALSE(child_paint_layer->NeedsPaintPhaseFloat()); // This changes clips for absolute-positioned descendants of "child" but not // normal-position ones, which are already clipped to 50x50. parent->setAttribute(html_names::kClassAttr, "clip"); UpdateAllLifecyclePhasesExceptPaint(); EXPECT_TRUE(child_paint_layer->SelfNeedsRepaint()); } TEST_P(PrePaintTreeWalkTest, ClipChangeRepaintsDescendants) { SetBodyInnerHTML(R"HTML(
)HTML"); GetDocument().getElementById("parent")->removeAttribute("style"); UpdateAllLifecyclePhasesExceptPaint(); auto* paint_layer = GetPaintLayerByElementId("greatgrandchild"); EXPECT_TRUE(paint_layer->SelfNeedsRepaint()); } TEST_P(PrePaintTreeWalkTest, ClipChangeHasRadius) { SetBodyInnerHTML(R"HTML(
)HTML"); auto* target = GetDocument().getElementById("target"); auto* target_object = To(target->GetLayoutObject()); target->setAttribute(html_names::kStyleAttr, "border-radius: 5px"); UpdateAllLifecyclePhasesExceptPaint(); EXPECT_TRUE(target_object->Layer()->SelfNeedsRepaint()); // And should not trigger any assert failure. UpdateAllLifecyclePhasesForTest(); } namespace { class PrePaintTreeWalkMockEventListener final : public NativeEventListener { public: void Invoke(ExecutionContext*, Event*) final {} }; } // namespace TEST_P(PrePaintTreeWalkTest, InsideBlockingTouchEventHandlerUpdate) { SetBodyInnerHTML(R"HTML(
)HTML"); UpdateAllLifecyclePhasesForTest(); auto& ancestor = *GetLayoutObjectByElementId("ancestor"); auto& handler = *GetLayoutObjectByElementId("handler"); auto& descendant = *GetLayoutObjectByElementId("descendant"); EXPECT_FALSE(ancestor.EffectiveAllowedTouchActionChanged()); EXPECT_FALSE(handler.EffectiveAllowedTouchActionChanged()); EXPECT_FALSE(descendant.EffectiveAllowedTouchActionChanged()); EXPECT_FALSE(ancestor.DescendantEffectiveAllowedTouchActionChanged()); EXPECT_FALSE(handler.DescendantEffectiveAllowedTouchActionChanged()); EXPECT_FALSE(descendant.DescendantEffectiveAllowedTouchActionChanged()); EXPECT_FALSE(ancestor.InsideBlockingTouchEventHandler()); EXPECT_FALSE(handler.InsideBlockingTouchEventHandler()); EXPECT_FALSE(descendant.InsideBlockingTouchEventHandler()); PrePaintTreeWalkMockEventListener* callback = MakeGarbageCollected(); auto* handler_element = GetDocument().getElementById("handler"); handler_element->addEventListener(event_type_names::kTouchstart, callback); EXPECT_FALSE(ancestor.EffectiveAllowedTouchActionChanged()); EXPECT_TRUE(handler.EffectiveAllowedTouchActionChanged()); EXPECT_FALSE(descendant.EffectiveAllowedTouchActionChanged()); EXPECT_TRUE(ancestor.DescendantEffectiveAllowedTouchActionChanged()); EXPECT_FALSE(handler.DescendantEffectiveAllowedTouchActionChanged()); EXPECT_FALSE(descendant.DescendantEffectiveAllowedTouchActionChanged()); UpdateAllLifecyclePhasesForTest(); EXPECT_FALSE(ancestor.EffectiveAllowedTouchActionChanged()); EXPECT_FALSE(handler.EffectiveAllowedTouchActionChanged()); EXPECT_FALSE(descendant.EffectiveAllowedTouchActionChanged()); EXPECT_FALSE(ancestor.DescendantEffectiveAllowedTouchActionChanged()); EXPECT_FALSE(handler.DescendantEffectiveAllowedTouchActionChanged()); EXPECT_FALSE(descendant.DescendantEffectiveAllowedTouchActionChanged()); EXPECT_FALSE(ancestor.InsideBlockingTouchEventHandler()); EXPECT_TRUE(handler.InsideBlockingTouchEventHandler()); EXPECT_TRUE(descendant.InsideBlockingTouchEventHandler()); } TEST_P(PrePaintTreeWalkTest, EffectiveTouchActionStyleUpdate) { SetBodyInnerHTML(R"HTML(
)HTML"); UpdateAllLifecyclePhasesForTest(); auto& ancestor = *GetLayoutObjectByElementId("ancestor"); auto& touchaction = *GetLayoutObjectByElementId("touchaction"); auto& descendant = *GetLayoutObjectByElementId("descendant"); EXPECT_FALSE(ancestor.EffectiveAllowedTouchActionChanged()); EXPECT_FALSE(touchaction.EffectiveAllowedTouchActionChanged()); EXPECT_FALSE(descendant.EffectiveAllowedTouchActionChanged()); EXPECT_FALSE(ancestor.DescendantEffectiveAllowedTouchActionChanged()); EXPECT_FALSE(touchaction.DescendantEffectiveAllowedTouchActionChanged()); EXPECT_FALSE(descendant.DescendantEffectiveAllowedTouchActionChanged()); GetDocument() .getElementById("touchaction") ->setAttribute(html_names::kClassAttr, "touchaction"); GetDocument().View()->UpdateLifecycleToLayoutClean( DocumentUpdateReason::kTest); EXPECT_FALSE(ancestor.EffectiveAllowedTouchActionChanged()); EXPECT_TRUE(touchaction.EffectiveAllowedTouchActionChanged()); EXPECT_TRUE(descendant.EffectiveAllowedTouchActionChanged()); EXPECT_TRUE(ancestor.DescendantEffectiveAllowedTouchActionChanged()); EXPECT_TRUE(touchaction.DescendantEffectiveAllowedTouchActionChanged()); EXPECT_FALSE(descendant.DescendantEffectiveAllowedTouchActionChanged()); UpdateAllLifecyclePhasesForTest(); EXPECT_FALSE(ancestor.EffectiveAllowedTouchActionChanged()); EXPECT_FALSE(touchaction.EffectiveAllowedTouchActionChanged()); EXPECT_FALSE(descendant.EffectiveAllowedTouchActionChanged()); EXPECT_FALSE(ancestor.DescendantEffectiveAllowedTouchActionChanged()); EXPECT_FALSE(touchaction.DescendantEffectiveAllowedTouchActionChanged()); EXPECT_FALSE(descendant.DescendantEffectiveAllowedTouchActionChanged()); } TEST_P(PrePaintTreeWalkTest, InsideBlockingWheelEventHandlerUpdate) { base::test::ScopedFeatureList scoped_feature_list; scoped_feature_list.InitAndEnableFeature(::features::kWheelEventRegions); SetBodyInnerHTML(R"HTML(
)HTML"); UpdateAllLifecyclePhasesForTest(); auto& ancestor = *GetLayoutObjectByElementId("ancestor"); auto& handler = *GetLayoutObjectByElementId("handler"); auto& descendant = *GetLayoutObjectByElementId("descendant"); EXPECT_FALSE(ancestor.BlockingWheelEventHandlerChanged()); EXPECT_FALSE(handler.BlockingWheelEventHandlerChanged()); EXPECT_FALSE(descendant.BlockingWheelEventHandlerChanged()); EXPECT_FALSE(ancestor.DescendantBlockingWheelEventHandlerChanged()); EXPECT_FALSE(handler.DescendantBlockingWheelEventHandlerChanged()); EXPECT_FALSE(descendant.DescendantBlockingWheelEventHandlerChanged()); EXPECT_FALSE(ancestor.InsideBlockingWheelEventHandler()); EXPECT_FALSE(handler.InsideBlockingWheelEventHandler()); EXPECT_FALSE(descendant.InsideBlockingWheelEventHandler()); PrePaintTreeWalkMockEventListener* callback = MakeGarbageCollected(); auto* handler_element = GetDocument().getElementById("handler"); handler_element->addEventListener(event_type_names::kWheel, callback); EXPECT_FALSE(ancestor.BlockingWheelEventHandlerChanged()); EXPECT_TRUE(handler.BlockingWheelEventHandlerChanged()); EXPECT_FALSE(descendant.BlockingWheelEventHandlerChanged()); EXPECT_TRUE(ancestor.DescendantBlockingWheelEventHandlerChanged()); EXPECT_FALSE(handler.DescendantBlockingWheelEventHandlerChanged()); EXPECT_FALSE(descendant.DescendantBlockingWheelEventHandlerChanged()); UpdateAllLifecyclePhasesForTest(); EXPECT_FALSE(ancestor.BlockingWheelEventHandlerChanged()); EXPECT_FALSE(handler.BlockingWheelEventHandlerChanged()); EXPECT_FALSE(descendant.BlockingWheelEventHandlerChanged()); EXPECT_FALSE(ancestor.DescendantBlockingWheelEventHandlerChanged()); EXPECT_FALSE(handler.DescendantBlockingWheelEventHandlerChanged()); EXPECT_FALSE(descendant.DescendantBlockingWheelEventHandlerChanged()); EXPECT_FALSE(ancestor.InsideBlockingWheelEventHandler()); EXPECT_TRUE(handler.InsideBlockingWheelEventHandler()); EXPECT_TRUE(descendant.InsideBlockingWheelEventHandler()); } } // namespace blink