blob: e7cf4856b41df14706de2142acd28e3fdcd82df3 [file] [log] [blame]
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/input/web_coalesced_input_event.h"
#include "third_party/blink/public/common/page/page_zoom.h"
#include "third_party/blink/public/platform/web_url_loader_mock_factory.h"
#include "third_party/blink/public/web/web_console_message.h"
#include "third_party/blink/public/web/web_hit_test_result.h"
#include "third_party/blink/public/web/web_remote_frame.h"
#include "third_party/blink/public/web/web_script_source.h"
#include "third_party/blink/public/web/web_settings.h"
#include "third_party/blink/renderer/bindings/core/v8/node_or_string_or_trusted_script.h"
#include "third_party/blink/renderer/core/frame/browser_controls.h"
#include "third_party/blink/renderer/core/frame/dom_visual_viewport.h"
#include "third_party/blink/renderer/core/frame/frame_test_helpers.h"
#include "third_party/blink/renderer/core/frame/local_dom_window.h"
#include "third_party/blink/renderer/core/frame/local_frame_view.h"
#include "third_party/blink/renderer/core/frame/root_frame_viewport.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/frame/web_remote_frame_impl.h"
#include "third_party/blink/renderer/core/geometry/dom_rect.h"
#include "third_party/blink/renderer/core/html/html_frame_owner_element.h"
#include "third_party/blink/renderer/core/layout/layout_box.h"
#include "third_party/blink/renderer/core/layout/layout_view.h"
#include "third_party/blink/renderer/core/loader/document_loader.h"
#include "third_party/blink/renderer/core/page/page.h"
#include "third_party/blink/renderer/core/page/scrolling/root_scroller_controller.h"
#include "third_party/blink/renderer/core/page/scrolling/top_document_root_scroller_controller.h"
#include "third_party/blink/renderer/core/paint/compositing/composited_layer_mapping.h"
#include "third_party/blink/renderer/core/paint/compositing/paint_layer_compositor.h"
#include "third_party/blink/renderer/core/paint/paint_layer.h"
#include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h"
#include "third_party/blink/renderer/core/testing/sim/sim_request.h"
#include "third_party/blink/renderer/core/testing/sim/sim_test.h"
#include "third_party/blink/renderer/platform/testing/runtime_enabled_features_test_helpers.h"
#include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
#include "third_party/blink/renderer/platform/testing/url_test_helpers.h"
#include "third_party/blink/renderer/platform/wtf/vector.h"
using blink::test::RunPendingTasks;
using testing::Mock;
namespace blink {
namespace {
class RootScrollerTest : public testing::Test,
private ScopedImplicitRootScrollerForTest {
public:
RootScrollerTest()
: ScopedImplicitRootScrollerForTest(true),
base_url_("http://www.test.com/") {
RegisterMockedHttpURLLoad("overflow-scrolling.html");
RegisterMockedHttpURLLoad("root-scroller.html");
RegisterMockedHttpURLLoad("root-scroller-rotation.html");
RegisterMockedHttpURLLoad("root-scroller-iframe.html");
RegisterMockedHttpURLLoad("root-scroller-child.html");
}
~RootScrollerTest() override {
features_backup_.Restore();
url_test_helpers::UnregisterAllURLsAndClearMemoryCache();
}
WebViewImpl* Initialize(const String& page_name) {
return InitializeInternal(base_url_ + page_name);
}
WebViewImpl* Initialize() { return InitializeInternal("about:blank"); }
static void ConfigureSettings(WebSettings* settings) {
settings->SetJavaScriptEnabled(true);
settings->SetPreferCompositingToLCDTextEnabled(true);
// Android settings.
settings->SetViewportEnabled(true);
settings->SetViewportMetaEnabled(true);
settings->SetShrinksViewportContentToFit(true);
settings->SetMainFrameResizesAreOrientationChanges(true);
}
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));
}
void ExecuteScript(const WebString& code) {
ExecuteScript(code, *MainWebFrame());
}
void ExecuteScript(const WebString& code, WebLocalFrame& frame) {
frame.ExecuteScript(WebScriptSource(code));
frame.View()->MainFrameWidget()->UpdateAllLifecyclePhases(
DocumentUpdateReason::kTest);
RunPendingTasks();
}
WebViewImpl* GetWebView() const { return helper_->GetWebView(); }
Page& GetPage() const { return *GetWebView()->GetPage(); }
PaintLayerScrollableArea* GetScrollableArea(const Element& element) const {
return To<LayoutBoxModelObject>(element.GetLayoutObject())
->GetScrollableArea();
}
LocalFrame* MainFrame() const {
return GetWebView()->MainFrameImpl()->GetFrame();
}
WebLocalFrame* MainWebFrame() const { return GetWebView()->MainFrameImpl(); }
LocalFrameView* MainFrameView() const {
return GetWebView()->MainFrameImpl()->GetFrame()->View();
}
VisualViewport& GetVisualViewport() const {
return GetPage().GetVisualViewport();
}
BrowserControls& GetBrowserControls() const {
return GetPage().GetBrowserControls();
}
Node* EffectiveRootScroller(Document* doc) const {
return &doc->GetRootScrollerController().EffectiveRootScroller();
}
WebCoalescedInputEvent GenerateTouchGestureEvent(WebInputEvent::Type type,
int delta_x = 0,
int delta_y = 0) {
return GenerateGestureEvent(type, WebGestureDevice::kTouchscreen, delta_x,
delta_y);
}
WebCoalescedInputEvent GenerateWheelGestureEvent(WebInputEvent::Type type,
int delta_x = 0,
int delta_y = 0) {
return GenerateGestureEvent(type, WebGestureDevice::kTouchpad, delta_x,
delta_y);
}
void SetCreateWebFrameWidgetCallback(
const frame_test_helpers::CreateTestWebFrameWidgetCallback&
create_widget_callback) {
create_widget_callback_ = create_widget_callback;
}
protected:
WebCoalescedInputEvent GenerateGestureEvent(WebInputEvent::Type type,
WebGestureDevice device,
int delta_x,
int delta_y) {
WebGestureEvent event(type, WebInputEvent::kNoModifiers,
WebInputEvent::GetStaticTimeStampForTests(), device);
event.SetPositionInWidget(gfx::PointF(100, 100));
if (type == WebInputEvent::Type::kGestureScrollUpdate) {
event.data.scroll_update.delta_x = delta_x;
event.data.scroll_update.delta_y = delta_y;
} else if (type == WebInputEvent::Type::kGestureScrollBegin) {
event.data.scroll_begin.delta_x_hint = delta_x;
event.data.scroll_begin.delta_y_hint = delta_y;
}
return WebCoalescedInputEvent(event, ui::LatencyInfo());
}
WebViewImpl* InitializeInternal(const String& url) {
helper_ = std::make_unique<frame_test_helpers::WebViewHelper>(
create_widget_callback_);
helper_->InitializeAndLoad(url.Utf8(), nullptr, nullptr,
&ConfigureSettings);
// Initialize browser controls to be shown.
GetWebView()->ResizeWithBrowserControls(gfx::Size(400, 400), 50, 60, true);
GetWebView()->GetBrowserControls().SetShownRatio(1, 1);
UpdateAllLifecyclePhases(MainFrameView());
return GetWebView();
}
void UpdateAllLifecyclePhases(LocalFrameView* view) {
view->UpdateAllLifecyclePhasesForTest();
}
String base_url_;
frame_test_helpers::CreateTestWebFrameWidgetCallback create_widget_callback_;
std::unique_ptr<frame_test_helpers::TestWebViewClient> view_client_;
std::unique_ptr<frame_test_helpers::WebViewHelper> helper_;
RuntimeEnabledFeatures::Backup features_backup_;
};
// Test that the document Node should be the default effective root scroller.
TEST_F(RootScrollerTest, TestDefaultRootScroller) {
Initialize("overflow-scrolling.html");
EXPECT_EQ(MainFrame()->GetDocument(),
EffectiveRootScroller(MainFrame()->GetDocument()));
}
// Make sure that replacing the documentElement doesn't change the effective
// root scroller when no root scroller is set.
TEST_F(RootScrollerTest, defaultEffectiveRootScrollerIsDocumentNode) {
Initialize("overflow-scrolling.html");
Document* document = MainFrame()->GetDocument();
Element* iframe = document->CreateRawElement(html_names::kIFrameTag);
EXPECT_EQ(MainFrame()->GetDocument(),
EffectiveRootScroller(MainFrame()->GetDocument()));
// Replace the documentElement with the iframe. The effectiveRootScroller
// should remain the same.
HeapVector<NodeOrStringOrTrustedScript> nodes;
nodes.push_back(NodeOrStringOrTrustedScript::FromNode(iframe));
document->documentElement()->ReplaceWith(nodes, ASSERT_NO_EXCEPTION);
UpdateAllLifecyclePhases(MainFrameView());
EXPECT_EQ(MainFrame()->GetDocument(),
EffectiveRootScroller(MainFrame()->GetDocument()));
}
class OverscrollWidgetInputHandlerHost
: public frame_test_helpers::TestWidgetInputHandlerHost {
public:
MOCK_METHOD5(DidOverscroll,
void(const gfx::Vector2dF&,
const gfx::Vector2dF&,
const gfx::PointF&,
const gfx::Vector2dF&,
cc::OverscrollBehavior));
void DidOverscroll(mojom::blink::DidOverscrollParamsPtr params) override {
DidOverscroll(params->latest_overscroll_delta,
params->accumulated_overscroll,
params->causal_event_viewport_point,
params->current_fling_velocity, params->overscroll_behavior);
}
};
class OverscrollTestWebFrameWidget
: public frame_test_helpers::TestWebFrameWidget {
public:
template <typename... Args>
explicit OverscrollTestWebFrameWidget(Args&&... args)
: frame_test_helpers::TestWebFrameWidget(std::forward<Args>(args)...) {}
// frame_test_helpers::TestWebFrameWidget overrides.
frame_test_helpers::TestWidgetInputHandlerHost* GetInputHandlerHost()
override {
return &input_handler_host_;
}
OverscrollWidgetInputHandlerHost& GetOverscrollWidgetInputHandlerHost() {
return input_handler_host_;
}
private:
OverscrollWidgetInputHandlerHost input_handler_host_;
};
// Tests that setting an element as the root scroller causes it to control url
// bar hiding and overscroll.
TEST_F(RootScrollerTest, TestSetRootScroller) {
SetCreateWebFrameWidgetCallback(base::BindRepeating(
&frame_test_helpers::WebViewHelper::CreateTestWebFrameWidget<
OverscrollTestWebFrameWidget>));
Initialize("root-scroller.html");
UpdateAllLifecyclePhases(MainFrameView());
Element* container = MainFrame()->GetDocument()->getElementById("container");
ASSERT_EQ(container, EffectiveRootScroller(MainFrame()->GetDocument()));
// Content is 1000x1000, WebView size is 400x400 but hiding the top controls
// makes it 400x450 so max scroll is 550px.
double maximum_scroll = 550;
OverscrollTestWebFrameWidget* widget =
static_cast<OverscrollTestWebFrameWidget*>(helper_->GetMainFrameWidget());
widget->HandleInputEvent(
GenerateTouchGestureEvent(WebInputEvent::Type::kGestureScrollBegin));
{
// Scrolling over the #container DIV should cause the browser controls to
// hide.
EXPECT_FLOAT_EQ(1, GetBrowserControls().TopShownRatio());
EXPECT_FLOAT_EQ(1, GetBrowserControls().BottomShownRatio());
widget->HandleInputEvent(
GenerateTouchGestureEvent(WebInputEvent::Type::kGestureScrollUpdate, 0,
-GetBrowserControls().TopHeight()));
EXPECT_FLOAT_EQ(0, GetBrowserControls().TopShownRatio());
EXPECT_FLOAT_EQ(0, GetBrowserControls().BottomShownRatio());
}
{
// Make sure we're actually scrolling the DIV and not the LocalFrameView.
widget->HandleInputEvent(GenerateTouchGestureEvent(
WebInputEvent::Type::kGestureScrollUpdate, 0, -100));
EXPECT_FLOAT_EQ(100, container->scrollTop());
EXPECT_FLOAT_EQ(
0, MainFrameView()->LayoutViewport()->GetScrollOffset().Height());
}
{
// Scroll 50 pixels past the end. Ensure we report the 50 pixels as
// overscroll.
EXPECT_CALL(widget->GetOverscrollWidgetInputHandlerHost(),
DidOverscroll(gfx::Vector2dF(0, 50), gfx::Vector2dF(0, 50),
gfx::PointF(100, 100), gfx::Vector2dF(),
cc::OverscrollBehavior()));
widget->HandleInputEvent(GenerateTouchGestureEvent(
WebInputEvent::Type::kGestureScrollUpdate, 0, -500));
EXPECT_FLOAT_EQ(maximum_scroll, container->scrollTop());
EXPECT_FLOAT_EQ(
0, MainFrameView()->LayoutViewport()->GetScrollOffset().Height());
RunPendingTasks();
Mock::VerifyAndClearExpectations(
&widget->GetOverscrollWidgetInputHandlerHost());
}
{
// Continue the gesture overscroll.
EXPECT_CALL(widget->GetOverscrollWidgetInputHandlerHost(),
DidOverscroll(gfx::Vector2dF(0, 20), gfx::Vector2dF(0, 70),
gfx::PointF(100, 100), gfx::Vector2dF(),
cc::OverscrollBehavior()));
widget->HandleInputEvent(GenerateTouchGestureEvent(
WebInputEvent::Type::kGestureScrollUpdate, 0, -20));
EXPECT_FLOAT_EQ(maximum_scroll, container->scrollTop());
EXPECT_FLOAT_EQ(
0, MainFrameView()->LayoutViewport()->GetScrollOffset().Height());
RunPendingTasks();
Mock::VerifyAndClearExpectations(
&widget->GetOverscrollWidgetInputHandlerHost());
}
widget->HandleInputEvent(
GenerateTouchGestureEvent(WebInputEvent::Type::kGestureScrollEnd));
{
// Make sure a new gesture scroll still won't scroll the frameview and
// overscrolls.
widget->HandleInputEvent(
GenerateTouchGestureEvent(WebInputEvent::Type::kGestureScrollBegin));
EXPECT_CALL(widget->GetOverscrollWidgetInputHandlerHost(),
DidOverscroll(gfx::Vector2dF(0, 30), gfx::Vector2dF(0, 30),
gfx::PointF(100, 100), gfx::Vector2dF(),
cc::OverscrollBehavior()));
widget->HandleInputEvent(GenerateTouchGestureEvent(
WebInputEvent::Type::kGestureScrollUpdate, 0, -30));
EXPECT_FLOAT_EQ(maximum_scroll, container->scrollTop());
EXPECT_FLOAT_EQ(
0, MainFrameView()->LayoutViewport()->GetScrollOffset().Height());
RunPendingTasks();
Mock::VerifyAndClearExpectations(
&widget->GetOverscrollWidgetInputHandlerHost());
widget->HandleInputEvent(
GenerateTouchGestureEvent(WebInputEvent::Type::kGestureScrollEnd));
}
{
// Scrolling up should show the browser controls.
widget->HandleInputEvent(
GenerateTouchGestureEvent(WebInputEvent::Type::kGestureScrollBegin));
EXPECT_FLOAT_EQ(0, GetBrowserControls().TopShownRatio());
EXPECT_FLOAT_EQ(0, GetBrowserControls().BottomShownRatio());
GetWebView()->MainFrameWidget()->HandleInputEvent(GenerateTouchGestureEvent(
WebInputEvent::Type::kGestureScrollUpdate, 0, 30));
EXPECT_FLOAT_EQ(0.6, GetBrowserControls().TopShownRatio());
EXPECT_FLOAT_EQ(0.6, GetBrowserControls().BottomShownRatio());
widget->HandleInputEvent(
GenerateTouchGestureEvent(WebInputEvent::Type::kGestureScrollEnd));
}
// Reset manually to avoid lifetime issues with custom WebViewClient.
helper_->Reset();
}
// Tests that removing the element that is the root scroller from the DOM tree
// changes the effective root scroller.
TEST_F(RootScrollerTest, TestRemoveRootScrollerFromDom) {
Initialize("root-scroller.html");
Element* container = MainFrame()->GetDocument()->getElementById("container");
UpdateAllLifecyclePhases(MainFrameView());
EXPECT_EQ(container, EffectiveRootScroller(MainFrame()->GetDocument()));
MainFrame()->GetDocument()->body()->RemoveChild(container);
UpdateAllLifecyclePhases(MainFrameView());
EXPECT_NE(container, EffectiveRootScroller(MainFrame()->GetDocument()));
}
// Test that the effective root scroller resets to the document Node when the
// current root scroller element becomes invalid as a scroller.
TEST_F(RootScrollerTest, TestRootScrollerBecomesInvalid) {
Initialize("root-scroller.html");
Element* container = MainFrame()->GetDocument()->getElementById("container");
{
EXPECT_EQ(container, EffectiveRootScroller(MainFrame()->GetDocument()));
ExecuteScript(
"document.querySelector('#container').style.display = 'inline'");
UpdateAllLifecyclePhases(MainFrameView());
EXPECT_EQ(MainFrame()->GetDocument(),
EffectiveRootScroller(MainFrame()->GetDocument()));
}
ExecuteScript("document.querySelector('#container').style.display = 'block'");
UpdateAllLifecyclePhases(MainFrameView());
{
EXPECT_EQ(container, EffectiveRootScroller(MainFrame()->GetDocument()));
ExecuteScript("document.querySelector('#container').style.width = '98%'");
UpdateAllLifecyclePhases(MainFrameView());
EXPECT_EQ(MainFrame()->GetDocument(),
EffectiveRootScroller(MainFrame()->GetDocument()));
}
}
// Ensures that disconnecting the element currently set as the root scroller
// recomputes the effective root scroller, before a lifecycle update.
TEST_F(RootScrollerTest, RemoveCurrentRootScroller) {
Initialize();
WebURL base_url = url_test_helpers::ToKURL("http://www.test.com/");
frame_test_helpers::LoadHTMLString(GetWebView()->MainFrameImpl(),
R"HTML(
<!DOCTYPE html>
<style>
body,html {
width: 100%;
height: 100%;
margin: 0px;
}
#container {
width: 100%;
height: 100%;
position: absolute;
overflow: auto;
}
#spacer {
width: 200vw;
height: 200vh;
}
</style>
<div id='container'>
<div id='spacer'></diiv>
</div>)HTML",
base_url);
RootScrollerController& controller =
MainFrame()->GetDocument()->GetRootScrollerController();
Element* container = MainFrame()->GetDocument()->getElementById("container");
UpdateAllLifecyclePhases(MainFrameView());
ASSERT_EQ(container, controller.EffectiveRootScroller());
// Remove the div from the document. It should be demoted from the effective
// root scroller. The effective will fallback to the document Node.
{
MainFrame()->GetDocument()->body()->setTextContent("");
EXPECT_EQ(MainFrame()->GetDocument(), controller.EffectiveRootScroller());
}
}
// Ensures that the root scroller always gets composited with scrolling layers.
// This is necessary since we replace the Frame scrolling layers in CC as the
// OuterViewport, we need something to replace them with.
TEST_F(RootScrollerTest, AlwaysCreateCompositedScrollingLayers) {
Initialize();
GetWebView()->GetSettings()->SetPreferCompositingToLCDTextEnabled(false);
WebURL base_url = url_test_helpers::ToKURL("http://www.test.com/");
frame_test_helpers::LoadHTMLString(GetWebView()->MainFrameImpl(),
R"HTML(
<!DOCTYPE html>
<style>
body,html {
width: 100%;
height: 100%;
margin: 0px;
}
#container {
width: 98%;
height: 100%;
position: absolute;
overflow: auto;
}
#spacer {
width: 200vw;
height: 200vh;
}
</style>
<div id='container'>
<div id='spacer'></div>
</div>)HTML",
base_url);
GetWebView()->ResizeWithBrowserControls(gfx::Size(400, 400), 50, 0, true);
UpdateAllLifecyclePhases(MainFrameView());
Element* container = MainFrame()->GetDocument()->getElementById("container");
PaintLayerScrollableArea* container_scroller = GetScrollableArea(*container);
PaintLayer* layer = container_scroller->Layer();
ASSERT_FALSE(layer->HasCompositedLayerMapping());
ExecuteScript("document.querySelector('#container').style.width = '100%'");
ASSERT_EQ(container, EffectiveRootScroller(MainFrame()->GetDocument()));
ASSERT_TRUE(layer->HasCompositedLayerMapping());
EXPECT_TRUE(layer->GetCompositedLayerMapping()->ScrollingContentsLayer());
ExecuteScript("document.querySelector('#container').style.width = '98%'");
ASSERT_EQ(MainFrame()->GetDocument(),
EffectiveRootScroller(MainFrame()->GetDocument()));
EXPECT_FALSE(layer->HasCompositedLayerMapping());
}
// Make sure that if an effective root scroller becomes a remote frame, it's
// immediately demoted.
TEST_F(RootScrollerTest, IFrameSwapToRemote) {
Initialize("root-scroller-iframe.html");
Element* iframe = MainFrame()->GetDocument()->getElementById("iframe");
ASSERT_EQ(iframe, EffectiveRootScroller(MainFrame()->GetDocument()));
// Swap in a remote frame. Make sure we revert back to the document.
{
MainWebFrame()->FirstChild()->Swap(frame_test_helpers::CreateRemote());
UpdateAllLifecyclePhases(MainFrameView());
EXPECT_EQ(MainFrame()->GetDocument(),
EffectiveRootScroller(MainFrame()->GetDocument()));
GetWebView()->ResizeWithBrowserControls(gfx::Size(400, 450), 50, 0, false);
UpdateAllLifecyclePhases(MainFrameView());
EXPECT_EQ(MainFrame()->GetDocument(),
EffectiveRootScroller(MainFrame()->GetDocument()));
}
}
// Tests that removing the root scroller element from the DOM resets the
// effective root scroller without waiting for any lifecycle events.
TEST_F(RootScrollerTest, RemoveRootScrollerFromDom) {
Initialize("root-scroller-iframe.html");
{
auto* iframe = To<HTMLFrameOwnerElement>(
MainFrame()->GetDocument()->getElementById("iframe"));
ASSERT_EQ(iframe, EffectiveRootScroller(MainFrame()->GetDocument()));
iframe->contentDocument()->body()->setInnerHTML("");
// If the root scroller wasn't updated by the DOM removal above, this
// will touch the disposed root scroller's ScrollableArea.
MainFrameView()->GetRootFrameViewport()->ServiceScrollAnimations(0);
}
}
// Tests that we still have a global root scroller layer when the HTML element
// has no layout object. crbug.com/637036.
TEST_F(RootScrollerTest, DocumentElementHasNoLayoutObject) {
Initialize("overflow-scrolling.html");
// There's no rootScroller set on this page so we should default to the
// document Node, which means we should use the layout viewport. Ensure this
// happens even if the <html> element has no LayoutObject.
ExecuteScript("document.documentElement.style.display = 'none';");
const TopDocumentRootScrollerController& global_controller =
MainFrame()->GetDocument()->GetPage()->GlobalRootScrollerController();
EXPECT_EQ(MainFrame()->GetDocument(), global_controller.GlobalRootScroller());
}
// On Android, the main scrollbars are owned by the visual viewport and the
// LocalFrameView's disabled. This functionality should extend to a rootScroller
// that isn't the main LocalFrameView.
TEST_F(RootScrollerTest, UseVisualViewportScrollbars) {
Initialize("root-scroller.html");
Element* container = MainFrame()->GetDocument()->getElementById("container");
ASSERT_EQ(container, EffectiveRootScroller(MainFrame()->GetDocument()));
ScrollableArea* container_scroller = GetScrollableArea(*container);
EXPECT_FALSE(container_scroller->HorizontalScrollbar());
EXPECT_FALSE(container_scroller->VerticalScrollbar());
EXPECT_GT(container_scroller->MaximumScrollOffset().Width(), 0);
EXPECT_GT(container_scroller->MaximumScrollOffset().Height(), 0);
}
// On Android, the main scrollbars are owned by the visual viewport and the
// LocalFrameView's disabled. This functionality should extend to a rootScroller
// that's a nested iframe.
TEST_F(RootScrollerTest, UseVisualViewportScrollbarsIframe) {
Initialize("root-scroller-iframe.html");
Element* iframe = MainFrame()->GetDocument()->getElementById("iframe");
auto* child_frame =
To<LocalFrame>(To<HTMLFrameOwnerElement>(iframe)->ContentFrame());
ASSERT_EQ(iframe, EffectiveRootScroller(MainFrame()->GetDocument()));
UpdateAllLifecyclePhases(MainFrameView());
ScrollableArea* container_scroller = child_frame->View()->LayoutViewport();
EXPECT_FALSE(container_scroller->HorizontalScrollbar());
EXPECT_FALSE(container_scroller->VerticalScrollbar());
EXPECT_GT(container_scroller->MaximumScrollOffset().Width(), 0);
EXPECT_GT(container_scroller->MaximumScrollOffset().Height(), 0);
}
TEST_F(RootScrollerTest, TopControlsAdjustmentAppliedToRootScroller) {
Initialize();
WebURL base_url = url_test_helpers::ToKURL("http://www.test.com/");
frame_test_helpers::LoadHTMLString(GetWebView()->MainFrameImpl(),
"<!DOCTYPE html>"
"<style>"
" body, html {"
" width: 100%;"
" height: 100%;"
" margin: 0px;"
" }"
" #container {"
" width: 100%;"
" height: 100%;"
" overflow: auto;"
" }"
"</style>"
"<div id='container'>"
" <div style='height:1000px'>test</div>"
"</div>",
base_url);
GetWebView()->ResizeWithBrowserControls(gfx::Size(400, 400), 50, 50, true);
UpdateAllLifecyclePhases(MainFrameView());
Element* container = MainFrame()->GetDocument()->getElementById("container");
ASSERT_EQ(container, EffectiveRootScroller(MainFrame()->GetDocument()));
ScrollableArea* container_scroller = GetScrollableArea(*container);
// Hide the top controls and scroll down maximally. We should account for the
// change in maximum scroll offset due to the top controls hiding. That is,
// since the controls are hidden, the "content area" is taller so the maximum
// scroll offset should shrink.
ASSERT_EQ(1000 - 400, container_scroller->MaximumScrollOffset().Height());
GetWebView()->MainFrameWidget()->HandleInputEvent(
GenerateTouchGestureEvent(WebInputEvent::Type::kGestureScrollBegin));
ASSERT_EQ(1, GetBrowserControls().TopShownRatio());
ASSERT_EQ(1, GetBrowserControls().BottomShownRatio());
GetWebView()->MainFrameWidget()->HandleInputEvent(
GenerateTouchGestureEvent(WebInputEvent::Type::kGestureScrollUpdate, 0,
-GetBrowserControls().TopHeight()));
ASSERT_EQ(0, GetBrowserControls().TopShownRatio());
ASSERT_EQ(0, GetBrowserControls().BottomShownRatio());
EXPECT_EQ(1000 - 450, container_scroller->MaximumScrollOffset().Height());
GetWebView()->MainFrameWidget()->HandleInputEvent(GenerateTouchGestureEvent(
WebInputEvent::Type::kGestureScrollUpdate, 0, -3000));
EXPECT_EQ(1000 - 450, container_scroller->GetScrollOffset().Height());
GetWebView()->MainFrameWidget()->HandleInputEvent(
GenerateTouchGestureEvent(WebInputEvent::Type::kGestureScrollEnd));
GetWebView()->ResizeWithBrowserControls(gfx::Size(400, 450), 50, 50, false);
EXPECT_EQ(1000 - 450, container_scroller->MaximumScrollOffset().Height());
}
TEST_F(RootScrollerTest, RotationAnchoring) {
Initialize("root-scroller-rotation.html");
ScrollableArea* container_scroller;
{
GetWebView()->ResizeWithBrowserControls(gfx::Size(250, 1000), 0, 0, true);
UpdateAllLifecyclePhases(MainFrameView());
Element* container =
MainFrame()->GetDocument()->getElementById("container");
ASSERT_EQ(container, EffectiveRootScroller(MainFrame()->GetDocument()));
container_scroller = GetScrollableArea(*container);
}
Element* target = MainFrame()->GetDocument()->getElementById("target");
// Zoom in and scroll the viewport so that the target is fully in the
// viewport and the visual viewport is fully scrolled within the layout
// viepwort.
{
int scroll_x = 250 * 4;
int scroll_y = 1000 * 4;
GetWebView()->SetPageScaleFactor(2);
GetWebView()->MainFrameWidget()->HandleInputEvent(
GenerateTouchGestureEvent(WebInputEvent::Type::kGestureScrollBegin));
GetWebView()->MainFrameWidget()->HandleInputEvent(GenerateTouchGestureEvent(
WebInputEvent::Type::kGestureScrollUpdate, -scroll_x, -scroll_y));
GetWebView()->MainFrameWidget()->HandleInputEvent(
GenerateTouchGestureEvent(WebInputEvent::Type::kGestureScrollEnd));
// The visual viewport should be 1.5 screens scrolled so that the target
// occupies the bottom quadrant of the layout viewport.
ASSERT_EQ((250 * 3) / 2, container_scroller->GetScrollOffset().Width());
ASSERT_EQ((1000 * 3) / 2, container_scroller->GetScrollOffset().Height());
// The visual viewport should have scrolled the last half layout viewport.
ASSERT_EQ((250) / 2, GetVisualViewport().GetScrollOffset().Width());
ASSERT_EQ((1000) / 2, GetVisualViewport().GetScrollOffset().Height());
}
// Now do a rotation resize.
GetWebView()->ResizeWithBrowserControls(gfx::Size(1000, 250), 50, 0, false);
UpdateAllLifecyclePhases(MainFrameView());
// The visual viewport should remain fully filled by the target.
DOMRect* rect = target->getBoundingClientRect();
EXPECT_EQ(rect->left(), GetVisualViewport().GetScrollOffset().Width());
EXPECT_EQ(rect->top(), GetVisualViewport().GetScrollOffset().Height());
}
// Tests that we don't crash if the default documentElement isn't a valid root
// scroller. This can happen in some edge cases where documentElement isn't
// <html>. crbug.com/668553.
TEST_F(RootScrollerTest, InvalidDefaultRootScroller) {
Initialize("overflow-scrolling.html");
Document* document = MainFrame()->GetDocument();
Element* br = document->CreateRawElement(html_names::kBrTag);
document->ReplaceChild(br, document->documentElement());
UpdateAllLifecyclePhases(MainFrameView());
Element* html = document->CreateRawElement(html_names::kHTMLTag);
Element* body = document->CreateRawElement(html_names::kBodyTag);
html->AppendChild(body);
body->AppendChild(br);
document->AppendChild(html);
UpdateAllLifecyclePhases(MainFrameView());
}
// Makes sure that when an iframe becomes the effective root scroller, its
// FrameView stops sizing layout to the frame rect and uses its parent's layout
// size instead. This allows matching the layout size semantics of the root
// FrameView since its layout size can differ from the frame rect due to
// resizes by the URL bar.
TEST_F(RootScrollerTest, IFrameRootScrollerGetsNonFixedLayoutSize) {
Initialize("root-scroller-iframe.html");
UpdateAllLifecyclePhases(MainFrameView());
auto* iframe = To<HTMLFrameOwnerElement>(
MainFrame()->GetDocument()->getElementById("iframe"));
auto* iframe_view = To<LocalFrame>(iframe->ContentFrame())->View();
ASSERT_EQ(IntSize(400, 400), iframe_view->GetLayoutSize());
ASSERT_EQ(IntSize(400, 400), iframe_view->Size());
// Make the iframe the rootscroller. This should cause the iframe's layout
// size to be manually controlled.
{
ASSERT_EQ(iframe, EffectiveRootScroller(MainFrame()->GetDocument()));
EXPECT_FALSE(iframe_view->LayoutSizeFixedToFrameSize());
EXPECT_EQ(IntSize(400, 400), iframe_view->GetLayoutSize());
EXPECT_EQ(IntSize(400, 400), iframe_view->Size());
}
// Hide the URL bar, the iframe's frame rect should expand but the layout
// size should remain the same.
{
GetWebView()->ResizeWithBrowserControls(gfx::Size(400, 450), 50, 0, false);
UpdateAllLifecyclePhases(MainFrameView());
EXPECT_EQ(IntSize(400, 400), iframe_view->GetLayoutSize());
EXPECT_EQ(IntSize(400, 450), iframe_view->Size());
}
// Simulate a rotation. This time the layout size should reflect the resize.
{
GetWebView()->ResizeWithBrowserControls(gfx::Size(450, 400), 50, 0, false);
UpdateAllLifecyclePhases(MainFrameView());
EXPECT_EQ(IntSize(450, 350), iframe_view->GetLayoutSize());
EXPECT_EQ(IntSize(450, 400), iframe_view->Size());
// "Un-rotate" for following tests.
GetWebView()->ResizeWithBrowserControls(gfx::Size(400, 450), 50, 0, false);
UpdateAllLifecyclePhases(MainFrameView());
}
// Show the URL bar again. The frame rect should match the viewport.
{
GetWebView()->ResizeWithBrowserControls(gfx::Size(400, 400), 50, 0, true);
UpdateAllLifecyclePhases(MainFrameView());
EXPECT_EQ(IntSize(400, 400), iframe_view->GetLayoutSize());
EXPECT_EQ(IntSize(400, 400), iframe_view->Size());
}
// Hide the URL bar and reset the rootScroller. The iframe should go back to
// tracking layout size by frame rect.
{
GetWebView()->ResizeWithBrowserControls(gfx::Size(400, 450), 50, 0, false);
UpdateAllLifecyclePhases(MainFrameView());
EXPECT_EQ(IntSize(400, 400), iframe_view->GetLayoutSize());
EXPECT_EQ(IntSize(400, 450), iframe_view->Size());
ExecuteScript("document.querySelector('#iframe').style.opacity = '0.5'");
ASSERT_EQ(MainFrame()->GetDocument(),
EffectiveRootScroller(MainFrame()->GetDocument()));
EXPECT_TRUE(iframe_view->LayoutSizeFixedToFrameSize());
EXPECT_EQ(IntSize(400, 400), iframe_view->GetLayoutSize());
EXPECT_EQ(IntSize(400, 400), iframe_view->Size());
}
}
// Ensure that removing the root scroller element causes an update to the
// RootFrameViewport's layout viewport immediately since old layout viewport is
// now part of a detached layout hierarchy.
TEST_F(RootScrollerTest, ImmediateUpdateOfLayoutViewport) {
Initialize("root-scroller-iframe.html");
auto* iframe = To<HTMLFrameOwnerElement>(
MainFrame()->GetDocument()->getElementById("iframe"));
ASSERT_EQ(iframe, EffectiveRootScroller(MainFrame()->GetDocument()));
RootScrollerController& main_controller =
MainFrame()->GetDocument()->GetRootScrollerController();
auto* iframe_local_frame = To<LocalFrame>(iframe->ContentFrame());
EXPECT_EQ(iframe, &main_controller.EffectiveRootScroller());
EXPECT_EQ(iframe_local_frame->View()->LayoutViewport(),
&MainFrameView()->GetRootFrameViewport()->LayoutViewport());
// Remove the <iframe> and make sure the layout viewport reverts to the
// LocalFrameView without a layout.
iframe->remove();
EXPECT_EQ(MainFrameView()->LayoutViewport(),
&MainFrameView()->GetRootFrameViewport()->LayoutViewport());
}
class ImplicitRootScrollerSimTest : public SimTest {
public:
ImplicitRootScrollerSimTest() : implicit_root_scroller_for_test_(true) {}
void SetUp() override {
SimTest::SetUp();
WebView().GetPage()->GetSettings().SetViewportEnabled(true);
}
private:
ScopedImplicitRootScrollerForTest implicit_root_scroller_for_test_;
};
// Test that the cached IsEffectiveRootScroller bit on LayoutObject is set
// correctly when the Document is the effective root scroller. It becomes the
// root scroller before Document has a LayoutView.
TEST_F(ImplicitRootScrollerSimTest, DocumentEffectiveSetsCachedBit) {
WebView().MainFrameViewWidget()->Resize(gfx::Size(800, 600));
SimRequest request("https://example.com/test.html", "text/html");
LoadURL("https://example.com/test.html");
request.Complete(R"HTML(
<!DOCTYPE html>
)HTML");
Compositor().BeginFrame();
EXPECT_TRUE(GetDocument().GetLayoutView()->IsEffectiveRootScroller());
}
// Test that layout from outside a lifecycle wont select a new effective root
// scroller.
TEST_F(ImplicitRootScrollerSimTest, NonLifecycleLayoutDoesntCauseReselection) {
WebView().MainFrameViewWidget()->Resize(gfx::Size(800, 600));
SimRequest request("https://example.com/test.html", "text/html");
LoadURL("https://example.com/test.html");
request.Complete(R"HTML(
<!DOCTYPE html>
<style>
body, html {
margin: 0;
width: 100%;
height: 100%;
}
#container {
width: 100%;
height: 100%;
overflow: scroll;
}
#spacer {
width: 200vw;
height: 200vh;
}
</style>
<div id="container">
<div id="spacer"></div>
</div>
)HTML");
Element* container = GetDocument().getElementById("container");
Compositor().BeginFrame();
ASSERT_EQ(container,
GetDocument().GetRootScrollerController().EffectiveRootScroller());
container->style()->setProperty(GetDocument().GetExecutionContext(), "width",
"95%", String(), ASSERT_NO_EXCEPTION);
ASSERT_TRUE(Compositor().NeedsBeginFrame());
// Cause a layout.
container->scrollTop();
ASSERT_TRUE(Compositor().NeedsBeginFrame());
// Shouldn't yet cause a change since we haven't done a full lifecycle update.
EXPECT_EQ(container,
GetDocument().GetRootScrollerController().EffectiveRootScroller());
Compositor().BeginFrame();
EXPECT_EQ(GetDocument(),
GetDocument().GetRootScrollerController().EffectiveRootScroller());
}
// Tests that we don't explode when a layout occurs and the effective
// rootScroller no longer has a ContentFrame(). We setup the frame tree such
// that the first iframe is the effective root scroller. The second iframe has
// an unload handler that reaches back to the common parent and causes a
// layout. This will cause us to recalculate the effective root scroller while
// the current one is valid in all ways except that it no longer has a content
// frame. This test passes if it doesn't crash. https://crbug.com/805317.
TEST_F(ImplicitRootScrollerSimTest, RecomputeEffectiveWithNoContentFrame) {
WebView().MainFrameViewWidget()->Resize(gfx::Size(800, 600));
SimRequest request("https://example.com/test.html", "text/html");
SimRequest first_request("https://example.com/first.html", "text/html");
SimRequest second_request("https://example.com/second.html", "text/html");
SimRequest final_request("https://newdomain.com/test.html", "text/html");
LoadURL("https://example.com/test.html");
request.Complete(R"HTML(
<!DOCTYPE html>
<style>
::-webkit-scrollbar {
width: 0px;
height: 0px;
}
body, html {
width: 100%;
height: 100%;
margin: 0px;
}
#first {
width: 100%;
height: 100%;
border: 0;
}
#second {
width: 10px;
height: 10px;
position: absolute;
left: 0px;
top: 0px;
}
</style>
<iframe id="first" src="https://example.com/first.html">
</iframe>
<iframe id="second" src="https://example.com/second.html">
</iframe>
<script>
// Dirty layout on unload
window.addEventListener('unload', function() {
document.getElementById("first").style.width="0";
});
</script>
)HTML");
first_request.Complete(R"HTML(
<!DOCTYPE html>
<style>
body {
height: 300vh;
}
</style>
)HTML");
second_request.Complete(R"HTML(
<!DOCTYPE html>
<body></body>
<script>
window.addEventListener('unload', function() {
// This will do a layout.
window.top.document.getElementById("first").clientWidth;
});
</script>
)HTML");
Element* container = GetDocument().getElementById("first");
Compositor().BeginFrame();
ASSERT_EQ(container,
GetDocument().GetRootScrollerController().EffectiveRootScroller());
// This will unload first the root, then the first frame, then the second.
LoadURL("https://newdomain.com/test.html");
final_request.Complete(R"HTML(
<!DOCTYPE html>
)HTML");
}
// Test that the element is considered to be viewport filling only if its
// padding box fills the viewport. That means it must have no border.
TEST_F(ImplicitRootScrollerSimTest, UsePaddingBoxForViewportFillingCondition) {
WebView().MainFrameViewWidget()->Resize(gfx::Size(800, 600));
SimRequest request("https://example.com/test.html", "text/html");
LoadURL("https://example.com/test.html");
request.Complete(R"HTML(
<!DOCTYPE html>
<style>
html,body {
margin: 0;
width: 100%;
height: 100%;
}
#container {
position: absolute;
width: 100%;
height: 100%;
box-sizing: border-box;
overflow: scroll;
}
#spacer {
width: 200vw;
height: 200vh;
}
</style>
<div id="container">
<div id="spacer"></div>
</div>
)HTML");
Element* container = GetDocument().getElementById("container");
Compositor().BeginFrame();
ASSERT_EQ(container,
GetDocument().GetRootScrollerController().EffectiveRootScroller());
// Setting a border should cause the element to no longer be valid as its
// padding box doesn't fill the viewport exactly.
container->setAttribute(html_names::kStyleAttr, "border: 1px solid black");
Compositor().BeginFrame();
EXPECT_EQ(&GetDocument(),
GetDocument().GetRootScrollerController().EffectiveRootScroller());
}
// Tests that the root scroller doesn't affect visualViewport pageLeft and
// pageTop.
TEST_F(ImplicitRootScrollerSimTest, RootScrollerDoesntAffectVisualViewport) {
WebView().MainFrameViewWidget()->Resize(gfx::Size(800, 600));
SimRequest request("https://example.com/test.html", "text/html");
LoadURL("https://example.com/test.html");
request.Write(R"HTML(
<!DOCTYPE html>
<style>
body, html {
width: 100%;
height: 100%;
margin: 0px;
}
#spacer {
width: 1000px;
height: 1000px;
}
#container {
width: 100%;
height: 100%;
overflow: auto;
}
</style>
<div id="container">
<div id="spacer"></div>
</div>
)HTML");
GetDocument().GetPage()->GetVisualViewport().SetScale(2);
GetDocument().GetPage()->GetVisualViewport().SetLocation(
FloatPoint(100, 120));
auto* frame = To<LocalFrame>(GetDocument().GetPage()->MainFrame());
EXPECT_EQ(100, frame->DomWindow()->visualViewport()->pageLeft());
EXPECT_EQ(120, frame->DomWindow()->visualViewport()->pageTop());
request.Finish();
Compositor().BeginFrame();
Element* container = GetDocument().getElementById("container");
ASSERT_EQ(container,
GetDocument().GetRootScrollerController().EffectiveRootScroller());
container->setScrollTop(50);
container->setScrollLeft(60);
ASSERT_EQ(50, container->scrollTop());
ASSERT_EQ(60, container->scrollLeft());
ASSERT_EQ(100, frame->DomWindow()->visualViewport()->pageLeft());
EXPECT_EQ(120, frame->DomWindow()->visualViewport()->pageTop());
}
// Tests that we don't crash or violate lifecycle assumptions when we resize
// from within layout.
TEST_F(ImplicitRootScrollerSimTest, ResizeFromResizeAfterLayout) {
WebView().GetSettings()->SetShrinksViewportContentToFit(true);
WebView().SetDefaultPageScaleLimits(0.25f, 5);
WebView().MainFrameViewWidget()->Resize(gfx::Size(800, 600));
SimRequest request("https://example.com/test.html", "text/html");
LoadURL("https://example.com/test.html");
request.Write(R"HTML(
<!DOCTYPE html>
<style>
body, html {
width: 100%;
height: 100%;
margin: 0px;
}
#container {
width: 100%;
height: 100%;
border: 0;
}
</style>
<iframe id="container"
srcdoc="<!DOCTYPE html>
<style>html {height: 300%;}</style>">
</iframe>
)HTML");
RunPendingTasks();
Compositor().BeginFrame();
Element* container = GetDocument().getElementById("container");
ASSERT_EQ(container,
GetDocument().GetRootScrollerController().EffectiveRootScroller());
ASSERT_EQ(IntSize(800, 600), GetDocument().View()->Size());
request.Write(R"HTML(
<div style="width:2000px;height:1000px"></div>
)HTML");
request.Finish();
Compositor().BeginFrame();
ASSERT_EQ(IntSize(2000, 1500), GetDocument().View()->Size());
}
// Tests basic implicit root scroller mode with a <div>.
TEST_F(ImplicitRootScrollerSimTest, ImplicitRootScroller) {
WebView().MainFrameViewWidget()->Resize(gfx::Size(800, 600));
SimRequest request("https://example.com/test.html", "text/html");
LoadURL("https://example.com/test.html");
request.Complete(R"HTML(
<!DOCTYPE html>
<style>
::-webkit-scrollbar {
width: 0px;
height: 0px;
}
html {
overflow: hidden;
}
body, html {
width: 100%;
height: 100%;
margin: 0px;
}
#spacer {
width: 1000px;
height: 1000px;
}
#container {
width: 100%;
height: 100%;
}
</style>
<div id="container">
<div id="spacer"></div>
</div>
)HTML");
Compositor().BeginFrame();
ASSERT_EQ(&GetDocument(),
GetDocument().GetRootScrollerController().EffectiveRootScroller());
Element* container = GetDocument().getElementById("container");
// overflow: auto and overflow: scroll should cause a valid element to be
// promoted to root scroller. Otherwise, they shouldn't, even if they're
// otherwise a valid root scroller element.
Vector<std::tuple<String, String, Node*>> test_cases = {
{"overflow", "hidden", &GetDocument()},
{"overflow", "auto", container},
{"overflow", "scroll", container},
{"overflow", "visible", &GetDocument()},
// Overflow: hidden in one axis forces the other axis to auto so it should
// be promoted.
{"overflow-x", "hidden", container},
{"overflow-x", "auto", container},
{"overflow-x", "scroll", container},
{"overflow-x", "visible", &GetDocument()},
{"overflow-y", "hidden", container},
{"overflow-y", "auto", container},
{"overflow-y", "scroll", container},
{"overflow-y", "visible", &GetDocument()}};
for (auto test_case : test_cases) {
String& style = std::get<0>(test_case);
String& style_val = std::get<1>(test_case);
Node* expected_root_scroller = std::get<2>(test_case);
container->style()->setProperty(GetDocument().GetExecutionContext(), style,
style_val, String(), ASSERT_NO_EXCEPTION);
Compositor().BeginFrame();
ASSERT_EQ(expected_root_scroller,
GetDocument().GetRootScrollerController().EffectiveRootScroller())
<< "Failed to set rootScroller after setting " << std::get<0>(test_case)
<< ": " << std::get<1>(test_case);
container->style()->setProperty(GetDocument().GetExecutionContext(),
std::get<0>(test_case), String(), String(),
ASSERT_NO_EXCEPTION);
Compositor().BeginFrame();
ASSERT_EQ(&GetDocument(),
GetDocument().GetRootScrollerController().EffectiveRootScroller())
<< "Failed to reset rootScroller after setting "
<< std::get<0>(test_case) << ": " << std::get<1>(test_case);
}
// Now remove the overflowing element and rerun the tests. The container
// element should no longer be implicitly promoted as it doesn't have any
// overflow.
Element* spacer = GetDocument().getElementById("spacer");
spacer->remove();
for (auto test_case : test_cases) {
String& style = std::get<0>(test_case);
String& style_val = std::get<1>(test_case);
Node* expected_root_scroller = &GetDocument();
container->style()->setProperty(GetDocument().GetExecutionContext(), style,
style_val, String(), ASSERT_NO_EXCEPTION);
Compositor().BeginFrame();
ASSERT_EQ(expected_root_scroller,
GetDocument().GetRootScrollerController().EffectiveRootScroller())
<< "Failed to set rootScroller after setting " << std::get<0>(test_case)
<< ": " << std::get<1>(test_case);
container->style()->setProperty(GetDocument().GetExecutionContext(),
std::get<0>(test_case), String(), String(),
ASSERT_NO_EXCEPTION);
Compositor().BeginFrame();
ASSERT_EQ(&GetDocument(),
GetDocument().GetRootScrollerController().EffectiveRootScroller())
<< "Failed to reset rootScroller after setting "
<< std::get<0>(test_case) << ": " << std::get<1>(test_case);
}
}
// Test that adding overflow to an element that would otherwise be eligable to
// be implicitly pomoted causes promotion.
TEST_F(ImplicitRootScrollerSimTest, ImplicitRootScrollerAddOverflow) {
WebView().MainFrameViewWidget()->Resize(gfx::Size(800, 600));
SimRequest request("https://example.com/test.html", "text/html");
LoadURL("https://example.com/test.html");
request.Complete(R"HTML(
<!DOCTYPE html>
<style>
::-webkit-scrollbar {
width: 0px;
height: 0px;
}
body, html {
width: 100%;
height: 100%;
margin: 0px;
}
#container {
width: 100%;
height: 100%;
overflow: auto;
}
</style>
<div id="container">
<div id="spacer"></div>
</div>
)HTML");
Compositor().BeginFrame();
ASSERT_EQ(&GetDocument(),
GetDocument().GetRootScrollerController().EffectiveRootScroller())
<< "Shouldn't promote 'container' since it has no overflow.";
Element* spacer = GetDocument().getElementById("spacer");
spacer->style()->setProperty(GetDocument().GetExecutionContext(), "height",
"2000px", String(), ASSERT_NO_EXCEPTION);
spacer->style()->setProperty(GetDocument().GetExecutionContext(), "width",
"2000px", String(), ASSERT_NO_EXCEPTION);
Compositor().BeginFrame();
Element* container = GetDocument().getElementById("container");
EXPECT_EQ(container,
GetDocument().GetRootScrollerController().EffectiveRootScroller())
<< "Adding overflow should cause 'container' to be promoted.";
}
// Tests that we don't crash if an implicit candidate is no longer a box. This
// test passes if it doesn't crash.
TEST_F(ImplicitRootScrollerSimTest, CandidateLosesLayoutBoxDontCrash) {
WebView().MainFrameViewWidget()->Resize(gfx::Size(800, 600));
SimRequest request("https://example.com/test.html", "text/html");
LoadURL("https://example.com/test.html");
request.Complete(R"HTML(
<!DOCTYPE html>
<style>
#spacer {
width: 300px;
height: 300px;
}
.box {
width: 200px;
height: 200px;
overflow: scroll;
display: block;
}
.nonbox {
display: inline;
}
</style>
<b id="container">
<div id="spacer"></div>
</b>
)HTML");
Element* container = GetDocument().getElementById("container");
// An overflowing box will be added to the implicit candidates list.
container->setAttribute(html_names::kClassAttr, "box");
Compositor().BeginFrame();
// This will make change from a box to an inline. Ensure we don't crash when
// we reevaluate the candidates list.
container->setAttribute(html_names::kClassAttr, "nonbox");
Compositor().BeginFrame();
}
// Ensure that a plugin view being considered for implicit promotion doesn't
// cause a crash. https://crbug.com/903440.
TEST_F(ImplicitRootScrollerSimTest, ConsiderEmbedCrash) {
WebView().MainFrameViewWidget()->Resize(gfx::Size(800, 600));
SimRequest request("https://example.com/test.html", "text/html");
LoadURL("https://example.com/test.html");
request.Complete(R"HTML(
<!DOCTYPE html>
<embed id="embed" height="1" src="data:video/mp4,">
<script>
embed.type = "JavaScript 1.5";
embed.src = "x";
</script>
)HTML");
Compositor().BeginFrame();
Element* embed = GetDocument().getElementById("embed");
GetDocument().GetRootScrollerController().ConsiderForImplicit(*embed);
}
// Test that a valid implicit root scroller wont be promoted/will be demoted if
// the main document has overflow.
TEST_F(ImplicitRootScrollerSimTest,
ImplicitRootScrollerDocumentScrollsOverflow) {
WebView().MainFrameViewWidget()->Resize(gfx::Size(800, 600));
SimRequest request("https://example.com/test.html", "text/html");
LoadURL("https://example.com/test.html");
request.Complete(R"HTML(
<!DOCTYPE html>
<style>
::-webkit-scrollbar {
width: 0px;
height: 0px;
}
body, html {
width: 100%;
height: 100%;
margin: 0px;
}
#container {
width: 100%;
height: 100%;
overflow: auto;
}
#spacer {
width: 2000px;
height: 2000px;
}
</style>
<div id="container">
<div id="spacer"></div>
</div>
<div id="overflow"></div>
)HTML");
Compositor().BeginFrame();
Element* container = GetDocument().getElementById("container");
ASSERT_EQ(container,
GetDocument().GetRootScrollerController().EffectiveRootScroller());
Element* overflow = GetDocument().getElementById("overflow");
overflow->style()->setProperty(GetDocument().GetExecutionContext(), "height",
"10px", String(), ASSERT_NO_EXCEPTION);
overflow->style()->setProperty(GetDocument().GetExecutionContext(), "width",
"10px", String(), ASSERT_NO_EXCEPTION);
Compositor().BeginFrame();
EXPECT_EQ(&GetDocument(),
GetDocument().GetRootScrollerController().EffectiveRootScroller())
<< "Adding overflow to document should cause 'container' to be demoted.";
overflow->remove();
Compositor().BeginFrame();
EXPECT_EQ(container,
GetDocument().GetRootScrollerController().EffectiveRootScroller())
<< "Removing document overflow should cause 'container' to be promoted.";
}
// Test that we'll only implicitly promote an element if its visible.
TEST_F(ImplicitRootScrollerSimTest, ImplicitRootScrollerVisibilityCondition) {
WebView().MainFrameViewWidget()->Resize(gfx::Size(800, 600));
SimRequest request("https://example.com/test.html", "text/html");
LoadURL("https://example.com/test.html");
request.Complete(R"HTML(
<!DOCTYPE html>
<style>
::-webkit-scrollbar {
width: 0px;
height: 0px;
}
body, html {
width: 100%;
height: 100%;
margin: 0px;
}
#container {
width: 100%;
height: 100%;
overflow: auto;
}
#spacer {
width: 2000px;
height: 2000px;
}
</style>
<div id="container">
<div id="spacer"></div>
</div>
)HTML");
Compositor().BeginFrame();
Element* container = GetDocument().getElementById("container");
ASSERT_EQ(container,
GetDocument().GetRootScrollerController().EffectiveRootScroller());
container->style()->setProperty(GetDocument().GetExecutionContext(),
"opacity", "0.5", String(),
ASSERT_NO_EXCEPTION);
Compositor().BeginFrame();
EXPECT_EQ(&GetDocument(),
GetDocument().GetRootScrollerController().EffectiveRootScroller())
<< "Adding opacity to 'container' causes it to be demoted.";
container->style()->setProperty(GetDocument().GetExecutionContext(),
"opacity", "", String(), ASSERT_NO_EXCEPTION);
Compositor().BeginFrame();
EXPECT_EQ(container,
GetDocument().GetRootScrollerController().EffectiveRootScroller())
<< "Removing opacity from 'container' causes it to be promoted.";
container->style()->setProperty(GetDocument().GetExecutionContext(),
"visibility", "hidden", String(),
ASSERT_NO_EXCEPTION);
Compositor().BeginFrame();
EXPECT_EQ(&GetDocument(),
GetDocument().GetRootScrollerController().EffectiveRootScroller())
<< "visibility:hidden causes 'container' to be demoted.";
container->style()->setProperty(GetDocument().GetExecutionContext(),
"visibility", "collapse", String(),
ASSERT_NO_EXCEPTION);
Compositor().BeginFrame();
EXPECT_EQ(&GetDocument(),
GetDocument().GetRootScrollerController().EffectiveRootScroller())
<< "visibility:collapse doesn't cause 'container' to be promoted.";
container->style()->setProperty(GetDocument().GetExecutionContext(),
"visibility", "visible", String(),
ASSERT_NO_EXCEPTION);
Compositor().BeginFrame();
EXPECT_EQ(container,
GetDocument().GetRootScrollerController().EffectiveRootScroller())
<< "visibility:visible causes promotion";
}
// Tests implicit root scroller mode for iframes.
TEST_F(ImplicitRootScrollerSimTest, ImplicitRootScrollerIframe) {
WebView().MainFrameViewWidget()->Resize(gfx::Size(800, 600));
SimRequest request("https://example.com/test.html", "text/html");
LoadURL("https://example.com/test.html");
request.Complete(R"HTML(
<!DOCTYPE html>
<style>
::-webkit-scrollbar {
width: 0px;
height: 0px;
}
body, html {
width: 100%;
height: 100%;
margin: 0px;
}
iframe {
width: 100%;
height: 100%;
border: 0;
}
</style>
<iframe id="container"
srcdoc="<!DOCTYPE html><style>html {height: 300%;}</style>">
</iframe>
)HTML");
// srcdoc iframe loads via posted tasks.
RunPendingTasks();
Compositor().BeginFrame();
Element* container = GetDocument().getElementById("container");
ASSERT_EQ(container,
GetDocument().GetRootScrollerController().EffectiveRootScroller());
container->style()->setProperty(GetDocument().GetExecutionContext(), "height",
"95%", String(), ASSERT_NO_EXCEPTION);
Compositor().BeginFrame();
ASSERT_EQ(&GetDocument(),
GetDocument().GetRootScrollerController().EffectiveRootScroller());
}
// Tests use counter for implicit root scroller. Ensure it's not counted on a
// page without an implicit root scroller.
TEST_F(ImplicitRootScrollerSimTest, UseCounterNegative) {
WebView().MainFrameViewWidget()->Resize(gfx::Size(800, 600));
SimRequest request("https://example.com/test.html", "text/html");
LoadURL("https://example.com/test.html");
request.Complete(R"HTML(
<!DOCTYPE html>
<style>
::-webkit-scrollbar {
width: 0px;
height: 0px;
}
body, html {
width: 100%;
height: 100%;
margin: 0px;
}
div {
width: 100%;
height: 100%;
}
</style>
<div id="container"></div>
)HTML");
Compositor().BeginFrame();
Element* container = GetDocument().getElementById("container");
ASSERT_NE(container,
GetDocument().GetRootScrollerController().EffectiveRootScroller());
EXPECT_FALSE(
GetDocument().IsUseCounted(WebFeature::kActivatedImplicitRootScroller));
container->style()->setProperty(GetDocument().GetExecutionContext(), "height",
"150%", String(), ASSERT_NO_EXCEPTION);
Compositor().BeginFrame();
EXPECT_FALSE(
GetDocument().IsUseCounted(WebFeature::kActivatedImplicitRootScroller));
}
// Tests use counter for implicit root scroller. Ensure it's counted on a
// page that loads with an implicit root scroller.
TEST_F(ImplicitRootScrollerSimTest, UseCounterPositive) {
WebView().MainFrameViewWidget()->Resize(gfx::Size(800, 600));
SimRequest request("https://example.com/test.html", "text/html");
LoadURL("https://example.com/test.html");
request.Complete(R"HTML(
<!DOCTYPE html>
<style>
::-webkit-scrollbar {
width: 0px;
height: 0px;
}
body, html {
width: 100%;
height: 100%;
margin: 0px;
}
#container {
width: 100%;
height: 100%;
overflow: auto;
}
#spacer {
height: 2000px;
}
</style>
<div id="container">
<div id="spacer"></div>
</div>
)HTML");
Compositor().BeginFrame();
Element* container = GetDocument().getElementById("container");
ASSERT_EQ(container,
GetDocument().GetRootScrollerController().EffectiveRootScroller());
EXPECT_TRUE(
GetDocument().IsUseCounted(WebFeature::kActivatedImplicitRootScroller));
container->style()->setProperty(GetDocument().GetExecutionContext(), "height",
"150%", String(), ASSERT_NO_EXCEPTION);
Compositor().BeginFrame();
ASSERT_NE(container,
GetDocument().GetRootScrollerController().EffectiveRootScroller());
EXPECT_TRUE(
GetDocument().IsUseCounted(WebFeature::kActivatedImplicitRootScroller));
}
// Tests use counter for implicit root scroller. Ensure it's counted on a
// page that loads without an implicit root scroller but later gets one.
TEST_F(ImplicitRootScrollerSimTest, UseCounterPositiveAfterLoad) {
WebView().MainFrameViewWidget()->Resize(gfx::Size(800, 600));
SimRequest request("https://example.com/test.html", "text/html");
LoadURL("https://example.com/test.html");
request.Complete(R"HTML(
<!DOCTYPE html>
<style>
::-webkit-scrollbar {
width: 0px;
height: 0px;
}
body, html {
width: 100%;
height: 100%;
margin: 0px;
}
#container {
width: 100%;
height: 40%;
overflow: auto;
}
#spacer {
height: 2000px;
}
</style>
<div id="container">
<div id="spacer"></div>
</div>
)HTML");
Compositor().BeginFrame();
Element* container = GetDocument().getElementById("container");
ASSERT_NE(container,
GetDocument().GetRootScrollerController().EffectiveRootScroller());
EXPECT_FALSE(
GetDocument().IsUseCounted(WebFeature::kActivatedImplicitRootScroller));
container->style()->setProperty(GetDocument().GetExecutionContext(), "height",
"100%", String(), ASSERT_NO_EXCEPTION);
Compositor().BeginFrame();
ASSERT_EQ(container,
GetDocument().GetRootScrollerController().EffectiveRootScroller());
EXPECT_TRUE(
GetDocument().IsUseCounted(WebFeature::kActivatedImplicitRootScroller));
}
// Test that we correctly recompute the cached bits and thus the root scroller
// properties in the event of a layout tree reattachment which causes the
// LayoutObject to be disposed and replaced with a new one.
TEST_F(ImplicitRootScrollerSimTest, LayoutTreeReplaced) {
WebView().MainFrameViewWidget()->Resize(gfx::Size(800, 600));
SimRequest request("https://example.com/test.html", "text/html");
LoadURL("https://example.com/test.html");
request.Complete(R"HTML(
<style>
::-webkit-scrollbar {
}
#rootscroller {
width: 100%;
height: 100%;
overflow: auto;
position: absolute;
left: 0;
top: 0;
}
#spacer {
height: 20000px;
width: 10px;
}
</style>
<div id="rootscroller">
<div id="spacer"></div>
</div>
)HTML");
Compositor().BeginFrame();
Element* scroller = GetDocument().getElementById("rootscroller");
ASSERT_EQ(scroller,
GetDocument().GetRootScrollerController().EffectiveRootScroller());
ASSERT_TRUE(scroller->GetLayoutObject()->IsEffectiveRootScroller());
ASSERT_TRUE(scroller->GetLayoutObject()->IsGlobalRootScroller());
// This will cause the layout tree to be rebuilt and reattached which creates
// new LayoutObjects. Ensure the bits are reapplied to the new layout
// objects after they're recreated.
GetDocument().setDesignMode("on");
Compositor().BeginFrame();
EXPECT_TRUE(scroller->GetLayoutObject()->IsEffectiveRootScroller());
EXPECT_TRUE(scroller->GetLayoutObject()->IsGlobalRootScroller());
}
// Tests that if we have multiple valid candidates for implicit promotion, we
// don't promote either.
TEST_F(ImplicitRootScrollerSimTest, DontPromoteWhenMultipleAreValid) {
WebView().MainFrameViewWidget()->Resize(gfx::Size(800, 600));
SimRequest request("https://example.com/test.html", "text/html");
LoadURL("https://example.com/test.html");
request.Complete(R"HTML(
<!DOCTYPE html>
<style>
::-webkit-scrollbar {
width: 0px;
height: 0px;
}
body, html {
width: 100%;
height: 100%;
margin: 0px;
}
iframe {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
border: 0;
}
</style>
<iframe id="container"
srcdoc="<!DOCTYPE html><style>html {height: 300%;}</style>">
</iframe>
<iframe id="container2"
srcdoc="<!DOCTYPE html><style>html {height: 300%;}</style>">
</iframe>
)HTML");
// srcdoc iframe loads via posted tasks.
RunPendingTasks();
Compositor().BeginFrame();
// Since both iframes are valid candidates, neither should be promoted.
ASSERT_EQ(&GetDocument(),
GetDocument().GetRootScrollerController().EffectiveRootScroller());
// Now make the second one invalid, that should cause the first to be
// promoted.
Element* container2 = GetDocument().getElementById("container2");
container2->style()->setProperty(GetDocument().GetExecutionContext(),
"height", "95%", String(),
ASSERT_NO_EXCEPTION);
Compositor().BeginFrame();
Element* container = GetDocument().getElementById("container");
ASSERT_EQ(container,
GetDocument().GetRootScrollerController().EffectiveRootScroller());
}
// Test that when a valid iframe becomes loaded and thus should be promoted, it
// becomes the root scroller, without needing an intervening layout.
TEST_F(ImplicitRootScrollerSimTest, IframeLoadedWithoutLayout) {
WebView().MainFrameViewWidget()->Resize(gfx::Size(800, 600));
SimRequest main_request("https://example.com/test.html", "text/html");
SimRequest child_request("https://example.com/child.html", "text/html");
LoadURL("https://example.com/test.html");
main_request.Complete(R"HTML(
<!DOCTYPE html>
<style>
::-webkit-scrollbar {
width: 0px;
height: 0px;
}
body, html {
width: 100%;
height: 100%;
margin: 0px;
}
iframe {
width: 100%;
height: 100%;
border: 0;
}
</style>
<iframe id="container" src="child.html">
</iframe>
)HTML");
Compositor().BeginFrame();
ASSERT_EQ(GetDocument(),
GetDocument().GetRootScrollerController().EffectiveRootScroller())
<< "The iframe isn't yet scrollable.";
// Ensure that it gets promoted when the new FrameView is connected even
// though there's no layout in the parent to trigger it.
child_request.Complete(R"HTML(
<!DOCTYPE html>
<style>
body {
height: 1000px;
}
</style>
)HTML");
Compositor().BeginFrame();
EXPECT_EQ(GetDocument().getElementById("container"),
GetDocument().GetRootScrollerController().EffectiveRootScroller())
<< "Once loaded, the iframe should be promoted.";
}
// Ensure that navigating an iframe while it is the effective root scroller,
// causes it to remain the effective root scroller after the navigation (to a
// page where it remains valid) is finished.
TEST_F(ImplicitRootScrollerSimTest, NavigateToValidRemainsRootScroller) {
WebView().MainFrameViewWidget()->Resize(gfx::Size(800, 600));
SimRequest main_request("https://example.com/test.html", "text/html");
SimRequest child_request("https://example.com/child.html", "text/html");
LoadURL("https://example.com/test.html");
main_request.Complete(R"HTML(
<!DOCTYPE html>
<style>
::-webkit-scrollbar {
width: 0px;
height: 0px;
}
body, html {
width: 100%;
height: 100%;
margin: 0px;
}
iframe {
width: 100%;
height: 100%;
border: 0;
}
</style>
<iframe id="container" src="child.html">
</iframe>
)HTML");
child_request.Complete(R"HTML(
<!DOCTYPE html>
<style>
body {
height: 1000px;
}
</style>
)HTML");
Compositor().BeginFrame();
ASSERT_EQ(GetDocument().getElementById("container"),
GetDocument().GetRootScrollerController().EffectiveRootScroller());
// Navigate the child frame. When it's loaded, the FrameView should swap.
// Ensure that we remain the root scroller even though there's no layout in
// the parent.
SimRequest child_request2("https://example.com/child-next.html", "text/html");
frame_test_helpers::LoadFrameDontWait(
WebView().MainFrameImpl()->FirstChild()->ToWebLocalFrame(),
KURL("https://example.com/child-next.html"));
child_request2.Write(R"HTML(
<!DOCTYPE html>
)HTML");
Compositor().BeginFrame();
EXPECT_EQ(GetDocument(),
GetDocument().GetRootScrollerController().EffectiveRootScroller())
<< "The iframe should be demoted once a navigation is committed";
// Ensure that it gets promoted when the new FrameView is connected even
// though there's no layout in the parent to trigger it.
child_request2.Write(R"HTML(
<style>
body {
height: 2000px;
}
</style>
)HTML");
child_request2.Finish();
Compositor().BeginFrame();
EXPECT_EQ(GetDocument().getElementById("container"),
GetDocument().GetRootScrollerController().EffectiveRootScroller())
<< "Once loaded, the iframe should be promoted again.";
}
// Ensure that scroll restoration logic in the document does not apply
// to the implicit root scroller, but rather to the document's LayoutViewport.
TEST_F(ImplicitRootScrollerSimTest, ScrollRestorationIgnoresImplicit) {
WebView().MainFrameViewWidget()->Resize(gfx::Size(800, 600));
SimRequest main_request("https://example.com/test.html", "text/html");
SimRequest child_request("https://example.com/child.html", "text/html");
LoadURL("https://example.com/test.html");
main_request.Complete(R"HTML(
<!DOCTYPE html>
<style>
::-webkit-scrollbar {
width: 0px;
height: 0px;
}
body, html {
width: 100%;
height: 100%;
margin: 0px;
}
iframe {
width: 100%;
height: 100%;
border: 0;
}
</style>
<iframe id="container" src="child.html">
</iframe>
)HTML");
child_request.Complete(R"HTML(
<!DOCTYPE html>
<style>
body {
height: 1000px;
}
</style>
)HTML");
Compositor().BeginFrame();
ASSERT_EQ(GetDocument().getElementById("container"),
GetDocument().GetRootScrollerController().EffectiveRootScroller());
HistoryItem::ViewState view_state;
view_state.scroll_offset_ = ScrollOffset(10, 20);
GetDocument()
.View()
->GetScrollableArea()
->SetPendingHistoryRestoreScrollOffset(view_state, true);
GetDocument().View()->LayoutViewport()->SetPendingHistoryRestoreScrollOffset(
view_state, true);
GetDocument().View()->ScheduleAnimation();
Compositor().BeginFrame();
EXPECT_EQ(ScrollOffset(0, 0),
GetDocument().View()->GetScrollableArea()->GetScrollOffset());
GetDocument().domWindow()->scrollTo(0, 20);
GetDocument().View()->ScheduleAnimation();
// Check that an implicit scroll offset is not saved.
// TODO(chrishtr): probably it should?
Compositor().BeginFrame();
EXPECT_FALSE(GetDocument()
.GetFrame()
->Loader()
.GetDocumentLoader()
->GetHistoryItem()
->GetViewState());
}
// Test that a root scroller is considered to fill the viewport at both the URL
// bar shown and URL bar hidden height.
TEST_F(ImplicitRootScrollerSimTest,
RootScrollerFillsViewportAtBothURLBarStates) {
WebView().ResizeWithBrowserControls(gfx::Size(800, 600), 50, 0, true);
SimRequest main_request("https://example.com/test.html", "text/html");
LoadURL("https://example.com/test.html");
main_request.Complete(R"HTML(
<!DOCTYPE html>
<style>
::-webkit-scrollbar {
width: 0px;
height: 0px;
}
body, html {
width: 100%;
height: 100%;
margin: 0px;
}
#container {
width: 100%;
height: 100%;
overflow: auto;
border: 0;
}
</style>
<div id="container">
<div style="height: 2000px;"></div>
</div>
<script>
onresize = () => {
document.getElementById("container").style.height =
window.innerHeight + "px";
};
</script>
)HTML");
Element* container = GetDocument().getElementById("container");
Compositor().BeginFrame();
ASSERT_EQ(container,
GetDocument().GetRootScrollerController().EffectiveRootScroller());
// Simulate hiding the top controls. The root scroller should remain valid at
// the new height.
WebView().GetPage()->GetBrowserControls().SetShownRatio(0, 0);
WebView().ResizeWithBrowserControls(gfx::Size(800, 650), 50, 50, false);
Compositor().BeginFrame();
EXPECT_EQ(container,
GetDocument().GetRootScrollerController().EffectiveRootScroller());
// Simulate showing the top controls. The root scroller should remain valid.
WebView().GetPage()->GetBrowserControls().SetShownRatio(1, 1);
WebView().ResizeWithBrowserControls(gfx::Size(800, 600), 50, 50, true);
Compositor().BeginFrame();
EXPECT_EQ(container,
GetDocument().GetRootScrollerController().EffectiveRootScroller());
// Set the height explicitly to a new value in-between. The root scroller
// should be demoted.
container->style()->setProperty(GetDocument().GetExecutionContext(), "height",
"601px", String(), ASSERT_NO_EXCEPTION);
Compositor().BeginFrame();
EXPECT_EQ(GetDocument(),
GetDocument().GetRootScrollerController().EffectiveRootScroller());
// Reset back to valid and hide the top controls. Zoom to 2x. Ensure we're
// still considered valid.
container->style()->setProperty(GetDocument().GetExecutionContext(), "height",
"", String(), ASSERT_NO_EXCEPTION);
Compositor().BeginFrame();
EXPECT_EQ(container,
GetDocument().GetRootScrollerController().EffectiveRootScroller());
EXPECT_EQ(To<LayoutBox>(container->GetLayoutObject())->Size().Height(), 600);
WebView().SetZoomLevel(PageZoomFactorToZoomLevel(2.0));
WebView().GetPage()->GetBrowserControls().SetShownRatio(0, 0);
WebView().ResizeWithBrowserControls(gfx::Size(800, 650), 50, 50, false);
Compositor().BeginFrame();
EXPECT_EQ(container->clientHeight(), 325);
EXPECT_EQ(container,
GetDocument().GetRootScrollerController().EffectiveRootScroller());
}
// Tests that implicit is continually reevaluating whether to promote or demote
// a scroller.
TEST_F(ImplicitRootScrollerSimTest, ContinuallyReevaluateImplicitPromotion) {
WebView().MainFrameViewWidget()->Resize(gfx::Size(800, 600));
SimRequest request("https://example.com/test.html", "text/html");
LoadURL("https://example.com/test.html");
request.Complete(R"HTML(
<!DOCTYPE html>
<style>
::-webkit-scrollbar {
width: 0px;
height: 0px;
}
html {
overflow: hidden;
}
body, html {
width: 100%;
height: 100%;
margin: 0px;
}
#container {
width: 100%;
height: 100%;
}
#parent {
width: 100%;
height: 100%;
}
</style>
<div id="parent">
<div id="container">
<div id="spacer"></div>
</div>
</div>
)HTML");
Compositor().BeginFrame();
Element* parent = GetDocument().getElementById("parent");
Element* container = GetDocument().getElementById("container");
Element* spacer = GetDocument().getElementById("spacer");
// The container isn't yet scrollable.
ASSERT_EQ(GetDocument(),
GetDocument().GetRootScrollerController().EffectiveRootScroller());
// The container now has overflow but still doesn't scroll.
spacer->style()->setProperty(GetDocument().GetExecutionContext(), "height",
"2000px", String(), ASSERT_NO_EXCEPTION);
Compositor().BeginFrame();
EXPECT_EQ(GetDocument(),
GetDocument().GetRootScrollerController().EffectiveRootScroller());
// The container is now scrollable and should be promoted.
container->style()->setProperty(GetDocument().GetExecutionContext(),
"overflow", "auto", String(),
ASSERT_NO_EXCEPTION);
Compositor().BeginFrame();
EXPECT_EQ(container,
GetDocument().GetRootScrollerController().EffectiveRootScroller());
// The container is now not viewport-filling so it should be demoted.
container->style()->setProperty(GetDocument().GetExecutionContext(),
"transform", "translateX(-50px)", String(),
ASSERT_NO_EXCEPTION);
Compositor().BeginFrame();
EXPECT_EQ(GetDocument(),
GetDocument().GetRootScrollerController().EffectiveRootScroller());
// The container is viewport-filling again so it should be promoted.
parent->style()->setProperty(GetDocument().GetExecutionContext(), "transform",
"translateX(50px)", String(),
ASSERT_NO_EXCEPTION);
Compositor().BeginFrame();
EXPECT_EQ(container,
GetDocument().GetRootScrollerController().EffectiveRootScroller());
// No longer scrollable so demote.
container->style()->setProperty(GetDocument().GetExecutionContext(),
"overflow", "hidden", String(),
ASSERT_NO_EXCEPTION);
Compositor().BeginFrame();
EXPECT_EQ(GetDocument(),
GetDocument().GetRootScrollerController().EffectiveRootScroller());
}
// Tests that implicit mode correctly recognizes when an iframe becomes
// scrollable.
TEST_F(ImplicitRootScrollerSimTest, IframeScrollingAffectsPromotion) {
WebView().MainFrameViewWidget()->Resize(gfx::Size(800, 600));
SimRequest request("https://example.com/test.html", "text/html");
LoadURL("https://example.com/test.html");
request.Complete(R"HTML(
<!DOCTYPE html>
<style>
::-webkit-scrollbar {
width: 0px;
height: 0px;
}
body, html {
width: 100%;
height: 100%;
margin: 0px;
}
iframe {
width: 100%;
height: 100%;
border: 0;
}
</style>
<iframe id="container"
srcdoc="<!DOCTYPE html><style>html {overflow: hidden; height: 300%;}</style>">
</iframe>
)HTML");
// srcdoc iframe loads via posted tasks.
RunPendingTasks();
Compositor().BeginFrame();
auto* container =
To<HTMLFrameOwnerElement>(GetDocument().getElementById("container"));
Element* inner_html_element = container->contentDocument()->documentElement();
// Shouldn't be promoted since it's not scrollable.
EXPECT_EQ(GetDocument(),
GetDocument().GetRootScrollerController().EffectiveRootScroller());
// Allows scrolling now so promote.
inner_html_element->style()->setProperty(
To<LocalDOMWindow>(container->contentWindow()), "overflow", "auto",
String(), ASSERT_NO_EXCEPTION);
Compositor().BeginFrame();
EXPECT_EQ(container,
GetDocument().GetRootScrollerController().EffectiveRootScroller());
// Demote again.
inner_html_element->style()->setProperty(
To<LocalDOMWindow>(container->contentWindow()), "overflow", "hidden",
String(), ASSERT_NO_EXCEPTION);
Compositor().BeginFrame();
EXPECT_EQ(GetDocument(),
GetDocument().GetRootScrollerController().EffectiveRootScroller());
}
// Loads with a larger than the ICB (but otherwise valid) implicit root
// scrolling iframe. When the iframe is promoted (which happens at the end of
// layout) its layout size is changed which makes it easy to violate lifecycle
// assumptions. (e.g. NeedsLayout at the end of layout)
TEST_F(ImplicitRootScrollerSimTest, PromotionChangesLayoutSize) {
WebView().ResizeWithBrowserControls(gfx::Size(800, 650), 50, 0, false);
SimRequest main_request("https://example.com/test.html", "text/html");
SimRequest child_request("https://example.com/child.html", "text/html");
LoadURL("https://example.com/test.html");
main_request.Complete(R"HTML(
<!DOCTYPE html>
<style>
::-webkit-scrollbar {
width: 0px;
height: 0px;
}
body, html {
width: 100%;
height: 100%;
margin: 0px;
}
iframe {
width: 100%;
height: 650px;
border: 0;
}
</style>
<iframe id="container" src="child.html">
</iframe>
)HTML");
child_request.Complete(R"HTML(
<!DOCTYPE html>
<style>
body {
height: 1000px;
}
</style>
)HTML");
Compositor().BeginFrame();
EXPECT_EQ(GetDocument().getElementById("container"),
GetDocument().GetRootScrollerController().EffectiveRootScroller())
<< "Once loaded, the iframe should be promoted.";
}
// Tests that bottom-fixed objects inside of an iframe root scroller and frame
// are marked as being affected by top controls movement. Those inside a
// non-rootScroller iframe should not be marked as such.
TEST_F(ImplicitRootScrollerSimTest, BottomFixedAffectedByTopControls) {
WebView().ResizeWithBrowserControls(gfx::Size(800, 650), 50, 0, false);
SimRequest main_request("https://example.com/test.html", "text/html");
SimRequest child_request1("https://example.com/child1.html", "text/html");
SimRequest child_request2("https://example.com/child2.html", "text/html");
LoadURL("https://example.com/test.html");
main_request.Complete(R"HTML(
<!DOCTYPE html>
<style>
::-webkit-scrollbar {
width: 0px;
height: 0px;
}
body, html {
width: 100%;
height: 100%;
margin: 0px;
}
#container1 {
width: 100%;
height: 100%;
border: 0;
}
#container2 {
position: absolute;
width: 10px;
height: 10px;
left: 100px;
top: 100px;
border: 0;
}
#fixed {
position: fixed;
bottom: 10px;
left: 10px;
width: 10px;
height: 10px;
background-color: red;
}
</style>
<iframe id="container1" src="child1.html">
</iframe>
<iframe id="container2" src="child2.html">
</iframe>
<div id="fixed"></div>
)HTML");
child_request1.Complete(R"HTML(
<!DOCTYPE html>
<style>
body {
height: 1000px;
}
#fixed {
width: 50px;
height: 50px;
position: fixed;
bottom: 0px;
left: 0px;
}
</style>
<div id="fixed"></div>
)HTML");
child_request2.Complete(R"HTML(
<!DOCTYPE html>
<style>
body {
height: 1000px;
}
#fixed {
width: 50px;
height: 50px;
position: fixed;
bottom: 0px;
left: 0px;
}
</style>
<div id="fixed"></div>
)HTML");
Compositor().BeginFrame();
Element* container1 = GetDocument().getElementById("container1");
Element* container2 = GetDocument().getElementById("container2");
ASSERT_EQ(container1,
GetDocument().GetRootScrollerController().EffectiveRootScroller())
<< "The #container1 iframe must be promoted.";
Document* child1_document =
To<HTMLFrameOwnerElement>(container1)->contentDocument();
Document* child2_document =
To<HTMLFrameOwnerElement>(container2)->contentDocument();
LayoutObject* fixed =
GetDocument().getElementById("fixed")->GetLayoutObject();
LayoutObject* fixed1 =
child1_document->getElementById("fixed")->GetLayoutObject();
LayoutObject* fixed2 =
child2_document->getElementById("fixed")->GetLayoutObject();
EXPECT_TRUE(fixed->FirstFragment()
.PaintProperties()
->PaintOffsetTranslation()
->IsAffectedByOuterViewportBoundsDelta());
EXPECT_TRUE(fixed1->FirstFragment()
.PaintProperties()
->PaintOffsetTranslation()
->IsAffectedByOuterViewportBoundsDelta());
EXPECT_FALSE(fixed2->FirstFragment()
.PaintProperties()
->PaintOffsetTranslation()
->IsAffectedByOuterViewportBoundsDelta());
}
// Ensure that we're using the content box for an iframe. Promotion will cause
// the content to use the layout size of the parent frame so having padding or
// a border would cause us to relayout.
TEST_F(ImplicitRootScrollerSimTest, IframeUsesContentBox) {
WebView().ResizeWithBrowserControls(gfx::Size(800, 600), 0, 0, false);
SimRequest main_request("https://example.com/test.html", "text/html");
SimRequest child_request("https://example.com/child.html", "text/html");
LoadURL("https://example.com/test.html");
main_request.Complete(R"HTML(
<!DOCTYPE>
<style>
iframe {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border: none;
box-sizing: border-box;
}
body, html {
margin: 0;
width: 100%;
height: 100%;
overflow:hidden;
}
</style>
<iframe id="container" src="child.html">
)HTML");
child_request.Complete(R"HTML(
<!DOCTYPE html>
<style>
div {
border: 5px solid black;
background-color: red;
width: 99%;
height: 100px;
}
html {
height: 200%;
}
</style>
<div></div>
)HTML");
Compositor().BeginFrame();
Element* iframe = GetDocument().getElementById("container");
ASSERT_EQ(iframe,
GetDocument().GetRootScrollerController().EffectiveRootScroller())
<< "The iframe should start off promoted.";
// Adding padding should cause the iframe to be demoted.
{
iframe->setAttribute(html_names::kStyleAttr, "padding-left: 20%");
Compositor().BeginFrame();
EXPECT_NE(iframe,
GetDocument().GetRootScrollerController().EffectiveRootScroller())
<< "The iframe should be demoted once it has padding.";
}
// Replacing padding with a border should also ensure the iframe remains
// demoted.
{
iframe->setAttribute(html_names::kStyleAttr, "border: 5px solid black");
Compositor().BeginFrame();
EXPECT_NE(iframe,
GetDocument().GetRootScrollerController().EffectiveRootScroller())
<< "The iframe should be demoted once it has border.";
}
// Removing the border should now cause the iframe to be promoted once again.
iframe->setAttribute(html_names::kStyleAttr, "");
Compositor().BeginFrame();
ASSERT_EQ(iframe,
GetDocument().GetRootScrollerController().EffectiveRootScroller())
<< "The iframe should once again be promoted when border is removed";
}
// Test that we don't promote any elements implicitly if the main document has
// vertical scrolling.
TEST_F(ImplicitRootScrollerSimTest, OverflowInMainDocumentRestrictsImplicit) {
WebView().ResizeWithBrowserControls(gfx::Size(800, 600), 50, 0, true);
SimRequest main_request("https://example.com/test.html", "text/html");
SimRequest child_request("https://example.com/child.html", "text/html");
LoadURL("https://example.com/test.html");
main_request.Complete(R"HTML(
<!DOCTYPE html>
<style>
::-webkit-scrollbar {
width: 0px;
height: 0px;
}
body, html {
width: 100%;
height: 100%;
margin: 0px;
}
iframe {
width: 100%;
height: 100%;
border: 0;
}
div {
position: absolute;
left: 0;
top: 0;
height: 150%;
width: 150%;
}
</style>
<iframe id="container" src="child.html">
</iframe>
<div id="spacer"></div>
)HTML");
child_request.Complete(R"HTML(
<!DOCTYPE html>
<style>
body {
height: 1000px;
}
</style>
)HTML");
Compositor().BeginFrame();
EXPECT_EQ(GetDocument(),
GetDocument().GetRootScrollerController().EffectiveRootScroller())
<< "iframe shouldn't be promoted due to overflow in the main document.";
Element* spacer = GetDocument().getElementById("spacer");
spacer->style()->setProperty(GetDocument().GetExecutionContext(), "height",
"100%", String(), ASSERT_NO_EXCEPTION);
Compositor().BeginFrame();
EXPECT_EQ(GetDocument().getElementById("container"),
GetDocument().GetRootScrollerController().EffectiveRootScroller())
<< "Once vertical overflow is removed, the iframe should be promoted.";
}
// Test that we overflow in the document allows promotion only so long as the
// document isn't scrollable.
TEST_F(ImplicitRootScrollerSimTest, OverflowHiddenDoesntRestrictImplicit) {
WebView().ResizeWithBrowserControls(gfx::Size(800, 600), 50, 0, true);
SimRequest main_request("https://example.com/test.html", "text/html");
SimRequest child_request("https://example.com/child.html", "text/html");
LoadURL("https://example.com/test.html");
main_request.Complete(R"HTML(
<!DOCTYPE html>
<style>
::-webkit-scrollbar {
width: 0px;
height: 0px;
}
html {
overflow: hidden;
}
body, html {
width: 100%;
height: 100%;
margin: 0px;
}
iframe {
width: 100%;
height: 100%;
border: 0;
}
#spacer {
position: absolute;
left: 0;
top: 0;
height: 150%;
width: 150%;
}
</style>
<iframe id="container" src="child.html">
</iframe>
<div id="spacer"></div>
)HTML");
child_request.Complete(R"HTML(
<!DOCTYPE html>
<style>
body {
height: 1000px;
}
</style>
)HTML");
Compositor().BeginFrame();
EXPECT_EQ(GetDocument().getElementById("container"),
GetDocument().GetRootScrollerController().EffectiveRootScroller())
<< "iframe should be promoted since document's overflow is hidden.";
Element* html = GetDocument().documentElement();
html->style()->setProperty(GetDocument().GetExecutionContext(), "overflow",
"auto", String(), ASSERT_NO_EXCEPTION);
Compositor().BeginFrame();
EXPECT_EQ(GetDocument(),
GetDocument().GetRootScrollerController().EffectiveRootScroller())
<< "iframe should now be demoted since main document scrolls overflow.";
html->style()->setProperty(GetDocument().GetExecutionContext(), "overflow",
"visible", String(), ASSERT_NO_EXCEPTION);
Compositor().BeginFrame();
EXPECT_EQ(GetDocument(),
GetDocument().GetRootScrollerController().EffectiveRootScroller())
<< "iframe should remain demoted since overflow:visible on document "
<< "allows scrolling.";
}
// Test that any non-document, clipping ancestor prevents implicit promotion.
TEST_F(ImplicitRootScrollerSimTest, ClippingAncestorPreventsPromotion) {
WebView().ResizeWithBrowserControls(gfx::Size(800, 600), 50, 0, true);
SimRequest main_request("https://example.com/test.html", "text/html");
SimRequest child_request("https://example.com/child.html", "text/html");
LoadURL("https://example.com/test.html");
main_request.Complete(R"HTML(
<!DOCTYPE html>
<style>
::-webkit-scrollbar {
width: 0px;
height: 0px;
}
html {
overflow: hidden;
}
body, html {
width: 100%;
height: 100%;
margin: 0px;
}
iframe {
width: 100%;
height: 100%;
border: 0;
}
#ancestor {
position: absolute;
width: 100%;
height: 100%;
overflow: visible;
/* opacity ensures #ancestor doesn't get considered for root
* scroller promotion. */
opacity: 0.5;
}
#spacer {
height: 150%;
width: 150%;
}
</style>
<div id="ancestor">
<iframe id="container" src="child.html"></iframe>
<div id="spacer"></div>
</div>
)HTML");
child_request.Complete(R"HTML(
<!DOCTYPE html>
<style>
body {
height: 1000px;
}
</style>
)HTML");
Compositor().BeginFrame();
// Each of these style-value pairs should prevent promotion of the iframe.
Vector<std::tuple<String, String>> test_cases = {
{"overflow", "scroll"},
{"overflow", "hidden"},
{"overflow", "auto"},
{"contain", "paint"},
{"-webkit-mask-image", "linear-gradient(black 25%, transparent 50%)"},
{"clip", "rect(10px, 290px, 190px, 10px"},
{"clip-path", "circle(40%)"}};
for (auto test_case : test_cases) {
String& style = std::get<0>(test_case);
String& style_val = std::get<1>(test_case);
Element* ancestor = GetDocument().getElementById("ancestor");
Element* iframe = GetDocument().getElementById("container");
ASSERT_EQ(iframe,
GetDocument().GetRootScrollerController().EffectiveRootScroller())
<< "iframe should start off promoted.";
ancestor->style()->setProperty(GetDocument().GetExecutionContext(), style,
style_val, String(), ASSERT_NO_EXCEPTION);
Compositor().BeginFrame();
EXPECT_EQ(GetDocument(),
GetDocument().GetRootScrollerController().EffectiveRootScroller())
<< "iframe should be demoted since ancestor has " << style << ": "
<< style_val;
ancestor->style()->setProperty(GetDocument().GetExecutionContext(), style,
String(), String(), ASSERT_NO_EXCEPTION);
Compositor().BeginFrame();
ASSERT_EQ(iframe,
GetDocument().GetRootScrollerController().EffectiveRootScroller())
<< "iframe should be promoted since ancestor removed " << style << ": "
<< style_val;
}
}
TEST_F(ImplicitRootScrollerSimTest, AppliedAtFractionalZoom) {
// Matches Pixel 2XL screen size of 412x671 at 3.5 DevicePixelRatio.
WebView().SetZoomFactorForDeviceScaleFactor(3.5f);
WebView().ResizeWithBrowserControls(gfx::Size(1442, 2349), 196, 0, true);
SimRequest main_request("https://example.com/test.html", "text/html");
SimRequest child_request("https://example.com/child.html", "text/html");
LoadURL("https://example.com/test.html");
main_request.Complete(R"HTML(
<!DOCTYPE html>
<style>
::-webkit-scrollbar {
width: 0px;
height: 0px;
}
body, html {
width: 100%;
height: 100%;
margin: 0px;
}
iframe {
border: 0;
display: block;
}
</style>
<iframe id="container" src="child.html">
</iframe>
<script>
// innerHeight is non-fractional so pages don't have a great way to
// set the size to "exctly" 100%. Ensure we still promote in this
// common pattern.
function resize_handler() {
document.getElementById("container").style.height =
window.innerHeight + "px";
document.getElementById("container").style.width =
window.innerWidth + "px";
}
resize_handler();
window.addEventHandler('resize', resize_handler);
</script>
)HTML");
child_request.Complete(R"HTML(
<!DOCTYPE html>
<style>
body {
height: 1000px;
}
</style>
)HTML");
Compositor().BeginFrame();
PaintLayerScrollableArea* area = GetDocument().View()->LayoutViewport();
ASSERT_FALSE(area->HasVerticalOverflow());
EXPECT_EQ(GetDocument().getElementById("container"),
GetDocument().GetRootScrollerController().EffectiveRootScroller())
<< "<iframe> should be promoted when URL bar is hidden";
WebView().ResizeWithBrowserControls(gfx::Size(1442, 2545), 196, 0, false);
Compositor().BeginFrame();
EXPECT_EQ(GetDocument().getElementById("container"),
GetDocument().GetRootScrollerController().EffectiveRootScroller())
<< "<iframe> should remain promoted when URL bar is hidden";
}
// Ensure that a scrollable fieldset doesn't get promoted to root scroller.
// With FieldsetNG, a scrollable fieldset creates an anonymous LayoutBox that
// doesn't have an associated Node. RootScroller is premised on the fact that a
// scroller is associated with a Node. It'd be non-trivial work to make this
// work without a clear benefit so for now ensure it doesn't get promoted and
// doesn't cause any crashes. https://crbug.com/1125621.
TEST_F(ImplicitRootScrollerSimTest, FieldsetNGCantBeRootScroller) {
// This test is specifically ensuring we avoid crashing with the LayoutNG
// version of fieldset since it uses an anonymous LayoutBox for scrolling.
if (!RuntimeEnabledFeatures::LayoutNGEnabled())
return;
WebView().MainFrameViewWidget()->Resize(gfx::Size(800, 600));
SimRequest request("https://example.com/test.html", "text/html");
LoadURL("https://example.com/test.html");
request.Complete(R"HTML(
<!DOCTYPE html>
<style>
::-webkit-scrollbar {
width: 0px;
height: 0px;
}
body, html {
width: 100%;
height: 100%;
margin: 0px;
}
fieldset {
width: 100%;
height: 100%;
overflow: scroll;
border: 0;
margin: 0;
padding: 0;
}
div {
height: 200%;
}
</style>
<fieldset>
<div></div>
</fieldset>
)HTML");
Compositor().BeginFrame();
EXPECT_TRUE(GetDocument().GetLayoutView()->IsEffectiveRootScroller());
}
class RootScrollerHitTest : public ImplicitRootScrollerSimTest {
public:
void CheckHitTestAtBottomOfScreen(Element* target) {
HideTopControlsWithMaximalScroll();
// Do a hit test at the very bottom of the screen. This should be outside
// the root scroller's LayoutBox since inert top controls won't resize the
// ICB but, since we expaned the clip, we should still be able to hit the
// target.
gfx::Point point(200, 445);
gfx::Size tap_area(20, 20);
WebHitTestResult result = WebView().HitTestResultForTap(point, tap_area);
Node* hit_node = result.GetNode().Unwrap<Node>();
EXPECT_EQ(target, hit_node);
}
BrowserControls& GetBrowserControls() {
return GetDocument().GetPage()->GetBrowserControls();
}
private:
void HideTopControlsWithMaximalScroll() {
// Do a scroll gesture that hides the top controls and scrolls all the way
// to the bottom.
ASSERT_EQ(1, GetBrowserControls().TopShownRatio());
ASSERT_EQ(1, GetBrowserControls().BottomShownRatio());
WebView().MainFrameWidget()->ApplyViewportChangesForTesting(
{gfx::ScrollOffset(), gfx::Vector2dF(), 1, false, -1, -1,
cc::BrowserControlsState::kBoth});
ASSERT_EQ(0, GetBrowserControls().TopShownRatio());
ASSERT_EQ(0, GetBrowserControls().BottomShownRatio());
Node* scroller = GetDocument()
.GetPage()
->GlobalRootScrollerController()
.GlobalRootScroller();
ScrollableArea* scrollable_area =
To<LayoutBox>(scroller->GetLayoutObject())->GetScrollableArea();
scrollable_area->DidScroll(FloatPoint(0, 100000));
WebView().ResizeWithBrowserControls(gfx::Size(400, 450), 50, 50, false);
Compositor().BeginFrame();
}
};
// Test that hit testing in the area revealed at the bottom of the screen
// revealed by hiding the URL bar works properly when using a root scroller
// when the target and scroller are in the same PaintLayer.
TEST_F(RootScrollerHitTest, HitTestInAreaRevealedByURLBarSameLayer) {
WebView().ResizeWithBrowserControls(gfx::Size(400, 400), 50, 50, true);
GetBrowserControls().SetShownRatio(1, 1);
SimRequest request("https://example.com/test.html", "text/html");
LoadURL("https://example.com/test.html");
// Add a target at the bottom of the root scroller that's the size of the url
// bar. We'll test that hiding the URL bar appropriately adjusts clipping so
// that we can hit this target.
request.Complete(R"HTML(
<!DOCTYPE html>
<style>
body, html {
width: 100%;
height: 100%;
margin: 0px;
}
#spacer {
height: 1000px;
}
#container {
position: absolute;
width: 100%;
height: 100%;
overflow: auto;
}
#target {
width: 100%;
height: 50px;
}
</style>
<div id='container'>
<div id='spacer'></div>
<div id='target'></div>
</div>
)HTML");
Compositor().BeginFrame();
Element* container = GetDocument().getElementById("container");
Element* target = GetDocument().getElementById("target");
ASSERT_EQ(container,
GetDocument().GetRootScrollerController().EffectiveRootScroller());
// This test checks hit testing while the target is in the same PaintLayer as
// the root scroller.
ASSERT_EQ(To<LayoutBox>(target->GetLayoutObject())->EnclosingLayer(),
To<LayoutBox>(container->GetLayoutObject())->Layer());
CheckHitTestAtBottomOfScreen(target);
}
// Test that hit testing in the area revealed at the bottom of the screen
// revealed by hiding the URL bar works properly when using a root scroller
// when the target and scroller are in different PaintLayers.
TEST_F(RootScrollerHitTest, HitTestInAreaRevealedByURLBarDifferentLayer) {
WebView().ResizeWithBrowserControls(gfx::Size(400, 400), 50, 50, true);
GetBrowserControls().SetShownRatio(1, 1);
SimRequest request("https://example.com/test.html", "text/html");
LoadURL("https://example.com/test.html");
// Add a target at the bottom of the root scroller that's the size of the url
// bar. We'll test that hiding the URL bar appropriately adjusts clipping so
// that we can hit this target.
request.Complete(R"HTML(
<!DOCTYPE html>
<style>
body, html {
height: 100%;
width: 100%;
margin: 0px;
}
#spacer {
height: 1000px;
}
#container {
position: absolute;
width: 100%;
height: 100%;
overflow: auto;
}
#target {
width: 100%;
height: 50px;
will-change: transform;
}
</style>
<div id='container'>
<div id='spacer'></div>
<div id='target'></div>
</div>
)HTML");
Compositor().BeginFrame();
Element* container = GetDocument().getElementById("container");
Element* target = GetDocument().getElementById("target");
ASSERT_EQ(container,
GetDocument().GetRootScrollerController().EffectiveRootScroller());
// Ensure the target and container weren't put into the same layer.
ASSERT_NE(To<LayoutBox>(target->GetLayoutObject())->EnclosingLayer(),
To<LayoutBox>(container->GetLayoutObject())->Layer());
CheckHitTestAtBottomOfScreen(target);
}
} // namespace
} // namespace blink