Chromium Code Reviews| Index: third_party/WebKit/Source/web/tests/FrameThrottlingTest.cpp |
| diff --git a/third_party/WebKit/Source/web/tests/FrameThrottlingTest.cpp b/third_party/WebKit/Source/web/tests/FrameThrottlingTest.cpp |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..fe44fd2906ebb278f90ab2b5864d998c1a4f69c7 |
| --- /dev/null |
| +++ b/third_party/WebKit/Source/web/tests/FrameThrottlingTest.cpp |
| @@ -0,0 +1,280 @@ |
| +// 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 "config.h" |
| + |
| +#include "core/dom/Document.h" |
| +#include "core/frame/FrameView.h" |
| +#include "platform/testing/UnitTestHelpers.h" |
| +#include "public/web/WebElement.h" |
| +#include "public/web/WebHitTestResult.h" |
| +#include "web/WebLocalFrameImpl.h" |
| +#include "web/tests/FrameTestHelpers.h" |
| +#include "web/tests/sim/SimCompositor.h" |
| +#include "web/tests/sim/SimDisplayItemList.h" |
| +#include "web/tests/sim/SimLayerTreeView.h" |
| +#include "web/tests/sim/SimWebViewClient.h" |
| +#include <gtest/gtest.h> |
| + |
| +namespace blink { |
| + |
| +class FrameThrottlingTest : public ::testing::Test { |
| +protected: |
| + using SecurityOriginStatus = FrameView::SecurityOriginStatus; |
| + using ViewportVisibility = FrameView::ViewportVisibility; |
| + |
| + FrameThrottlingTest() |
| + : m_webViewClient(m_layerTreeView) |
| + , m_compositor(m_layerTreeView) |
| + { |
| + m_webViewHelper.initialize(true, &m_webFrameClient, &m_webViewClient); |
| + m_compositor.setWebViewImpl(webView()); |
| + m_layerTreeView.setDeferCommits(false); |
| + webView().resize(WebSize(640, 480)); |
| + } |
| + |
| + ~FrameThrottlingTest() override { } |
| + |
| + void setInnerHTML(const String& html) |
| + { |
| + document().documentElement()->setInnerHTML(html, ASSERT_NO_EXCEPTION); |
| + m_webFrameClient.waitForLoadToComplete(); |
| + webView().layout(); |
|
esprehn
2015/10/14 22:09:47
You want to use SimRequest instead, don't use inne
Sami
2015/10/16 16:48:09
Done.
|
| + } |
| + |
| + WebElement setupPageWithThrottleableFrame() |
| + { |
| + setInnerHTML("<iframe sandbox=\"\" id=\"frame\"></iframe>"); |
|
esprehn
2015/10/14 22:09:46
use single quotes, (or no quotes) you don't need t
Sami
2015/10/16 16:48:09
Done.
|
| + WebExceptionCode ec; |
| + WebElement frameElement = root().querySelector("#frame", ec); |
| + EXPECT_EQ(0, ec); |
| + return frameElement; |
| + } |
| + |
| + Document& document() |
| + { |
| + return *webView().mainFrameImpl()->frame()->document(); |
| + } |
| + |
| + WebNode root() |
| + { |
| + return WebNode(document().documentElement()); |
| + } |
| + |
| + WebViewImpl& webView() |
| + { |
| + return *m_webViewHelper.webViewImpl(); |
| + } |
| + |
| + ViewportVisibility viewportVisibility(FrameView* frameView) |
| + { |
| + return frameView->m_viewportVisibilityForThrottling; |
|
esprehn
2015/10/14 22:09:47
add a getter, don't reach into members
Sami
2015/10/16 16:48:09
Done.
|
| + } |
| + |
| + SimDisplayItemList compositeFrame() |
| + { |
| + SimDisplayItemList displayItems = m_compositor.beginFrame(); |
| + // Ensure intersection observer notifications get delivered. |
| + testing::runPendingTasks(); |
| + return displayItems; |
| + } |
| + |
| + SimLayerTreeView m_layerTreeView; |
| + SimWebViewClient m_webViewClient; |
| + SimCompositor m_compositor; |
| + FrameTestHelpers::TestWebFrameClient m_webFrameClient; |
| + FrameTestHelpers::WebViewHelper m_webViewHelper; |
| +}; |
| + |
| +TEST_F(FrameThrottlingTest, throttleInvisibleFrames) |
| +{ |
| + WebElement frameElement = setupPageWithThrottleableFrame(); |
| + |
| + // Initially both frames are visible. |
| + FrameView* frameView = document().view(); |
| + FrameView* childFrameView = toLocalFrame(frameView->frame().tree().firstChild())->view(); |
| + EXPECT_EQ(ViewportVisibility::Visible, viewportVisibility(frameView)); |
| + EXPECT_EQ(ViewportVisibility::Visible, viewportVisibility(childFrameView)); |
| + |
| + // Moving the child fully outside the parent makes it invisible. |
| + frameElement.setAttribute("style", "transform: translateY(480px)"); |
| + compositeFrame(); |
| + EXPECT_EQ(ViewportVisibility::Visible, viewportVisibility(frameView)); |
| + EXPECT_EQ(ViewportVisibility::Hidden, viewportVisibility(childFrameView)); |
| + |
| + // A partially visible child is considered visible. |
| + frameElement.setAttribute("style", "transform: translate(-50px, 0px, 0px)"); |
| + compositeFrame(); |
| + EXPECT_EQ(ViewportVisibility::Visible, viewportVisibility(frameView)); |
| + EXPECT_EQ(ViewportVisibility::Visible, viewportVisibility(childFrameView)); |
| +} |
| + |
| +TEST_F(FrameThrottlingTest, viewportVisibilityFullyClipped) |
| +{ |
| + setupPageWithThrottleableFrame(); |
| + |
| + // A child which is fully clipped away by its ancestor should become invisible. |
| + FrameView* frameView = document().view(); |
| + FrameView* childFrameView = toLocalFrame(frameView->frame().tree().firstChild())->view(); |
| + webView().resize(WebSize(0, 0)); |
| + compositeFrame(); |
| + EXPECT_EQ(ViewportVisibility::Hidden, viewportVisibility(frameView)); |
| + EXPECT_EQ(ViewportVisibility::Hidden, viewportVisibility(childFrameView)); |
| +} |
| + |
| +TEST_F(FrameThrottlingTest, hiddenSameOriginFramesAreNotThrottled) |
| +{ |
| + // Create a document with doubly nested iframes. |
| + setInnerHTML("<iframe id=\"frame\" srcdoc=\"<iframe></iframe>\"></iframe>"); |
| + WebExceptionCode ec; |
| + WebElement frameElement = root().querySelector("#frame", ec); |
| + EXPECT_EQ(0, ec); |
| + |
| + FrameView* frameView = document().view(); |
| + LocalFrame* childFrame = toLocalFrame(frameView->frame().tree().firstChild()); |
| + LocalFrame* grandChildFrame = toLocalFrame(childFrame->tree().firstChild()); |
| + FrameView* childFrameView = childFrame->view(); |
| + FrameView* grandChildFrameView = grandChildFrame->view(); |
| + EXPECT_FALSE(frameView->shouldThrottleRendering()); |
| + EXPECT_FALSE(childFrameView->shouldThrottleRendering()); |
| + EXPECT_FALSE(grandChildFrameView->shouldThrottleRendering()); |
| + |
| + // Hidden same origin frames are not throttled. |
| + frameElement.setAttribute("style", "transform: translateY(480px)"); |
| + compositeFrame(); |
| + EXPECT_FALSE(frameView->shouldThrottleRendering()); |
| + EXPECT_FALSE(childFrameView->shouldThrottleRendering()); |
| + EXPECT_FALSE(grandChildFrameView->shouldThrottleRendering()); |
| +} |
| + |
| +TEST_F(FrameThrottlingTest, hiddenCrossOriginFramesAreThrottled) |
| +{ |
| + // Create a document with doubly nested iframes. |
| + setInnerHTML("<iframe id=\"frame\" srcdoc=\"<iframe sandbox></iframe>\"></iframe>"); |
|
esprehn
2015/10/14 22:09:47
ditto
Sami
2015/10/16 16:48:09
Done.
|
| + WebExceptionCode ec; |
| + WebElement frameElement = root().querySelector("#frame", ec); |
| + EXPECT_EQ(0, ec); |
| + |
| + FrameView* frameView = document().view(); |
| + LocalFrame* childFrame = toLocalFrame(frameView->frame().tree().firstChild()); |
| + LocalFrame* grandChildFrame = toLocalFrame(childFrame->tree().firstChild()); |
| + FrameView* childFrameView = childFrame->view(); |
| + FrameView* grandChildFrameView = grandChildFrame->view(); |
| + EXPECT_FALSE(frameView->shouldThrottleRendering()); |
| + EXPECT_FALSE(childFrameView->shouldThrottleRendering()); |
| + EXPECT_FALSE(grandChildFrameView->shouldThrottleRendering()); |
| + |
| + // Hidden cross origin frames are throttled. |
| + frameElement.setAttribute("style", "transform: translateY(480px)"); |
| + compositeFrame(); |
| + EXPECT_FALSE(frameView->shouldThrottleRendering()); |
| + EXPECT_FALSE(childFrameView->shouldThrottleRendering()); |
| + EXPECT_TRUE(grandChildFrameView->shouldThrottleRendering()); |
| +} |
| + |
| +TEST_F(FrameThrottlingTest, throttledLifecycleUpdate) |
| +{ |
| + WebElement frameElement = setupPageWithThrottleableFrame(); |
| + FrameView* frameView = document().view(); |
| + LocalFrame* childFrame = toLocalFrame(frameView->frame().tree().firstChild()); |
| + FrameView* childFrameView = childFrame->view(); |
| + |
| + // Enable throttling for the child frame. |
| + frameElement.setAttribute("style", "transform: translateY(480px)"); |
| + compositeFrame(); |
| + EXPECT_TRUE(childFrameView->shouldThrottleRendering()); |
| + EXPECT_EQ(DocumentLifecycle::PaintInvalidationClean, childFrame->document()->lifecycle().state()); |
| + |
| + // Mutating the throttled frame followed by a beginFrame will not result in |
| + // a complete lifecycle update. |
| + frameElement.setAttribute("width", "50"); |
| + compositeFrame(); |
| + EXPECT_EQ(DocumentLifecycle::StyleClean, childFrame->document()->lifecycle().state()); |
| + |
| + // A hit test will force a complete lifecycle update. |
| + webView().hitTestResultAt(WebPoint(0, 0)); |
| + EXPECT_EQ(DocumentLifecycle::CompositingClean, childFrame->document()->lifecycle().state()); |
| +} |
| + |
| +TEST_F(FrameThrottlingTest, unthrottlingFrameSchedulesAnimation) |
| +{ |
| + WebElement frameElement = setupPageWithThrottleableFrame(); |
| + FrameView* frameView = document().view(); |
| + LocalFrame* childFrame = toLocalFrame(frameView->frame().tree().firstChild()); |
| + FrameView* childFrameView = childFrame->view(); |
| + |
| + // First make the child hidden to enable throttling. |
| + frameElement.setAttribute("style", "transform: translateY(480px)"); |
| + compositeFrame(); |
| + EXPECT_TRUE(childFrameView->shouldThrottleRendering()); |
| + EXPECT_FALSE(m_layerTreeView.needsAnimate()); |
| + |
| + // Then bring it back on-screen. This should schedule an animation update. |
| + frameElement.setAttribute("style", ""); |
| + compositeFrame(); |
| + EXPECT_TRUE(m_layerTreeView.needsAnimate()); |
| +} |
| + |
| +TEST_F(FrameThrottlingTest, mutatingThrottledFrameDoesNotCauseAnimation) |
| +{ |
| + setInnerHTML("<iframe id=\"frame\" sandbox=\"\" srcdoc=\"<style> html { background: red; } </style>\"></iframe>"); |
|
esprehn
2015/10/14 22:09:47
remove quotes and us single quotes, remove black s
Sami
2015/10/16 16:48:09
Done.
|
| + WebExceptionCode ec; |
| + WebElement frameElement = root().querySelector("#frame", ec); |
| + EXPECT_EQ(0, ec); |
| + |
| + // Check that the frame initially shows up. |
| + m_layerTreeView.setDeferCommits(false); |
| + SimDisplayItemList displayItems1 = compositeFrame(); |
| + EXPECT_TRUE(displayItems1.contains(SimCanvas::Rect, "red")); |
| + |
| + // Move the frame offscreen to throttle it. |
| + FrameView* frameView = document().view(); |
| + LocalFrame* childFrame = toLocalFrame(frameView->frame().tree().firstChild()); |
| + frameElement.setAttribute("style", "transform: translateY(480px)"); |
| + compositeFrame(); |
| + EXPECT_TRUE(childFrame->view()->shouldThrottleRendering()); |
| + |
| + // Mutating the throttled frame should not cause an animation to be scheduled. |
| + childFrame->document()->documentElement()->setInnerHTML("<style> html { background: green; } </style>", ASSERT_NO_EXCEPTION); |
| + EXPECT_FALSE(m_layerTreeView.needsAnimate()); |
| + |
| + // Moving the frame back on screen to unthrottle it. |
| + frameElement.setAttribute("style", ""); |
| + EXPECT_TRUE(m_layerTreeView.needsAnimate()); |
| + |
| + // 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. |
| + SimDisplayItemList displayItems2 = compositeFrame(); |
| + EXPECT_FALSE(displayItems2.contains(SimCanvas::Rect, "green")); |
| + EXPECT_TRUE(m_layerTreeView.needsAnimate()); |
| + |
| + SimDisplayItemList displayItems3 = compositeFrame(); |
| + EXPECT_TRUE(displayItems3.contains(SimCanvas::Rect, "green")); |
| +} |
| + |
| +TEST_F(FrameThrottlingTest, synchronousLayoutInThrottledFrame) |
| +{ |
| + // Create a hidden frame which is throttled. |
| + setInnerHTML("<iframe id=\"frame\" sandbox=\"\" srcdoc=\"<div id=div></div>\"></iframe>"); |
| + WebExceptionCode ec; |
| + WebElement frameElement = root().querySelector("#frame", ec); |
| + EXPECT_EQ(0, ec); |
| + frameElement.setAttribute("style", "transform: translateY(480px)"); |
| + compositeFrame(); |
| + |
| + FrameView* frameView = document().view(); |
| + LocalFrame* childFrame = toLocalFrame(frameView->frame().tree().firstChild()); |
| + |
| + // Change the size of a div in the throttled frame. |
| + Element* divElement = childFrame->document()->getElementById("div"); |
| + divElement->setAttribute(HTMLNames::styleAttr, "width: 50px"); |
| + |
| + // Querying the width of the div should do a synchronous layout update even |
| + // though the frame is being throttled. |
| + EXPECT_EQ(50, divElement->clientWidth()); |
| +} |
| + |
| +} // namespace blink |