| // 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 "cc/layers/picture_layer.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/blink/public/web/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/sanitize_script_errors.h" |
| #include "third_party/blink/renderer/bindings/core/v8/script_controller.h" |
| #include "third_party/blink/renderer/bindings/core/v8/script_source_code.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/exported/web_remote_frame_impl.h" |
| #include "third_party/blink/renderer/core/frame/local_frame.h" |
| #include "third_party/blink/renderer/core/frame/local_frame_view.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/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/paint_layer.h" |
| #include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.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/transform_paint_property_node.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 namespace html_names; |
| |
| // NOTE: This test uses <iframe sandbox> to create cross origin iframes. |
| |
| class FrameThrottlingTest : public PaintTestConfigurations, public SimTest { |
| protected: |
| void SetUp() override { |
| SimTest::SetUp(); |
| WebView().MainFrameWidget()->Resize(WebSize(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() { |
| size_t result = 0; |
| PaintLayer* layer = |
| WebView().MainFrameImpl()->GetFrame()->ContentLayoutObject()->Layer(); |
| GraphicsLayer* own_graphics_layer = |
| layer->GraphicsLayerBacking(&layer->GetLayoutObject()); |
| if (own_graphics_layer) { |
| result += own_graphics_layer->CcLayer() |
| ->touch_action_region() |
| .region() |
| .GetRegionComplexity(); |
| } |
| GraphicsLayer* child_graphics_layer = layer->GraphicsLayerBacking(); |
| if (child_graphics_layer && child_graphics_layer != own_graphics_layer) { |
| result += child_graphics_layer->CcLayer() |
| ->touch_action_region() |
| .region() |
| .GetRegionComplexity(); |
| } |
| return result; |
| } |
| |
| void UpdateAllLifecyclePhases() { |
| GetDocument().View()->UpdateAllLifecyclePhases( |
| DocumentLifecycle::LifecycleUpdateReason::kTest); |
| } |
| }; |
| |
| INSTANTIATE_PAINT_TEST_CASE_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 = |
| ToHTMLIFrameElement(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 = |
| ToHTMLIFrameElement(GetDocument().getElementById("frame")); |
| auto* frame_document = frame_element->contentDocument(); |
| |
| HTMLIFrameElement* inner_frame_element = |
| ToHTMLIFrameElement(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 = |
| ToHTMLIFrameElement(GetDocument().getElementById("frame")); |
| auto* frame_document = frame_element->contentDocument(); |
| |
| auto* inner_frame_element = |
| ToHTMLIFrameElement(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 = |
| ToHTMLIFrameElement(GetDocument().getElementById("frame")); |
| auto* frame_document = frame_element->contentDocument(); |
| |
| auto* inner_frame_element = |
| ToHTMLIFrameElement(frame_document->getElementById("innerFrame")); |
| auto* inner_frame_document = inner_frame_element->contentDocument(); |
| |
| DocumentLifecycle::AllowThrottlingScope throttling_scope( |
| GetDocument().Lifecycle()); |
| |
| // 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()->ShouldThrottleRendering()); |
| |
| // An intersection observation overrides... |
| inner_frame_document->View()->SetIntersectionObservationState( |
| LocalFrameView::kRequired); |
| EXPECT_FALSE(inner_frame_document->View()->ShouldThrottleRendering()); |
| inner_frame_document->View()->ScheduleAnimation(); |
| |
| LayoutView* inner_view = inner_frame_document->View()->GetLayoutView(); |
| |
| inner_view->SetNeedsLayout("test"); |
| inner_view->Compositor()->SetNeedsCompositingUpdate( |
| kCompositingUpdateRebuildTree); |
| inner_view->SetShouldDoFullPaintInvalidation( |
| PaintInvalidationReason::kForTesting); |
| inner_view->Layer()->SetNeedsRepaint(); |
| EXPECT_TRUE(inner_frame_document->View() |
| ->GetLayoutView() |
| ->ShouldDoFullPaintInvalidation()); |
| inner_view->Compositor()->SetNeedsCompositingUpdate( |
| kCompositingUpdateRebuildTree); |
| EXPECT_EQ(kCompositingUpdateRebuildTree, |
| inner_view->Compositor()->pending_update_type_); |
| EXPECT_TRUE(inner_view->Layer()->NeedsRepaint()); |
| |
| CompositeFrame(); |
| // ...but only for one frame. |
| EXPECT_TRUE(inner_frame_document->View()->ShouldThrottleRendering()); |
| |
| EXPECT_FALSE(inner_view->NeedsLayout()); |
| EXPECT_TRUE(inner_frame_document->View() |
| ->GetLayoutView() |
| ->ShouldDoFullPaintInvalidation()); |
| EXPECT_EQ(kCompositingUpdateRebuildTree, |
| inner_view->Compositor()->pending_update_type_); |
| EXPECT_TRUE(inner_view->Layer()->NeedsRepaint()); |
| } |
| |
| 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 = |
| ToHTMLIFrameElement(GetDocument().getElementById("frame")); |
| auto* frame_document = frame_element->contentDocument(); |
| |
| auto* inner_frame_element = |
| ToHTMLIFrameElement(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 = |
| ToHTMLIFrameElement(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(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().HitTestResultAt(gfx::Point()); |
| 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().HitTestResultAt(gfx::Point()); |
| 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 = |
| ToHTMLIFrameElement(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()); |
| } |
| |
| 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 = |
| ToHTMLIFrameElement(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 = |
| ToHTMLIFrameElement(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 = |
| ToHTMLIFrameElement(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), kProgrammaticScroll); |
| 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 = |
| ToHTMLIFrameElement(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), kProgrammaticScroll); |
| 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 = |
| ToHTMLIFrameElement(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), kProgrammaticScroll); |
| 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 = |
| ToHTMLIFrameElement(GetDocument().getElementById("frame")); |
| |
| CompositeFrame(); |
| |
| EXPECT_TRUE(frame_element->contentDocument()->View()->CanThrottleRendering()); |
| EXPECT_TRUE( |
| frame_element->contentDocument()->GetFrame()->IsCrossOriginSubframe()); |
| 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()->IsCrossOriginSubframe()); |
| EXPECT_FALSE( |
| frame_element->contentDocument()->View()->CanThrottleRendering()); |
| EXPECT_TRUE(frame_element->contentDocument() |
| ->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 = |
| ToHTMLIFrameElement(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) { |
| // TODO(crbug.com/809638): Make this test pass for CAP. |
| if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) |
| return; |
| |
| // 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-image: linear-gradient(red, blue); " |
| "background-attachment: fixed; } </style>"); |
| |
| // Move the frame offscreen to throttle it. |
| auto* frame_element = |
| ToHTMLIFrameElement(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()); |
| |
| DocumentLifecycle::AllowThrottlingScope throttling_scope( |
| GetDocument().Lifecycle()); |
| // 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()); |
| // 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 = |
| ToHTMLIFrameElement(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()); |
| |
| DocumentLifecycle::AllowThrottlingScope throttling_scope( |
| GetDocument().Lifecycle()); |
| // 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 = |
| ToHTMLIFrameElement(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()); |
| |
| DocumentLifecycle::AllowThrottlingScope throttling_scope( |
| GetDocument().Lifecycle()); |
| // 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()->ShouldThrottleRendering()); |
| CompositeFrame(); // Handle the pending visual update of the unthrottled |
| // frame. |
| EXPECT_EQ(DocumentLifecycle::kPaintClean, |
| frame_element->contentDocument()->Lifecycle().GetState()); |
| // TODO(szager): Re-enable this check for CAP when it properly sets the |
| // bits for composited scrolling. |
| if (!RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) { |
| EXPECT_TRUE(frame_element->contentDocument() |
| ->View() |
| ->LayoutViewport() |
| ->UsesCompositedScrolling()); |
| } |
| } |
| |
| 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 = |
| ToHTMLIFrameElement(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, ThrottledTopLevelEventHandlerIgnored) { |
| // TODO(crbug.com/809638): Make this test pass for CAP. |
| if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) |
| return; |
| |
| WebView().GetSettings()->SetJavaScriptEnabled(true); |
| EXPECT_EQ(0u, TouchHandlerRegionSize()); |
| |
| // Create a frame which is throttled and has two different types of |
| // top-level touchstart handlers. |
| 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(R"HTML( |
| <script> |
| window.addEventListener('touchstart', function(){}, {passive: false}); |
| document.addEventListener('touchstart', function(){}, {passive: false}); |
| </script> |
| )HTML"); |
| auto* frame_element = |
| ToHTMLIFrameElement(GetDocument().getElementById("frame")); |
| frame_element->setAttribute(kStyleAttr, "transform: translateY(480px)"); |
| DocumentLifecycle::AllowThrottlingScope throttling_scope( |
| GetDocument().Lifecycle()); |
| CompositeFrame(); // Throttle the frame. |
| CompositeFrame(); // Update touch handler regions. |
| |
| // In here, throttle iframe doesn't throttle the main frame. |
| EXPECT_TRUE( |
| frame_element->contentDocument()->View()->ShouldThrottleRendering()); |
| EXPECT_FALSE(GetDocument().View()->ShouldThrottleRendering()); |
| |
| // In this test, the iframe has the same origin as the main frame, so we have |
| // two documents but one graphics layer tree. The test throttles the iframe |
| // document only. In ScrollingCoordinator::UpdateLayerTouchActionRects, we |
| // check whether the document associated with a certain grahpics layer is |
| // throttled or not. Since the layers are associated with the main document |
| // which is not throttled, we expect the main document to have one touch |
| // handler region. |
| // In the Non-PaintTouchActionRects world, the |
| // AccumulateDocumentTouchEventTargetRects goes through every document and |
| // check whether the document is throttled or not. So we expect no touch |
| // handler region. |
| if (RuntimeEnabledFeatures::PaintTouchActionRectsEnabled()) |
| EXPECT_EQ(1u, TouchHandlerRegionSize()); |
| else |
| EXPECT_EQ(0u, TouchHandlerRegionSize()); |
| |
| // Unthrottling the frame makes the touch handlers active again. Note that |
| // both handlers get combined into the same rectangle in the region, so |
| // there is only one rectangle in total. |
| frame_element->setAttribute(kStyleAttr, "transform: translateY(0px)"); |
| CompositeFrame(); // Unthrottle the frame. |
| CompositeFrame(); // Update touch handler regions. |
| EXPECT_EQ(1u, TouchHandlerRegionSize()); |
| } |
| |
| TEST_P(FrameThrottlingTest, ThrottledEventHandlerIgnored) { |
| // TODO(crbug.com/809638): Make this test pass for CAP. |
| if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) |
| return; |
| |
| WebView().GetSettings()->SetJavaScriptEnabled(true); |
| EXPECT_EQ(0u, TouchHandlerRegionSize()); |
| |
| // Create a frame which is throttled and has a non-top-level touchstart |
| // handler. |
| 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(R"HTML( |
| <div id=d>touch handler</div> |
| <script> |
| document.querySelector('#d').addEventListener('touchstart', |
| function(){}); |
| </script> |
| )HTML"); |
| auto* frame_element = |
| ToHTMLIFrameElement(GetDocument().getElementById("frame")); |
| frame_element->setAttribute(kStyleAttr, "transform: translateY(480px)"); |
| DocumentLifecycle::AllowThrottlingScope throttling_scope( |
| GetDocument().Lifecycle()); |
| CompositeFrame(); // Throttle the frame. |
| CompositeFrame(); // Update touch handler regions. |
| |
| // In here, throttle iframe doesn't throttle the main frame. |
| EXPECT_TRUE( |
| frame_element->contentDocument()->View()->ShouldThrottleRendering()); |
| EXPECT_FALSE(GetDocument().View()->ShouldThrottleRendering()); |
| |
| // In this test, the iframe has the same origin as the main frame, so we have |
| // two documents but one graphics layer tree. The test throttles the iframe |
| // document only. In ScrollingCoordinator::UpdateLayerTouchActionRects, we |
| // check whether the document associated with a certain grahpics layer is |
| // throttled or not. Since the layers are associated with the main document |
| // which is not throttled, we expect the main document to have one touch |
| // handler region. |
| // In the Non-PaintTouchActionRects world, the |
| // AccumulateDocumentTouchEventTargetRects goes through every document and |
| // check whether the document is throttled or not. So we expect no touch |
| // handler region. |
| if (RuntimeEnabledFeatures::PaintTouchActionRectsEnabled()) |
| EXPECT_EQ(1u, TouchHandlerRegionSize()); |
| else |
| EXPECT_EQ(0u, TouchHandlerRegionSize()); |
| |
| // Unthrottling the frame makes the touch handler active again. |
| frame_element->setAttribute(kStyleAttr, "transform: translateY(0px)"); |
| CompositeFrame(); // Unthrottle the frame. |
| CompositeFrame(); // Update touch handler regions. |
| EXPECT_EQ(1u, TouchHandlerRegionSize()); |
| } |
| |
| 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 = |
| ToHTMLIFrameElement(GetDocument().getElementById("frame")); |
| frame_element->setAttribute(kStyleAttr, "transform: translateY(480px)"); |
| CompositeFrame(); |
| EXPECT_TRUE(frame_element->contentDocument()->View()->CanThrottleRendering()); |
| |
| LocalFrame* local_frame = ToLocalFrame(frame_element->ContentFrame()); |
| local_frame->GetScriptController().ExecuteScriptInMainWorld( |
| "document.body.innerHTML = 'throttled'"); |
| EXPECT_FALSE(Compositor().NeedsBeginFrame()); |
| |
| // The dumped contents should not include the throttled frame. |
| DocumentLifecycle::AllowThrottlingScope throttling_scope( |
| GetDocument().Lifecycle()); |
| WebString result = WebFrameContentDumper::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) { |
| if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) |
| 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 = |
| ToHTMLIFrameElement(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. |
| auto commands_throttled = Compositor().PaintFrame(); |
| EXPECT_EQ(5u, commands_throttled.DrawCount()); |
| EXPECT_FALSE(Compositor().NeedsBeginFrame()); |
| } |
| |
| TEST_P(FrameThrottlingTest, ThrottleInnerCompositedLayer) { |
| if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) |
| 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( |
| "<div id=div style='will-change: transform; background: blue'>DIV</div>"); |
| auto commands_not_throttled = CompositeFrame(); |
| |
| auto* frame_element = |
| ToHTMLIFrameElement(GetDocument().getElementById("frame")); |
| // The inner div is composited. |
| auto* inner_div = frame_element->contentDocument()->getElementById("div"); |
| EXPECT_NE(nullptr, |
| inner_div->GetLayoutBox()->Layer()->GraphicsLayerBacking()); |
| |
| // Before the iframe is throttled, we should create all drawing commands. |
| EXPECT_EQ(7u, 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_NE(nullptr, |
| inner_div->GetLayoutBox()->Layer()->GraphicsLayerBacking()); |
| |
| // If painting of the iframe is throttled, we should only receive drawing |
| // commands for the main frame. |
| auto commands_throttled = Compositor().PaintFrame(); |
| EXPECT_EQ(5u, commands_throttled.DrawCount()); |
| |
| // Remove compositing trigger of inner_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(); |
| { |
| // And a throttled full lifecycle update. |
| DocumentLifecycle::AllowThrottlingScope throttling_scope( |
| GetDocument().Lifecycle()); |
| UpdateAllLifecyclePhases(); |
| } |
| // The inner div should still be composited because compositing update is |
| // throttled, though the inner_div's self-painting status has been updated. |
| EXPECT_FALSE(inner_div->GetLayoutBox()->Layer()->IsSelfPaintingLayer()); |
| { |
| DisableCompositingQueryAsserts disabler; |
| EXPECT_NE(nullptr, |
| inner_div->GetLayoutBox()->Layer()->GraphicsLayerBacking()); |
| } |
| |
| auto commands_throttled1 = CompositeFrame(); |
| EXPECT_EQ(5u, commands_throttled1.DrawCount()); |
| |
| // 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 no longer composited. |
| EXPECT_EQ(nullptr, |
| inner_div->GetLayoutBox()->Layer()->GraphicsLayerBacking()); |
| |
| // After the iframe is unthrottled, we should create all drawing items. |
| EXPECT_EQ(7u, commands_not_throttled1.DrawCount()); |
| } |
| |
| TEST_P(FrameThrottlingTest, ThrottleSubtreeAtomically) { |
| // TODO(crbug.com/922419): The test is broken for LayoutNG. |
| if (RuntimeEnabledFeatures::LayoutNGEnabled()) |
| return; |
| |
| // 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, but don't run the intersection observers yet. |
| auto* frame_element = |
| ToHTMLIFrameElement(GetDocument().getElementById("frame")); |
| auto* child_frame_element = ToHTMLIFrameElement( |
| frame_element->contentDocument()->getElementById("child-frame")); |
| frame_element->setAttribute(kStyleAttr, "transform: translateY(480px)"); |
| Compositor().BeginFrame(); |
| EXPECT_FALSE( |
| frame_element->contentDocument()->View()->CanThrottleRendering()); |
| EXPECT_FALSE( |
| child_frame_element->contentDocument()->View()->CanThrottleRendering()); |
| |
| // Only run the intersection observer for the parent frame. Both frames |
| // should immediately become throttled. This simulates the case where a task |
| // such as BeginMainFrame runs in the middle of dispatching intersection |
| // observer notifications. |
| frame_element->contentDocument() |
| ->View() |
| ->UpdateRenderThrottlingStatusForTesting(); |
| EXPECT_TRUE(frame_element->contentDocument()->View()->CanThrottleRendering()); |
| EXPECT_TRUE( |
| child_frame_element->contentDocument()->View()->CanThrottleRendering()); |
| |
| // Both frames should still be throttled after the second notification. |
| child_frame_element->contentDocument() |
| ->View() |
| ->UpdateRenderThrottlingStatusForTesting(); |
| EXPECT_TRUE(frame_element->contentDocument()->View()->CanThrottleRendering()); |
| EXPECT_TRUE( |
| child_frame_element->contentDocument()->View()->CanThrottleRendering()); |
| |
| // Move the frame back on screen but don't update throttling yet. |
| frame_element->setAttribute(kStyleAttr, "transform: translateY(0px)"); |
| Compositor().BeginFrame(); |
| EXPECT_TRUE(frame_element->contentDocument()->View()->CanThrottleRendering()); |
| EXPECT_TRUE( |
| child_frame_element->contentDocument()->View()->CanThrottleRendering()); |
| |
| // Update throttling for the child. It should remain throttled because the |
| // parent is still throttled. |
| child_frame_element->contentDocument() |
| ->View() |
| ->UpdateRenderThrottlingStatusForTesting(); |
| EXPECT_TRUE(frame_element->contentDocument()->View()->CanThrottleRendering()); |
| EXPECT_TRUE( |
| child_frame_element->contentDocument()->View()->CanThrottleRendering()); |
| |
| // Updating throttling on the parent should unthrottle both frames. |
| frame_element->contentDocument() |
| ->View() |
| ->UpdateRenderThrottlingStatusForTesting(); |
| 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 = |
| ToHTMLIFrameElement(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() |
| ->InvalidatePaintForViewAndCompositedLayers(); |
| |
| // 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 = |
| ToHTMLIFrameElement(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 = |
| ToHTMLIFrameElement(GetDocument().getElementById("second")); |
| LocalFrame* local_frame = ToLocalFrame(second_frame_element->ContentFrame()); |
| local_frame->GetScriptController().ExecuteScriptInMainWorld( |
| "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"); |
| 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 = |
| ToHTMLIFrameElement(GetDocument().getElementById("frame")); |
| CompositeFrame(); |
| EXPECT_TRUE(frame_element->contentDocument()->View()->CanThrottleRendering()); |
| |
| LocalFrame* local_frame = ToLocalFrame(frame_element->ContentFrame()); |
| v8::HandleScope scope(v8::Isolate::GetCurrent()); |
| v8::Local<v8::Value> result = |
| local_frame->GetScriptController().ExecuteScriptInMainWorldAndReturnValue( |
| ScriptSourceCode("window.didRaf;"), KURL(), |
| SanitizeScriptErrors::kSanitize); |
| 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 = |
| ToHTMLIFrameElement(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()->ShouldThrottleRendering()); |
| |
| 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()); |
| { |
| DocumentLifecycle::AllowThrottlingScope throttling_scope( |
| GetDocument().Lifecycle()); |
| 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(); |
| CompositeFrame(); |
| EXPECT_FALSE(frame_document->View()->CanThrottleRendering()); |
| EXPECT_EQ(TransformationMatrix().Translate(0, 20), |
| inner_div->GetLayoutObject() |
| ->FirstFragment() |
| .PaintProperties() |
| ->Transform() |
| ->Matrix()); |
| } |
| |
| 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 = |
| ToHTMLIFrameElement(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 = |
| ToHTMLIFrameElement(GetDocument().getElementById("frame")); |
| auto* child_frame_element = ToHTMLIFrameElement( |
| 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 = |
| ToHTMLIFrameElement(GetDocument().getElementById("frame")); |
| { |
| DocumentLifecycle::AllowThrottlingScope throttling_scope( |
| GetDocument().Lifecycle()); |
| EXPECT_TRUE( |
| frame_element->contentDocument()->View()->ShouldThrottleRendering()); |
| } |
| |
| auto* scroller_element = |
| frame_element->contentDocument()->getElementById("scroller"); |
| ASSERT_TRUE(scroller_element->GetLayoutObject()->HasLayer()); |
| auto* scroller_layer = |
| ToLayoutBoxModelObject(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 = |
| ToLayoutBoxModelObject(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() |
| ->UpdateStyleAndLayoutIgnorePendingStylesheets(); |
| EXPECT_EQ(DocumentLifecycle::kLayoutClean, |
| frame_element->contentDocument()->Lifecycle().GetState()); |
| { |
| DocumentLifecycle::AllowThrottlingScope throttling_scope( |
| GetDocument().Lifecycle()); |
| EXPECT_FALSE( |
| frame_element->contentDocument()->View()->ShouldThrottleRendering()); |
| } |
| |
| CompositeFrame(); |
| { |
| DocumentLifecycle::AllowThrottlingScope throttling_scope( |
| GetDocument().Lifecycle()); |
| EXPECT_TRUE( |
| frame_element->contentDocument()->View()->ShouldThrottleRendering()); |
| } |
| EXPECT_EQ(DocumentLifecycle::kCompositingClean, |
| 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 = |
| ToHTMLIFrameElement(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(); |
| |
| { |
| // Then do a full lifecycle with throttling enabled. This should not crash. |
| DocumentLifecycle::AllowThrottlingScope throttling_scope( |
| GetDocument().Lifecycle()); |
| EXPECT_TRUE(frame_document->View()->ShouldThrottleRendering()); |
| UpdateAllLifecyclePhases(); |
| } |
| } |
| |
| } // namespace blink |