| // Copyright 2015 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 "base/callback.h" |
| #include "base/test/bind.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "cc/base/features.h" |
| #include "cc/layers/picture_layer.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/blink/public/common/features.h" |
| #include "third_party/blink/public/test/test_web_frame_content_dumper.h" |
| #include "third_party/blink/public/web/web_hit_test_result.h" |
| #include "third_party/blink/public/web/web_settings.h" |
| #include "third_party/blink/renderer/bindings/core/v8/script_source_code.h" |
| #include "third_party/blink/renderer/bindings/core/v8/v8_intersection_observer_init.h" |
| #include "third_party/blink/renderer/core/dom/document.h" |
| #include "third_party/blink/renderer/core/dom/element.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/frame/local_frame_view.h" |
| #include "third_party/blink/renderer/core/frame/web_frame_widget_impl.h" |
| #include "third_party/blink/renderer/core/frame/web_local_frame_impl.h" |
| #include "third_party/blink/renderer/core/html/html_iframe_element.h" |
| #include "third_party/blink/renderer/core/intersection_observer/intersection_observer.h" |
| #include "third_party/blink/renderer/core/layout/layout_view.h" |
| #include "third_party/blink/renderer/core/page/focus_controller.h" |
| #include "third_party/blink/renderer/core/page/page.h" |
| #include "third_party/blink/renderer/core/paint/compositing/composited_layer_mapping.h" |
| #include "third_party/blink/renderer/core/paint/compositing/paint_layer_compositor.h" |
| #include "third_party/blink/renderer/core/paint/paint_layer.h" |
| #include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h" |
| #include "third_party/blink/renderer/core/resize_observer/resize_observer.h" |
| #include "third_party/blink/renderer/core/resize_observer/resize_observer_entry.h" |
| #include "third_party/blink/renderer/core/script/classic_script.h" |
| #include "third_party/blink/renderer/core/testing/core_unit_test_helper.h" |
| #include "third_party/blink/renderer/core/testing/intersection_observer_test_helper.h" |
| #include "third_party/blink/renderer/core/testing/sim/sim_compositor.h" |
| #include "third_party/blink/renderer/core/testing/sim/sim_request.h" |
| #include "third_party/blink/renderer/core/testing/sim/sim_test.h" |
| #include "third_party/blink/renderer/platform/graphics/paint/paint_controller.h" |
| #include "third_party/blink/renderer/platform/graphics/paint/transform_paint_property_node.h" |
| #include "third_party/blink/renderer/platform/testing/find_cc_layer.h" |
| #include "third_party/blink/renderer/platform/testing/paint_test_configurations.h" |
| #include "third_party/blink/renderer/platform/testing/unit_test_helpers.h" |
| #include "third_party/blink/renderer/platform/testing/url_test_helpers.h" |
| |
| using testing::_; |
| |
| namespace blink { |
| |
| using html_names::kStyleAttr; |
| |
| // NOTE: This test uses <iframe sandbox> to create cross origin iframes. |
| |
| class FrameThrottlingTest : public PaintTestConfigurations, public SimTest { |
| protected: |
| void SetUp() override { |
| SimTest::SetUp(); |
| WebView().MainFrameViewWidget()->Resize(gfx::Size(640, 480)); |
| } |
| |
| SimCanvas::Commands CompositeFrame() { |
| auto commands = Compositor().BeginFrame(); |
| // Ensure intersection observer notifications get delivered. |
| test::RunPendingTasks(); |
| return commands; |
| } |
| |
| // Number of rectangles that make up the root layer's touch handler region. |
| size_t TouchHandlerRegionSize() { |
| const auto* frame_view = WebView().MainFrameImpl()->GetFrameView(); |
| return ScrollingContentsCcLayerByScrollElementId( |
| frame_view->RootCcLayer(), |
| frame_view->LayoutViewport()->GetScrollElementId()) |
| ->touch_action_region() |
| .GetAllRegions() |
| .GetRegionComplexity(); |
| } |
| |
| void UpdateAllLifecyclePhases() { |
| GetDocument().View()->UpdateAllLifecyclePhasesForTest(); |
| } |
| |
| class EmptyEventListener final : public NativeEventListener { |
| public: |
| void Invoke(ExecutionContext* execution_context, Event*) override {} |
| }; |
| }; |
| |
| INSTANTIATE_PAINT_TEST_SUITE_P(FrameThrottlingTest); |
| |
| TEST_P(FrameThrottlingTest, ThrottleInvisibleFrames) { |
| SimRequest main_resource("https://example.com/", "text/html"); |
| |
| LoadURL("https://example.com/"); |
| main_resource.Complete("<iframe sandbox id=frame></iframe>"); |
| |
| auto* frame_element = |
| To<HTMLIFrameElement>(GetDocument().getElementById("frame")); |
| auto* frame_document = frame_element->contentDocument(); |
| |
| // Initially both frames are visible. |
| EXPECT_FALSE(GetDocument().View()->IsHiddenForThrottling()); |
| EXPECT_FALSE(frame_document->View()->IsHiddenForThrottling()); |
| |
| // Moving the child fully outside the parent makes it invisible. |
| frame_element->setAttribute(kStyleAttr, "transform: translateY(480px)"); |
| CompositeFrame(); |
| EXPECT_FALSE(GetDocument().View()->IsHiddenForThrottling()); |
| EXPECT_TRUE(frame_document->View()->IsHiddenForThrottling()); |
| |
| // A partially visible child is considered visible. |
| frame_element->setAttribute(kStyleAttr, |
| "transform: translate(-50px, 0px, 0px)"); |
| CompositeFrame(); |
| EXPECT_FALSE(GetDocument().View()->IsHiddenForThrottling()); |
| EXPECT_FALSE(frame_document->View()->IsHiddenForThrottling()); |
| } |
| |
| TEST_P(FrameThrottlingTest, HiddenSameOriginFramesAreNotThrottled) { |
| SimRequest main_resource("https://example.com/", "text/html"); |
| SimRequest frame_resource("https://example.com/iframe.html", "text/html"); |
| |
| LoadURL("https://example.com/"); |
| main_resource.Complete("<iframe id=frame src=iframe.html></iframe>"); |
| frame_resource.Complete("<iframe id=innerFrame></iframe>"); |
| |
| auto* frame_element = |
| To<HTMLIFrameElement>(GetDocument().getElementById("frame")); |
| auto* frame_document = frame_element->contentDocument(); |
| |
| auto* inner_frame_element = |
| To<HTMLIFrameElement>(frame_document->getElementById("innerFrame")); |
| auto* inner_frame_document = inner_frame_element->contentDocument(); |
| |
| EXPECT_FALSE(GetDocument().View()->CanThrottleRendering()); |
| EXPECT_FALSE(frame_document->View()->CanThrottleRendering()); |
| EXPECT_FALSE(inner_frame_document->View()->CanThrottleRendering()); |
| |
| // Hidden same origin frames are not throttled. |
| frame_element->setAttribute(kStyleAttr, "transform: translateY(480px)"); |
| CompositeFrame(); |
| EXPECT_FALSE(GetDocument().View()->CanThrottleRendering()); |
| EXPECT_FALSE(frame_document->View()->CanThrottleRendering()); |
| EXPECT_FALSE(inner_frame_document->View()->CanThrottleRendering()); |
| } |
| |
| TEST_P(FrameThrottlingTest, HiddenCrossOriginFramesAreThrottled) { |
| // Create a document with doubly nested iframes. |
| SimRequest main_resource("https://example.com/", "text/html"); |
| SimRequest frame_resource("https://example.com/iframe.html", "text/html"); |
| |
| LoadURL("https://example.com/"); |
| main_resource.Complete("<iframe id=frame src=iframe.html></iframe>"); |
| frame_resource.Complete("<iframe id=innerFrame sandbox></iframe>"); |
| |
| auto* frame_element = |
| To<HTMLIFrameElement>(GetDocument().getElementById("frame")); |
| auto* frame_document = frame_element->contentDocument(); |
| |
| auto* inner_frame_element = |
| To<HTMLIFrameElement>(frame_document->getElementById("innerFrame")); |
| auto* inner_frame_document = inner_frame_element->contentDocument(); |
| |
| EXPECT_FALSE(GetDocument().View()->CanThrottleRendering()); |
| EXPECT_FALSE(frame_document->View()->CanThrottleRendering()); |
| EXPECT_FALSE(inner_frame_document->View()->CanThrottleRendering()); |
| |
| // Hidden cross origin frames are throttled. |
| frame_element->setAttribute(kStyleAttr, "transform: translateY(480px)"); |
| CompositeFrame(); |
| EXPECT_FALSE(GetDocument().View()->CanThrottleRendering()); |
| EXPECT_FALSE(frame_document->View()->CanThrottleRendering()); |
| EXPECT_TRUE(inner_frame_document->View()->CanThrottleRendering()); |
| } |
| |
| TEST_P(FrameThrottlingTest, IntersectionObservationOverridesThrottling) { |
| // Create a document with doubly nested iframes. |
| SimRequest main_resource("https://example.com/", "text/html"); |
| SimRequest frame_resource("https://example.com/iframe.html", "text/html"); |
| |
| LoadURL("https://example.com/"); |
| main_resource.Complete("<iframe id=frame src=iframe.html></iframe>"); |
| frame_resource.Complete("<iframe id=innerFrame sandbox></iframe>"); |
| |
| auto* frame_element = |
| To<HTMLIFrameElement>(GetDocument().getElementById("frame")); |
| auto* frame_document = frame_element->contentDocument(); |
| |
| auto* inner_frame_element = |
| To<HTMLIFrameElement>(frame_document->getElementById("innerFrame")); |
| auto* inner_frame_document = inner_frame_element->contentDocument(); |
| |
| // Hidden cross origin frames are throttled. |
| frame_element->setAttribute(kStyleAttr, "transform: translateY(480px)"); |
| CompositeFrame(); |
| EXPECT_FALSE(GetDocument().View()->CanThrottleRendering()); |
| EXPECT_FALSE(frame_document->View()->CanThrottleRendering()); |
| EXPECT_TRUE(inner_frame_document->View()->ShouldThrottleRenderingForTest()); |
| |
| // An intersection observation overrides throttling during a lifecycle update. |
| inner_frame_document->View()->SetIntersectionObservationState( |
| LocalFrameView::kRequired); |
| { |
| GetDocument().GetFrame()->View()->SetTargetStateForTest( |
| DocumentLifecycle::kPaintClean); |
| inner_frame_document->Lifecycle().EnsureStateAtMost( |
| DocumentLifecycle::kVisualUpdatePending); |
| EXPECT_FALSE( |
| inner_frame_document->View()->ShouldThrottleRenderingForTest()); |
| GetDocument().GetFrame()->View()->SetTargetStateForTest( |
| DocumentLifecycle::kUninitialized); |
| } |
| |
| inner_frame_document->View()->ScheduleAnimation(); |
| |
| LayoutView* inner_view = inner_frame_document->View()->GetLayoutView(); |
| |
| inner_view->SetNeedsLayout("test"); |
| if (!RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) { |
| inner_view->Compositor()->SetNeedsCompositingUpdate( |
| kCompositingUpdateRebuildTree); |
| } |
| inner_view->SetShouldDoFullPaintInvalidation( |
| PaintInvalidationReason::kForTesting); |
| inner_view->Layer()->SetNeedsRepaint(); |
| EXPECT_TRUE(inner_frame_document->View() |
| ->GetLayoutView() |
| ->ShouldDoFullPaintInvalidation()); |
| if (!RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) { |
| inner_view->Compositor()->SetNeedsCompositingUpdate( |
| kCompositingUpdateRebuildTree); |
| EXPECT_EQ(kCompositingUpdateRebuildTree, |
| inner_view->Compositor()->pending_update_type_); |
| } |
| EXPECT_TRUE(inner_view->Layer()->SelfNeedsRepaint()); |
| |
| CompositeFrame(); |
| // The lifecycle update should only be overridden for one frame. |
| EXPECT_TRUE(inner_frame_document->View()->ShouldThrottleRenderingForTest()); |
| |
| EXPECT_FALSE(inner_view->NeedsLayout()); |
| EXPECT_LT(inner_frame_document->Lifecycle().GetState(), |
| DocumentLifecycle::kPaintClean); |
| if (!RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) { |
| // If IntersectionObserver is required to run, lifecycle will be updated |
| // through pre-paint, but not compositing assignment. |
| EXPECT_EQ(kCompositingUpdateRebuildTree, |
| inner_view->Compositor()->pending_update_type_); |
| } |
| EXPECT_TRUE(inner_view->Layer()->SelfNeedsRepaint()); |
| } |
| |
| TEST_P(FrameThrottlingTest, NestedIntersectionObservationStateUpdated) { |
| // Create two nested frames which are throttled. |
| SimRequest main_resource("https://example.com/", "text/html"); |
| SimRequest frame_resource("https://example.com/iframe.html", "text/html"); |
| SimRequest child_frame_resource("https://example.com/child-iframe.html", |
| "text/html"); |
| |
| LoadURL("https://example.com/"); |
| main_resource.Complete("<iframe id=frame sandbox src=iframe.html></iframe>"); |
| frame_resource.Complete( |
| "<iframe id=child-frame sandbox src=child-iframe.html></iframe>"); |
| child_frame_resource.Complete(""); |
| |
| // Move both frames offscreen to make them throttled. |
| auto* frame_element = |
| To<HTMLIFrameElement>(GetDocument().getElementById("frame")); |
| auto* child_frame_element = To<HTMLIFrameElement>( |
| frame_element->contentDocument()->getElementById("child-frame")); |
| frame_element->setAttribute(kStyleAttr, "transform: translateY(480px)"); |
| |
| CompositeFrame(); |
| |
| auto* root_view = LocalFrameRoot().GetFrame()->View(); |
| ASSERT_FALSE(root_view->ShouldThrottleRenderingForTest()); |
| auto* frame_view = frame_element->contentDocument()->View(); |
| ASSERT_TRUE(frame_view->ShouldThrottleRenderingForTest()); |
| auto* child_view = child_frame_element->contentDocument()->View(); |
| ASSERT_TRUE(child_view->ShouldThrottleRenderingForTest()); |
| |
| // Force |child_view| to do an intersection observation. |
| child_view->SetIntersectionObservationState(LocalFrameView::kRequired); |
| |
| // Ensure all frames need a layout. |
| root_view->SetNeedsLayout(); |
| frame_view->SetNeedsLayout(); |
| child_view->SetNeedsLayout(); |
| |
| // Though |frame_view| is throttled, the descendant (|child_view|) should |
| // still be updated which resets the intersection observation state. |
| CompositeFrame(); |
| EXPECT_EQ(LocalFrameView::kNotNeeded, |
| root_view->GetIntersectionObservationStateForTesting()); |
| EXPECT_EQ(LocalFrameView::kNotNeeded, |
| frame_view->GetIntersectionObservationStateForTesting()); |
| EXPECT_EQ(LocalFrameView::kNotNeeded, |
| child_view->GetIntersectionObservationStateForTesting()); |
| } |
| |
| // This test creates a throttled local root (simulating a throttled OOPIF) and |
| // ensures the intersection observation state of descendants can still be |
| // updated. |
| TEST_P(FrameThrottlingTest, |
| IntersectionObservationStateUpdatedWithThrottledLocalRoot) { |
| SimRequest local_root_resource("https://example.com/", "text/html"); |
| SimRequest child_resource("https://example.com/iframe.html", "text/html"); |
| |
| LoadURL("https://example.com/"); |
| local_root_resource.Complete( |
| "<iframe id=frame sandbox src=iframe.html></iframe>"); |
| child_resource.Complete(""); |
| CompositeFrame(); |
| |
| auto* root_frame = LocalFrameRoot().GetFrame(); |
| auto* root_frame_view = root_frame->View(); |
| root_frame_view->SetNeedsLayout(); |
| root_frame_view->ScheduleAnimation(); |
| root_frame_view->SetLifecycleUpdatesThrottledForTesting(true); |
| ASSERT_TRUE(root_frame->IsLocalRoot()); |
| ASSERT_TRUE(root_frame_view->ShouldThrottleRenderingForTest()); |
| |
| auto* child_frame_document = |
| To<HTMLIFrameElement>(root_frame->GetDocument()->getElementById("frame")) |
| ->contentDocument(); |
| auto* child_frame_view = child_frame_document->View(); |
| // Force |child_frame_view| to do an intersection observation. |
| child_frame_view->SetIntersectionObservationState(LocalFrameView::kRequired); |
| |
| // Though |root_frame_view| is throttled, the descendant (|child_frame_view|) |
| // should still be updated which resets the intersection observation state. |
| CompositeFrame(); |
| EXPECT_EQ(LocalFrameView::kNotNeeded, |
| root_frame_view->GetIntersectionObservationStateForTesting()); |
| EXPECT_EQ(LocalFrameView::kNotNeeded, |
| child_frame_view->GetIntersectionObservationStateForTesting()); |
| } |
| |
| TEST_P(FrameThrottlingTest, |
| ThrottlingOverrideOnlyAppliesDuringLifecycleUpdate) { |
| // Create a document with a hidden cross-origin subframe. |
| SimRequest main_resource("https://example.com/", "text/html"); |
| SimRequest frame_resource("https://example.com/iframe.html", "text/html"); |
| LoadURL("https://example.com/"); |
| main_resource.Complete(R"HTML( |
| <iframe id="frame" sandbox src="iframe.html" |
| style="transform: translateY(480px)"> |
| )HTML"); |
| frame_resource.Complete("<!doctype html>"); |
| |
| CompositeFrame(); |
| |
| auto* frame_element = |
| To<HTMLIFrameElement>(GetDocument().getElementById("frame")); |
| auto* frame_document = frame_element->contentDocument(); |
| // Hidden cross origin frames are throttled. |
| EXPECT_TRUE(frame_document->View()->ShouldThrottleRenderingForTest()); |
| |
| // An intersection observation overrides throttling, but this is only during |
| // the lifecycle. |
| frame_document->View()->SetIntersectionObservationState( |
| LocalFrameView::kRequired); |
| EXPECT_TRUE(frame_document->View()->ShouldThrottleRenderingForTest()); |
| { |
| GetDocument().GetFrame()->View()->SetTargetStateForTest( |
| DocumentLifecycle::kPaintClean); |
| frame_document->Lifecycle().EnsureStateAtMost( |
| DocumentLifecycle::kVisualUpdatePending); |
| EXPECT_FALSE(frame_document->View()->ShouldThrottleRenderingForTest()); |
| GetDocument().GetFrame()->View()->SetTargetStateForTest( |
| DocumentLifecycle::kUninitialized); |
| } |
| |
| // A lifecycle update can update the throttled frame to just LayoutClean but |
| // the frame should still be considered throttled outside the lifecycle |
| // because it is not fully running the lifecycle. |
| frame_document->View()->GetLayoutView()->SetNeedsLayout("test"); |
| frame_document->View()->ScheduleAnimation(); |
| frame_document->View()->GetLayoutView()->Layer()->SetNeedsRepaint(); |
| CompositeFrame(); |
| EXPECT_EQ(DocumentLifecycle::kPrePaintClean, |
| frame_document->Lifecycle().GetState()); |
| EXPECT_TRUE(frame_document->View()->ShouldThrottleRenderingForTest()); |
| } |
| |
| TEST_P(FrameThrottlingTest, ForAllThrottledLocalFrameViews) { |
| // Create a document with a hidden cross-origin subframe. |
| SimRequest main_resource("https://example.com/", "text/html"); |
| SimRequest frame_resource("https://example.com/iframe.html", "text/html"); |
| LoadURL("https://example.com/"); |
| main_resource.Complete(R"HTML( |
| <iframe id="frame" sandbox src="iframe.html" |
| style="transform: translateY(480px)"> |
| )HTML"); |
| frame_resource.Complete("<!doctype html>"); |
| |
| CompositeFrame(); |
| |
| auto* frame_element = |
| To<HTMLIFrameElement>(GetDocument().getElementById("frame")); |
| auto* frame_document = frame_element->contentDocument(); |
| // Hidden cross origin frames are throttled. |
| EXPECT_TRUE(frame_document->View()->ShouldThrottleRenderingForTest()); |
| // Main frame is not throttled. |
| EXPECT_FALSE(GetDocument().View()->ShouldThrottleRenderingForTest()); |
| |
| unsigned throttled_count = 0; |
| auto throttled_callback = base::BindLambdaForTesting( |
| [&throttled_count](LocalFrameView&) { throttled_count++; }); |
| GetDocument().View()->ForAllThrottledLocalFrameViewsForTesting( |
| throttled_callback); |
| EXPECT_EQ(1u, throttled_count); |
| } |
| |
| TEST_P(FrameThrottlingTest, HiddenCrossOriginZeroByZeroFramesAreNotThrottled) { |
| // Create a document with doubly nested iframes. |
| SimRequest main_resource("https://example.com/", "text/html"); |
| SimRequest frame_resource("https://example.com/iframe.html", "text/html"); |
| |
| LoadURL("https://example.com/"); |
| main_resource.Complete("<iframe id=frame src=iframe.html></iframe>"); |
| frame_resource.Complete( |
| "<iframe id=innerFrame width=0 height=0 sandbox></iframe>"); |
| |
| auto* frame_element = |
| To<HTMLIFrameElement>(GetDocument().getElementById("frame")); |
| auto* frame_document = frame_element->contentDocument(); |
| |
| auto* inner_frame_element = |
| To<HTMLIFrameElement>(frame_document->getElementById("innerFrame")); |
| auto* inner_frame_document = inner_frame_element->contentDocument(); |
| |
| EXPECT_FALSE(GetDocument().View()->CanThrottleRendering()); |
| EXPECT_FALSE(frame_document->View()->CanThrottleRendering()); |
| EXPECT_FALSE(inner_frame_document->View()->CanThrottleRendering()); |
| |
| // The frame is not throttled because its dimensions are 0x0. |
| frame_element->setAttribute(kStyleAttr, "transform: translateY(480px)"); |
| CompositeFrame(); |
| EXPECT_FALSE(GetDocument().View()->CanThrottleRendering()); |
| EXPECT_FALSE(frame_document->View()->CanThrottleRendering()); |
| EXPECT_FALSE(inner_frame_document->View()->CanThrottleRendering()); |
| } |
| |
| TEST_P(FrameThrottlingTest, ThrottledLifecycleUpdate) { |
| SimRequest main_resource("https://example.com/", "text/html"); |
| |
| LoadURL("https://example.com/"); |
| main_resource.Complete("<iframe sandbox id=frame></iframe>"); |
| |
| auto* frame_element = |
| To<HTMLIFrameElement>(GetDocument().getElementById("frame")); |
| auto* frame_document = frame_element->contentDocument(); |
| |
| // Enable throttling for the child frame. |
| frame_element->setAttribute(kStyleAttr, "transform: translateY(480px)"); |
| CompositeFrame(); |
| EXPECT_TRUE(frame_document->View()->CanThrottleRendering()); |
| EXPECT_EQ(DocumentLifecycle::kPaintClean, |
| frame_document->Lifecycle().GetState()); |
| |
| // Mutating the throttled frame followed by a beginFrame will not result in |
| // a complete lifecycle update. |
| // TODO(skyostil): these expectations are either wrong, or the test is |
| // not exercising the code correctly. PaintClean means the entire lifecycle |
| // ran. |
| frame_element->setAttribute(html_names::kWidthAttr, "50"); |
| CompositeFrame(); |
| |
| if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) { |
| EXPECT_EQ(DocumentLifecycle::kPaintClean, |
| frame_document->Lifecycle().GetState()); |
| |
| // A hit test will not force a complete lifecycle update. |
| WebView().MainFrameWidget()->HitTestResultAt(gfx::PointF()); |
| EXPECT_EQ(DocumentLifecycle::kPaintClean, |
| frame_document->Lifecycle().GetState()); |
| } else { |
| // TODO(chrishtr): fix this test by manually resetting to |
| // kVisualUpdatePending before call to CompositeFrame. |
| EXPECT_EQ(DocumentLifecycle::kPaintClean, |
| frame_document->Lifecycle().GetState()); |
| |
| // A hit test will not force a complete lifecycle update. |
| WebView().MainFrameWidget()->HitTestResultAt(gfx::PointF()); |
| EXPECT_EQ(DocumentLifecycle::kPaintClean, |
| frame_document->Lifecycle().GetState()); |
| } |
| } |
| |
| TEST_P(FrameThrottlingTest, UnthrottlingFrameSchedulesAnimation) { |
| SimRequest main_resource("https://example.com/", "text/html"); |
| |
| LoadURL("https://example.com/"); |
| main_resource.Complete("<iframe sandbox id=frame></iframe>"); |
| |
| auto* frame_element = |
| To<HTMLIFrameElement>(GetDocument().getElementById("frame")); |
| |
| // First make the child hidden to enable throttling. |
| frame_element->setAttribute(kStyleAttr, "transform: translateY(480px)"); |
| CompositeFrame(); |
| EXPECT_TRUE(frame_element->contentDocument()->View()->CanThrottleRendering()); |
| EXPECT_FALSE(Compositor().NeedsBeginFrame()); |
| |
| // Then bring it back on-screen. This should schedule an animation update. |
| frame_element->setAttribute(kStyleAttr, ""); |
| CompositeFrame(); |
| EXPECT_TRUE(Compositor().NeedsBeginFrame()); |
| CompositeFrame(); |
| EXPECT_FALSE(Compositor().NeedsBeginFrame()); |
| } |
| |
| TEST_P(FrameThrottlingTest, ThrottledFrameCompositing) { |
| if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) |
| return; |
| |
| SimRequest main_resource("https://example.com/", "text/html"); |
| |
| LoadURL("https://example.com/"); |
| main_resource.Complete(R"HTML( |
| <div id="container"> |
| <iframe sandbox id="frame"></iframe> |
| </div> |
| )HTML"); |
| |
| CompositeFrame(); |
| |
| auto* container_element = GetDocument().getElementById("container"); |
| auto* container = container_element->GetLayoutBox(); |
| EXPECT_EQ(container->GetCompositingState(), kNotComposited); |
| auto* frame_element = |
| To<HTMLIFrameElement>(GetDocument().getElementById("frame")); |
| auto* frame_view = frame_element->contentDocument()->View(); |
| EXPECT_FALSE(frame_view->CanThrottleRendering()); |
| auto* frame_layout_view = frame_view->GetLayoutView(); |
| EXPECT_TRUE(frame_layout_view->Compositor()->CanBeComposited( |
| frame_layout_view->Layer())); |
| auto* frame_graphics_layer = |
| frame_layout_view->Layer()->GraphicsLayerBacking(frame_layout_view); |
| EXPECT_TRUE(frame_graphics_layer); |
| auto* main_graphics_layer = |
| GetDocument().View()->GetLayoutView()->Layer()->GraphicsLayerBacking(); |
| EXPECT_EQ(main_graphics_layer, frame_graphics_layer->Parent()); |
| |
| // First make the child hidden to enable throttling, and composite |
| // the container. |
| container_element->setAttribute( |
| kStyleAttr, "will-change: transform; transform: translateY(480px)"); |
| CompositeFrame(); |
| EXPECT_TRUE(frame_view->CanThrottleRendering()); |
| auto* container_graphics_layer = container->Layer()->GraphicsLayerBacking(); |
| ASSERT_TRUE(container_graphics_layer); |
| EXPECT_EQ( |
| frame_graphics_layer, |
| frame_layout_view->Layer()->GraphicsLayerBacking(frame_layout_view)); |
| EXPECT_EQ(container_graphics_layer, frame_graphics_layer->Parent()); |
| EXPECT_FALSE(Compositor().NeedsBeginFrame()); |
| |
| // Then bring it back on-screen, and decomposite container. |
| container_element->setAttribute(kStyleAttr, ""); |
| CompositeFrame(); |
| ASSERT_TRUE(Compositor().NeedsBeginFrame()); |
| CompositeFrame(); |
| EXPECT_FALSE(frame_view->CanThrottleRendering()); |
| ASSERT_EQ(frame_layout_view, frame_view->GetLayoutView()); |
| EXPECT_TRUE(frame_layout_view->Compositor()->CanBeComposited( |
| frame_layout_view->Layer())); |
| EXPECT_EQ(container->GetCompositingState(), kNotComposited); |
| EXPECT_EQ( |
| frame_graphics_layer, |
| frame_layout_view->Layer()->GraphicsLayerBacking(frame_layout_view)); |
| EXPECT_EQ(main_graphics_layer, frame_graphics_layer->Parent()); |
| } |
| |
| TEST_P(FrameThrottlingTest, MutatingThrottledFrameDoesNotCauseAnimation) { |
| SimRequest main_resource("https://example.com/", "text/html"); |
| SimRequest frame_resource("https://example.com/iframe.html", "text/html"); |
| |
| LoadURL("https://example.com/"); |
| main_resource.Complete("<iframe id=frame sandbox src=iframe.html></iframe>"); |
| frame_resource.Complete("<style> html { background: red; } </style>"); |
| |
| // Check that the frame initially shows up. |
| auto commands1 = CompositeFrame(); |
| EXPECT_TRUE(commands1.Contains(SimCanvas::kRect, "red")); |
| |
| auto* frame_element = |
| To<HTMLIFrameElement>(GetDocument().getElementById("frame")); |
| |
| // Move the frame offscreen to throttle it. |
| frame_element->setAttribute(kStyleAttr, "transform: translateY(480px)"); |
| CompositeFrame(); |
| EXPECT_TRUE(frame_element->contentDocument()->View()->CanThrottleRendering()); |
| |
| // Mutating the throttled frame should not cause an animation to be scheduled. |
| frame_element->contentDocument()->documentElement()->setAttribute( |
| kStyleAttr, "background: green"); |
| EXPECT_FALSE(Compositor().NeedsBeginFrame()); |
| |
| // Move the frame back on screen to unthrottle it. |
| frame_element->setAttribute(kStyleAttr, ""); |
| EXPECT_TRUE(Compositor().NeedsBeginFrame()); |
| |
| // The first frame we composite after unthrottling won't contain the |
| // frame's new contents because unthrottling happens at the end of the |
| // lifecycle update. We need to do another composite to refresh the frame's |
| // contents. |
| auto commands2 = CompositeFrame(); |
| EXPECT_FALSE(commands2.Contains(SimCanvas::kRect, "green")); |
| EXPECT_TRUE(Compositor().NeedsBeginFrame()); |
| |
| auto commands3 = CompositeFrame(); |
| EXPECT_TRUE(commands3.Contains(SimCanvas::kRect, "green")); |
| } |
| |
| TEST_P(FrameThrottlingTest, SynchronousLayoutInThrottledFrame) { |
| // Create a hidden frame which is throttled. |
| SimRequest main_resource("https://example.com/", "text/html"); |
| SimRequest frame_resource("https://example.com/iframe.html", "text/html"); |
| |
| LoadURL("https://example.com/"); |
| main_resource.Complete("<iframe id=frame sandbox src=iframe.html></iframe>"); |
| frame_resource.Complete("<div id=div></div>"); |
| |
| auto* frame_element = |
| To<HTMLIFrameElement>(GetDocument().getElementById("frame")); |
| |
| frame_element->setAttribute(kStyleAttr, "transform: translateY(480px)"); |
| CompositeFrame(); |
| |
| // Change the size of a div in the throttled frame. |
| auto* div_element = frame_element->contentDocument()->getElementById("div"); |
| div_element->setAttribute(kStyleAttr, "width: 50px"); |
| |
| // Querying the width of the div should do a synchronous layout update even |
| // though the frame is being throttled. |
| EXPECT_EQ(50, div_element->clientWidth()); |
| } |
| |
| TEST_P(FrameThrottlingTest, UnthrottlingTriggersRepaint) { |
| // Create a hidden frame which is throttled. |
| SimRequest main_resource("https://example.com/", "text/html"); |
| SimRequest frame_resource("https://example.com/iframe.html", "text/html"); |
| |
| LoadURL("https://example.com/"); |
| main_resource.Complete("<iframe id=frame sandbox src=iframe.html></iframe>"); |
| frame_resource.Complete("<style> html { background: green; } </style>"); |
| |
| // Move the frame offscreen to throttle it. |
| auto* frame_element = |
| To<HTMLIFrameElement>(GetDocument().getElementById("frame")); |
| frame_element->setAttribute(kStyleAttr, "transform: translateY(480px)"); |
| EXPECT_FALSE( |
| frame_element->contentDocument()->View()->CanThrottleRendering()); |
| CompositeFrame(); |
| EXPECT_TRUE(frame_element->contentDocument()->View()->CanThrottleRendering()); |
| |
| // Scroll down to unthrottle the frame. The first frame we composite after |
| // scrolling won't contain the frame yet, but will schedule another repaint. |
| WebView().MainFrameImpl()->GetFrameView()->LayoutViewport()->SetScrollOffset( |
| ScrollOffset(0, 480), mojom::blink::ScrollType::kProgrammatic); |
| auto commands = CompositeFrame(); |
| EXPECT_FALSE(commands.Contains(SimCanvas::kRect, "green")); |
| |
| // Now the frame contents should be visible again. |
| auto commands2 = CompositeFrame(); |
| EXPECT_TRUE(commands2.Contains(SimCanvas::kRect, "green")); |
| } |
| |
| TEST_P(FrameThrottlingTest, UnthrottlingTriggersRepaintInCompositedChild) { |
| // Create a hidden frame with a composited child layer. |
| SimRequest main_resource("https://example.com/", "text/html"); |
| SimRequest frame_resource("https://example.com/iframe.html", "text/html"); |
| |
| LoadURL("https://example.com/"); |
| main_resource.Complete("<iframe id=frame sandbox src=iframe.html></iframe>"); |
| frame_resource.Complete(R"HTML( |
| <style> |
| div { |
| width: 100px; |
| height: 100px; |
| background-color: green; |
| transform: translateZ(0); |
| } |
| </style><div></div> |
| )HTML"); |
| |
| // Move the frame offscreen to throttle it. |
| auto* frame_element = |
| To<HTMLIFrameElement>(GetDocument().getElementById("frame")); |
| frame_element->setAttribute(kStyleAttr, "transform: translateY(480px)"); |
| EXPECT_FALSE( |
| frame_element->contentDocument()->View()->CanThrottleRendering()); |
| CompositeFrame(); |
| EXPECT_TRUE(frame_element->contentDocument()->View()->CanThrottleRendering()); |
| |
| // Scroll down to unthrottle the frame. The first frame we composite after |
| // scrolling won't contain the frame yet, but will schedule another repaint. |
| WebView().MainFrameImpl()->GetFrameView()->LayoutViewport()->SetScrollOffset( |
| ScrollOffset(0, 480), mojom::blink::ScrollType::kProgrammatic); |
| auto commands = CompositeFrame(); |
| EXPECT_FALSE(commands.Contains(SimCanvas::kRect, "green")); |
| |
| // Now the composited child contents should be visible again. |
| auto commands2 = CompositeFrame(); |
| EXPECT_TRUE(commands2.Contains(SimCanvas::kRect, "green")); |
| } |
| |
| TEST_P(FrameThrottlingTest, ChangeStyleInThrottledFrame) { |
| // Create a hidden frame which is throttled. |
| SimRequest main_resource("https://example.com/", "text/html"); |
| SimRequest frame_resource("https://example.com/iframe.html", "text/html"); |
| |
| LoadURL("https://example.com/"); |
| main_resource.Complete("<iframe id=frame sandbox src=iframe.html></iframe>"); |
| frame_resource.Complete("<style> html { background: red; } </style>"); |
| |
| // Move the frame offscreen to throttle it. |
| auto* frame_element = |
| To<HTMLIFrameElement>(GetDocument().getElementById("frame")); |
| frame_element->setAttribute(kStyleAttr, "transform: translateY(480px)"); |
| EXPECT_FALSE( |
| frame_element->contentDocument()->View()->CanThrottleRendering()); |
| CompositeFrame(); |
| EXPECT_TRUE(frame_element->contentDocument()->View()->CanThrottleRendering()); |
| |
| // Change the background color of the frame's contents from red to green. |
| frame_element->contentDocument()->body()->setAttribute(kStyleAttr, |
| "background: green"); |
| |
| // Scroll down to unthrottle the frame. |
| WebView().MainFrameImpl()->GetFrameView()->LayoutViewport()->SetScrollOffset( |
| ScrollOffset(0, 480), mojom::blink::ScrollType::kProgrammatic); |
| auto commands = CompositeFrame(); |
| EXPECT_FALSE(commands.Contains(SimCanvas::kRect, "red")); |
| EXPECT_FALSE(commands.Contains(SimCanvas::kRect, "green")); |
| |
| // Make sure the new style shows up instead of the old one. |
| auto commands2 = CompositeFrame(); |
| EXPECT_TRUE(commands2.Contains(SimCanvas::kRect, "green")); |
| } |
| |
| TEST_P(FrameThrottlingTest, ChangeOriginInThrottledFrame) { |
| // Create a hidden frame which is throttled. |
| SimRequest main_resource("http://example.com/", "text/html"); |
| SimRequest frame_resource("http://sub.example.com/iframe.html", "text/html"); |
| LoadURL("http://example.com/"); |
| main_resource.Complete( |
| "<iframe style='position: absolute; top: 10000px' id=frame " |
| "src=http://sub.example.com/iframe.html></iframe>"); |
| frame_resource.Complete(""); |
| |
| auto* frame_element = |
| To<HTMLIFrameElement>(GetDocument().getElementById("frame")); |
| |
| CompositeFrame(); |
| |
| EXPECT_TRUE(frame_element->contentDocument()->View()->CanThrottleRendering()); |
| EXPECT_TRUE( |
| frame_element->contentDocument()->GetFrame()->IsCrossOriginToMainFrame()); |
| EXPECT_FALSE(frame_element->contentDocument() |
| ->View() |
| ->GetLayoutView() |
| ->NeedsPaintPropertyUpdate()); |
| |
| NonThrowableExceptionState exception_state; |
| |
| // Security policy requires setting domain on both frames. |
| GetDocument().setDomain(String("example.com"), exception_state); |
| frame_element->contentDocument()->setDomain(String("example.com"), |
| exception_state); |
| |
| EXPECT_FALSE( |
| frame_element->contentDocument()->GetFrame()->IsCrossOriginToMainFrame()); |
| EXPECT_FALSE( |
| frame_element->contentDocument()->View()->CanThrottleRendering()); |
| EXPECT_TRUE(frame_element->contentDocument() |
| ->View() |
| ->GetLayoutView() |
| ->NeedsPaintPropertyUpdate()); |
| } |
| |
| TEST_P(FrameThrottlingTest, MainFrameOriginChangeInvalidatesDescendants) { |
| // Create a hidden frame which is throttled. |
| SimRequest main_resource("https://sub.example.com/", "text/html"); |
| SimRequest frame_resource("https://example.com/iframe.html", "text/html"); |
| LoadURL("https://sub.example.com/"); |
| main_resource.Complete(R"HTML( |
| <iframe id='frame' style='position: absolute; top: 10000px' |
| src='https://example.com/iframe.html'></iframe> |
| )HTML"); |
| frame_resource.Complete(""); |
| |
| CompositeFrame(); |
| |
| auto* frame_element = |
| To<HTMLIFrameElement>(GetDocument().getElementById("frame")); |
| auto* frame_document = frame_element->contentDocument(); |
| EXPECT_TRUE(frame_document->View()->CanThrottleRendering()); |
| EXPECT_TRUE(frame_document->GetFrame()->IsCrossOriginToMainFrame()); |
| EXPECT_FALSE( |
| frame_document->View()->GetLayoutView()->NeedsPaintPropertyUpdate()); |
| |
| // Set the domain of the child frame first which should be a no-op in terms of |
| // cross-origin status changes. |
| NonThrowableExceptionState exception_state; |
| frame_element->contentDocument()->setDomain(String("example.com"), |
| exception_state); |
| EXPECT_TRUE(frame_document->View()->CanThrottleRendering()); |
| EXPECT_TRUE(frame_document->GetFrame()->IsCrossOriginToMainFrame()); |
| EXPECT_FALSE( |
| frame_document->View()->GetLayoutView()->NeedsPaintPropertyUpdate()); |
| |
| // Then change the main frame origin which needs to invalidate the newly |
| // cross-origin child. |
| GetDocument().setDomain(String("example.com"), exception_state); |
| EXPECT_FALSE(frame_document->GetFrame()->IsCrossOriginToMainFrame()); |
| EXPECT_FALSE(frame_document->View()->CanThrottleRendering()); |
| EXPECT_TRUE( |
| frame_document->View()->GetLayoutView()->NeedsPaintPropertyUpdate()); |
| } |
| |
| TEST_P(FrameThrottlingTest, ThrottledFrameWithFocus) { |
| WebView().GetSettings()->SetJavaScriptEnabled(true); |
| ScopedCompositedSelectionUpdateForTest composited_selection_update(true); |
| |
| // Create a hidden frame which is throttled and has a text selection. |
| SimRequest main_resource("https://example.com/", "text/html"); |
| SimRequest frame_resource("https://example.com/iframe.html", "text/html"); |
| |
| LoadURL("https://example.com/"); |
| main_resource.Complete( |
| "<iframe id=frame sandbox=allow-scripts src=iframe.html></iframe>"); |
| frame_resource.Complete( |
| "some text to select\n" |
| "<script>\n" |
| "var range = document.createRange();\n" |
| "range.selectNode(document.body);\n" |
| "window.getSelection().addRange(range);\n" |
| "</script>\n"); |
| |
| // Move the frame offscreen to throttle it. |
| auto* frame_element = |
| To<HTMLIFrameElement>(GetDocument().getElementById("frame")); |
| frame_element->setAttribute(kStyleAttr, "transform: translateY(480px)"); |
| EXPECT_FALSE( |
| frame_element->contentDocument()->View()->CanThrottleRendering()); |
| CompositeFrame(); |
| EXPECT_TRUE(frame_element->contentDocument()->View()->CanThrottleRendering()); |
| |
| // Give the frame focus and do another composite. The selection in the |
| // compositor should be cleared because the frame is throttled. |
| EXPECT_FALSE(Compositor().HasSelection()); |
| GetDocument().GetPage()->GetFocusController().SetFocusedFrame( |
| frame_element->contentDocument()->GetFrame()); |
| GetDocument().body()->setAttribute(kStyleAttr, "background: green"); |
| CompositeFrame(); |
| EXPECT_FALSE(Compositor().HasSelection()); |
| } |
| |
| TEST_P(FrameThrottlingTest, ScrollingCoordinatorShouldSkipThrottledFrame) { |
| // Create a hidden frame which is throttled. |
| SimRequest main_resource("https://example.com/", "text/html"); |
| SimRequest frame_resource("https://example.com/iframe.html", "text/html"); |
| |
| LoadURL("https://example.com/"); |
| main_resource.Complete("<iframe id=frame sandbox src=iframe.html></iframe>"); |
| frame_resource.Complete(R"HTML( |
| <style> |
| html { |
| background-image: linear-gradient(red, blue); |
| background-attachment: fixed; |
| will-change: transform; |
| } |
| </style> |
| )HTML"); |
| |
| // Move the frame offscreen to throttle it. |
| auto* frame_element = |
| To<HTMLIFrameElement>(GetDocument().getElementById("frame")); |
| frame_element->setAttribute(kStyleAttr, "transform: translateY(480px)"); |
| EXPECT_FALSE( |
| frame_element->contentDocument()->View()->CanThrottleRendering()); |
| CompositeFrame(); |
| EXPECT_TRUE(frame_element->contentDocument()->View()->CanThrottleRendering()); |
| |
| // Change style of the frame's content to make it in VisualUpdatePending |
| // state. |
| frame_element->contentDocument()->body()->setAttribute(kStyleAttr, |
| "background: green"); |
| // Change root frame's layout so that the next lifecycle update will call |
| // ScrollingCoordinator::UpdateAfterPaint(). |
| GetDocument().body()->setAttribute(kStyleAttr, "margin: 20px"); |
| EXPECT_EQ(DocumentLifecycle::kVisualUpdatePending, |
| frame_element->contentDocument()->Lifecycle().GetState()); |
| |
| // This will call ScrollingCoordinator::UpdateAfterPaint() and should not |
| // cause assert failure about isAllowedToQueryCompositingState() in the |
| // throttled frame. |
| UpdateAllLifecyclePhases(); |
| test::RunPendingTasks(); |
| EXPECT_EQ(DocumentLifecycle::kVisualUpdatePending, |
| frame_element->contentDocument()->Lifecycle().GetState()); |
| // The fixed background in the throttled sub frame should not cause main |
| // thread scrolling. |
| EXPECT_FALSE( |
| GetDocument().View()->LayoutViewport()->ShouldScrollOnMainThread()); |
| |
| // Make the frame visible by changing its transform. This doesn't cause a |
| // layout, but should still unthrottle the frame. |
| frame_element->setAttribute(kStyleAttr, "transform: translateY(0px)"); |
| CompositeFrame(); |
| EXPECT_FALSE( |
| frame_element->contentDocument()->View()->CanThrottleRendering()); |
| // This CompositeFrame handles the visual update scheduled when we unthrottle |
| // the iframe. |
| CompositeFrame(); |
| // The fixed background in the throttled sub frame should be considered. |
| EXPECT_TRUE(frame_element->contentDocument() |
| ->View() |
| ->LayoutViewport() |
| ->ShouldScrollOnMainThread()); |
| EXPECT_FALSE( |
| GetDocument().View()->LayoutViewport()->ShouldScrollOnMainThread()); |
| } |
| |
| TEST_P(FrameThrottlingTest, ScrollingCoordinatorShouldSkipThrottledLayer) { |
| WebView().GetSettings()->SetJavaScriptEnabled(true); |
| WebView().GetSettings()->SetPreferCompositingToLCDTextEnabled(true); |
| |
| // Create a hidden frame which is throttled and has a touch handler inside a |
| // composited layer. |
| SimRequest main_resource("https://example.com/", "text/html"); |
| SimRequest frame_resource("https://example.com/iframe.html", "text/html"); |
| |
| LoadURL("https://example.com/"); |
| main_resource.Complete( |
| "<iframe id=frame sandbox=allow-scripts src=iframe.html></iframe>"); |
| frame_resource.Complete( |
| "<div id=div style='transform: translateZ(0)' ontouchstart='foo()'>touch " |
| "handler</div>"); |
| |
| // Move the frame offscreen to throttle it. |
| auto* frame_element = |
| To<HTMLIFrameElement>(GetDocument().getElementById("frame")); |
| frame_element->setAttribute(kStyleAttr, "transform: translateY(480px)"); |
| EXPECT_FALSE( |
| frame_element->contentDocument()->View()->CanThrottleRendering()); |
| CompositeFrame(); |
| EXPECT_TRUE(frame_element->contentDocument()->View()->CanThrottleRendering()); |
| |
| // Change style of the frame's content to make it in VisualUpdatePending |
| // state. |
| frame_element->contentDocument()->body()->setAttribute(kStyleAttr, |
| "background: green"); |
| // Change root frame's layout so that the next lifecycle update will call |
| // ScrollingCoordinator::UpdateAfterPaint(). |
| GetDocument().body()->setAttribute(kStyleAttr, "margin: 20px"); |
| EXPECT_EQ(DocumentLifecycle::kVisualUpdatePending, |
| frame_element->contentDocument()->Lifecycle().GetState()); |
| |
| // This will call ScrollingCoordinator::UpdateAfterPaint() and should not |
| // cause an assert failure about isAllowedToQueryCompositingState() in the |
| // throttled frame. |
| UpdateAllLifecyclePhases(); |
| test::RunPendingTasks(); |
| EXPECT_EQ(DocumentLifecycle::kVisualUpdatePending, |
| frame_element->contentDocument()->Lifecycle().GetState()); |
| } |
| |
| TEST_P(FrameThrottlingTest, |
| ScrollingCoordinatorShouldSkipCompositedThrottledFrame) { |
| WebView().GetSettings()->SetPreferCompositingToLCDTextEnabled(true); |
| |
| // Create a hidden frame which is throttled. |
| SimRequest main_resource("https://example.com/", "text/html"); |
| SimRequest frame_resource("https://example.com/iframe.html", "text/html"); |
| |
| LoadURL("https://example.com/"); |
| main_resource.Complete("<iframe id=frame sandbox src=iframe.html></iframe>"); |
| frame_resource.Complete("<div style='height: 2000px'></div>"); |
| |
| // Move the frame offscreen to throttle it. |
| auto* frame_element = |
| To<HTMLIFrameElement>(GetDocument().getElementById("frame")); |
| frame_element->setAttribute(kStyleAttr, "transform: translateY(480px)"); |
| EXPECT_FALSE( |
| frame_element->contentDocument()->View()->CanThrottleRendering()); |
| CompositeFrame(); |
| EXPECT_TRUE(frame_element->contentDocument()->View()->CanThrottleRendering()); |
| |
| // Change style of the frame's content to make it in VisualUpdatePending |
| // state. |
| frame_element->contentDocument()->body()->setAttribute(kStyleAttr, |
| "background: green"); |
| // Change root frame's layout so that the next lifecycle update will call |
| // ScrollingCoordinator::UpdateAfterPaint(). |
| GetDocument().body()->setAttribute(kStyleAttr, "margin: 20px"); |
| EXPECT_EQ(DocumentLifecycle::kVisualUpdatePending, |
| frame_element->contentDocument()->Lifecycle().GetState()); |
| |
| // This will call ScrollingCoordinator::UpdateAfterPaint() and should not |
| // cause an assert failure about isAllowedToQueryCompositingState() in the |
| // throttled frame. |
| CompositeFrame(); |
| EXPECT_EQ(DocumentLifecycle::kVisualUpdatePending, |
| frame_element->contentDocument()->Lifecycle().GetState()); |
| |
| // Make the frame visible by changing its transform. This doesn't cause a |
| // layout, but should still unthrottle the frame. |
| frame_element->setAttribute(kStyleAttr, "transform: translateY(0px)"); |
| CompositeFrame(); // Unthrottle the frame. |
| |
| EXPECT_FALSE(frame_element->contentDocument() |
| ->View() |
| ->ShouldThrottleRenderingForTest()); |
| // Handle the pending visual update of the unthrottled frame. |
| CompositeFrame(); |
| EXPECT_EQ(DocumentLifecycle::kPaintClean, |
| frame_element->contentDocument()->Lifecycle().GetState()); |
| } |
| |
| TEST_P(FrameThrottlingTest, UnthrottleByTransformingWithoutLayout) { |
| // Create a hidden frame which is throttled. |
| SimRequest main_resource("https://example.com/", "text/html"); |
| SimRequest frame_resource("https://example.com/iframe.html", "text/html"); |
| |
| LoadURL("https://example.com/"); |
| main_resource.Complete("<iframe id=frame sandbox src=iframe.html></iframe>"); |
| frame_resource.Complete(""); |
| |
| // Move the frame offscreen to throttle it. |
| auto* frame_element = |
| To<HTMLIFrameElement>(GetDocument().getElementById("frame")); |
| frame_element->setAttribute(kStyleAttr, "transform: translateY(480px)"); |
| EXPECT_FALSE( |
| frame_element->contentDocument()->View()->CanThrottleRendering()); |
| CompositeFrame(); |
| EXPECT_TRUE(frame_element->contentDocument()->View()->CanThrottleRendering()); |
| |
| // Make the frame visible by changing its transform. This doesn't cause a |
| // layout, but should still unthrottle the frame. |
| frame_element->setAttribute(kStyleAttr, "transform: translateY(0px)"); |
| CompositeFrame(); |
| EXPECT_FALSE( |
| frame_element->contentDocument()->View()->CanThrottleRendering()); |
| } |
| |
| TEST_P(FrameThrottlingTest, DumpThrottledFrame) { |
| WebView().GetSettings()->SetJavaScriptEnabled(true); |
| |
| // Create a frame which is throttled. |
| SimRequest main_resource("https://example.com/", "text/html"); |
| SimRequest frame_resource("https://example.com/iframe.html", "text/html"); |
| |
| LoadURL("https://example.com/"); |
| main_resource.Complete( |
| "main <iframe id=frame sandbox=allow-scripts src=iframe.html></iframe>"); |
| frame_resource.Complete(""); |
| auto* frame_element = |
| To<HTMLIFrameElement>(GetDocument().getElementById("frame")); |
| frame_element->setAttribute(kStyleAttr, "transform: translateY(480px)"); |
| CompositeFrame(); |
| EXPECT_TRUE(frame_element->contentDocument()->View()->CanThrottleRendering()); |
| |
| ClassicScript::CreateUnspecifiedScript( |
| ScriptSourceCode("document.body.innerHTML = 'throttled'")) |
| ->RunScript(To<LocalDOMWindow>(frame_element->contentWindow())); |
| EXPECT_FALSE(Compositor().NeedsBeginFrame()); |
| |
| // The dumped contents should not include the throttled frame. |
| WebString result = |
| TestWebFrameContentDumper::DumpWebViewAsText(&WebView(), 1024); |
| EXPECT_NE(std::string::npos, result.Utf8().find("main")); |
| EXPECT_EQ(std::string::npos, result.Utf8().find("throttled")); |
| } |
| |
| TEST_P(FrameThrottlingTest, PaintingViaGraphicsLayerIsThrottled) { |
| // GraphicsLayers are not created with CompositeAfterPaint. |
| if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) |
| return; |
| |
| // TODO(wangxianzhu): See the TODO in CullRectUpdater::SetFragmentCullRects(). |
| if (RuntimeEnabledFeatures::CullRectUpdateEnabled()) |
| return; |
| |
| WebView().GetSettings()->SetPreferCompositingToLCDTextEnabled(true); |
| |
| // Create a hidden frame which is throttled. |
| SimRequest main_resource("https://example.com/", "text/html"); |
| SimRequest frame_resource("https://example.com/iframe.html", "text/html"); |
| |
| LoadURL("https://example.com/"); |
| main_resource.Complete("<iframe id=frame sandbox src=iframe.html></iframe>"); |
| frame_resource.Complete("throttled"); |
| |
| // Before the iframe is throttled, we should create all drawing items. |
| auto commands_not_throttled = CompositeFrame(); |
| EXPECT_EQ(6u, commands_not_throttled.DrawCount()); |
| |
| // Move the frame offscreen to throttle it and make sure it is backed by a |
| // graphics layer. |
| auto* frame_element = |
| To<HTMLIFrameElement>(GetDocument().getElementById("frame")); |
| frame_element->setAttribute(kStyleAttr, |
| "transform: translateY(480px) translateZ(0px)"); |
| EXPECT_FALSE( |
| frame_element->contentDocument()->View()->CanThrottleRendering()); |
| CompositeFrame(); |
| EXPECT_TRUE(frame_element->contentDocument()->View()->CanThrottleRendering()); |
| |
| // If painting of the iframe is throttled, we should only receive drawing |
| // commands for the main frame. We have to explicitly schedule a frame here |
| // because the iframe becoming throttled will affect the painted output; |
| // but it will not by itself schedule an animation frame, because it doesn't |
| // need display. |
| GetDocument().View()->ScheduleAnimation(); |
| auto commands_throttled = CompositeFrame(); |
| EXPECT_EQ(5u, commands_throttled.DrawCount()); |
| EXPECT_FALSE(Compositor().NeedsBeginFrame()); |
| } |
| |
| TEST_P(FrameThrottlingTest, ThrottleInnerCompositedLayer) { |
| WebView().GetSettings()->SetPreferCompositingToLCDTextEnabled(true); |
| |
| // Create a hidden frame which is throttled. |
| SimRequest main_resource("https://example.com/", "text/html"); |
| SimRequest frame_resource("https://example.com/iframe.html", "text/html"); |
| |
| LoadURL("https://example.com/"); |
| main_resource.Complete("<iframe id=frame sandbox src=iframe.html></iframe>"); |
| frame_resource.Complete( |
| "<div id=div style='will-change: transform; background: blue'>DIV</div>"); |
| auto commands_not_throttled = CompositeFrame(); |
| |
| auto* frame_element = |
| To<HTMLIFrameElement>(GetDocument().getElementById("frame")); |
| auto* root_layer = WebView().MainFrameImpl()->GetFrameView()->RootCcLayer(); |
| // The inner div is composited. |
| EXPECT_EQ(1u, CcLayersByDOMElementId(root_layer, "div").size()); |
| |
| // Before the iframe is throttled, we should create all drawing commands. |
| unsigned full_draw_count = 7u; |
| EXPECT_EQ(full_draw_count, commands_not_throttled.DrawCount()); |
| |
| // Move the frame offscreen to throttle it. |
| frame_element->setAttribute(kStyleAttr, "transform: translateY(480px)"); |
| EXPECT_FALSE( |
| frame_element->contentDocument()->View()->CanThrottleRendering()); |
| CompositeFrame(); |
| EXPECT_TRUE(frame_element->contentDocument()->View()->CanThrottleRendering()); |
| // The inner div should still be composited. |
| EXPECT_EQ(1u, CcLayersByDOMElementId(root_layer, "div").size()); |
| |
| // If painting of the iframe is throttled, we should only receive drawing |
| // commands for the main frame. |
| GetDocument().View()->ScheduleAnimation(); |
| auto commands_throttled = Compositor().BeginFrame(); |
| EXPECT_LT(commands_throttled.DrawCount(), full_draw_count); |
| |
| // Remove compositing trigger of inner_div. |
| auto* inner_div = frame_element->contentDocument()->getElementById("div"); |
| inner_div->setAttribute(kStyleAttr, "background: yellow; overflow: hidden"); |
| // Do an unthrottled style and layout update, simulating the situation |
| // triggered by script style/layout access. |
| GetDocument().View()->UpdateLifecycleToLayoutClean( |
| DocumentUpdateReason::kTest); |
| |
| // And a throttled full lifecycle update. |
| UpdateAllLifecyclePhases(); |
| |
| if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) { |
| // Leave the composited layer of the inner div as-is because we don't |
| // repaint it. |
| EXPECT_EQ(1u, CcLayersByDOMElementId(root_layer, "div").size()); |
| } else { |
| // The inner div is no longer composited. |
| EXPECT_EQ(0u, CcLayersByDOMElementId(root_layer, "div").size()); |
| } |
| |
| auto commands_throttled1 = CompositeFrame(); |
| EXPECT_LT(commands_throttled1.DrawCount(), full_draw_count); |
| |
| // Move the frame back on screen. |
| frame_element->setAttribute(kStyleAttr, ""); |
| CompositeFrame(); |
| EXPECT_FALSE( |
| frame_element->contentDocument()->View()->CanThrottleRendering()); |
| auto commands_not_throttled1 = CompositeFrame(); |
| // The inner div is still not composited. |
| EXPECT_EQ(0u, CcLayersByDOMElementId(root_layer, "div").size()); |
| |
| // After the iframe is unthrottled, we should create all drawing items. |
| EXPECT_EQ(commands_not_throttled1.DrawCount(), full_draw_count); |
| } |
| |
| TEST_P(FrameThrottlingTest, ThrottleSubtreeAtomically) { |
| // Create two nested frames which are throttled. |
| SimRequest main_resource("https://example.com/", "text/html"); |
| SimRequest frame_resource("https://example.com/iframe.html", "text/html"); |
| SimRequest child_frame_resource("https://example.com/child-iframe.html", |
| "text/html"); |
| |
| LoadURL("https://example.com/"); |
| main_resource.Complete("<iframe id=frame sandbox src=iframe.html></iframe>"); |
| frame_resource.Complete( |
| "<iframe id=child-frame sandbox src=child-iframe.html></iframe>"); |
| child_frame_resource.Complete(""); |
| |
| // Move both frames offscreen. IntersectionObservers will run during |
| // post-lifecycle steps and synchronously update throttling status. |
| auto* frame_element = |
| To<HTMLIFrameElement>(GetDocument().getElementById("frame")); |
| auto* child_frame_element = To<HTMLIFrameElement>( |
| frame_element->contentDocument()->getElementById("child-frame")); |
| frame_element->setAttribute(kStyleAttr, "transform: translateY(480px)"); |
| Compositor().BeginFrame(); |
| EXPECT_TRUE(frame_element->contentDocument()->View()->CanThrottleRendering()); |
| EXPECT_TRUE( |
| child_frame_element->contentDocument()->View()->CanThrottleRendering()); |
| |
| // Move the frame back on screen. |
| frame_element->setAttribute(kStyleAttr, "transform: translateY(0px)"); |
| Compositor().BeginFrame(); |
| EXPECT_FALSE( |
| frame_element->contentDocument()->View()->CanThrottleRendering()); |
| EXPECT_FALSE( |
| child_frame_element->contentDocument()->View()->CanThrottleRendering()); |
| } |
| |
| TEST_P(FrameThrottlingTest, SkipPaintingLayersInThrottledFrames) { |
| WebView().GetSettings()->SetPreferCompositingToLCDTextEnabled(true); |
| |
| SimRequest main_resource("https://example.com/", "text/html"); |
| SimRequest frame_resource("https://example.com/iframe.html", "text/html"); |
| |
| LoadURL("https://example.com/"); |
| main_resource.Complete("<iframe id=frame sandbox src=iframe.html></iframe>"); |
| frame_resource.Complete( |
| "<div id=div style='transform: translateZ(0); background: " |
| "red'>layer</div>"); |
| auto commands = CompositeFrame(); |
| EXPECT_TRUE(commands.Contains(SimCanvas::kRect, "red")); |
| |
| auto* frame_element = |
| To<HTMLIFrameElement>(GetDocument().getElementById("frame")); |
| frame_element->setAttribute(kStyleAttr, "transform: translateY(480px)"); |
| CompositeFrame(); |
| EXPECT_TRUE(frame_element->contentDocument()->View()->CanThrottleRendering()); |
| |
| auto* frame_document = frame_element->contentDocument(); |
| EXPECT_EQ(DocumentLifecycle::kPaintClean, |
| frame_document->Lifecycle().GetState()); |
| |
| // Simulate the paint for a graphics layer being externally invalidated |
| // (e.g., by video playback). |
| frame_document->View() |
| ->GetLayoutView() |
| ->InvalidatePaintForViewAndDescendants(); |
| |
| // The layer inside the throttled frame should not get painted. |
| auto commands2 = CompositeFrame(); |
| EXPECT_FALSE(commands2.Contains(SimCanvas::kRect, "red")); |
| } |
| |
| TEST_P(FrameThrottlingTest, SynchronousLayoutInAnimationFrameCallback) { |
| WebView().GetSettings()->SetJavaScriptEnabled(true); |
| |
| // Prepare a page with two cross origin frames (from the same origin so they |
| // are able to access eachother). |
| SimRequest main_resource("https://example.com/", "text/html"); |
| SimRequest first_frame_resource("https://thirdparty.com/first.html", |
| "text/html"); |
| SimRequest second_frame_resource("https://thirdparty.com/second.html", |
| "text/html"); |
| LoadURL("https://example.com/"); |
| main_resource.Complete(R"HTML( |
| <iframe id=first name=first |
| src='https://thirdparty.com/first.html'></iframe>\n |
| <iframe id=second name=second |
| src='https://thirdparty.com/second.html'></iframe> |
| )HTML"); |
| |
| // The first frame contains just a simple div. This frame will be made |
| // throttled. |
| first_frame_resource.Complete("<div id=d>first frame</div>"); |
| |
| // The second frame just used to execute a requestAnimationFrame callback. |
| second_frame_resource.Complete(""); |
| |
| // Throttle the first frame. |
| auto* first_frame_element = |
| To<HTMLIFrameElement>(GetDocument().getElementById("first")); |
| first_frame_element->setAttribute(kStyleAttr, "transform: translateY(480px)"); |
| CompositeFrame(); |
| EXPECT_TRUE( |
| first_frame_element->contentDocument()->View()->CanThrottleRendering()); |
| |
| // Run a animation frame callback in the second frame which mutates the |
| // contents of the first frame and causes a synchronous style update. This |
| // should not result in an unexpected lifecycle state even if the first |
| // frame is throttled during the animation frame callback. |
| auto* second_frame_element = |
| To<HTMLIFrameElement>(GetDocument().getElementById("second")); |
| ClassicScript::CreateUnspecifiedScript( |
| ScriptSourceCode( |
| "window.requestAnimationFrame(function() {\n" |
| " var throttledFrame = window.parent.frames.first;\n" |
| " throttledFrame.document.documentElement.style = 'margin: 50px';\n" |
| " " |
| "throttledFrame.document.querySelector('#d').getBoundingClientRect();" |
| "\n" |
| "});\n")) |
| ->RunScript(To<LocalDOMWindow>(second_frame_element->contentWindow())); |
| CompositeFrame(); |
| } |
| |
| TEST_P(FrameThrottlingTest, AllowOneAnimationFrame) { |
| WebView().GetSettings()->SetJavaScriptEnabled(true); |
| |
| // Prepare a page with two cross origin frames (from the same origin so they |
| // are able to access eachother). |
| SimRequest main_resource("https://example.com/", "text/html"); |
| SimRequest frame_resource("https://thirdparty.com/frame.html", "text/html"); |
| LoadURL("https://example.com/"); |
| main_resource.Complete( |
| "<iframe id=frame style=\"position: fixed; top: -10000px\" " |
| "src='https://thirdparty.com/frame.html'></iframe>"); |
| |
| frame_resource.Complete(R"HTML( |
| <script> |
| window.requestAnimationFrame(() => { window.didRaf = true; }); |
| </script> |
| )HTML"); |
| |
| auto* frame_element = |
| To<HTMLIFrameElement>(GetDocument().getElementById("frame")); |
| CompositeFrame(); |
| EXPECT_TRUE(frame_element->contentDocument()->View()->CanThrottleRendering()); |
| |
| v8::HandleScope scope(v8::Isolate::GetCurrent()); |
| v8::Local<v8::Value> result = |
| ClassicScript::CreateUnspecifiedScript(ScriptSourceCode("window.didRaf;")) |
| ->RunScriptAndReturnValue( |
| To<LocalDOMWindow>(frame_element->contentWindow())); |
| EXPECT_TRUE(result->IsTrue()); |
| } |
| |
| TEST_P(FrameThrottlingTest, UpdatePaintPropertiesOnUnthrottling) { |
| SimRequest main_resource("https://example.com/", "text/html"); |
| SimRequest frame_resource("https://example.com/iframe.html", "text/html"); |
| |
| LoadURL("https://example.com/"); |
| main_resource.Complete("<iframe id=frame sandbox src=iframe.html></iframe>"); |
| frame_resource.Complete("<div id='div'>Inner</div>"); |
| CompositeFrame(); |
| |
| auto* frame_element = |
| To<HTMLIFrameElement>(GetDocument().getElementById("frame")); |
| auto* frame_document = frame_element->contentDocument(); |
| auto* inner_div = frame_document->getElementById("div"); |
| auto* inner_div_object = inner_div->GetLayoutObject(); |
| EXPECT_FALSE(frame_document->View()->ShouldThrottleRenderingForTest()); |
| |
| frame_element->setAttribute(html_names::kStyleAttr, |
| "transform: translateY(1000px)"); |
| CompositeFrame(); |
| EXPECT_TRUE(frame_document->View()->CanThrottleRendering()); |
| EXPECT_FALSE(inner_div_object->FirstFragment().PaintProperties()); |
| |
| // Mutating the throttled frame should not cause paint property update. |
| inner_div->setAttribute(html_names::kStyleAttr, |
| "transform: translateY(20px)"); |
| EXPECT_FALSE(Compositor().NeedsBeginFrame()); |
| EXPECT_TRUE(frame_document->View()->CanThrottleRendering()); |
| UpdateAllLifecyclePhases(); |
| EXPECT_FALSE(inner_div_object->FirstFragment().PaintProperties()); |
| |
| // Move the frame back on screen to unthrottle it. |
| frame_element->setAttribute(html_names::kStyleAttr, ""); |
| // The first update unthrottles the frame, the second actually update layout |
| // and paint properties etc. |
| CompositeFrame(); |
| EXPECT_TRUE(frame_document->GetLayoutView()->Layer()->SelfNeedsRepaint()); |
| CompositeFrame(); |
| EXPECT_FALSE(frame_document->View()->CanThrottleRendering()); |
| EXPECT_EQ(FloatSize(0, 20), inner_div->GetLayoutObject() |
| ->FirstFragment() |
| .PaintProperties() |
| ->Transform() |
| ->Translation2D()); |
| } |
| |
| TEST_P(FrameThrottlingTest, DisplayNoneNotThrottled) { |
| SimRequest main_resource("https://example.com/", "text/html"); |
| |
| LoadURL("https://example.com/"); |
| main_resource.Complete( |
| "<style>iframe { transform: translateY(480px); }</style>" |
| "<iframe sandbox id=frame></iframe>"); |
| |
| auto* frame_element = |
| To<HTMLIFrameElement>(GetDocument().getElementById("frame")); |
| auto* frame_document = frame_element->contentDocument(); |
| |
| // Initially the frame is throttled as it is offscreen. |
| CompositeFrame(); |
| EXPECT_TRUE(frame_document->View()->CanThrottleRendering()); |
| |
| // Setting display:none unthrottles the frame. |
| frame_element->setAttribute(kStyleAttr, "display: none"); |
| CompositeFrame(); |
| EXPECT_FALSE(frame_document->View()->CanThrottleRendering()); |
| } |
| |
| TEST_P(FrameThrottlingTest, DisplayNoneChildrenRemainThrottled) { |
| // Create two nested frames which are throttled. |
| SimRequest main_resource("https://example.com/", "text/html"); |
| SimRequest frame_resource("https://example.com/iframe.html", "text/html"); |
| SimRequest child_frame_resource("https://example.com/child-iframe.html", |
| "text/html"); |
| |
| LoadURL("https://example.com/"); |
| main_resource.Complete("<iframe id=frame sandbox src=iframe.html></iframe>"); |
| frame_resource.Complete( |
| "<iframe id=child-frame sandbox src=child-iframe.html></iframe>"); |
| child_frame_resource.Complete(""); |
| |
| // Move both frames offscreen to make them throttled. |
| auto* frame_element = |
| To<HTMLIFrameElement>(GetDocument().getElementById("frame")); |
| auto* child_frame_element = To<HTMLIFrameElement>( |
| frame_element->contentDocument()->getElementById("child-frame")); |
| frame_element->setAttribute(kStyleAttr, "transform: translateY(480px)"); |
| CompositeFrame(); |
| EXPECT_TRUE(frame_element->contentDocument()->View()->CanThrottleRendering()); |
| EXPECT_TRUE( |
| child_frame_element->contentDocument()->View()->CanThrottleRendering()); |
| |
| // Setting display:none for the parent frame unthrottles the parent but not |
| // the child. This behavior matches Safari. |
| frame_element->setAttribute(kStyleAttr, "display: none"); |
| CompositeFrame(); |
| EXPECT_FALSE( |
| frame_element->contentDocument()->View()->CanThrottleRendering()); |
| EXPECT_TRUE( |
| child_frame_element->contentDocument()->View()->CanThrottleRendering()); |
| } |
| |
| TEST_P(FrameThrottlingTest, RebuildCompositedLayerTreeOnLayerRemoval) { |
| if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) |
| return; |
| |
| // This test verifies removal of PaintLayer due to style change will force |
| // unthrottling a frame. This is because destructing PaintLayer would cause |
| // CompositedLayerMapping and composited layers to be destructed and detach |
| // from layer tree immediately. Layers could have dangling scroll/clip |
| // parent if compositing update were omitted. |
| WebView().GetSettings()->SetPreferCompositingToLCDTextEnabled(true); |
| |
| SimRequest main_resource("https://example.com/", "text/html"); |
| SimRequest frame_resource("https://example.com/iframe.html", "text/html"); |
| LoadURL("https://example.com/"); |
| main_resource.Complete( |
| "<iframe sandbox id='frame' src='iframe.html' style='position:relative; " |
| "top:1000px;'></iframe>"); |
| frame_resource.Complete(R"HTML( |
| <div id='scroller' style='overflow:scroll; width:300px; height:200px;'> |
| <div style='height:1000px;'></div> |
| <div id='sibling' style='transform:translateZ(0);'>Foo</div> |
| </div> |
| )HTML"); |
| |
| CompositeFrame(); |
| auto* frame_element = |
| To<HTMLIFrameElement>(GetDocument().getElementById("frame")); |
| EXPECT_TRUE(frame_element->contentDocument() |
| ->View() |
| ->ShouldThrottleRenderingForTest()); |
| |
| auto* scroller_element = |
| frame_element->contentDocument()->getElementById("scroller"); |
| ASSERT_TRUE(scroller_element->GetLayoutObject()->HasLayer()); |
| auto* scroller_layer = |
| To<LayoutBoxModelObject>(scroller_element->GetLayoutObject())->Layer(); |
| EXPECT_TRUE(scroller_layer->NeedsCompositedScrolling()); |
| |
| auto* sibling_element = |
| frame_element->contentDocument()->getElementById("sibling"); |
| ASSERT_TRUE(sibling_element->GetLayoutObject()->HasLayer()); |
| auto* sibling_layer = |
| To<LayoutBoxModelObject>(sibling_element->GetLayoutObject())->Layer(); |
| auto* sibling_clm = sibling_layer->GetCompositedLayerMapping(); |
| ASSERT_TRUE(sibling_clm); |
| |
| scroller_element->setAttribute(kStyleAttr, "overflow:visible;"); |
| EXPECT_EQ(DocumentLifecycle::kVisualUpdatePending, |
| frame_element->contentDocument()->Lifecycle().GetState()); |
| |
| // This simulates a javascript query to layout results, e.g. |
| // document.body.offsetTop, which will force style & layout to be computed, |
| // whether the frame is throttled or not. |
| frame_element->contentDocument()->UpdateStyleAndLayout( |
| DocumentUpdateReason::kTest); |
| EXPECT_EQ(DocumentLifecycle::kLayoutClean, |
| frame_element->contentDocument()->Lifecycle().GetState()); |
| EXPECT_FALSE(frame_element->contentDocument() |
| ->View() |
| ->ShouldThrottleRenderingForTest()); |
| |
| CompositeFrame(); |
| EXPECT_TRUE(frame_element->contentDocument() |
| ->View() |
| ->ShouldThrottleRenderingForTest()); |
| EXPECT_EQ(DocumentLifecycle::kCompositingAssignmentsClean, |
| frame_element->contentDocument()->Lifecycle().GetState()); |
| } |
| |
| TEST_P(FrameThrottlingTest, LifecycleUpdateAfterUnthrottledCompositingUpdate) { |
| SimRequest main_resource("https://example.com/", "text/html"); |
| SimRequest frame_resource("https://example.com/iframe.html", "text/html"); |
| |
| LoadURL("https://example.com/"); |
| // The frame is initially throttled. |
| main_resource.Complete(R"HTML( |
| <iframe id='frame' sandbox src='iframe.html' |
| style='transform: translateY(480px)'></iframe> |
| )HTML"); |
| frame_resource.Complete("<div id='div'>Foo</div>"); |
| |
| CompositeFrame(); |
| auto* frame_element = |
| To<HTMLIFrameElement>(GetDocument().getElementById("frame")); |
| auto* frame_document = frame_element->contentDocument(); |
| EXPECT_TRUE(frame_document->View()->CanThrottleRendering()); |
| EXPECT_FALSE(frame_document->View()->ShouldThrottleRendering()); |
| |
| frame_document->getElementById("div")->setAttribute(kStyleAttr, |
| "will-change: transform"); |
| GetDocument().View()->UpdateLifecycleToCompositingCleanPlusScrolling( |
| DocumentUpdateReason::kTest); |
| |
| // Then do a full lifecycle with throttling enabled. This should not crash. |
| EXPECT_TRUE(frame_document->View()->ShouldThrottleRenderingForTest()); |
| UpdateAllLifecyclePhases(); |
| } |
| |
| TEST_P(FrameThrottlingTest, GraphicsLayerCollection) { |
| // GraphicsLayers are not created with CompositeAfterPaint. |
| if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) |
| return; |
| |
| SimRequest main_resource("https://example.com/", "text/html"); |
| SimRequest frame_resource("https://example.com/iframe.html", "text/html"); |
| |
| LoadURL("https://example.com/"); |
| // The frame is initially throttled. |
| main_resource.Complete( |
| "<iframe id='frame' sandbox src='iframe.html'></iframe>"); |
| frame_resource.Complete( |
| "<div id='div' style='will-change: transform'>Foo</div>"); |
| |
| CompositeFrame(); |
| |
| auto* frame_element = |
| To<HTMLIFrameElement>(GetDocument().getElementById("frame")); |
| auto* frame_document = frame_element->contentDocument(); |
| EXPECT_FALSE(frame_document->View()->ShouldThrottleRenderingForTest()); |
| auto layer_count = GetDocument().View()->RootCcLayer()->children().size(); |
| |
| // Moving the child fully outside the parent makes it invisible. |
| frame_element->setAttribute(kStyleAttr, "transform: translateY(480px)"); |
| CompositeFrame(); |
| EXPECT_TRUE(frame_document->View()->ShouldThrottleRenderingForTest()); |
| // Change of throttling will force PaintArtifactCompositor update in the next |
| // frame. |
| EXPECT_TRUE( |
| GetDocument().View()->GetPaintArtifactCompositor()->NeedsUpdate()); |
| |
| // Force a frame update. We should re-collect the graphics layers. |
| GetDocument().GetPage()->Animator().ScheduleVisualUpdate( |
| GetDocument().GetFrame()); |
| CompositeFrame(); |
| EXPECT_TRUE(frame_document->View()->ShouldThrottleRenderingForTest()); |
| // We no longer collect the graphics layers of the iframe and the composited |
| // content. |
| EXPECT_GT(layer_count, |
| GetDocument().View()->RootCcLayer()->children().size()); |
| |
| // Move the child back to the visible viewport. |
| frame_element->setAttribute(kStyleAttr, |
| "transform: translate(-50px, 0px, 0px)"); |
| // Update throttling, which will schedule visual update on unthrottling of the |
| // frame. |
| CompositeFrame(); |
| EXPECT_FALSE(frame_document->View()->ShouldThrottleRenderingForTest()); |
| EXPECT_TRUE( |
| GetDocument().View()->GetPaintArtifactCompositor()->NeedsUpdate()); |
| |
| CompositeFrame(); |
| EXPECT_FALSE(frame_document->View()->ShouldThrottleRenderingForTest()); |
| // Now we should collect all graphics layers again. |
| EXPECT_EQ(layer_count, |
| GetDocument().View()->RootCcLayer()->children().size()); |
| } |
| |
| TEST_P(FrameThrottlingTest, NestedFramesInRemoteFrameHiddenAndShown) { |
| InitializeRemote(); |
| |
| SimRequest local_root_resource("https://example.com/", "text/html"); |
| SimRequest frame_resource("https://example.com/iframe.html", "text/html"); |
| SimRequest child_frame_resource("https://example.com/child-iframe.html", |
| "text/html"); |
| |
| LoadURL("https://example.com/"); |
| local_root_resource.Complete( |
| "<iframe id=frame sandbox src=iframe.html></iframe>"); |
| frame_resource.Complete( |
| "<iframe id=child-frame sandbox src=child-iframe.html></iframe>"); |
| child_frame_resource.Complete(""); |
| |
| mojom::blink::ViewportIntersectionState intersection; |
| intersection.main_frame_intersection = gfx::Rect(0, 0, 100, 100); |
| intersection.main_frame_viewport_size = gfx::Size(100, 100); |
| intersection.viewport_intersection = gfx::Rect(0, 0, 100, 100); |
| LocalFrameRoot().FrameWidget()->Resize(gfx::Size(300, 200)); |
| static_cast<WebFrameWidgetImpl*>(LocalFrameRoot().FrameWidget()) |
| ->ApplyViewportIntersectionForTesting(intersection.Clone()); |
| |
| auto* root_frame = LocalFrameRoot().GetFrame(); |
| auto* frame_document = |
| To<HTMLIFrameElement>(root_frame->GetDocument()->getElementById("frame")) |
| ->contentDocument(); |
| auto* frame_view = frame_document->View(); |
| auto* child_document = |
| To<HTMLIFrameElement>(frame_document->getElementById("child-frame")) |
| ->contentDocument(); |
| auto* child_view = child_document->View(); |
| |
| CompositeFrame(); |
| EXPECT_FALSE(frame_view->CanThrottleRendering()); |
| EXPECT_FALSE(child_view->CanThrottleRendering()); |
| |
| // Hide the frame without any other change. The new throttling state will not |
| // be computed until the next lifecycle update; but merely hiding the frame |
| // will not schedule an update, so we must force one for the purpose of |
| // testing. |
| LocalFrameRoot().WasHidden(); |
| root_frame->View()->ScheduleAnimation(); |
| CompositeFrame(); |
| EXPECT_EQ(root_frame->RemoteViewportIntersection(), IntRect(0, 0, 100, 100)); |
| EXPECT_TRUE(root_frame->View()->CanThrottleRenderingForPropagation()); |
| EXPECT_EQ(root_frame->GetOcclusionState(), |
| mojom::FrameOcclusionState::kPossiblyOccluded); |
| EXPECT_TRUE(frame_view->CanThrottleRendering()); |
| EXPECT_TRUE(child_view->CanThrottleRendering()); |
| EXPECT_FALSE(Compositor().NeedsBeginFrame()); |
| |
| // Simulate a trivial style change that doesn't trigger layout, compositing |
| // update, but schedules layout tree update. |
| frame_document->documentElement()->setAttribute(html_names::kStyleAttr, |
| "color: blue"); |
| // This is needed to reproduce crbug.com/1054644 before the fix. |
| frame_view->SetNeedsPaintPropertyUpdate(); |
| |
| // Show the frame without any other change. |
| LocalFrameRoot().WasShown(); |
| static_cast<WebFrameWidgetImpl*>(LocalFrameRoot().FrameWidget()) |
| ->ApplyViewportIntersectionForTesting(intersection.Clone()); |
| CompositeFrame(); |
| EXPECT_EQ(root_frame->RemoteViewportIntersection(), IntRect(0, 0, 100, 100)); |
| EXPECT_FALSE(root_frame->View()->CanThrottleRenderingForPropagation()); |
| EXPECT_NE(root_frame->GetOcclusionState(), |
| mojom::FrameOcclusionState::kPossiblyOccluded); |
| EXPECT_FALSE(frame_view->CanThrottleRendering()); |
| // The child frame's throtting status is not updated because the parent |
| // document has pending visual update. |
| EXPECT_TRUE(child_view->CanThrottleRendering()); |
| |
| CompositeFrame(); |
| EXPECT_FALSE(frame_view->CanThrottleRendering()); |
| // The child frame's throttling status should be updated now. |
| EXPECT_FALSE(child_view->CanThrottleRendering()); |
| } |
| |
| TEST_P(FrameThrottlingTest, LifecycleThrottledFrameNeedsRepaint) { |
| SimRequest main_resource("https://example.com/", "text/html"); |
| SimRequest frame_resource("https://example.com/iframe.html", "text/html"); |
| |
| LoadURL("https://example.com/"); |
| // The frame is initially throttled. |
| main_resource.Complete("<iframe id='frame' src='iframe.html'></iframe>"); |
| frame_resource.Complete("<body style='background: red'></body>"); |
| |
| auto commands = CompositeFrame(); |
| EXPECT_TRUE(commands.Contains(SimCanvas::kRect, "red")); |
| |
| auto* frame_element = |
| To<HTMLIFrameElement>(GetDocument().getElementById("frame")); |
| auto* frame_document = frame_element->contentDocument(); |
| frame_document->View()->SetLifecycleUpdatesThrottledForTesting(true); |
| GetDocument().View()->ScheduleAnimation(); |
| EXPECT_TRUE(frame_document->View()->ShouldThrottleRenderingForTest()); |
| |
| commands = CompositeFrame(); |
| // The throttled frame is omitted for paint. |
| EXPECT_FALSE(commands.Contains(SimCanvas::kRect, "red")); |
| |
| frame_document->body()->setAttribute(kStyleAttr, "background: green"); |
| // Update life cycle update except paint without throttling, which will do |
| // paint invalidation. |
| GetDocument().View()->UpdateAllLifecyclePhasesExceptPaint( |
| DocumentUpdateReason::kTest); |
| EXPECT_TRUE( |
| frame_document->GetLayoutView()->Layer()->SelfOrDescendantNeedsRepaint()); |
| // The NeedsRepaint flag doesn't propagte across frame boundary for now. |
| EXPECT_FALSE( |
| GetDocument().GetLayoutView()->Layer()->SelfOrDescendantNeedsRepaint()); |
| |
| commands = CompositeFrame(); |
| EXPECT_FALSE(commands.Contains(SimCanvas::kRect, "green")); |
| EXPECT_TRUE( |
| frame_document->GetLayoutView()->Layer()->SelfOrDescendantNeedsRepaint()); |
| EXPECT_FALSE( |
| GetDocument().GetLayoutView()->Layer()->SelfOrDescendantNeedsRepaint()); |
| |
| frame_document->View()->BeginLifecycleUpdates(); |
| commands = CompositeFrame(); |
| EXPECT_TRUE(commands.Contains(SimCanvas::kRect, "green")); |
| EXPECT_FALSE( |
| frame_document->GetLayoutView()->Layer()->SelfOrDescendantNeedsRepaint()); |
| EXPECT_FALSE( |
| GetDocument().GetLayoutView()->Layer()->SelfOrDescendantNeedsRepaint()); |
| } |
| |
| TEST_P(FrameThrottlingTest, AncestorTouchActionAndWheelEventHandlers) { |
| base::test::ScopedFeatureList scoped_feature_list; |
| scoped_feature_list.InitAndEnableFeature(::features::kWheelEventRegions); |
| |
| SimRequest main_resource("https://example.com/", "text/html"); |
| SimRequest frame_resource("https://example.com/iframe.html", "text/html"); |
| |
| LoadURL("https://example.com/"); |
| // The frame is initially throttled. |
| main_resource.Complete(R"HTML( |
| <div id="parent"> |
| <iframe id="frame" sandbox src="iframe.html"></iframe> |
| </div> |
| )HTML"); |
| frame_resource.Complete("<div id='child'></div>"); |
| CompositeFrame(); |
| |
| auto* frame_element = |
| To<HTMLIFrameElement>(GetDocument().getElementById("frame")); |
| auto* frame_document = frame_element->contentDocument(); |
| auto* parent = GetDocument().getElementById("parent"); |
| auto* parent_object = parent->GetLayoutObject(); |
| auto* child_layout_view = frame_document->GetLayoutView(); |
| auto* child = frame_document->getElementById("child"); |
| auto* child_object = child->GetLayoutObject(); |
| EXPECT_FALSE(frame_document->View()->ShouldThrottleRenderingForTest()); |
| EXPECT_FALSE(parent_object->InsideBlockingTouchEventHandler()); |
| EXPECT_FALSE(parent_object->InsideBlockingWheelEventHandler()); |
| EXPECT_FALSE(child_layout_view->InsideBlockingTouchEventHandler()); |
| EXPECT_FALSE(child_layout_view->InsideBlockingWheelEventHandler()); |
| EXPECT_FALSE(child_object->InsideBlockingTouchEventHandler()); |
| EXPECT_FALSE(child_object->InsideBlockingWheelEventHandler()); |
| |
| // Moving the child fully outside the parent makes it invisible. |
| frame_element->setAttribute(kStyleAttr, "transform: translateY(480px)"); |
| CompositeFrame(); |
| EXPECT_TRUE(frame_document->View()->ShouldThrottleRenderingForTest()); |
| |
| auto* handler = MakeGarbageCollected<EmptyEventListener>(); |
| parent->addEventListener(event_type_names::kTouchstart, handler); |
| parent->addEventListener(event_type_names::kWheel, handler); |
| EXPECT_TRUE(parent_object->EffectiveAllowedTouchActionChanged()); |
| EXPECT_TRUE(parent_object->BlockingWheelEventHandlerChanged()); |
| UpdateAllLifecyclePhases(); |
| EXPECT_TRUE(parent_object->InsideBlockingTouchEventHandler()); |
| EXPECT_TRUE(parent_object->InsideBlockingWheelEventHandler()); |
| // Event handler status update is pending in the throttled frame. |
| EXPECT_TRUE(child_layout_view->EffectiveAllowedTouchActionChanged()); |
| EXPECT_TRUE(child_layout_view->BlockingWheelEventHandlerChanged()); |
| EXPECT_FALSE(child_layout_view->InsideBlockingTouchEventHandler()); |
| EXPECT_FALSE(child_layout_view->InsideBlockingWheelEventHandler()); |
| EXPECT_FALSE(child_object->InsideBlockingTouchEventHandler()); |
| EXPECT_FALSE(child_object->InsideBlockingWheelEventHandler()); |
| |
| // Move the child back to the visible viewport. |
| frame_element->setAttribute(kStyleAttr, |
| "transform: translate(-50px, 0px, 0px)"); |
| // Update throttling, which will schedule visual update on unthrottling of the |
| // frame. |
| CompositeFrame(); |
| EXPECT_FALSE(frame_document->View()->ShouldThrottleRenderingForTest()); |
| CompositeFrame(); |
| EXPECT_FALSE(frame_document->View()->ShouldThrottleRenderingForTest()); |
| EXPECT_TRUE(parent_object->InsideBlockingTouchEventHandler()); |
| EXPECT_TRUE(parent_object->InsideBlockingWheelEventHandler()); |
| // Event handler status is updated in the unthrottled frame. |
| EXPECT_FALSE(child_layout_view->EffectiveAllowedTouchActionChanged()); |
| EXPECT_FALSE(child_layout_view->BlockingWheelEventHandlerChanged()); |
| EXPECT_TRUE(child_layout_view->InsideBlockingTouchEventHandler()); |
| EXPECT_TRUE(child_layout_view->InsideBlockingWheelEventHandler()); |
| EXPECT_TRUE(child_object->InsideBlockingTouchEventHandler()); |
| EXPECT_TRUE(child_object->InsideBlockingWheelEventHandler()); |
| } |
| |
| TEST_P(FrameThrottlingTest, DescendantTouchActionAndWheelEventHandlers) { |
| base::test::ScopedFeatureList scoped_feature_list; |
| scoped_feature_list.InitAndEnableFeature(::features::kWheelEventRegions); |
| |
| SimRequest main_resource("https://example.com/", "text/html"); |
| SimRequest frame_resource("https://example.com/iframe.html", "text/html"); |
| |
| LoadURL("https://example.com/"); |
| // The frame is initially throttled. |
| main_resource.Complete(R"HTML( |
| <div id="parent"> |
| <iframe id="frame" sandbox src="iframe.html"></iframe> |
| </div> |
| )HTML"); |
| frame_resource.Complete("<div id='child'></div>"); |
| CompositeFrame(); |
| |
| auto* frame_element = |
| To<HTMLIFrameElement>(GetDocument().getElementById("frame")); |
| auto* frame_document = frame_element->contentDocument(); |
| auto* parent = GetDocument().getElementById("parent"); |
| auto* parent_object = parent->GetLayoutObject(); |
| auto* child_layout_view = frame_document->GetLayoutView(); |
| auto* child = frame_document->getElementById("child"); |
| auto* child_object = child->GetLayoutObject(); |
| EXPECT_FALSE(frame_document->View()->ShouldThrottleRenderingForTest()); |
| EXPECT_FALSE(parent_object->InsideBlockingTouchEventHandler()); |
| EXPECT_FALSE(parent_object->InsideBlockingWheelEventHandler()); |
| EXPECT_FALSE(child_layout_view->InsideBlockingTouchEventHandler()); |
| EXPECT_FALSE(child_layout_view->InsideBlockingWheelEventHandler()); |
| EXPECT_FALSE(child_object->InsideBlockingTouchEventHandler()); |
| EXPECT_FALSE(child_object->InsideBlockingWheelEventHandler()); |
| |
| // Moving the child fully outside the parent makes it invisible. |
| frame_element->setAttribute(kStyleAttr, "transform: translateY(480px)"); |
| CompositeFrame(); |
| EXPECT_TRUE(frame_document->View()->ShouldThrottleRenderingForTest()); |
| |
| auto* handler = MakeGarbageCollected<EmptyEventListener>(); |
| child->addEventListener(event_type_names::kTouchstart, handler); |
| child->addEventListener(event_type_names::kWheel, handler); |
| EXPECT_TRUE(child_object->EffectiveAllowedTouchActionChanged()); |
| EXPECT_TRUE(child_object->BlockingWheelEventHandlerChanged()); |
| EXPECT_TRUE( |
| child_layout_view->DescendantEffectiveAllowedTouchActionChanged()); |
| EXPECT_TRUE(child_layout_view->DescendantBlockingWheelEventHandlerChanged()); |
| EXPECT_FALSE(parent_object->DescendantEffectiveAllowedTouchActionChanged()); |
| EXPECT_FALSE(parent_object->DescendantBlockingWheelEventHandlerChanged()); |
| UpdateAllLifecyclePhases(); |
| // Event handler status update is pending in the throttled frame. |
| EXPECT_TRUE(child_object->EffectiveAllowedTouchActionChanged()); |
| EXPECT_TRUE(child_object->BlockingWheelEventHandlerChanged()); |
| EXPECT_TRUE( |
| child_layout_view->DescendantEffectiveAllowedTouchActionChanged()); |
| EXPECT_TRUE(child_layout_view->DescendantBlockingWheelEventHandlerChanged()); |
| EXPECT_FALSE(child_layout_view->InsideBlockingTouchEventHandler()); |
| EXPECT_FALSE(child_layout_view->InsideBlockingWheelEventHandler()); |
| EXPECT_FALSE(child_object->InsideBlockingTouchEventHandler()); |
| EXPECT_FALSE(child_object->InsideBlockingWheelEventHandler()); |
| |
| // Move the child back to the visible viewport. |
| frame_element->setAttribute(kStyleAttr, |
| "transform: translate(-50px, 0px, 0px)"); |
| // Update throttling, which will schedule visual update on unthrottling of the |
| // frame. |
| CompositeFrame(); |
| EXPECT_FALSE(frame_document->View()->ShouldThrottleRenderingForTest()); |
| CompositeFrame(); |
| EXPECT_FALSE(frame_document->View()->ShouldThrottleRenderingForTest()); |
| // Event handler status is updated in the unthrottled frame. |
| EXPECT_FALSE(child_object->EffectiveAllowedTouchActionChanged()); |
| EXPECT_FALSE(child_object->BlockingWheelEventHandlerChanged()); |
| EXPECT_FALSE( |
| child_layout_view->DescendantEffectiveAllowedTouchActionChanged()); |
| EXPECT_FALSE(child_layout_view->DescendantBlockingWheelEventHandlerChanged()); |
| EXPECT_FALSE(child_layout_view->InsideBlockingTouchEventHandler()); |
| EXPECT_FALSE(child_layout_view->InsideBlockingWheelEventHandler()); |
| EXPECT_TRUE(child_object->InsideBlockingTouchEventHandler()); |
| EXPECT_TRUE(child_object->InsideBlockingWheelEventHandler()); |
| } |
| |
| namespace { |
| |
| class TestResizeObserverDelegate : public ResizeObserver::Delegate { |
| public: |
| explicit TestResizeObserverDelegate() {} |
| void OnResize( |
| const HeapVector<Member<ResizeObserverEntry>>& entries) override { |
| entries[0]->target()->SetInlineStyleProperty(CSSPropertyID::kWidth, |
| "100px"); |
| } |
| }; |
| |
| } // namespace |
| |
| TEST_P(FrameThrottlingTest, ForceUnthrottled) { |
| SimRequest main_resource("https://example.com/", "text/html"); |
| SimRequest frame_resource("https://example.com/iframe.html", "text/html"); |
| LoadURL("https://example.com/"); |
| main_resource.Complete(R"HTML( |
| <!DOCTYPE html> |
| <iframe id="frame" sandbox src="iframe.html" |
| style="border:0;transform:translateY(480px)"> |
| )HTML"); |
| frame_resource.Complete(R"HTML( |
| <!DOCTYPE html> |
| <div style="width:120px">Hello, world!</div> |
| )HTML"); |
| CompositeFrame(); |
| HTMLIFrameElement* frame_element = |
| To<HTMLIFrameElement>(GetDocument().getElementById("frame")); |
| LocalFrameView* inner_frame_view = |
| To<LocalFrameView>(frame_element->OwnedEmbeddedContentView()); |
| EXPECT_TRUE(inner_frame_view->ShouldThrottleRenderingForTest()); |
| |
| IntersectionObserverInit* intersection_init = |
| IntersectionObserverInit::Create(); |
| TestIntersectionObserverDelegate* intersection_delegate = |
| MakeGarbageCollected<TestIntersectionObserverDelegate>( |
| *frame_element->contentDocument()); |
| IntersectionObserver* intersection_observer = |
| IntersectionObserver::Create(intersection_init, *intersection_delegate); |
| intersection_observer->observe(frame_element->contentDocument()->body()); |
| |
| ResizeObserver::Delegate* resize_delegate = |
| MakeGarbageCollected<TestResizeObserverDelegate>(); |
| ResizeObserver* resize_observer = |
| ResizeObserver::Create(&Window(), resize_delegate); |
| resize_observer->observe(frame_element); |
| |
| // Apply style change here to ensure ResizeObserver will force a second pass |
| // through the lifecycle loop on the next update. |
| frame_element->SetInlineStyleProperty(CSSPropertyID::kWidth, "200px"); |
| |
| // Because there is a new IntersectionObserver target, the iframe will be |
| // force-unthrottled going into the lifecycle update. During the first pass |
| // through the lifecycle loop, the style change will cause the ResizeObserver |
| // callback to run. The ResizeObserver will dirty the iframe element by |
| // setting its width to 100px. At this point, the lifecycle state of the |
| // iframe will be kCompositingAssignmentsClean, which will cause |
| // ShouldThrottleRendering() to return true. |
| // |
| // Because ResizeObserver dirtied layout, there will be a second pass through |
| // the main lifecycle loop. When the iframe element runs layout again, setting |
| // its width to 100px, it will cause the iframe's contents to overflow, so the |
| // iframe will add a horizontal scrollbar and mark its LayoutView as needing |
| // paint property update. If the iframe's lifecycle state is still |
| // kCompositingAssignmentsClean, then it will skip pre-paint on the second |
| // pass through the lifecycle loop, leaving its paint properties in a dirty |
| // state (bad). If, however, the iframe's lifecycle state is reset to |
| // kVisualUpdatePending prior to the second pass through the loop, then it |
| // will be once again force-unthrottled, and will run lifecycle steps up |
| // through pre-paint (good). |
| CompositeFrame(); |
| |
| EXPECT_TRUE(inner_frame_view->ShouldThrottleRenderingForTest()); |
| EXPECT_FALSE(inner_frame_view->GetLayoutView()->NeedsPaintPropertyUpdate()); |
| } |
| |
| TEST_P(FrameThrottlingTest, CullRectUpdate) { |
| if (!RuntimeEnabledFeatures::CullRectUpdateEnabled()) |
| return; |
| |
| SimRequest main_resource("https://example.com/", "text/html"); |
| SimRequest frame_resource("https://example.com/iframe.html", "text/html"); |
| |
| LoadURL("https://example.com/"); |
| // The frame is initially throttled. |
| main_resource.Complete(R"HTML( |
| <style>#clip { width: 100px; height: 100px; overflow: hidden }</style> |
| <div id="container" style="transform: translateY(480px)"> |
| <div id="clip"> |
| <iframe id="frame" sandbox src="iframe.html" |
| style="width: 400px; height: 400px; border: none"></iframe> |
| </div> |
| </div> |
| )HTML"); |
| frame_resource.Complete(""); |
| CompositeFrame(); |
| |
| auto* frame_element = |
| To<HTMLIFrameElement>(GetDocument().getElementById("frame")); |
| auto* frame_object = frame_element->GetLayoutBox(); |
| auto* frame_document = frame_element->contentDocument(); |
| auto* child_layout_view = frame_document->GetLayoutView(); |
| |
| EXPECT_TRUE(frame_document->View()->ShouldThrottleRenderingForTest()); |
| EXPECT_EQ(IntRect(0, 0, 100, 100), |
| frame_object->FirstFragment().GetCullRect().Rect()); |
| EXPECT_FALSE(child_layout_view->Layer()->NeedsCullRectUpdate()); |
| |
| // Change clip. |frame_element| should update its cull rect. |
| // |child_layout_view|'s cull rect update is pending. |
| GetDocument().getElementById("clip")->setAttribute(kStyleAttr, |
| "width: 630px"); |
| CompositeFrame(); |
| EXPECT_EQ(IntRect(0, 0, 630, 100), |
| frame_object->FirstFragment().GetCullRect().Rect()); |
| EXPECT_TRUE(child_layout_view->Layer()->NeedsCullRectUpdate()); |
| |
| // Move the frame into the visible viewport. |
| GetDocument() |
| .getElementById("container") |
| ->setAttribute(kStyleAttr, "transform: translate(0)"); |
| // Update throttling, which will schedule visual update on unthrottling of the |
| // frame. |
| CompositeFrame(); |
| EXPECT_FALSE(frame_document->View()->ShouldThrottleRenderingForTest()); |
| EXPECT_EQ(IntRect(0, 0, 630, 100), |
| frame_object->FirstFragment().GetCullRect().Rect()); |
| EXPECT_TRUE(child_layout_view->Layer()->NeedsCullRectUpdate()); |
| |
| // The frame is unthrottled. |
| CompositeFrame(); |
| EXPECT_FALSE(frame_document->View()->ShouldThrottleRenderingForTest()); |
| EXPECT_EQ(IntRect(0, 0, 630, 100), |
| frame_object->FirstFragment().GetCullRect().Rect()); |
| EXPECT_EQ(IntRect(-4000, -4000, 8630, 8100), |
| child_layout_view->FirstFragment().GetCullRect().Rect()); |
| EXPECT_FALSE(child_layout_view->Layer()->NeedsCullRectUpdate()); |
| } |
| |
| TEST_P(FrameThrottlingTest, ForceCompositingUpdateOnVisibilityChange) { |
| if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) |
| return; |
| |
| SimRequest main_resource("https://example.com/", "text/html"); |
| SimRequest child_resource("https://example.com/child.html", "text/html"); |
| SimRequest grandchild_resource("https://example.com/grandchild.html", |
| "text/html"); |
| |
| LoadURL("https://example.com/"); |
| main_resource.Complete(R"HTML( |
| <iframe src="child.html"></iframe> |
| )HTML"); |
| child_resource.Complete(R"HTML( |
| <iframe sandbox src="grandchild.html" style="margin-top: 1000px"></iframe> |
| )HTML"); |
| grandchild_resource.Complete(R"HTML( |
| <div style="width:100px;height:100px;will-change:transform"></div> |
| )HTML"); |
| CompositeFrame(); |
| |
| LocalFrame* child_frame = |
| To<LocalFrame>(MainFrame().GetFrame()->FirstChild()); |
| LocalFrame* grandchild_frame = To<LocalFrame>(child_frame->FirstChild()); |
| Element* composited_div = |
| grandchild_frame->GetDocument()->QuerySelector("div"); |
| |
| EXPECT_TRUE(grandchild_frame->View()->ShouldThrottleRenderingForTest()); |
| EXPECT_TRUE( |
| child_frame->ContentLayoutObject()->Compositor()->InCompositingMode()); |
| EXPECT_TRUE(grandchild_frame->ContentLayoutObject() |
| ->Compositor() |
| ->InCompositingMode()); |
| EXPECT_TRUE(To<LayoutBoxModelObject>(composited_div->GetLayoutObject()) |
| ->Layer() |
| ->HasCompositedLayerMapping()); |
| |
| // Remove the compositing trigger. Because the grandchild is throttled, it |
| // will not update its compositing state. |
| composited_div->RemoveInlineStyleProperty(CSSPropertyID::kWillChange); |
| MainFrame().GetFrame()->View()->ScheduleAnimation(); |
| CompositeFrame(); |
| EXPECT_TRUE(grandchild_frame->View()->ShouldThrottleRenderingForTest()); |
| EXPECT_TRUE( |
| child_frame->ContentLayoutObject()->Compositor()->InCompositingMode()); |
| EXPECT_TRUE(grandchild_frame->ContentLayoutObject() |
| ->Compositor() |
| ->InCompositingMode()); |
| EXPECT_TRUE(To<LayoutBoxModelObject>(composited_div->GetLayoutObject()) |
| ->Layer() |
| ->HasCompositedLayerMapping()); |
| |
| // Hide the child frame. This will force a compositing update of the throttled |
| // grandchild on the next lifecycle update, which should decomposite the div. |
| child_frame->DeprecatedLocalOwner()->SetInlineStyleProperty( |
| CSSPropertyID::kVisibility, CSSValueID::kHidden); |
| CompositeFrame(); |
| EXPECT_TRUE(grandchild_frame->View()->ShouldThrottleRenderingForTest()); |
| EXPECT_FALSE( |
| child_frame->ContentLayoutObject()->Compositor()->InCompositingMode()); |
| EXPECT_FALSE(grandchild_frame->ContentLayoutObject() |
| ->Compositor() |
| ->InCompositingMode()); |
| EXPECT_FALSE(composited_div->GetLayoutObject()->HasLayer()); |
| } |
| |
| } // namespace blink |