blob: 0825e6b80beb8412766ff8e00ee1da39bb0df748 [file] [log] [blame]
// Copyright 2018 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/input/main_thread_scrolling_reason.h"
#include "cc/layers/picture_layer.h"
#include "cc/trees/property_tree.h"
#include "cc/trees/scroll_node.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/platform/web_url_loader_mock_factory.h"
#include "third_party/blink/renderer/core/frame/frame_test_helpers.h"
#include "third_party/blink/renderer/core/frame/local_frame_view.h"
#include "third_party/blink/renderer/core/frame/visual_viewport.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/layout/layout_embedded_content.h"
#include "third_party/blink/renderer/core/layout/layout_view.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_scrollable_area.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/graphics_layer.h"
#include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
#include "third_party/blink/renderer/platform/testing/url_test_helpers.h"
namespace blink {
#define EXPECT_MAIN_THREAD_SCROLLING_REASON(expected, actual) \
EXPECT_EQ(expected, actual) \
<< " expected: " << cc::MainThreadScrollingReason::AsText(expected) \
<< " actual: " << cc::MainThreadScrollingReason::AsText(actual)
#define EXPECT_NO_MAIN_THREAD_SCROLLING_REASON(actual) \
EXPECT_EQ(cc::MainThreadScrollingReason::kNotScrollingOnMain, actual)
class MainThreadScrollingReasonsTest : public testing::Test {
public:
MainThreadScrollingReasonsTest() : base_url_("http://www.test.com/") {
helper_.InitializeWithSettings(&ConfigureSettings);
GetWebView()->MainFrameViewWidget()->Resize(gfx::Size(320, 240));
GetWebView()->MainFrameViewWidget()->UpdateAllLifecyclePhases(
DocumentUpdateReason::kTest);
}
~MainThreadScrollingReasonsTest() override {
url_test_helpers::UnregisterAllURLsAndClearMemoryCache();
}
void NavigateTo(const String& url) {
frame_test_helpers::LoadFrame(GetWebView()->MainFrameImpl(), url.Utf8());
}
void LoadHTML(const String& html) {
frame_test_helpers::LoadHTMLString(GetWebView()->MainFrameImpl(),
html.Utf8(),
url_test_helpers::ToKURL("about:blank"));
}
void ForceFullCompositingUpdate() {
GetWebView()->MainFrameViewWidget()->UpdateAllLifecyclePhases(
DocumentUpdateReason::kTest);
}
void RegisterMockedHttpURLLoad(const String& file_name) {
// TODO(crbug.com/751425): We should use the mock functionality
// via |helper_|.
url_test_helpers::RegisterMockedURLLoadFromBase(
WebString(base_url_), test::CoreTestDataPath(), WebString(file_name));
}
const cc::ScrollNode* GetScrollNode(const cc::Layer* layer) const {
return layer->layer_tree_host()
->property_trees()
->scroll_tree.FindNodeFromElementId(layer->element_id());
}
bool IsScrollable(const cc::Layer* layer) const {
return GetScrollNode(layer)->scrollable;
}
uint32_t GetMainThreadScrollingReasons(const cc::Layer* layer) const {
return GetScrollNode(layer)->main_thread_scrolling_reasons;
}
uint32_t GetViewMainThreadScrollingReasons() const {
const auto* scroll = GetFrame()
->View()
->GetLayoutView()
->FirstFragment()
.PaintProperties()
->Scroll();
return scroll->GetMainThreadScrollingReasons();
}
WebViewImpl* GetWebView() const { return helper_.GetWebView(); }
LocalFrame* GetFrame() const { return helper_.LocalMainFrame()->GetFrame(); }
PaintLayerScrollableArea* GetScrollableArea(const Element& element) const {
return To<LayoutBoxModelObject>(element.GetLayoutObject())
->GetScrollableArea();
}
protected:
String base_url_;
private:
static void ConfigureSettings(WebSettings* settings) {
settings->SetPreferCompositingToLCDTextEnabled(true);
}
frame_test_helpers::WebViewHelper helper_;
};
// More cases are tested in LocalFrameViewTest
// .RequiresMainThreadScrollingForBackgroundFixedAttachment.
TEST_F(MainThreadScrollingReasonsTest,
BackgroundAttachmentFixedShouldTriggerMainThreadScroll) {
RegisterMockedHttpURLLoad("iframe-background-attachment-fixed.html");
RegisterMockedHttpURLLoad("iframe-background-attachment-fixed-inner.html");
RegisterMockedHttpURLLoad("white-1x1.png");
NavigateTo(base_url_ + "iframe-background-attachment-fixed.html");
ForceFullCompositingUpdate();
Element* iframe = GetFrame()->GetDocument()->getElementById("iframe");
ASSERT_TRUE(iframe);
LayoutObject* layout_object = iframe->GetLayoutObject();
ASSERT_TRUE(layout_object);
ASSERT_TRUE(layout_object->IsLayoutEmbeddedContent());
auto* layout_embedded_content = To<LayoutEmbeddedContent>(layout_object);
ASSERT_TRUE(layout_embedded_content);
LocalFrameView* inner_frame_view =
To<LocalFrameView>(layout_embedded_content->ChildFrameView());
ASSERT_TRUE(inner_frame_view);
auto* inner_layout_view = inner_frame_view->GetLayoutView();
ASSERT_TRUE(inner_layout_view);
PaintLayerCompositor* inner_compositor = inner_layout_view->Compositor();
ASSERT_TRUE(inner_compositor->InCompositingMode());
cc::Layer* cc_scroll_layer =
inner_frame_view->LayoutViewport()->LayerForScrolling();
ASSERT_TRUE(cc_scroll_layer);
ASSERT_TRUE(IsScrollable(cc_scroll_layer));
EXPECT_MAIN_THREAD_SCROLLING_REASON(
cc::MainThreadScrollingReason::kHasBackgroundAttachmentFixedObjects,
GetMainThreadScrollingReasons(cc_scroll_layer));
// Remove fixed background-attachment should make the iframe
// scroll on cc.
auto* iframe_doc = To<HTMLIFrameElement>(iframe)->contentDocument();
iframe = iframe_doc->getElementById("scrollable");
ASSERT_TRUE(iframe);
iframe->removeAttribute("class");
ForceFullCompositingUpdate();
layout_object = iframe->GetLayoutObject();
ASSERT_TRUE(layout_object);
cc_scroll_layer =
layout_object->GetFrameView()->LayoutViewport()->LayerForScrolling();
ASSERT_TRUE(cc_scroll_layer);
ASSERT_TRUE(IsScrollable(cc_scroll_layer));
EXPECT_NO_MAIN_THREAD_SCROLLING_REASON(
GetMainThreadScrollingReasons(cc_scroll_layer));
// Force main frame to scroll on main thread. All its descendants
// should scroll on main thread as well.
Element* element = GetFrame()->GetDocument()->getElementById("scrollable");
element->setAttribute(
"style",
"background-image: url('white-1x1.png'); background-attachment: fixed;",
ASSERT_NO_EXCEPTION);
ForceFullCompositingUpdate();
layout_object = iframe->GetLayoutObject();
ASSERT_TRUE(layout_object);
cc_scroll_layer =
layout_object->GetFrameView()->LayoutViewport()->LayerForScrolling();
ASSERT_TRUE(cc_scroll_layer);
ASSERT_TRUE(IsScrollable(cc_scroll_layer));
EXPECT_MAIN_THREAD_SCROLLING_REASON(
cc::MainThreadScrollingReason::kHasBackgroundAttachmentFixedObjects,
GetMainThreadScrollingReasons(cc_scroll_layer));
}
// Upon resizing the content size, the main thread scrolling reason
// kHasBackgroundAttachmentFixedObjects should be updated on all frames
TEST_F(MainThreadScrollingReasonsTest,
RecalculateMainThreadScrollingReasonsUponResize) {
GetWebView()->GetSettings()->SetPreferCompositingToLCDTextEnabled(false);
RegisterMockedHttpURLLoad("has-non-layer-viewport-constrained-objects.html");
RegisterMockedHttpURLLoad("white-1x1.png");
NavigateTo(base_url_ + "has-non-layer-viewport-constrained-objects.html");
ForceFullCompositingUpdate();
// When the main document is not scrollable, there should be no reasons.
EXPECT_FALSE(GetViewMainThreadScrollingReasons());
// When the div forces the document to be scrollable, it should scroll on main
// thread.
Element* element = GetFrame()->GetDocument()->getElementById("scrollable");
element->setAttribute(
"style",
"background-image: url('white-1x1.png'); background-attachment: fixed;",
ASSERT_NO_EXCEPTION);
ForceFullCompositingUpdate();
EXPECT_MAIN_THREAD_SCROLLING_REASON(
cc::MainThreadScrollingReason::kHasBackgroundAttachmentFixedObjects,
GetViewMainThreadScrollingReasons());
// The main thread scrolling reason should be reset upon the following change.
element->setAttribute("style", "", ASSERT_NO_EXCEPTION);
ForceFullCompositingUpdate();
EXPECT_FALSE(GetViewMainThreadScrollingReasons());
}
TEST_F(MainThreadScrollingReasonsTest, FastScrollingCanBeDisabledWithSetting) {
GetWebView()->MainFrameViewWidget()->Resize(gfx::Size(800, 600));
LoadHTML("<div id='spacer' style='height: 1000px'></div>");
GetWebView()->GetSettings()->SetThreadedScrollingEnabled(false);
GetFrame()->View()->SetNeedsPaintPropertyUpdate();
ForceFullCompositingUpdate();
// Main scrolling should be enabled with the setting override.
EXPECT_TRUE(GetViewMainThreadScrollingReasons());
// Main scrolling should also propagate to inner viewport layer.
const cc::Layer* visual_viewport_scroll_layer =
GetFrame()->GetPage()->GetVisualViewport().LayerForScrolling();
ASSERT_TRUE(IsScrollable(visual_viewport_scroll_layer));
EXPECT_TRUE(GetMainThreadScrollingReasons(visual_viewport_scroll_layer));
}
TEST_F(MainThreadScrollingReasonsTest, FastScrollingForFixedPosition) {
RegisterMockedHttpURLLoad("fixed-position.html");
NavigateTo(base_url_ + "fixed-position.html");
ForceFullCompositingUpdate();
// Fixed position should not fall back to main thread scrolling.
EXPECT_FALSE(GetViewMainThreadScrollingReasons());
}
TEST_F(MainThreadScrollingReasonsTest, FastScrollingForStickyPosition) {
RegisterMockedHttpURLLoad("sticky-position.html");
NavigateTo(base_url_ + "sticky-position.html");
ForceFullCompositingUpdate();
// Sticky position should not fall back to main thread scrolling.
EXPECT_FALSE(GetViewMainThreadScrollingReasons());
}
TEST_F(MainThreadScrollingReasonsTest, FastScrollingByDefault) {
GetWebView()->MainFrameViewWidget()->Resize(gfx::Size(800, 600));
LoadHTML("<div id='spacer' style='height: 1000px'></div>");
ForceFullCompositingUpdate();
// Fast scrolling should be enabled by default.
EXPECT_FALSE(GetViewMainThreadScrollingReasons());
const cc::Layer* visual_viewport_scroll_layer =
GetFrame()->GetPage()->GetVisualViewport().LayerForScrolling();
ASSERT_TRUE(IsScrollable(visual_viewport_scroll_layer));
EXPECT_FALSE(GetMainThreadScrollingReasons(visual_viewport_scroll_layer));
}
TEST_F(MainThreadScrollingReasonsTest,
ScrollbarsForceMainThreadOrHaveCompositorScrollbarLayer) {
RegisterMockedHttpURLLoad("trivial-scroller.html");
NavigateTo(base_url_ + "trivial-scroller.html");
ForceFullCompositingUpdate();
Document* document = GetFrame()->GetDocument();
Element* scrollable_element = document->getElementById("scroller");
DCHECK(scrollable_element);
LayoutObject* layout_object = scrollable_element->GetLayoutObject();
auto* box = To<LayoutBox>(layout_object);
ASSERT_TRUE(box->UsesCompositedScrolling());
CompositedLayerMapping* composited_layer_mapping =
box->Layer()->GetCompositedLayerMapping();
GraphicsLayer* scrollbar_graphics_layer =
composited_layer_mapping->LayerForVerticalScrollbar();
ASSERT_TRUE(scrollbar_graphics_layer);
bool has_cc_scrollbar_layer = !scrollbar_graphics_layer->DrawsContent();
EXPECT_TRUE(
has_cc_scrollbar_layer ||
GetMainThreadScrollingReasons(scrollbar_graphics_layer->ContentsLayer()));
}
class NonCompositedMainThreadScrollingReasonsTest
: public MainThreadScrollingReasonsTest {
static const uint32_t kLCDTextRelatedReasons =
cc::MainThreadScrollingReason::kNotOpaqueForTextAndLCDText;
protected:
NonCompositedMainThreadScrollingReasonsTest() {
RegisterMockedHttpURLLoad("two_scrollable_area.html");
NavigateTo(base_url_ + "two_scrollable_area.html");
}
void TestNonCompositedReasons(const AtomicString& style_class,
const uint32_t reason) {
GetWebView()->GetSettings()->SetPreferCompositingToLCDTextEnabled(false);
Document* document = GetFrame()->GetDocument();
Element* container = document->getElementById("scroller1");
ForceFullCompositingUpdate();
PaintLayerScrollableArea* scrollable_area = GetScrollableArea(*container);
ASSERT_TRUE(scrollable_area);
EXPECT_NO_MAIN_THREAD_SCROLLING_REASON(
scrollable_area->GetNonCompositedMainThreadScrollingReasons());
container->classList().Add(style_class);
ForceFullCompositingUpdate();
ASSERT_TRUE(scrollable_area);
EXPECT_MAIN_THREAD_SCROLLING_REASON(
reason, scrollable_area->GetNonCompositedMainThreadScrollingReasons());
Element* container2 = document->getElementById("scroller2");
PaintLayerScrollableArea* scrollable_area2 = GetScrollableArea(*container2);
ASSERT_TRUE(scrollable_area2);
// Different scrollable area should remain unaffected.
EXPECT_NO_MAIN_THREAD_SCROLLING_REASON(
scrollable_area2->GetNonCompositedMainThreadScrollingReasons());
LocalFrameView* frame_view = GetFrame()->View();
ASSERT_TRUE(frame_view);
EXPECT_NO_MAIN_THREAD_SCROLLING_REASON(
frame_view->GetMainThreadScrollingReasons());
// Remove class from the scroller 1 would lead to scroll on impl.
container->classList().Remove(style_class);
ForceFullCompositingUpdate();
EXPECT_NO_MAIN_THREAD_SCROLLING_REASON(
scrollable_area->GetNonCompositedMainThreadScrollingReasons());
EXPECT_NO_MAIN_THREAD_SCROLLING_REASON(
frame_view->GetMainThreadScrollingReasons());
// Add target attribute would again lead to scroll on main thread
container->classList().Add(style_class);
ForceFullCompositingUpdate();
EXPECT_MAIN_THREAD_SCROLLING_REASON(
reason, scrollable_area->GetNonCompositedMainThreadScrollingReasons());
EXPECT_NO_MAIN_THREAD_SCROLLING_REASON(
frame_view->GetMainThreadScrollingReasons());
if ((reason & kLCDTextRelatedReasons) &&
!(reason & ~kLCDTextRelatedReasons)) {
GetWebView()->GetSettings()->SetPreferCompositingToLCDTextEnabled(true);
ForceFullCompositingUpdate();
EXPECT_NO_MAIN_THREAD_SCROLLING_REASON(
scrollable_area->GetNonCompositedMainThreadScrollingReasons());
EXPECT_NO_MAIN_THREAD_SCROLLING_REASON(
frame_view->GetMainThreadScrollingReasons());
}
}
};
TEST_F(NonCompositedMainThreadScrollingReasonsTest, TransparentTest) {
TestNonCompositedReasons("transparent",
cc::MainThreadScrollingReason::kNotScrollingOnMain);
}
TEST_F(NonCompositedMainThreadScrollingReasonsTest, TransformTest) {
TestNonCompositedReasons("transform",
cc::MainThreadScrollingReason::kNotScrollingOnMain);
}
TEST_F(NonCompositedMainThreadScrollingReasonsTest, BackgroundNotOpaqueTest) {
TestNonCompositedReasons(
"background-not-opaque",
cc::MainThreadScrollingReason::kNotOpaqueForTextAndLCDText);
}
TEST_F(NonCompositedMainThreadScrollingReasonsTest,
CantPaintScrollingBackgroundTest) {
TestNonCompositedReasons(
"cant-paint-scrolling-background",
cc::MainThreadScrollingReason::kCantPaintScrollingBackgroundAndLCDText);
}
TEST_F(NonCompositedMainThreadScrollingReasonsTest, ClipTest) {
TestNonCompositedReasons("clip",
cc::MainThreadScrollingReason::kNotScrollingOnMain);
}
TEST_F(NonCompositedMainThreadScrollingReasonsTest, ClipPathTest) {
TestNonCompositedReasons("clip-path",
cc::MainThreadScrollingReason::kNotScrollingOnMain);
}
TEST_F(NonCompositedMainThreadScrollingReasonsTest, BoxShadowTest) {
TestNonCompositedReasons("box-shadow",
cc::MainThreadScrollingReason::kNotScrollingOnMain);
}
TEST_F(NonCompositedMainThreadScrollingReasonsTest, InsetBoxShadowTest) {
TestNonCompositedReasons(
"inset-box-shadow",
cc::MainThreadScrollingReason::kCantPaintScrollingBackgroundAndLCDText);
}
TEST_F(NonCompositedMainThreadScrollingReasonsTest, StackingContextTest) {
TestNonCompositedReasons("non-stacking-context",
cc::MainThreadScrollingReason::kNotScrollingOnMain);
}
TEST_F(NonCompositedMainThreadScrollingReasonsTest, BorderRadiusTest) {
TestNonCompositedReasons("border-radius",
cc::MainThreadScrollingReason::kNotScrollingOnMain);
}
TEST_F(NonCompositedMainThreadScrollingReasonsTest,
ForcedComositingWithLCDRelatedReasons) {
// With "will-change:transform" we composite elements with
// LCDTextRelatedReasons only. For elements with other
// NonCompositedReasons, we don't create scrollingLayer for their
// CompositedLayerMapping therefore they don't get composited.
GetWebView()->GetSettings()->SetPreferCompositingToLCDTextEnabled(false);
Document* document = GetFrame()->GetDocument();
Element* container = document->getElementById("scroller1");
ASSERT_TRUE(container);
container->setAttribute("class", "composited transparent",
ASSERT_NO_EXCEPTION);
ForceFullCompositingUpdate();
PaintLayerScrollableArea* scrollable_area = GetScrollableArea(*container);
ASSERT_TRUE(scrollable_area);
EXPECT_NO_MAIN_THREAD_SCROLLING_REASON(
scrollable_area->GetNonCompositedMainThreadScrollingReasons());
Element* container2 = document->getElementById("scroller2");
ASSERT_TRUE(container2);
container2->setAttribute("class", "composited border-radius",
ASSERT_NO_EXCEPTION);
ForceFullCompositingUpdate();
PaintLayerScrollableArea* scrollable_area2 = GetScrollableArea(*container2);
ASSERT_TRUE(scrollable_area2);
ASSERT_TRUE(scrollable_area2->UsesCompositedScrolling());
}
} // namespace blink